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

View File

@@ -372,7 +372,7 @@ export function plugin(client: PluginClient<Events>) {
}
type UIActions = {
onHoverNode: (node: Id) => void;
onHoverNode: (node?: Id) => void;
onFocusNode: (focused?: Id) => void;
onContextMenuOpen: (open: boolean) => 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]);
} else {
uiState.hoveredNodes.set([]);
}
};
const onContextMenuOpen = (open: boolean) => {