From 1638080e8528c33f3314cd71879725aa9ffa1053 Mon Sep 17 00:00:00 2001 From: Thomas Nordquist Date: Tue, 25 Jun 2019 14:30:47 +0200 Subject: [PATCH] Improve keyboard arrow navigation --- app/src/actions/visibleTreeTraversal.ts | 58 ++++++++++++++++++++++--- app/src/components/Tree/TreeNode.tsx | 8 +++- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/app/src/actions/visibleTreeTraversal.ts b/app/src/actions/visibleTreeTraversal.ts index 3c5549b..8019964 100644 --- a/app/src/actions/visibleTreeTraversal.ts +++ b/app/src/actions/visibleTreeTraversal.ts @@ -63,19 +63,67 @@ function nextVisibleElementInTree( node: q.TreeNode, direction: 'next' | 'previous' ): q.TreeNode | undefined { - const nodes = flattenVisibleTree(settings, tree) + const startNode = (node.sourceEdge && node.sourceEdge.source) || tree + const nodes = flattenNeighbors(settings, node, startNode) const idx = nodes.findIndex(n => n.path() === node.path()) const indexDirection = direction === 'next' ? 1 : -1 return nodes[idx + indexDirection] } +/** Used to select partial relevant trees, to prevent the whole tree from being flattened */ +function flattenNeighbors( + settings: SettingsState, + selected: q.TreeNode, + treeNode: q.TreeNode +): Array> { + let candidates: Array> = [] + const nextNode = findNextNodeDownward(settings, selected) + + const neighborsOfSelected = sortedNodes(settings, treeNode) + const nodeIdx = neighborsOfSelected.findIndex(n => n.path() === selected.path()) + const previousNeighbor = neighborsOfSelected[nodeIdx - 1] + const parentNode = selected.sourceEdge && selected.sourceEdge.source + + if (previousNeighbor) { + candidates = candidates + .concat(flattenVisibleTree(settings, previousNeighbor)) + .concat(flattenVisibleTree(settings, selected)) + } else if (parentNode) { + candidates = candidates.concat(flattenVisibleTree(settings, parentNode)) + } + + return nextNode ? candidates.concat([nextNode]) : candidates +} + /** Not very efficient but easy to implement, complexity should not be an issue here */ function flattenVisibleTree( settings: SettingsState, treeNode: q.TreeNode ): Array> { - return sortedNodes(settings, treeNode) - .filter(isTreeNodeVisible) - .map(node => [node].concat(flattenVisibleTree(settings, node))) - .reduce((a, b) => a.concat(b), []) + return [treeNode].concat( + sortedNodes(settings, treeNode) + .filter(isTreeNodeVisible) + .map(node => flattenVisibleTree(settings, node)) + .reduce((a, b) => a.concat(b), []) + ) +} + +function findNextNodeDownward( + settings: SettingsState, + treeNode: q.TreeNode +): q.TreeNode | undefined { + const parent = treeNode.sourceEdge && treeNode.sourceEdge.source + if (!parent) { + return undefined + } + + const parentNodes = sortedNodes(settings, parent) + const nodeIdx = parentNodes.findIndex(n => n.path() === treeNode.path()) + + const nextNode = parentNodes[nodeIdx + 1] + if (nextNode) { + return nextNode + } else { + return findNextNodeDownward(settings, parent) + } } diff --git a/app/src/components/Tree/TreeNode.tsx b/app/src/components/Tree/TreeNode.tsx index e1dd247..5ad050c 100644 --- a/app/src/components/Tree/TreeNode.tsx +++ b/app/src/components/Tree/TreeNode.tsx @@ -95,7 +95,11 @@ class TreeNodeComponent extends React.Component { } private selectionDidChange = () => { - this.props.treeNode.viewModel && this.setState({ selected: this.props.treeNode.viewModel.isSelected() }) + const selected = this.props.treeNode.viewModel && this.props.treeNode.viewModel.isSelected() + this.props.treeNode.viewModel && this.setState({ selected: Boolean(selected) }) + if (selected && this.nodeRef && this.nodeRef.current) { + this.nodeRef.current.focus({ preventScroll: false }) + } } private expandedDidChange = () => { @@ -243,7 +247,9 @@ class TreeNodeComponent extends React.Component { onMouseOver={this.mouseOver} onMouseOut={this.mouseOut} onClick={this.didClickTitle} + onFocus={() => this.props.didSelectTopic(this.props.treeNode)} ref={this.nodeRef} + tabIndex={1000} >