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

View File

@ -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' })
]),

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 { 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()
}
}

View File

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