Improve keyboard arrow navigation

This commit is contained in:
Thomas Nordquist
2019-06-25 14:30:47 +02:00
parent f4051b4cdf
commit 1638080e85
2 changed files with 60 additions and 6 deletions

View File

@@ -63,19 +63,67 @@ function nextVisibleElementInTree(
node: q.TreeNode<TopicViewModel>,
direction: 'next' | 'previous'
): q.TreeNode<TopicViewModel> | 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<TopicViewModel>,
treeNode: q.TreeNode<TopicViewModel>
): Array<q.TreeNode<TopicViewModel>> {
let candidates: Array<q.TreeNode<TopicViewModel>> = []
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<TopicViewModel>
): Array<q.TreeNode<TopicViewModel>> {
return sortedNodes(settings, treeNode)
return [treeNode].concat(
sortedNodes(settings, treeNode)
.filter(isTreeNodeVisible)
.map(node => [node].concat(flattenVisibleTree(settings, node)))
.map(node => flattenVisibleTree(settings, node))
.reduce((a, b) => a.concat(b), [])
)
}
function findNextNodeDownward(
settings: SettingsState,
treeNode: q.TreeNode<TopicViewModel>
): q.TreeNode<TopicViewModel> | 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)
}
}

View File

@@ -95,7 +95,11 @@ class TreeNodeComponent extends React.Component<Props, State> {
}
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<Props, State> {
onMouseOver={this.mouseOver}
onMouseOut={this.mouseOut}
onClick={this.didClickTitle}
onFocus={() => this.props.didSelectTopic(this.props.treeNode)}
ref={this.nodeRef}
tabIndex={1000}
>
<TreeNodeTitle
toggleCollapsed={this.toggleCollapsed}