diff --git a/src/jsoneditor/components/menu/ModeButton.js b/src/jsoneditor/components/menu/ModeButton.js deleted file mode 100644 index d340cf0..0000000 --- a/src/jsoneditor/components/menu/ModeButton.js +++ /dev/null @@ -1,56 +0,0 @@ -import { createElement as h, Component } from 'react' -import ModeSelector from './ModeSelector' -import { toCapital } from '../../utils/stringUtils' - -import fontawesome from '@fortawesome/fontawesome' -import faChevronDown from '@fortawesome/fontawesome-free-solid/faChevronDown' - -import './Menu.css' - -import './Search.css' - -fontawesome.library.add(faChevronDown) - -export default class ModeButton extends Component { - constructor (props) { - super (props) - - this.state = { - open: false // whether the menu is open or not - } - } - - /** - * props {{modes: string[], mode: string, onChangeMode: function, onError: function}} - */ - render () { - const { props, state} = this - - return h('div', {className: 'jsoneditor-modes'}, [ - h('button', { - key: 'button', - className: 'current-mode', - title: 'Switch mode', - onClick: this.handleOpen - }, [ - toCapital(props.mode) + ' ', - h('i', { key: 'icon', className: 'fa fa-chevron-down' }) - ]), - - h(ModeSelector, { - key: 'menu', - ...props, - open: state.open, - onRequestClose: this.handleRequestClose - }) - ]) - } - - handleOpen = () => { - this.setState({open: true}) - } - - handleRequestClose = () => { - this.setState({open: false}) - } -} diff --git a/src/jsoneditor/components/menu/ModeSelector.js b/src/jsoneditor/components/menu/ModeDropDown.js similarity index 51% rename from src/jsoneditor/components/menu/ModeSelector.js rename to src/jsoneditor/components/menu/ModeDropDown.js index cc10340..c42787e 100644 --- a/src/jsoneditor/components/menu/ModeSelector.js +++ b/src/jsoneditor/components/menu/ModeDropDown.js @@ -1,16 +1,68 @@ import { createElement as h, Component } from 'react' import { toCapital } from '../../utils/stringUtils' + +import fontawesome from '@fortawesome/fontawesome' +import faChevronDown from '@fortawesome/fontawesome-free-solid/faChevronDown' import { keyComboFromEvent } from '../../utils/keyBindings' +import './Menu.css' + +fontawesome.library.add(faChevronDown) + const MENU_CLASS_NAME = 'jsoneditor-actionmenu' const MODE_MENU_CLASS_NAME = MENU_CLASS_NAME + ' jsoneditor-modemenu' -export default class ModeSelector extends Component { +export default class ModeDropDown extends Component { + constructor (props) { + super (props) + + this.state = { + open: false // whether the menu is open or not + } + } + + componentWillUnmount () { + this.removeRequestCloseListener() + } + + /** + * props {{modes: string[], mode: string, onChangeMode: function, onError: function}} + */ + render () { + return h('div', {className: 'jsoneditor-modes'}, [ + h('button', { + key: 'button', + className: 'current-mode', + title: 'Switch mode', + onClick: this.handleOpen + }, [ + toCapital(this.props.mode) + ' ', + h('i', { key: 'icon', className: 'fa fa-chevron-down' }) + ]), + + this.renderDropDown() + ]) + } + + handleOpen = () => { + this.setState({open: true}) + + this.addRequestCloseListener() + + setTimeout(() => this.focusToFirstEntry()) + } + + handleRequestClose = () => { + this.setState({open: false}) + + this.removeRequestCloseListener() + } + /** * {{open, modes, mode, onChangeMode, onRequestClose, onError}} props */ - render () { - if (this.props.open) { + renderDropDown () { + if (this.state.open) { const items = this.props.modes.map(mode => { return h('button', { key: mode, @@ -18,22 +70,20 @@ export default class ModeSelector extends Component { className: 'jsoneditor-menu-button jsoneditor-type-modes' + ((mode === this.props.mode) ? ' jsoneditor-selected' : ''), onClick: () => { - this.props.onRequestClose() + try { + this.handleRequestClose() - // send the onChangeMode on the next tick, after onRequestClose is executed and applied - setTimeout(() => { - try { - this.props.onChangeMode(mode) - } + this.props.onChangeMode(mode) + } catch (err) { - this.props.onError(err) - } - }) + this.props.onError(err) + } } }, toCapital(mode)) }) return h('div', { + key: 'dropdown', className: MODE_MENU_CLASS_NAME, ref: 'menu', onKeyDown: this.handleKeyDown @@ -44,65 +94,6 @@ export default class ModeSelector extends Component { } } - componentDidMount () { - this.updateRequestCloseListener() - - if (this.props.open) { - this.focusToFirstEntry () - } - } - - componentDidUpdate (prevProps) { - this.updateRequestCloseListener() - - if (this.props.open && !prevProps.open) { - this.focusToFirstEntry () - } - } - - componentWillUnmount () { - // remove on next tick, since a listener can be created on next tick too - setTimeout(() => this.removeRequestCloseListener()) - } - - updateRequestCloseListener () { - if (this.props.open) { - this.addRequestCloseListener() - } - else { - this.removeRequestCloseListener() - } - } - - addRequestCloseListener () { - // Attach event listener on next tick, else the current click to open - // the menu will immediately result in requestClose event as well - setTimeout(() => { - if (!this.handleRequestClose) { - this.handleRequestClose = (event) => { - this.props.onRequestClose() - } - window.addEventListener('click', this.handleRequestClose) - } - }) - } - - removeRequestCloseListener () { - if (this.handleRequestClose) { - window.removeEventListener('click', this.handleRequestClose) - this.handleRequestClose = null - } - } - - focusToFirstEntry () { - if (this.refs.menu) { - const firstButton = this.refs.menu.querySelector('button') - if (firstButton) { - firstButton.focus() - } - } - } - handleKeyDown = (event) => { const combo = keyComboFromEvent (event) @@ -123,5 +114,32 @@ export default class ModeSelector extends Component { } } - handleRequestClose = null -} \ No newline at end of file + addRequestCloseListener () { + // Attach event listener on next tick, else the current click to open + // the menu will immediately result in requestClose event as well + setTimeout(() => { + if (!this.handleWindowRequestClose) { + this.handleWindowRequestClose = this.handleRequestClose + window.addEventListener('click', this.handleWindowRequestClose) + } + }) + } + + removeRequestCloseListener () { + if (this.handleWindowRequestClose) { + window.removeEventListener('click', this.handleWindowRequestClose) + this.handleWindowRequestClose = null + } + } + + focusToFirstEntry () { + if (this.refs.menu) { + const firstButton = this.refs.menu.querySelector('button') + if (firstButton) { + firstButton.focus() + } + } + } + + handleWindowRequestClose = null +} diff --git a/src/jsoneditor/components/menu/TextModeMenu.js b/src/jsoneditor/components/menu/TextModeMenu.js index 48fb6fa..235228a 100644 --- a/src/jsoneditor/components/menu/TextModeMenu.js +++ b/src/jsoneditor/components/menu/TextModeMenu.js @@ -1,5 +1,5 @@ import { createElement as h, PureComponent } from 'react' -import ModeButton from './ModeButton' +import ModeDropDown from './ModeDropDown' import PropTypes from 'prop-types' import fontawesome from '@fortawesome/fontawesome' @@ -30,7 +30,7 @@ export default class TreeModeMenu extends PureComponent { if (this.props.modes ) { items = items.concat([ h('div', {className: 'jsoneditor-menu-group', key: 'mode'}, [ - h(ModeButton, { + h(ModeDropDown, { key: 'mode', modes: this.props.modes, mode: this.props.mode, diff --git a/src/jsoneditor/components/menu/TreeModeMenu.js b/src/jsoneditor/components/menu/TreeModeMenu.js index 3ed9fa9..8ae34a9 100644 --- a/src/jsoneditor/components/menu/TreeModeMenu.js +++ b/src/jsoneditor/components/menu/TreeModeMenu.js @@ -1,5 +1,5 @@ import { createElement as h, PureComponent } from 'react' -import ModeButton from './ModeButton' +import ModeDropDown from './ModeDropDown' import PropTypes from 'prop-types' import fontawesome from '@fortawesome/fontawesome' @@ -69,7 +69,7 @@ export default class TreeModeMenu extends PureComponent { if (this.props.modes ) { items = items.concat([ h('div', {className: 'jsoneditor-menu-group', key: 'mode'}, [ - h(ModeButton, { + h(ModeDropDown, { key: 'mode', modes: this.props.modes, mode: this.props.mode,