Implemented key bindings in Menu

This commit is contained in:
jos 2017-07-19 20:50:05 +02:00
parent 3ca23568cf
commit 633daf6c95
3 changed files with 74 additions and 16 deletions

View File

@ -58,11 +58,8 @@ const KEY_BINDINGS = {
'left': ['Alt+Left', 'Option+Left'], 'left': ['Alt+Left', 'Option+Left'],
'right': ['Alt+Right', 'Option+Right'], 'right': ['Alt+Right', 'Option+Right'],
'openUrl': ['Ctrl+Enter', 'Command+Enter'] 'openUrl': ['Ctrl+Enter', 'Command+Enter']
// TODO: implement all quick keys // TODO: implement Ctrl+Shift+Arrow Up/Down Select multiple fields
// Ctrl+Shift+Arrow Up/Down Select multiple fields // TODO: implement Shift+Alt+Arrows Move current field or selected fields up/down/left/right
// Shift+Alt+Arrows Move current field or selected fields up/down/left/right
// Ctrl+Z Undo last action
// Ctrl+Shift+Z Redo
} }
export default class TreeMode extends Component { export default class TreeMode extends Component {

View File

@ -1,8 +1,12 @@
import { createElement as h, Component } from 'react' 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 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 { 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 // 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') ((orientation === 'top') ? 'jsoneditor-actionmenu-top' : 'jsoneditor-actionmenu-bottom')
return h('div', { return h('div', {
className: className, className: className,
'data-menu': 'true', ref: 'menu',
onKeyDown: this.handleKeyDown
}, },
this.props.items.map(this.renderMenuItem) this.props.items.map(this.renderMenuItem)
) )
@ -151,11 +156,17 @@ export default class Menu extends Component {
componentDidMount () { componentDidMount () {
this.updateRequestCloseListener() this.updateRequestCloseListener()
if (this.props.open) {
this.focusToFirstEntry ()
}
} }
componentDidUpdate () { componentDidUpdate (prevProps, prevState) {
this.updateRequestCloseListener() this.updateRequestCloseListener()
if (this.props.open && !prevProps.open) {
this.focusToFirstEntry ()
}
} }
componentWillUnmount () { componentWillUnmount () {
@ -163,6 +174,15 @@ export default class Menu extends Component {
setTimeout(() => this.removeRequestCloseListener()) setTimeout(() => this.removeRequestCloseListener())
} }
focusToFirstEntry () {
if (this.refs.menu) {
const firstButton = this.refs.menu.querySelector('button')
if (firstButton) {
firstButton.focus()
}
}
}
updateRequestCloseListener () { updateRequestCloseListener () {
if (this.props.open) { if (this.props.open) {
this.addRequestCloseListener() this.addRequestCloseListener()
@ -178,9 +198,7 @@ export default class Menu extends Component {
setTimeout(() => { setTimeout(() => {
if (!this.handleRequestClose) { if (!this.handleRequestClose) {
this.handleRequestClose = (event) => { this.handleRequestClose = (event) => {
if (!findParentWithAttribute(event.target, 'data-menu', 'true')) { this.props.onRequestClose()
this.props.onRequestClose()
}
} }
window.addEventListener('click', this.handleRequestClose) 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 handleRequestClose = null
} }

View File

@ -1,6 +1,5 @@
import { createElement as h, Component } from 'react' import { createElement as h, Component } from 'react'
import { toCapital } from '../../utils/stringUtils' import { toCapital } from '../../utils/stringUtils'
import { findParentWithAttribute } from '../../utils/domUtils'
export default class ModeMenu extends Component { export default class ModeMenu extends Component {
/** /**
@ -63,9 +62,7 @@ export default class ModeMenu extends Component {
setTimeout(() => { setTimeout(() => {
if (!this.handleRequestClose) { if (!this.handleRequestClose) {
this.handleRequestClose = (event) => { this.handleRequestClose = (event) => {
if (!findParentWithAttribute(event.target, 'data-menu', 'true')) {
this.props.onRequestClose() this.props.onRequestClose()
}
} }
window.addEventListener('click', this.handleRequestClose) window.addEventListener('click', this.handleRequestClose)
} }