feat(focus): hierarchical focus system with explicit keybindings#43
Merged
Conversation
…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.
… explicit keybindings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
useFocus→useFocusNodefor focus node registration; addeduseFocusas a scope hook for reading ancestor focus statenext,prev,escape) toFocusGroup, replacing implicit navigation with opt-in key mapsisFocusedancestor check,indicatorOpentheme token, file tree example, and fixednavigateSiblingFocusGroupcollapses —unregisterNodenow walks a persistentparentMapRefto find the nearest living ancestor, making focus recovery order-independent regardless of React's cleanup effect orderingTest plan
j/k, expand withl, collapse withh— confirm focus stays on the directory after collapseuseFocusNodeanduseFocusbehave correctly per updated docs