Search focus and quick keys working
This commit is contained in:
parent
b4a0dd6a3d
commit
263f38f45c
|
@ -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
|
||||||
|
|
|
@ -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' })
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue