123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- /* @flow */
-
- import React, { Component } from 'react';
-
- import { Drawer, JitsiPortal, DialogPortal } from '../../../toolbox/components/web';
- import { isMobileBrowser } from '../../environment/utils';
- import { getContextMenuStyle } from '../functions.web';
-
- /**
- * The type of the React {@code Component} props of {@link Popover}.
- */
- type Props = {
-
- /**
- * A child React Element to use as the trigger for showing the dialog.
- */
- children: React$Node,
-
- /**
- * Additional CSS classnames to apply to the root of the {@code Popover}
- * component.
- */
- className: string,
-
- /**
- * The ReactElement to display within the dialog.
- */
- content: Object,
-
- /**
- * Whether displaying of the popover should be prevented.
- */
- disablePopover: boolean,
-
- /**
- * An id attribute to apply to the root of the {@code Popover}
- * component.
- */
- id: string,
-
- /**
- * Callback to invoke when the popover has closed.
- */
- onPopoverClose: Function,
-
- /**
- * Callback to invoke when the popover has opened.
- */
- onPopoverOpen: Function,
-
- /**
- * Whether to display the Popover as a drawer.
- */
- overflowDrawer: boolean,
-
- /**
- * From which side of the dialog trigger the dialog should display. The
- * value will be passed to {@code InlineDialog}.
- */
- position: string
- };
-
- /**
- * The type of the React {@code Component} state of {@link Popover}.
- */
- type State = {
-
- /**
- * The style to apply to the context menu in order to position it correctly.
- */
- contextMenuStyle: Object,
-
- /**
- * Whether or not the {@code InlineDialog} should be displayed.
- */
- showDialog: boolean
- };
-
- /**
- * Implements a React {@code Component} for showing an {@code InlineDialog} on
- * mouseenter of the trigger and contents, and hiding the dialog on mouseleave.
- *
- * @extends Component
- */
- class Popover extends Component<Props, State> {
- /**
- * Default values for {@code Popover} component's properties.
- *
- * @static
- */
- static defaultProps = {
- className: '',
- id: ''
- };
-
- /**
- * Reference to the dialog container.
- */
- _containerRef: Object;
-
- _contextMenuRef: HTMLElement;
-
- /**
- * Initializes a new {@code Popover} instance.
- *
- * @param {Object} props - The read-only properties with which the new
- * instance is to be initialized.
- */
- constructor(props: Props) {
- super(props);
-
- this.state = {
- showDialog: false,
- contextMenuStyle: null
- };
-
- // Bind event handlers so they are only bound once for every instance.
- this._onHideDialog = this._onHideDialog.bind(this);
- this._onShowDialog = this._onShowDialog.bind(this);
- this._onKeyPress = this._onKeyPress.bind(this);
- this._containerRef = React.createRef();
- this._onEscKey = this._onEscKey.bind(this);
- this._onThumbClick = this._onThumbClick.bind(this);
- this._onTouchStart = this._onTouchStart.bind(this);
- this._setContextMenuRef = this._setContextMenuRef.bind(this);
- this._setContextMenuStyle = this._setContextMenuStyle.bind(this);
- this._getCustomDialogStyle = this._getCustomDialogStyle.bind(this);
- }
-
- /**
- * Public method for triggering showing the context menu dialog.
- *
- * @returns {void}
- * @public
- */
- showDialog() {
- this._onShowDialog();
- }
-
- /**
- * Sets up a touch event listener to attach.
- *
- * @inheritdoc
- * @returns {void}
- */
- componentDidMount() {
- window.addEventListener('touchstart', this._onTouchStart);
- }
-
- /**
- * Removes the listener set up in the {@code componentDidMount} method.
- *
- * @inheritdoc
- * @returns {void}
- */
- componentWillUnmount() {
- window.removeEventListener('touchstart', this._onTouchStart);
- }
-
- /**
- * Implements React's {@link Component#render()}.
- *
- * @inheritdoc
- * @returns {ReactElement}
- */
- render() {
- const { children, className, content, id, overflowDrawer } = this.props;
-
- if (overflowDrawer) {
- return (
- <div
- className = { className }
- id = { id }
- onClick = { this._onShowDialog }>
- { children }
- <JitsiPortal>
- <Drawer
- isOpen = { this.state.showDialog }
- onClose = { this._onHideDialog }>
- { content }
- </Drawer>
- </JitsiPortal>
- </div>
- );
- }
-
- return (
- <div
- className = { className }
- id = { id }
- onClick = { this._onThumbClick }
- onKeyPress = { this._onKeyPress }
- onMouseEnter = { this._onShowDialog }
- onMouseLeave = { this._onHideDialog }
- ref = { this._containerRef }>
- { this.state.showDialog && (
- <DialogPortal
- getRef = { this._setContextMenuRef }
- setSize = { this._setContextMenuStyle }
- style = { this.state.contextMenuStyle }>
- {this._renderContent()}
- </DialogPortal>
- )}
- { children }
- </div>
- );
- }
-
- _setContextMenuStyle: (size: Object) => void;
-
- /**
- * Sets the context menu dialog style for positioning it on screen.
- *
- * @param {DOMRectReadOnly} size -The size info of the current context menu.
- *
- * @returns {void}
- */
- _setContextMenuStyle(size) {
- const style = this._getCustomDialogStyle(size);
-
- this.setState({ contextMenuStyle: style });
- }
-
- _setContextMenuRef: (elem: HTMLElement) => void;
-
- /**
- * Sets the context menu's ref.
- *
- * @param {HTMLElement} elem -The html element of the context menu.
- *
- * @returns {void}
- */
- _setContextMenuRef(elem) {
- this._contextMenuRef = elem;
- }
-
- _onTouchStart: (event: TouchEvent) => void;
-
- /**
- * Hide dialog on touch outside of the context menu.
- *
- * @param {TouchEvent} event - The touch event.
- * @private
- * @returns {void}
- */
- _onTouchStart(event) {
- if (this.state.showDialog
- && !this.props.overflowDrawer
- && this._contextMenuRef
- && this._contextMenuRef.contains
- && !this._contextMenuRef.contains(event.target)) {
- this._onHideDialog();
- }
- }
-
- _onHideDialog: () => void;
-
- /**
- * Stops displaying the {@code InlineDialog}.
- *
- * @private
- * @returns {void}
- */
- _onHideDialog() {
- this.setState({
- showDialog: false,
- contextMenuStyle: null
- });
-
- if (this.props.onPopoverClose) {
- this.props.onPopoverClose();
- }
- }
-
- _onShowDialog: (Object) => void;
-
- /**
- * Displays the {@code InlineDialog} and calls any registered onPopoverOpen
- * callbacks.
- *
- * @param {Object} event - The mouse event or the keypress event to intercept.
- * @private
- * @returns {void}
- */
- _onShowDialog(event) {
- event && event.stopPropagation();
- if (!this.props.disablePopover) {
- this.setState({ showDialog: true });
-
- if (this.props.onPopoverOpen) {
- this.props.onPopoverOpen();
- }
- }
- }
-
- _onThumbClick: (Object) => void;
-
- /**
- * Prevents switching from tile view to stage view on accidentally clicking
- * the popover thumbs.
- *
- * @param {Object} event - The mouse event or the keypress event to intercept.
- * @private
- * @returns {void}
- */
- _onThumbClick(event) {
- event.stopPropagation();
- }
-
- _onKeyPress: (Object) => void;
-
- /**
- * KeyPress handler for accessibility.
- *
- * @param {Object} e - The key event to handle.
- *
- * @returns {void}
- */
- _onKeyPress(e) {
- if (e.key === ' ' || e.key === 'Enter') {
- e.preventDefault();
- if (this.state.showDialog) {
- this._onHideDialog();
- } else {
- this._onShowDialog(e);
- }
- }
- }
-
- _onEscKey: (Object) => void;
-
- /**
- * KeyPress handler for accessibility.
- *
- * @param {Object} e - The key event to handle.
- *
- * @returns {void}
- */
- _onEscKey(e) {
- if (e.key === 'Escape') {
- e.preventDefault();
- e.stopPropagation();
- if (this.state.showDialog) {
- this._onHideDialog();
- }
- }
- }
-
- _getCustomDialogStyle: (DOMRectReadOnly) => void;
-
- /**
- * Gets style for positioning the context menu on screen in regards to the trigger's
- * position.
- *
- * @param {DOMRectReadOnly} size -The current context menu's size info.
- *
- * @returns {Object} - The new style of the context menu.
- */
- _getCustomDialogStyle(size) {
- if (this._containerRef && this._containerRef.current) {
- const bounds = this._containerRef.current.getBoundingClientRect();
-
- return getContextMenuStyle(bounds, size, this.props.position);
- }
- }
-
- /**
- * Renders the React Element to be displayed in the {@code InlineDialog}.
- * Also adds padding to support moving the mouse from the trigger to the
- * dialog to prevent mouseleave events.
- *
- * @private
- * @returns {ReactElement}
- */
- _renderContent() {
- const { content } = this.props;
-
- return (
- <div
- className = 'popover popupmenu'
- onKeyDown = { this._onEscKey }>
- { content }
- {!isMobileBrowser() && (
- <>
- <div className = 'popover-mousemove-padding-top' />
- <div className = 'popover-mousemove-padding-right' />
- <div className = 'popover-mousemove-padding-left' />
- <div className = 'popover-mousemove-padding-bottom' />
- </>)}
- </div>
- );
- }
- }
-
- export default Popover;
|