Improve keyboard arrow navigation
This commit is contained in:
@@ -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(
|
||||||
.filter(isTreeNodeVisible)
|
sortedNodes(settings, treeNode)
|
||||||
.map(node => [node].concat(flattenVisibleTree(settings, node)))
|
.filter(isTreeNodeVisible)
|
||||||
.reduce((a, b) => a.concat(b), [])
|
.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user