Files
dbx/apps/desktop/src/lib/sidebar/sidebarTreeArrowNavigation.ts
T

57 lines
2.9 KiB
TypeScript

import type { FlatTreeNode } from "@/composables/useFlatTree";
import type { DatabaseType, TreeNode } from "@/types/database";
import { canTreeNodeShowExpander } from "@/lib/sidebar/sidebarTreeItemLayout";
import { customTypeCapabilities } from "@/lib/database/databaseObjectCapabilities";
export type SidebarTreeArrowAction = { kind: "select"; nodeId: string } | { kind: "toggle"; nodeId: string; expanded: boolean } | { kind: "none" };
/**
* Resolves the standard tree-navigation action (VS Code / file explorer
* semantics) for an arrow key pressed on the row identified by
* `currentNodeId`. Callers pass the visible rows they render, excluding
* non-selectable pseudo rows (e.g. per-database table search controls), so
* vertical movement naturally skips them. `databaseType` is the connection's
* dialect of the active row and mirrors the chevron expandability gates.
*/
export function sidebarTreeArrowAction(rows: readonly FlatTreeNode[], currentNodeId: string, key: string, options?: { databaseType?: DatabaseType }): SidebarTreeArrowAction {
const currentIndex = rows.findIndex((row) => row.id === currentNodeId);
if (currentIndex < 0) return { kind: "none" };
const current = rows[currentIndex]!;
const node = current.node;
if (key === "ArrowUp") {
return currentIndex > 0 ? { kind: "select", nodeId: rows[currentIndex - 1]!.id } : { kind: "none" };
}
if (key === "ArrowDown") {
return currentIndex < rows.length - 1 ? { kind: "select", nodeId: rows[currentIndex + 1]!.id } : { kind: "none" };
}
if (key === "ArrowRight") {
const next = rows[currentIndex + 1];
if (node.isExpanded && next && next.depth === current.depth + 1) return { kind: "select", nodeId: next.id };
if (!node.isExpanded && isArrowExpandable(node, options?.databaseType)) return { kind: "toggle", nodeId: node.id, expanded: true };
return { kind: "none" };
}
if (key === "ArrowLeft") {
if (node.isExpanded && node.children?.length) return { kind: "toggle", nodeId: node.id, expanded: false };
for (let index = currentIndex - 1; index >= 0; index -= 1) {
if (rows[index]!.depth < current.depth) return { kind: "select", nodeId: rows[index]!.id };
}
return { kind: "none" };
}
return { kind: "none" };
}
function isArrowExpandable(node: TreeNode, databaseType?: DatabaseType): boolean {
// Match the chevron gate (TreeItem/SidebarTreeRuntimeHost canExpand):
// PostgreSQL-family custom types only expand when the catalog marked them
// with loadable members; member-less kinds (enum/domain/range/base) stay
// collapsed even when child metadata rows exist.
if (node.type === "type" && customTypeCapabilities(databaseType).details) return node.hasMembers === true;
return canTreeNodeShowExpander({
type: node.type,
childCount: node.children?.length ?? 0,
explicitContainer: (node.type === "package" && node.children !== undefined) || node.xuguTypeMembersExpandable === true,
});
}