Skip to content

feat(focus): hierarchical focus system with explicit keybindings#43

Merged
zion-off merged 8 commits into
mainfrom
hierarchical-focus
Feb 23, 2026
Merged

feat(focus): hierarchical focus system with explicit keybindings#43
zion-off merged 8 commits into
mainfrom
hierarchical-focus

Conversation

@zion-off
Copy link
Copy Markdown
Owner

Summary

  • Renamed useFocususeFocusNode for focus node registration; added useFocus as a scope hook for reading ancestor focus state
  • Added explicit keybinding helpers (next, prev, escape) to FocusGroup, replacing implicit navigation with opt-in key maps
  • Added isFocused ancestor check, indicatorOpen theme token, file tree example, and fixed navigateSibling
  • Fixed focus being lost entirely when a conditional FocusGroup collapses — unregisterNode now walks a persistent parentMapRef to find the nearest living ancestor, making focus recovery order-independent regardless of React's cleanup effect ordering

Test plan

  • Open the file tree example and navigate with j/k, expand with l, collapse with h — confirm focus stays on the directory after collapse
  • Verify useFocusNode and useFocus behave correctly per updated docs
  • Confirm explicit keybindings work across existing examples

…ncestor isFocused, explicit keybinding helpers

- Rename src/core/focus/useFocus.ts → useFocusNode.ts; rename export to useFocusNode
- New useFocus() reads focused state from nearest FocusGroup ancestor without registering a node
- isFocused() now walks the ancestor chain instead of exact-matching, so container nodes report focused=true when any descendant has focus
- FocusGroup: remove direction/navigable props and auto-keybinding logic; keybindings prop now accepts Keybindings | ((helpers) => Keybindings); expose next/prev/escape helpers
- Export FocusGroupHelpers type from core/focus/index.ts and src/index.ts
- Export useFocusNode in public API
…mers

Update imports and call sites in FocusTrap, Select, Autocomplete, MultiSelect,
Confirm, TextInput, CommandPalette, Modal, and Viewport. No logic changes.
…sGroup to explicit keybindings

- Replace useFocus() with useFocusNode() in all example components
- Remove direction/navigable props from FocusGroup; add explicit keybinding functions
- controlled-focus: remove navigable={false}
- focus-trap, use-focus, navigable-menu: vertical j/k/down/up via helpers
- horizontal-tabs: horizontal h/l/left/right via helpers
- command-palette: tab/shift+tab via helpers
focus.mdx:
- Rewrite prose to explain FocusGroup as scoping primitive and the useFocus/useFocusNode split
- Update useFocus() API: no parameters, reads from nearest FocusGroup, focused=true when any descendant has focus
- Add useFocusNode() API reference
- Add FocusGroup keybindings function form and FocusGroupHelpers type table
- Remove direction/navigable from FocusGroup prop table
- Update all code examples to use explicit keybindings and correct hooks
- Add "Panel borders with useFocus" example

core-concepts.mdx:
- Update "How nodes register" section: useFocus → useFocusNode, add useFocus description
- Update FocusGroup section: remove auto-keybinding description, reflect explicit helpers

README: add useFocusNode to feature bullet
…rOpen theme token

- Replace horizontal tabs example with collapsible file tree in focus.mdx
- Fix navigateSibling to focus first/last child when no child of the group is
  currently focused, instead of returning early
- Add indicatorOpen to GigglesTheme (default '▼') alongside existing indicator ('▶')
- Update theme.mdx to document indicatorOpen
- Use indicator/indicatorOpen from theme in file tree example
unregisterNode's setFocusedId updater previously returned node.parentId
directly, which could be a deleted node if React ran parent cleanup effects
before child cleanup effects. Add a parentMapRef that retains parent
relationships for all registered nodes (never deleted), so the updater can
walk up the chain and find the first ancestor that still exists in the live
nodesRef — regardless of cleanup order.
@zion-off zion-off merged commit 9611d9b into main Feb 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant