Search focus and quick keys working

This commit is contained in:
jos 2018-09-19 14:12:56 +02:00
parent b4a0dd6a3d
commit 263f38f45c
4 changed files with 86 additions and 30 deletions

View File

@ -40,7 +40,6 @@ import {
moveRight, moveRight,
moveUp, moveUp,
searchHasFocus, searchHasFocus,
selectFind,
setSelection setSelection
} from './utils/domSelector' } from './utils/domSelector'
import { createFindKeyBinding } from '../utils/keyBindings' import { createFindKeyBinding } from '../utils/keyBindings'
@ -319,6 +318,7 @@ export default class TreeMode extends PureComponent {
return h(Search, { return h(Search, {
key: 'search', key: 'search',
ref: 'search',
text: this.state.searchResult.text, text: this.state.searchResult.text,
resultCount: this.state.searchResult.matches resultCount: this.state.searchResult.matches
@ -327,6 +327,7 @@ export default class TreeMode extends PureComponent {
onNext: this.handleSearchNext, onNext: this.handleSearchNext,
onPrevious: this.handleSearchPrevious, onPrevious: this.handleSearchPrevious,
onClose: this.handleCloseSearch, onClose: this.handleCloseSearch,
onFocusActive: this.handleSearchFocusActive,
findKeyBinding: this.props.findKeyBinding, findKeyBinding: this.props.findKeyBinding,
delay: SEARCH_DEBOUNCE delay: SEARCH_DEBOUNCE
}) })
@ -681,7 +682,14 @@ export default class TreeMode extends PureComponent {
handleFocusFind = (event) => { handleFocusFind = (event) => {
event.preventDefault() 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) => { handleSearchNext = (event) => {
@ -733,9 +741,7 @@ export default class TreeMode extends PureComponent {
} }
} }
handleCloseSearch = (event) => { handleCloseSearch = () => {
event.preventDefault()
const { eson, searchResult } = search(this.state.eson, '') const { eson, searchResult } = search(this.state.eson, '')
this.setState({ 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 * Apply a JSONPatch to the current JSON document and emit a change event
* @param {JSONPatchDocument} operations * @param {JSONPatchDocument} operations

View File

@ -14,7 +14,7 @@ export default class DropDown extends Component {
static propTypes = { static propTypes = {
value: PropTypes.string, value: PropTypes.string,
text: PropTypes.string, text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
title: PropTypes.string, title: PropTypes.string,
options: PropTypes.arrayOf(PropTypes.shape({ options: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
@ -56,10 +56,10 @@ export default class DropDown extends Component {
title: this.props.title, title: this.props.title,
onClick: this.handleOpen onClick: this.handleOpen
}, [ }, [
typeof selectedText === 'string' h('span', {key: 'text'}, typeof selectedText === 'string'
? toCapital(selectedText) ? toCapital(selectedText)
: selectedText, : selectedText,
'\u00A0\u00A0', '\u00A0\u00A0'),
h('i', { key: 'icon', className: 'fa fa-chevron-down' }) h('i', { key: 'icon', className: 'fa fa-chevron-down' })
]), ]),

View File

@ -1,7 +1,6 @@
import { createElement as h, Component } from 'react' import { Component, createElement as h } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { keyComboFromEvent } from '../../utils/keyBindings' import { keyComboFromEvent } from '../../utils/keyBindings'
import { findEditorContainer, setSelection } from '../utils/domSelector'
import fontawesome from '@fortawesome/fontawesome' import fontawesome from '@fortawesome/fontawesome'
import faSearch from '@fortawesome/fontawesome-free-solid/faSearch' import faSearch from '@fortawesome/fontawesome-free-solid/faSearch'
@ -16,6 +15,17 @@ import './Search.css'
fontawesome.library.add(faSearch, faChevronUp, faChevronDown, faTimes) fontawesome.library.add(faSearch, faChevronUp, faChevronDown, faTimes)
export default class Search extends Component { 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) { constructor (props) {
super (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 () { render () {
return h('div', {className: 'jsoneditor-search'}, return h('div', {className: 'jsoneditor-search'},
h('form', { h('form', {
@ -37,6 +58,7 @@ export default class Search extends Component {
h('input', { h('input', {
key: 'input', key: 'input',
type: 'text', type: 'text',
ref: this.setSearchInputRef,
className: 'jsoneditor-search-text', className: 'jsoneditor-search-text',
value: this.state.text, value: this.state.text,
onInput: this.handleChange, onInput: this.handleChange,
@ -61,7 +83,7 @@ export default class Search extends Component {
type: 'button', type: 'button',
className: 'jsoneditor-search-close', className: 'jsoneditor-search-close',
title: 'Close search', title: 'Close search',
onClick: this.props.onClose onClick: this.handleClose
}, h('i', {className: 'fa fa-times'})), }, h('i', {className: 'fa fa-times'})),
]), ]),
// this.renderResultsCount(this.props.resultCount) // FIXME: show result count // this.renderResultsCount(this.props.resultCount) // FIXME: show result count
@ -86,10 +108,15 @@ export default class Search extends Component {
return null return null
} }
componentWillReceiveProps (nextProps) { searchInput = null
if (nextProps.text !== this.props.text) {
// clear a pending onChange callback (if any) setSearchInputRef = (element) => {
clearTimeout(this.timeout) 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) this.timeout = setTimeout(this.debouncedOnChange, delay)
} }
handleClose = () => {
this.props.onClose()
}
handleKeyDown = (event) => { handleKeyDown = (event) => {
// TODO: make submit (Enter) and focus to search result (Ctrl+Enter) customizable // TODO: make submit (Enter) and focus to search result (Ctrl+Enter) customizable
const combo = keyComboFromEvent(event) const combo = keyComboFromEvent(event)
if (combo === 'Ctrl+Enter' || combo === 'Command+Enter') { if (combo === 'Ctrl+Enter' || combo === 'Command+Enter') {
event.preventDefault() event.preventDefault()
const active = this.props.searchResults[0] this.props.onFocusActive()
if (active) {
const container = findEditorContainer(event.target)
setSelection(container, active.path, active.type)
} }
if (combo === 'Escape') {
event.preventDefault()
this.handleClose()
} }
} }

View File

@ -8,7 +8,6 @@ import { compileJSONPointer, parseJSONPointer } from '../../jsonPointer'
let lastInputName = null let lastInputName = null
// TODO: create a constants file with the CSS names that are used in domSelector // 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 SEARCH_COMPONENT_CLASS_NAME = 'jsoneditor-search'
const NODE_CONTAINER_CLASS_NAME = 'jsoneditor-node-container' const NODE_CONTAINER_CLASS_NAME = 'jsoneditor-node-container'
const CONTENTS_CONTAINER_CLASS_NAME = 'jsoneditor-tree-contents' const CONTENTS_CONTAINER_CLASS_NAME = 'jsoneditor-tree-contents'
@ -142,15 +141,6 @@ export function moveRight (fromElement) {
setSelection(container, path, lastInputName) 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 * Set selection to a specific node and input field
* @param {Element} container * @param {Element} container
@ -215,6 +205,10 @@ function findPreviousNode (element) {
const container = findContentsContainer(element) const container = findContentsContainer(element)
const node = findBaseNode(element) const node = findBaseNode(element)
if (!container || !node) {
return
}
// TODO: is the following querySelectorAll a performance bottleneck? // TODO: is the following querySelectorAll a performance bottleneck?
const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME)) const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME))
const index = all.indexOf(node) const index = all.indexOf(node)
@ -226,6 +220,10 @@ function findNextNode (element) {
const container = findContentsContainer(element) const container = findContentsContainer(element)
const node = findBaseNode(element) const node = findBaseNode(element)
if (!container || !node) {
return
}
// TODO: is the following querySelectorAll a performance bottleneck? // TODO: is the following querySelectorAll a performance bottleneck?
const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME)) const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME))
const index = all.indexOf(node) const index = all.indexOf(node)
@ -236,6 +234,10 @@ function findNextNode (element) {
function findFirstNode (element) { function findFirstNode (element) {
const container = findContentsContainer(element) const container = findContentsContainer(element)
if (!container) {
return
}
// TODO: is the following querySelectorAll a performance bottleneck? // TODO: is the following querySelectorAll a performance bottleneck?
const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME)) const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME))
@ -245,6 +247,10 @@ function findFirstNode (element) {
function findLastNode (element) { function findLastNode (element) {
const container = findContentsContainer(element) const container = findContentsContainer(element)
if (!container) {
return
}
// TODO: is the following querySelectorAll a performance bottleneck? // TODO: is the following querySelectorAll a performance bottleneck?
const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME)) const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME))
@ -255,6 +261,10 @@ function findNextSibling (element) {
const container = findContentsContainer(element) const container = findContentsContainer(element)
const node = findBaseNode(element) const node = findBaseNode(element)
if (!container || !node) {
return
}
// TODO: is the following querySelectorAll a performance bottleneck? // TODO: is the following querySelectorAll a performance bottleneck?
const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME)) const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME))
const index = all.indexOf(node) const index = all.indexOf(node)