diff --git a/src/jsoneditor/components/TreeMode.js b/src/jsoneditor/components/TreeMode.js index 200e008..af2a1e3 100644 --- a/src/jsoneditor/components/TreeMode.js +++ b/src/jsoneditor/components/TreeMode.js @@ -40,7 +40,6 @@ import { moveRight, moveUp, searchHasFocus, - selectFind, setSelection } from './utils/domSelector' import { createFindKeyBinding } from '../utils/keyBindings' @@ -319,6 +318,7 @@ export default class TreeMode extends PureComponent { return h(Search, { key: 'search', + ref: 'search', text: this.state.searchResult.text, resultCount: this.state.searchResult.matches @@ -327,6 +327,7 @@ export default class TreeMode extends PureComponent { onNext: this.handleSearchNext, onPrevious: this.handleSearchPrevious, onClose: this.handleCloseSearch, + onFocusActive: this.handleSearchFocusActive, findKeyBinding: this.props.findKeyBinding, delay: SEARCH_DEBOUNCE }) @@ -681,7 +682,14 @@ export default class TreeMode extends PureComponent { handleFocusFind = (event) => { event.preventDefault() - selectFind(event.target) + + if (this.refs.search) { + this.refs.search.select() + } + else { + // search will select automatically when created + this.setState({ showSearch: true }) + } } handleSearchNext = (event) => { @@ -733,9 +741,7 @@ export default class TreeMode extends PureComponent { } } - handleCloseSearch = (event) => { - event.preventDefault() - + handleCloseSearch = () => { const { eson, searchResult } = search(this.state.eson, '') this.setState({ @@ -745,6 +751,13 @@ export default class TreeMode extends PureComponent { }) } + handleSearchFocusActive = () => { + const active = this.state.searchResult.active + if (active && active.area) { + setSelection(this.refs.contents, active.path, active.area) + } + } + /** * Apply a JSONPatch to the current JSON document and emit a change event * @param {JSONPatchDocument} operations diff --git a/src/jsoneditor/components/menu/DropDown.js b/src/jsoneditor/components/menu/DropDown.js index 9f541e3..37e8124 100644 --- a/src/jsoneditor/components/menu/DropDown.js +++ b/src/jsoneditor/components/menu/DropDown.js @@ -14,7 +14,7 @@ export default class DropDown extends Component { static propTypes = { value: PropTypes.string, - text: PropTypes.string, + text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), title: PropTypes.string, options: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string.isRequired, @@ -56,10 +56,10 @@ export default class DropDown extends Component { title: this.props.title, onClick: this.handleOpen }, [ - typeof selectedText === 'string' + h('span', {key: 'text'}, typeof selectedText === 'string' ? toCapital(selectedText) : selectedText, - '\u00A0\u00A0', + '\u00A0\u00A0'), h('i', { key: 'icon', className: 'fa fa-chevron-down' }) ]), diff --git a/src/jsoneditor/components/menu/Search.js b/src/jsoneditor/components/menu/Search.js index aed15d7..be57441 100644 --- a/src/jsoneditor/components/menu/Search.js +++ b/src/jsoneditor/components/menu/Search.js @@ -1,7 +1,6 @@ -import { createElement as h, Component } from 'react' +import { Component, createElement as h } from 'react' import PropTypes from 'prop-types' import { keyComboFromEvent } from '../../utils/keyBindings' -import { findEditorContainer, setSelection } from '../utils/domSelector' import fontawesome from '@fortawesome/fontawesome' import faSearch from '@fortawesome/fontawesome-free-solid/faSearch' @@ -16,6 +15,17 @@ import './Search.css' fontawesome.library.add(faSearch, faChevronUp, faChevronDown, faTimes) export default class Search extends Component { + + static propTypes = { + text: PropTypes.string, + onChange: PropTypes.func, + onNext: PropTypes.func.isRequired, + onPrevious: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, + onFocusActive: PropTypes.func.isRequired, + delay: PropTypes.number, + } + constructor (props) { super (props) @@ -24,6 +34,17 @@ export default class Search extends Component { } } + componentDidMount () { + this.select() + } + + componentWillReceiveProps (nextProps) { + if (nextProps.text !== this.props.text) { + // clear a pending onChange callback (if any) + clearTimeout(this.timeout) + } + } + render () { return h('div', {className: 'jsoneditor-search'}, h('form', { @@ -37,6 +58,7 @@ export default class Search extends Component { h('input', { key: 'input', type: 'text', + ref: this.setSearchInputRef, className: 'jsoneditor-search-text', value: this.state.text, onInput: this.handleChange, @@ -61,7 +83,7 @@ export default class Search extends Component { type: 'button', className: 'jsoneditor-search-close', title: 'Close search', - onClick: this.props.onClose + onClick: this.handleClose }, h('i', {className: 'fa fa-times'})), ]), // this.renderResultsCount(this.props.resultCount) // FIXME: show result count @@ -86,10 +108,15 @@ export default class Search extends Component { return null } - componentWillReceiveProps (nextProps) { - if (nextProps.text !== this.props.text) { - // clear a pending onChange callback (if any) - clearTimeout(this.timeout) + searchInput = null + + setSearchInputRef = (element) => { + this.searchInput = element + } + + select () { + if (this.searchInput) { + this.searchInput.select() } } @@ -113,16 +140,22 @@ export default class Search extends Component { this.timeout = setTimeout(this.debouncedOnChange, delay) } + handleClose = () => { + this.props.onClose() + } + handleKeyDown = (event) => { // TODO: make submit (Enter) and focus to search result (Ctrl+Enter) customizable const combo = keyComboFromEvent(event) if (combo === 'Ctrl+Enter' || combo === 'Command+Enter') { event.preventDefault() - const active = this.props.searchResults[0] - if (active) { - const container = findEditorContainer(event.target) - setSelection(container, active.path, active.type) - } + this.props.onFocusActive() + } + + if (combo === 'Escape') { + event.preventDefault() + + this.handleClose() } } diff --git a/src/jsoneditor/components/utils/domSelector.js b/src/jsoneditor/components/utils/domSelector.js index 7455721..c81a762 100644 --- a/src/jsoneditor/components/utils/domSelector.js +++ b/src/jsoneditor/components/utils/domSelector.js @@ -8,7 +8,6 @@ import { compileJSONPointer, parseJSONPointer } from '../../jsonPointer' let lastInputName = null // TODO: create a constants file with the CSS names that are used in domSelector -const SEARCH_TEXT_CLASS_NAME = 'jsoneditor-search-text' const SEARCH_COMPONENT_CLASS_NAME = 'jsoneditor-search' const NODE_CONTAINER_CLASS_NAME = 'jsoneditor-node-container' const CONTENTS_CONTAINER_CLASS_NAME = 'jsoneditor-tree-contents' @@ -142,15 +141,6 @@ export function moveRight (fromElement) { setSelection(container, path, lastInputName) } -export function selectFind (eventTarget) { - const container = findEditorContainer(eventTarget) - const searchText = container.querySelector('input.' + SEARCH_TEXT_CLASS_NAME) - - if (searchText) { - searchText.select() - } -} - /** * Set selection to a specific node and input field * @param {Element} container @@ -215,6 +205,10 @@ function findPreviousNode (element) { const container = findContentsContainer(element) const node = findBaseNode(element) + if (!container || !node) { + return + } + // TODO: is the following querySelectorAll a performance bottleneck? const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME)) const index = all.indexOf(node) @@ -226,6 +220,10 @@ function findNextNode (element) { const container = findContentsContainer(element) const node = findBaseNode(element) + if (!container || !node) { + return + } + // TODO: is the following querySelectorAll a performance bottleneck? const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME)) const index = all.indexOf(node) @@ -236,6 +234,10 @@ function findNextNode (element) { function findFirstNode (element) { const container = findContentsContainer(element) + if (!container) { + return + } + // TODO: is the following querySelectorAll a performance bottleneck? const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME)) @@ -245,6 +247,10 @@ function findFirstNode (element) { function findLastNode (element) { const container = findContentsContainer(element) + if (!container) { + return + } + // TODO: is the following querySelectorAll a performance bottleneck? const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME)) @@ -255,6 +261,10 @@ function findNextSibling (element) { const container = findContentsContainer(element) const node = findBaseNode(element) + if (!container || !node) { + return + } + // TODO: is the following querySelectorAll a performance bottleneck? const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME)) const index = all.indexOf(node)