From 633daf6c953d7b9dae501ec1c56552e786441e80 Mon Sep 17 00:00:00 2001 From: jos Date: Wed, 19 Jul 2017 20:50:05 +0200 Subject: [PATCH] Implemented key bindings in Menu --- src/components/TreeMode.js | 7 +-- src/components/menu/Menu.js | 80 +++++++++++++++++++++++++++++---- src/components/menu/ModeMenu.js | 3 -- 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/src/components/TreeMode.js b/src/components/TreeMode.js index f04f4f1..f04cd5e 100644 --- a/src/components/TreeMode.js +++ b/src/components/TreeMode.js @@ -58,11 +58,8 @@ const KEY_BINDINGS = { 'left': ['Alt+Left', 'Option+Left'], 'right': ['Alt+Right', 'Option+Right'], 'openUrl': ['Ctrl+Enter', 'Command+Enter'] - // TODO: implement all quick keys - // Ctrl+Shift+Arrow Up/Down Select multiple fields - // Shift+Alt+Arrows Move current field or selected fields up/down/left/right - // Ctrl+Z Undo last action - // Ctrl+Shift+Z Redo + // TODO: implement Ctrl+Shift+Arrow Up/Down Select multiple fields + // TODO: implement Shift+Alt+Arrows Move current field or selected fields up/down/left/right } export default class TreeMode extends Component { diff --git a/src/components/menu/Menu.js b/src/components/menu/Menu.js index ed3d1eb..226beec 100644 --- a/src/components/menu/Menu.js +++ b/src/components/menu/Menu.js @@ -1,8 +1,12 @@ import { createElement as h, Component } from 'react' -import { findParentWithAttribute } from '../../utils/domUtils' +import { keyComboFromEvent } from '../../utils/keyBindings' +import { findParentWithClassName } from '../../utils/domUtils' export let CONTEXT_MENU_HEIGHT = 240 +const MENU_CLASS_NAME = 'jsoneditor-actionmenu' +const MENU_ITEM_CLASS_NAME = 'jsoneditor-menu-item' + export default class Menu extends Component { /** @@ -33,12 +37,13 @@ export default class Menu extends Component { // TODO: create a non-visible button to set the focus to the menu - const className = 'jsoneditor-actionmenu ' + + const className = MENU_CLASS_NAME + ' ' + ((orientation === 'top') ? 'jsoneditor-actionmenu-top' : 'jsoneditor-actionmenu-bottom') return h('div', { className: className, - 'data-menu': 'true', + ref: 'menu', + onKeyDown: this.handleKeyDown }, this.props.items.map(this.renderMenuItem) ) @@ -151,11 +156,17 @@ export default class Menu extends Component { componentDidMount () { this.updateRequestCloseListener() - + if (this.props.open) { + this.focusToFirstEntry () + } } - componentDidUpdate () { + componentDidUpdate (prevProps, prevState) { this.updateRequestCloseListener() + + if (this.props.open && !prevProps.open) { + this.focusToFirstEntry () + } } componentWillUnmount () { @@ -163,6 +174,15 @@ export default class Menu extends Component { setTimeout(() => this.removeRequestCloseListener()) } + focusToFirstEntry () { + if (this.refs.menu) { + const firstButton = this.refs.menu.querySelector('button') + if (firstButton) { + firstButton.focus() + } + } + } + updateRequestCloseListener () { if (this.props.open) { this.addRequestCloseListener() @@ -178,9 +198,7 @@ export default class Menu extends Component { setTimeout(() => { if (!this.handleRequestClose) { this.handleRequestClose = (event) => { - if (!findParentWithAttribute(event.target, 'data-menu', 'true')) { - this.props.onRequestClose() - } + this.props.onRequestClose() } window.addEventListener('click', this.handleRequestClose) } @@ -194,5 +212,51 @@ export default class Menu extends Component { } } + // TODO: implement the same in ModeMenu + handleKeyDown = (event) => { + const combo = keyComboFromEvent (event) + if (combo === 'Up') { + event.preventDefault() + + const items = Menu.getItems (event.target) + const index = items.findIndex(item => item === event.target.parentNode) + const prev = items[index - 1] + if (prev) { + prev.querySelector('button').focus() + } + } + + if (combo === 'Down') { + event.preventDefault() + + const items = Menu.getItems (event.target) + const index = items.findIndex(item => item === event.target.parentNode) + const next = items[index + 1] + if (next) { + next.querySelector('button').focus() + } + } + + if (combo === 'Left') { + const left = event.target.previousSibling + if (left && left.nodeName === 'BUTTON') { + left.focus() + } + } + + if (combo === 'Right') { + const right = event.target.nextSibling + if (right && right.nodeName === 'BUTTON') { + right.focus() + } + } + } + + static getItems (element) { + const menu = findParentWithClassName(element, MENU_CLASS_NAME) + return Array.from(menu.querySelectorAll('.' + MENU_ITEM_CLASS_NAME)) + .filter(item => window.getComputedStyle(item).visibility === 'visible') + } + handleRequestClose = null } diff --git a/src/components/menu/ModeMenu.js b/src/components/menu/ModeMenu.js index 849667a..106ab1a 100644 --- a/src/components/menu/ModeMenu.js +++ b/src/components/menu/ModeMenu.js @@ -1,6 +1,5 @@ import { createElement as h, Component } from 'react' import { toCapital } from '../../utils/stringUtils' -import { findParentWithAttribute } from '../../utils/domUtils' export default class ModeMenu extends Component { /** @@ -63,9 +62,7 @@ export default class ModeMenu extends Component { setTimeout(() => { if (!this.handleRequestClose) { this.handleRequestClose = (event) => { - if (!findParentWithAttribute(event.target, 'data-menu', 'true')) { this.props.onRequestClose() - } } window.addEventListener('click', this.handleRequestClose) }