Skip to content

refactor(useGridFocus): roving tabindex + handleFocus + isRtl; Calendar uses it#3489

Draft
cixzhang wants to merge 1 commit into
mainfrom
navi/refactor/gridfocus-roving-tabindex
Draft

refactor(useGridFocus): roving tabindex + handleFocus + isRtl; Calendar uses it#3489
cixzhang wants to merge 1 commit into
mainfrom
navi/refactor/gridfocus-roving-tabindex

Conversation

@cixzhang

@cixzhang cixzhang commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Brings useGridFocus to API parity with useListFocus by adding hook-owned roving-tabindex support, and migrates Calendar to use it instead of the separate useCalendarRovingTabindex hook.

Previously useListFocus could own a single tab stop across its items (hasRovingTabIndex: stamps tabindex 0/-1, repairs the stop on layout, moves it with navigation, and exposes a handleFocus to re-sync), while useGridFocus could only move focus — so Calendar needed a bespoke useCalendarRovingTabindex hook to decide which day button was tabbable and thread isTabbable(day.iso) into every cell. That split is no longer necessary.

Changes

useGridFocus

  • New hasRovingTabIndex?: boolean (default false). When enabled the hook owns a single tab stop: exactly one focusable cell's resolved focus target carries tabindex="0", the rest -1. Stamped/repaired on every commit via a layout effect (same shape as useListFocus.syncTabStops) and moved with arrow/Home/End navigation. An existing tabindex="0" is honored, so the caller can seed which cell starts tabbable.
  • The stop is stamped on the resolved focus target (via the existing getFocusTarget), not the role="gridcell" wrapper — so it works when the focusable element is a descendant of the cell (Calendar's day <button>).
  • Uses setAttribute('tabindex', …) (matching useListFocus), skipping redundant writes.
  • New returned handleFocus to attach to the container's onFocus (no-op unless roving is on, always safe to attach).
  • New isRtl?: boolean (default false) that flips ArrowLeft/ArrowRight, same mechanism as useListFocus.

Calendar

  • Passes hasRovingTabIndex: true, destructures handleFocus, and wires it on the grid container's onFocus.
  • Drops the useCalendarRovingTabindex import/usage. The initial tab-stop seed (selected → today → first-enabled in-month day) is computed inline and rendered as each day button's initial tabIndex; the grid hook owns the stop thereafter.

useCalendarRovingTabindex — left in place. It's a public export (@astryxdesign/core/hooks and the Calendar barrel), so removing it would be a breaking change; keeping it avoids that while Calendar no longer depends on it. Removal can be a follow-up.

Tests

  • New useGridFocus.test.tsx covering seeding, repair, arrow-move-the-stop, RTL flip, and roving-off passthrough.
  • Existing Calendar keyboard tests (arrow nav, disabled days, cross-month) unchanged and passing.
  • Verified: @astryxdesign/core typecheck (clean), eslint on changed files (clean), and Calendar + useGridFocus + DateInput/DateRangeInput/DateTimeInput test suites (all pass).

Bring useGridFocus to parity with useListFocus: opt into a hook-owned
roving tab stop that stamps tabindex 0/-1 on the focusable cells' focus
targets, repairs it on render, and moves it with arrow navigation.
Returns a handleFocus for onFocus sync and gains isRtl to flip the
horizontal arrows.

Calendar now uses this instead of the separate useCalendarRovingTabindex
hook: it seeds the initial tabbable day button (selected > today >
first-enabled) and lets the grid hook own the tab stop thereafter.
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 3, 2026
@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview, Comment Jul 3, 2026 5:44am

Request Review

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.6KB 0B

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

github-actions Bot added a commit that referenced this pull request Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants