From 8e3dfcf0d0cb99d9bbf808a6715b8c2a74b11bf0 Mon Sep 17 00:00:00 2001 From: damencho Date: Tue, 6 Jun 2017 15:36:25 -0500 Subject: [PATCH] Handles Enter key to submit dialogs. If there is no focused node inside the dialog we focus any of the available buttons, submit button is first. --- .../dialog/components/StatelessDialog.web.js | 139 +++++++++++++++--- 1 file changed, 122 insertions(+), 17 deletions(-) diff --git a/react/features/base/dialog/components/StatelessDialog.web.js b/react/features/base/dialog/components/StatelessDialog.web.js index ff39b0845c..b74b8df385 100644 --- a/react/features/base/dialog/components/StatelessDialog.web.js +++ b/react/features/base/dialog/components/StatelessDialog.web.js @@ -7,6 +7,18 @@ import { translate } from '../../i18n'; import { DIALOG_PROP_TYPES } from '../constants'; +/** + * The ID to be used for the cancel button if enabled. + * @type {string} + */ +const CANCEL_BUTTON_ID = 'modal-dialog-cancel-button'; + +/** + * The ID to be used for the ok button if enabled. + * @type {string} + */ +const OK_BUTTON_ID = 'modal-dialog-ok-button'; + /** * Web dialog that uses atlaskit modal-dialog to display dialogs. */ @@ -63,7 +75,34 @@ class StatelessDialog extends Component { // Bind event handlers so they are only bound once for every instance. this._onCancel = this._onCancel.bind(this); this._onDialogDismissed = this._onDialogDismissed.bind(this); + this._onKeyDown = this._onKeyDown.bind(this); this._onSubmit = this._onSubmit.bind(this); + this._setDialogElement = this._setDialogElement.bind(this); + } + + /** + * React Component method that executes once component is mounted. + * + * @inheritdoc + */ + componentDidMount() { + this._updateButtonFocus(); + } + + /** + * React Component method that executes once component is updated. + * + * @param {Object} prevProps - The previous properties, before the update. + * @returns {void} + */ + componentDidUpdate(prevProps) { + // if there is an update in any of the buttons enable/disable props + // update the focus if needed + if (prevProps.okDisabled !== this.props.okDisabled + || prevProps.cancelDisabled !== this.props.cancelDisabled + || prevProps.submitDisabled !== this.props.submitDisabled) { + this._updateButtonFocus(); + } } /** @@ -74,21 +113,25 @@ class StatelessDialog extends Component { */ render() { return ( - -
- -
-
+
+ +
+ +
+
+
); } @@ -139,7 +182,7 @@ class StatelessDialog extends Component { return ( { this.props.t(this.props.cancelTitleKey || 'dialog.Cancel') } @@ -196,13 +239,75 @@ class StatelessDialog extends Component { { this.props.t(this.props.okTitleKey || 'dialog.Ok') } ); } + + /** + * Sets the instance variable for the div containing the component's dialog + * element so it can be accessed directly. + * + * @param {Object} element - The DOM element for the component's dialog. + * @private + * @returns {void} + */ + _setDialogElement(element) { + this._dialogElement = element; + } + + /** + * Handles 'Enter' key in the dialog to submit/hide dialog depending on + * the available buttons and their disabled state. + * + * @param {Object} event - the key event. + * @private + * @returns {void} + */ + _onKeyDown(event) { + if (event.key === 'Enter') { + if (this.props.submitDisabled && !this.props.cancelDisabled) { + this._onCancel(); + } else if (!this.props.okDisabled) { + this._onSubmit(); + } + } + } + + /** + * Updates focused button, if we have a reference to the dialog element. + * Focus on available button if there is no focus already. + * + * @private + * @returns {void} + */ + _updateButtonFocus() { + if (this._dialogElement) { + + // if we have a focused element inside the dialog, skip changing + // the focus + if (this._dialogElement.contains(document.activeElement)) { + return; + } + + let buttonToFocus; + + if (this.props.submitDisabled) { + buttonToFocus = this._dialogElement + .querySelector(`[id=${CANCEL_BUTTON_ID}]`); + } else if (!this.props.okDisabled) { + buttonToFocus = this._dialogElement + .querySelector(`[id=${OK_BUTTON_ID}]`); + } + + if (buttonToFocus) { + buttonToFocus.focus(); + } + } + } } export default translate(StatelessDialog);