diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js
index d332f8f824..756ebec863 100644
--- a/src/components/structures/ContextualMenu.js
+++ b/src/components/structures/ContextualMenu.js
@@ -20,8 +20,7 @@ import React, {useRef, useState} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
-import {focusCapturedRef} from "../../utils/Accessibility";
-import {Key, KeyCode} from "../../Keyboard";
+import {Key} from "../../Keyboard";
import sdk from "../../index";
import AccessibleButton from "../views/elements/AccessibleButton";
@@ -43,194 +42,6 @@ function getOrCreateContainer() {
return container;
}
-export default class ContextualMenu extends React.Component {
- propTypes: {
- top: PropTypes.number,
- bottom: PropTypes.number,
- left: PropTypes.number,
- right: PropTypes.number,
- menuWidth: PropTypes.number,
- menuHeight: PropTypes.number,
- chevronOffset: PropTypes.number,
- chevronFace: PropTypes.string, // top, bottom, left, right or none
- // Function to be called on menu close
- onFinished: PropTypes.func,
- menuPaddingTop: PropTypes.number,
- menuPaddingRight: PropTypes.number,
- menuPaddingBottom: PropTypes.number,
- menuPaddingLeft: PropTypes.number,
- zIndex: PropTypes.number,
-
- // If true, insert an invisible screen-sized element behind the
- // menu that when clicked will close it.
- hasBackground: PropTypes.bool,
-
- // The component to render as the context menu
- elementClass: PropTypes.element.isRequired,
- // on resize callback
- windowResize: PropTypes.func,
- // method to close menu
- closeMenu: PropTypes.func.isRequired,
- };
-
- constructor() {
- super();
- this.state = {
- contextMenuRect: null,
- };
-
- this.onContextMenu = this.onContextMenu.bind(this);
- this.collectContextMenuRect = this.collectContextMenuRect.bind(this);
- }
-
- collectContextMenuRect(element) {
- // We don't need to clean up when unmounting, so ignore
- if (!element) return;
-
- // For screen readers to find the thing
- focusCapturedRef(element);
-
- this.setState({
- contextMenuRect: element.getBoundingClientRect(),
- });
- }
-
- onContextMenu(e) {
- if (this.props.closeMenu) {
- this.props.closeMenu();
-
- e.preventDefault();
- const x = e.clientX;
- const y = e.clientY;
-
- // XXX: This isn't pretty but the only way to allow opening a different context menu on right click whilst
- // a context menu and its click-guard are up without completely rewriting how the context menus work.
- setImmediate(() => {
- const clickEvent = document.createEvent('MouseEvents');
- clickEvent.initMouseEvent(
- 'contextmenu', true, true, window, 0,
- 0, 0, x, y, false, false,
- false, false, 0, null,
- );
- document.elementFromPoint(x, y).dispatchEvent(clickEvent);
- });
- }
- }
-
- _onKeyDown = (ev) => {
- if (ev.keyCode === KeyCode.ESCAPE) {
- ev.stopPropagation();
- ev.preventDefault();
- this.props.closeMenu();
- }
- };
-
- render() {
- const position = {};
- let chevronFace = null;
- const props = this.props;
-
- if (props.top) {
- position.top = props.top;
- } else {
- position.bottom = props.bottom;
- }
-
- if (props.left) {
- position.left = props.left;
- chevronFace = 'left';
- } else {
- position.right = props.right;
- chevronFace = 'right';
- }
-
- const contextMenuRect = this.state.contextMenuRect || null;
- const padding = 10;
-
- const chevronOffset = {};
- if (props.chevronFace) {
- chevronFace = props.chevronFace;
- }
- const hasChevron = chevronFace && chevronFace !== "none";
-
- if (chevronFace === 'top' || chevronFace === 'bottom') {
- chevronOffset.left = props.chevronOffset;
- } else {
- const target = position.top;
-
- // By default, no adjustment is made
- let adjusted = target;
-
- // If we know the dimensions of the context menu, adjust its position
- // such that it does not leave the (padded) window.
- if (contextMenuRect) {
- adjusted = Math.min(position.top, document.body.clientHeight - contextMenuRect.height - padding);
- }
-
- position.top = adjusted;
- chevronOffset.top = Math.max(props.chevronOffset, props.chevronOffset + target - adjusted);
- }
-
- const chevron = hasChevron ?
-
:
- undefined;
- const className = 'mx_ContextualMenu_wrapper';
-
- const menuClasses = classNames({
- 'mx_ContextualMenu': true,
- 'mx_ContextualMenu_left': !hasChevron && position.left,
- 'mx_ContextualMenu_right': !hasChevron && position.right,
- 'mx_ContextualMenu_top': !hasChevron && position.top,
- 'mx_ContextualMenu_bottom': !hasChevron && position.bottom,
- 'mx_ContextualMenu_withChevron_left': chevronFace === 'left',
- 'mx_ContextualMenu_withChevron_right': chevronFace === 'right',
- 'mx_ContextualMenu_withChevron_top': chevronFace === 'top',
- 'mx_ContextualMenu_withChevron_bottom': chevronFace === 'bottom',
- });
-
- const menuStyle = {};
- if (props.menuWidth) {
- menuStyle.width = props.menuWidth;
- }
-
- if (props.menuHeight) {
- menuStyle.height = props.menuHeight;
- }
-
- if (!isNaN(Number(props.menuPaddingTop))) {
- menuStyle["paddingTop"] = props.menuPaddingTop;
- }
- if (!isNaN(Number(props.menuPaddingLeft))) {
- menuStyle["paddingLeft"] = props.menuPaddingLeft;
- }
- if (!isNaN(Number(props.menuPaddingBottom))) {
- menuStyle["paddingBottom"] = props.menuPaddingBottom;
- }
- if (!isNaN(Number(props.menuPaddingRight))) {
- menuStyle["paddingRight"] = props.menuPaddingRight;
- }
-
- const wrapperStyle = {};
- if (!isNaN(Number(props.zIndex))) {
- menuStyle["zIndex"] = props.zIndex + 1;
- wrapperStyle["zIndex"] = props.zIndex;
- }
-
- const ElementClass = props.elementClass;
-
- // FIXME: If a menu uses getDefaultProps it clobbers the onFinished
- // property set here so you can't close the menu from a button click!
- return
-
- { chevron }
-
-
- { props.hasBackground &&
}
-
;
- }
-}
-
const ARIA_MENU_ITEM_ROLES = new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]);
// Generic ContextMenu Portal wrapper
// all options inside the menu should be of role=menuitem/menuitemcheckbox/menuitemradiobutton and have tabIndex={-1}
@@ -396,7 +207,7 @@ export class ContextMenu extends React.Component {
ev.preventDefault();
};
- render() {
+ renderMenu(hasBackground=this.props.hasBackground) {
const position = {};
let chevronFace = null;
const props = this.props;
@@ -488,13 +299,13 @@ export class ContextMenu extends React.Component {
}
let background;
- if (props.hasBackground) {
+ if (hasBackground) {
background = (
);
}
- const menu = (
+ return (
{ chevron }
@@ -503,7 +314,10 @@ export class ContextMenu extends React.Component {
{ background }
);
- return ReactDOM.createPortal(menu, getOrCreateContainer());
+ }
+
+ render() {
+ return ReactDOM.createPortal(this.renderMenu(), getOrCreateContainer());
}
}
@@ -589,8 +403,8 @@ MenuItemRadio.propTypes = {
// Placement method for
to position context menu to right of elementRect with chevronOffset
export const toRightOf = (elementRect, chevronOffset=12) => {
const left = elementRect.right + window.pageXOffset + 3;
- let top = (elementRect.top + (elementRect.height / 2) + window.pageYOffset);
- top = top - (chevronOffset + 8); // where 8 is half the height of the chevron
+ let top = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
+ top -= chevronOffset + 8; // where 8 is half the height of the chevron
return {left, top};
};
@@ -626,8 +440,15 @@ export const useContextMenu = () => {
return [isOpen, _button, open, close, setIsOpen];
};
-export function createMenu(ElementClass, props, hasBackground=true) {
- const closeMenu = function(...args) {
+export default class LegacyContextMenu extends ContextMenu {
+ render() {
+ return this.renderMenu(false);
+ }
+}
+
+// XXX: Deprecated, used only for dynamic Tooltips. Avoid using at all costs.
+export function createMenu(ElementClass, props) {
+ const onFinished = function(...args) {
ReactDOM.unmountComponentAtNode(getOrCreateContainer());
if (props && props.onFinished) {
@@ -635,16 +456,15 @@ export function createMenu(ElementClass, props, hasBackground=true) {
}
};
- // We only reference closeMenu once per call to createMenu
- const menu =
;
+ onFinished={onFinished} // eslint-disable-line react/jsx-no-bind
+ windowResize={onFinished} // eslint-disable-line react/jsx-no-bind
+ >
+
+ ;
ReactDOM.render(menu, getOrCreateContainer());
- return {close: closeMenu};
+ return {close: onFinished};
}
diff --git a/src/components/views/dialogs/ShareDialog.js b/src/components/views/dialogs/ShareDialog.js
index 65d581c3bc..f34f2c0174 100644
--- a/src/components/views/dialogs/ShareDialog.js
+++ b/src/components/views/dialogs/ShareDialog.js
@@ -22,6 +22,7 @@ import { _t } from '../../../languageHandler';
import QRCode from 'qrcode-react';
import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import * as ContextualMenu from "../../structures/ContextualMenu";
+import {toRightOf} from "../../structures/ContextualMenu";
const socials = [
{
@@ -102,18 +103,12 @@ export default class ShareDialog extends React.Component {
console.error('Failed to copy: ', err);
}
- const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const buttonRect = e.target.getBoundingClientRect();
-
- // The window X and Y offsets are to adjust position when zoomed in to page
- const x = buttonRect.right + window.pageXOffset;
- const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
+ const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const {close} = ContextualMenu.createMenu(GenericTextContextMenu, {
- chevronOffset: 10,
- left: x,
- top: y,
+ ...toRightOf(buttonRect, 11),
message: successful ? _t('Copied!') : _t('Failed to copy'),
- }, false);
+ });
// Drop a reference to this close handler for componentWillUnmount
this.closeCopiedTooltip = e.target.onmouseleave = close;
}
diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js
index b04de8a9c4..b2cf44a3d1 100644
--- a/src/components/views/elements/AppTile.js
+++ b/src/components/views/elements/AppTile.js
@@ -681,10 +681,10 @@ export default class AppTile extends React.Component {
diff --git a/src/components/views/elements/Tooltip.js b/src/components/views/elements/Tooltip.js
index 8ff3ce9bdb..fa5b53ccd0 100644
--- a/src/components/views/elements/Tooltip.js
+++ b/src/components/views/elements/Tooltip.js
@@ -129,9 +129,6 @@ module.exports = createReactClass({
render: function() {
// Render a placeholder
- return (
-
-
- );
+ return
;
},
});
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index dab03cd537..d88f6511d8 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -33,6 +33,7 @@ import ReplyThread from "../elements/ReplyThread";
import {pillifyLinks} from '../../../utils/pillify';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
+import {toRightOf} from "../../structures/ContextualMenu";
module.exports = createReactClass({
displayName: 'TextualBody',
@@ -272,18 +273,12 @@ module.exports = createReactClass({
const copyCode = button.parentNode.getElementsByTagName("code")[0];
const successful = this.copyToClipboard(copyCode.textContent);
- const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const buttonRect = e.target.getBoundingClientRect();
-
- // The window X and Y offsets are to adjust position when zoomed in to page
- const x = buttonRect.right + window.pageXOffset;
- const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
+ const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const {close} = ContextualMenu.createMenu(GenericTextContextMenu, {
- chevronOffset: 10,
- left: x,
- top: y,
+ ...toRightOf(buttonRect, 11),
message: successful ? _t('Copied!') : _t('Failed to copy'),
- }, false);
+ });
e.target.onmouseleave = close;
};