Refactor ModeDropDown

This commit is contained in:
jos 2018-09-19 11:43:40 +02:00
parent 0349f94ead
commit 2c560264c0
4 changed files with 95 additions and 133 deletions

View File

@ -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})
}
}

View File

@ -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
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
}

View File

@ -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,

View File

@ -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,