diff --git a/src/components/TreeMode.js b/src/components/TreeMode.js index f04cd5e..c380c6a 100644 --- a/src/components/TreeMode.js +++ b/src/components/TreeMode.js @@ -23,7 +23,7 @@ import JSONNodeForm from './JSONNodeForm' import ModeButton from './menu/ModeButton' import Search from './menu/Search' import { - moveUp, moveDown, moveLeft, moveRight, moveDownSibling, + moveUp, moveDown, moveLeft, moveRight, moveDownSibling, moveHome, moveEnd, findNode, selectFind, searchHasFocus, setSelection } from './utils/domSelector' import { keyComboFromEvent } from '../utils/keyBindings' @@ -41,7 +41,6 @@ const SEARCH_DEBOUNCE = 300 // milliseconds const SCROLL_DURATION = 400 // milliseconds // TODO: make key bindings configurable -// TODO: implement support for namespaces for key bindings const KEY_BINDINGS = { 'duplicate': ['Ctrl+D', 'Command+D'], 'insert': ['Ctrl+Insert', 'Command+Insert'], @@ -57,6 +56,8 @@ const KEY_BINDINGS = { 'down': ['Alt+Down', 'Option+Down'], 'left': ['Alt+Left', 'Option+Left'], 'right': ['Alt+Right', 'Option+Right'], + 'home': ['Alt+Home', 'Option+Home'], + 'end': ['Alt+End', 'Option+End'], 'openUrl': ['Ctrl+Enter', 'Command+Enter'] // TODO: implement Ctrl+Shift+Arrow Up/Down Select multiple fields // TODO: implement Shift+Alt+Arrows Move current field or selected fields up/down/left/right @@ -71,6 +72,8 @@ export default class TreeMode extends Component { 'down': (event) => moveDown(event.target), 'left': (event) => moveLeft(event.target), 'right': (event) => moveRight(event.target), + 'home': (event) => moveHome(event.target), + 'end': (event) => moveEnd(event.target), 'undo': (event) => this.undo(), 'redo': (event) => this.redo(), 'find': (event) => selectFind(event.target), diff --git a/src/components/utils/domSelector.js b/src/components/utils/domSelector.js index b7bdf1f..17cd28e 100644 --- a/src/components/utils/domSelector.js +++ b/src/components/utils/domSelector.js @@ -46,21 +46,10 @@ const INPUT_NAME_RULES = { * Move the selection to the input field above current selected input * @param {Element} fromElement * @param {String} [inputName] Optional name of the input where to move the focus - * @return {boolean} Returns true when successfully moved down + * @return {boolean} Returns true when successfully moved up */ export function moveUp (fromElement, inputName = null) { - const prev = findPreviousNode(fromElement) - if (prev) { - if (!lastInputName) { - lastInputName = inputName || getInputName(fromElement) - } - - const container = findContentsContainer(fromElement) - const path = parseJSONPointer(prev.getAttribute(PATH_ATTRIBUTE)) - return setSelection(container, path, lastInputName) - } - - return false + return moveTo(fromElement, findPreviousNode(fromElement), inputName) } /** @@ -70,18 +59,7 @@ export function moveUp (fromElement, inputName = null) { * @return {boolean} Returns true when successfully moved up */ export function moveDown (fromElement, inputName = null) { - const next = findNextNode(fromElement) - if (next) { - if (!lastInputName) { - lastInputName = inputName || getInputName(fromElement) - } - - const container = findContentsContainer(fromElement) - const path = parseJSONPointer(next.getAttribute(PATH_ATTRIBUTE)) - return setSelection(container, path, lastInputName) - } - - return false + return moveTo(fromElement, findNextNode(fromElement), inputName) } /** @@ -92,18 +70,50 @@ export function moveDown (fromElement, inputName = null) { * @return {boolean} Returns true when successfully moved up */ export function moveDownSibling (fromElement, inputName = null) { - const next = findNextSibling(fromElement) - if (next) { + return moveTo(fromElement, findNextSibling(fromElement), inputName) +} + +/** + * Move the selection to the first node + * @param {Element} fromElement + * @param {String} [inputName] Optional name of the input where to move the focus + * @return {boolean} Returns true when successfully moved to home + */ +export function moveHome (fromElement, inputName = null) { + return moveTo(fromElement, findFirstNode(fromElement), inputName) +} + +/** + * Move the selection to the last node + * @param {Element} fromElement + * @param {String} [inputName] Optional name of the input where to move the focus + * @return {boolean} Returns true when successfully moved to home + */ +export function moveEnd (fromElement, inputName = null) { + return moveTo(fromElement, findLastNode(fromElement), inputName) +} + +/** + * Move from an element to another element + * @param {Element} fromElement + * @param {Element} toElement + * @param {string} [inputName] + * @return {boolean} Returns true when successfully moved + */ +function moveTo (fromElement, toElement, inputName = null) { + if (toElement) { if (!lastInputName) { lastInputName = inputName || getInputName(fromElement) } const container = findContentsContainer(fromElement) - const path = parseJSONPointer(next.getAttribute(PATH_ATTRIBUTE)) + const path = parseJSONPointer(toElement.getAttribute(PATH_ATTRIBUTE)) + return setSelection(container, path, lastInputName) } - - return false + else { + return false + } } /** @@ -222,6 +232,24 @@ function findNextNode (element) { return all[index + 1] } +function findFirstNode (element) { + const container = findContentsContainer(element) + + // TODO: is the following querySelectorAll a performance bottleneck? + const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME)) + + return all[0] +} + +function findLastNode (element) { + const container = findContentsContainer(element) + + // TODO: is the following querySelectorAll a performance bottleneck? + const all = Array.from(container.querySelectorAll('div.' + NODE_CONTAINER_CLASS_NAME)) + + return all[all.length - 1] +} + function findNextSibling (element) { const container = findContentsContainer(element) const node = findBaseNode(element)