Keyboard controls moved selected and hovered node together

Summary:
Following feedback when using keyboard controls its a little bit awkward to have to move with arrows and then select with enter.

Now when using keyboard controls you are manipulating the selected state.

Enter still selects / unselects but its not really needed anymore

When using the mouse the hover state is still there

Changelog: [UIDebugger] Using keyboard arrow control changes the selected and hovered state together for faster / easier navigation

Reviewed By: lblasa

Differential Revision: D47212492

fbshipit-source-id: 996196880d623885b4d4b7d1a70954201f809d28
This commit is contained in:
Luke De Feo
2023-07-10 09:22:39 -07:00
committed by Facebook GitHub Bot
parent f3f5018f71
commit 36447d550a
2 changed files with 44 additions and 21 deletions

View File

@@ -122,6 +122,7 @@ export function Tree2({nodes, rootId}: {nodes: Map<Id, UINode>; rootId: Id}) {
selectedNode, selectedNode,
hoveredNode, hoveredNode,
instance.uiActions.onSelectNode, instance.uiActions.onSelectNode,
instance.uiActions.onHoverNode,
instance.uiActions.onExpandNode, instance.uiActions.onExpandNode,
instance.uiActions.onCollapseNode, instance.uiActions.onCollapseNode,
isUsingKBToScrollUtill, isUsingKBToScrollUtill,
@@ -163,7 +164,11 @@ export function Tree2({nodes, rootId}: {nodes: Map<Id, UINode>; rootId: Id}) {
useLayoutEffect(() => { useLayoutEffect(() => {
if (selectedNode) { if (selectedNode) {
const idx = treeNodes.findIndex((node) => node.id === selectedNode); const idx = treeNodes.findIndex((node) => node.id === selectedNode);
if (idx !== -1) {
const kbIsNoLongerReservingScroll =
new Date().getTime() > (isUsingKBToScrollUtill.current ?? 0);
if (idx !== -1 && kbIsNoLongerReservingScroll) {
parentRef.current!!.scrollLeft = parentRef.current!!.scrollLeft =
Math.max(0, treeNodes[idx].depth - 10) * renderDepthOffset; Math.max(0, treeNodes[idx].depth - 10) * renderDepthOffset;
@@ -680,6 +685,7 @@ function useKeyboardShortcuts(
selectedNode: Id | undefined, selectedNode: Id | undefined,
hoveredNodeId: Id | undefined, hoveredNodeId: Id | undefined,
onSelectNode: (id?: Id) => void, onSelectNode: (id?: Id) => void,
onHoverNode: (id?: Id) => void,
onExpandNode: (id: Id) => void, onExpandNode: (id: Id) => void,
onCollapseNode: (id: Id) => void, onCollapseNode: (id: Id) => void,
isUsingKBToScrollUntill: React.MutableRefObject<number>, isUsingKBToScrollUntill: React.MutableRefObject<number>,
@@ -692,7 +698,6 @@ function useKeyboardShortcuts(
switch (event.key) { switch (event.key) {
case 'Enter': { case 'Enter': {
if (hoveredNodeId != null) { if (hoveredNodeId != null) {
extendKBControlLease(isUsingKBToScrollUntill);
onSelectNode(hoveredNodeId); onSelectNode(hoveredNodeId);
} }
@@ -703,11 +708,14 @@ function useKeyboardShortcuts(
event.preventDefault(); event.preventDefault();
if (hoveredNode) { if (hoveredNode) {
if (hoveredNode.isExpanded) { if (hoveredNode.isExpanded) {
moveHoveredNodeUpOrDown( moveSelectedNodeUpOrDown(
'ArrowDown', 'ArrowDown',
treeNodes, treeNodes,
rowVirtualizer, rowVirtualizer,
instance.uiState.hoveredNodes, hoveredNodeId,
selectedNode,
onSelectNode,
onHoverNode,
isUsingKBToScrollUntill, isUsingKBToScrollUntill,
); );
} else { } else {
@@ -724,11 +732,12 @@ function useKeyboardShortcuts(
const parentIdx = treeNodes.findIndex( const parentIdx = treeNodes.findIndex(
(treeNode) => treeNode.id === hoveredNode.parent, (treeNode) => treeNode.id === hoveredNode.parent,
); );
moveHoveredNodeViaKeyBoard( moveSelectedNodeViaKeyBoard(
parentIdx, parentIdx,
treeNodes, treeNodes,
rowVirtualizer, rowVirtualizer,
instance.uiState.hoveredNodes, onSelectNode,
onHoverNode,
isUsingKBToScrollUntill, isUsingKBToScrollUntill,
); );
} }
@@ -740,11 +749,14 @@ function useKeyboardShortcuts(
case 'ArrowDown': case 'ArrowDown':
event.preventDefault(); event.preventDefault();
moveHoveredNodeUpOrDown( moveSelectedNodeUpOrDown(
event.key, event.key,
treeNodes, treeNodes,
rowVirtualizer, rowVirtualizer,
instance.uiState.hoveredNodes, hoveredNodeId,
selectedNode,
onSelectNode,
onHoverNode,
isUsingKBToScrollUntill, isUsingKBToScrollUntill,
); );
@@ -765,47 +777,54 @@ function useKeyboardShortcuts(
instance.uiState.hoveredNodes, instance.uiState.hoveredNodes,
hoveredNodeId, hoveredNodeId,
rowVirtualizer, rowVirtualizer,
onHoverNode,
]); ]);
} }
export type UpOrDown = 'ArrowDown' | 'ArrowUp'; export type UpOrDown = 'ArrowDown' | 'ArrowUp';
function moveHoveredNodeUpOrDown( function moveSelectedNodeUpOrDown(
direction: UpOrDown, direction: UpOrDown,
treeNodes: TreeNode[], treeNodes: TreeNode[],
rowVirtualizer: Virtualizer<HTMLDivElement, Element>, rowVirtualizer: Virtualizer<HTMLDivElement, Element>,
hoveredNodes: Atom<Id[]>, hoveredNode: Id | undefined,
selectedNode: Id | undefined,
onSelectNode: (id?: Id) => void,
onHoverNode: (id?: Id) => void,
isUsingKBToScrollUntill: React.MutableRefObject<MillisSinceEpoch>, isUsingKBToScrollUntill: React.MutableRefObject<MillisSinceEpoch>,
) { ) {
const curIdx = treeNodes.findIndex( const nodeToUse = selectedNode != null ? selectedNode : hoveredNode;
(item) => item.id === head(hoveredNodes.get()), const curIdx = treeNodes.findIndex((item) => item.id === nodeToUse);
);
if (curIdx != -1) { if (curIdx != -1) {
const increment = direction === 'ArrowDown' ? 1 : -1; const increment = direction === 'ArrowDown' ? 1 : -1;
const newIdx = curIdx + increment; const newIdx = curIdx + increment;
moveHoveredNodeViaKeyBoard( moveSelectedNodeViaKeyBoard(
newIdx, newIdx,
treeNodes, treeNodes,
rowVirtualizer, rowVirtualizer,
hoveredNodes, onSelectNode,
onHoverNode,
isUsingKBToScrollUntill, isUsingKBToScrollUntill,
); );
} }
} }
function moveHoveredNodeViaKeyBoard( function moveSelectedNodeViaKeyBoard(
newIdx: number, newIdx: number,
treeNodes: TreeNode[], treeNodes: TreeNode[],
rowVirtualizer: Virtualizer<HTMLDivElement, Element>, rowVirtualizer: Virtualizer<HTMLDivElement, Element>,
hoveredNodes: Atom<Id[]>, onSelectNode: (id?: Id) => void,
onHoverNode: (id?: Id) => void,
isUsingKBToScrollUntil: React.MutableRefObject<number>, isUsingKBToScrollUntil: React.MutableRefObject<number>,
) { ) {
if (newIdx >= 0 && newIdx < treeNodes.length) { if (newIdx >= 0 && newIdx < treeNodes.length) {
const newNode = treeNodes[newIdx]; const newNode = treeNodes[newIdx];
hoveredNodes.set([newNode.id]);
extendKBControlLease(isUsingKBToScrollUntil); extendKBControlLease(isUsingKBToScrollUntil);
onSelectNode(newNode.id);
onHoverNode(newNode.id);
rowVirtualizer.scrollToIndex(newIdx, {align: 'auto'}); rowVirtualizer.scrollToIndex(newIdx, {align: 'auto'});
} }
} }

View File

@@ -372,7 +372,7 @@ export function plugin(client: PluginClient<Events>) {
} }
type UIActions = { type UIActions = {
onHoverNode: (node: Id) => void; onHoverNode: (node?: Id) => void;
onFocusNode: (focused?: Id) => void; onFocusNode: (focused?: Id) => void;
onContextMenuOpen: (open: boolean) => void; onContextMenuOpen: (open: boolean) => void;
onSelectNode: (node?: Id) => void; onSelectNode: (node?: Id) => void;
@@ -418,8 +418,12 @@ function uiActions(uiState: UIState, nodes: Atom<Map<Id, UINode>>): UIActions {
}); });
}; };
const onHoverNode = (node: Id) => { const onHoverNode = (node?: Id) => {
if (node != null) {
uiState.hoveredNodes.set([node]); uiState.hoveredNodes.set([node]);
} else {
uiState.hoveredNodes.set([]);
}
}; };
const onContextMenuOpen = (open: boolean) => { const onContextMenuOpen = (open: boolean) => {