Skip to content

Feature/unified input system backup#34

Open
to-be-coder wants to merge 16 commits intomainfrom
feature/unified-input-system-backup
Open

Feature/unified input system backup#34
to-be-coder wants to merge 16 commits intomainfrom
feature/unified-input-system-backup

Conversation

@to-be-coder
Copy link
Copy Markdown
Collaborator

No description provided.

cjroth and others added 16 commits March 18, 2026 01:50
Add a complete input system with focus management, keyboard routing,
mouse/pointer events, and tab navigation that works in both runtimes.

Focus system (packages/core/src/react/focus/):
- FocusProvider with external store (useSyncExternalStore) for reliable
  state synchronization with the concurrent reconciler
- useFocus hook for registering focusable components with tab order
- FocusScope for trapping focus within boundaries (modals, menus)
- useShortcuts/useFocusedShortcuts for StatusBar integration
- Tab/Shift+Tab and Up/Down arrow navigation with wrap-around
- Arrow key conflict resolution via event.defaultPrevented

useKeyboard rework:
- New focusId option: handler fires only when component is focused
- New global option: handler always fires regardless of focus
- Backward-compatible: no options = fires always (existing behavior)

Web hit-testing & pointer events:
- BrowserRenderContext: real hit-grid with addToHitGrid/hitTest/scissor
- BrowserRenderer: mouseDown/mouseUp/mouseMove dispatch to Renderables
- Over/out events on hover transitions, scroll via wheel
- Click-to-focus: walks parent chain to find focusable Renderable
- onClick synthetic event on Renderable (mouseDown+mouseUp pairing)
- Reconciler prop setter handles onClick on intrinsic elements

Cursor cell highlighting:
- CanvasPainter draws highlight at mouse position (Pass 5)
- Configurable via TUI props: cursorHighlight, color, opacity

Runtime detection:
- useRuntime() returns 'web' | 'terminal'
- RuntimeProvider wraps createBrowserRoot (web) automatically
- Exported from @gridland/utils

Integration:
- GridlandProvider wraps children with FocusProvider
- Re-exports from @gridland/utils
- Documentation guide and interactive demo
- 35 new tests (457 total, 0 failures)
- FocusProvider: use reconciler.flushSync to force synchronous React
  updates in the opentui custom reconciler (needed because useState
  updates from EventEmitter callbacks aren't auto-flushed)
- useShortcuts: prevent infinite re-render loop when inline array
  literals are passed by serializing shortcuts for stable comparison
- Revert FocusProvider wrapping from GridlandProvider (user preference)
- Add interactive demo to focus-and-navigation docs guide
- Fix autoFocus test timing
…Store

The opentui custom reconciler doesn't propagate React context updates
from EventEmitter callbacks — no combination of useState, useReducer,
useSyncExternalStore, or flushSync on the FocusProvider triggers child
re-renders in the browser runtime.

Fix: each hook (useFocus, useFocusedShortcuts) subscribes to the focus
store directly via useSyncExternalStore, bypassing context propagation.
The FocusProvider passes the store through context for hook access, and
calls requestRender() to trigger TUI canvas repaints.

Also simplify the demo to use useKeyboard directly (matching the pattern
used by SelectInput and other existing demos) and increase demo height.
Three panels (Language, Framework, Runtime) each contain a mini select
menu. Arrow keys navigate between panels, Enter enters a panel to browse
its options, arrow keys cycle within, Enter confirms selection, Escape
exits. Shows the two-level focus pattern from the architecture spec.
Switch panel navigation from up/down to left/right arrows (keep tab).
Up/down reserved for select menus inside panels. Give panels fixed
height so the status bar hint text is always visible.
Clickable color cells with mouse click support and keyboard nav fallback.
Shows onMouseDown handler dispatching to Renderables via hit-test.
Checkerboard grid with cursorHighlight enabled on the TUI canvas.
Moving the mouse highlights the cell under the cursor. Also adds
cursorHighlight prop passthrough to DemoWindow.
Default from rgba(128,128,128,0.15) to rgba(255,255,255,0.3) — white
at 30% opacity so the highlight is actually visible on dark backgrounds.
…unter

Adds a hover-reactive box that turns green on mouseOver/mouseOut, shows
mouse x,y coordinates on click, and keeps the clickable color cells
with keyboard nav fallback.
Increase default opacity to 0.5 and add a white border outline around
the highlighted cell. The previous 0.3 opacity fill-only was too faint
to notice on dark backgrounds.
… interactive elements

- cursor: none when cursorHighlight is on (the painted highlight replaces it)
- cursor: pointer when hovering renderables with onClick/onMouseDown handlers
- cursor: pointer for links (existing behavior preserved)
- Falls back to cursor: text otherwise
- Walk parent chain when checking for mouse handlers, so gap cells
  between interactive siblings inherit pointer cursor from parent
- Use consistent borderStyle="rounded" in pointer demo to prevent
  layout shift when selection changes
Bottom-aligned status bar shows live mouse coordinates as the cursor
moves over the checkerboard grid. Uses onMouseMove on the root box
to track position.
Run via: bunx @gridland/demo focus

Three panels (Language, Framework, Runtime) with two-level navigation:
←→/Tab to move between panels, Enter to dive in, ↑↓ to pick options,
Enter to confirm, Esc to back out. Same interaction as the docs demo.
Move FocusApp, PointerApp, CursorHighlightApp to demo-apps.tsx as the
single source of truth. Docs demos import from there and wrap with
DemoWindow. CLI demos use them via the existing DemoShell.

Add root scripts:
  bun demo <name>       — run a demo directly
  bun demo:dev <name>   — build first, then run

New CLI demos: focus, pointer, cursor-highlight
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.

2 participants