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>, node: q.TreeNode<TopicViewModel>,
direction: 'next' | 'previous' direction: 'next' | 'previous'
): q.TreeNode<TopicViewModel> | undefined { ): 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 idx = nodes.findIndex(n => n.path() === node.path())
const indexDirection = direction === 'next' ? 1 : -1 const indexDirection = direction === 'next' ? 1 : -1
return nodes[idx + indexDirection] 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 */ /** Not very efficient but easy to implement, complexity should not be an issue here */
function flattenVisibleTree( function flattenVisibleTree(
settings: SettingsState, settings: SettingsState,
treeNode: q.TreeNode<TopicViewModel> treeNode: q.TreeNode<TopicViewModel>
): Array<q.TreeNode<TopicViewModel>> { ): Array<q.TreeNode<TopicViewModel>> {
return sortedNodes(settings, treeNode) return [treeNode].concat(
sortedNodes(settings, treeNode)
.filter(isTreeNodeVisible) .filter(isTreeNodeVisible)
.map(node => [node].concat(flattenVisibleTree(settings, node))) .map(node => flattenVisibleTree(settings, node))
.reduce((a, b) => a.concat(b), []) .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 = () => { 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 = () => { private expandedDidChange = () => {
@@ -243,7 +247,9 @@ class TreeNodeComponent extends React.Component<Props, State> {
onMouseOver={this.mouseOver} onMouseOver={this.mouseOver}
onMouseOut={this.mouseOut} onMouseOut={this.mouseOut}
onClick={this.didClickTitle} onClick={this.didClickTitle}
onFocus={() => this.props.didSelectTopic(this.props.treeNode)}
ref={this.nodeRef} ref={this.nodeRef}
tabIndex={1000}
> >
<TreeNodeTitle <TreeNodeTitle
toggleCollapsed={this.toggleCollapsed} toggleCollapsed={this.toggleCollapsed}