From 89e5dc8992a648c567f27653832d69fccce63b86 Mon Sep 17 00:00:00 2001 From: Alex Rock <1675576+nynexman4464@users.noreply.github.com> Date: Mon, 3 Aug 2026 08:25:36 -0700 Subject: [PATCH] fix(core): RTL arrow-key nav for TreeList; auto-detect direction + deprecate isRtl override on focus hooks --- .changeset/deprecate-focus-hook-isrtl.md | 6 +++ .changeset/tree-focus-rtl-arrows.md | 6 +++ packages/core/src/AvatarGroup/AvatarGroup.tsx | 8 +-- packages/core/src/hooks/useGridFocus.ts | 14 ++--- packages/core/src/hooks/useListFocus.ts | 15 +++--- packages/core/src/hooks/useTreeFocus.test.tsx | 54 ++++++++++++++++++- packages/core/src/hooks/useTreeFocus.ts | 15 +++++- 7 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 .changeset/deprecate-focus-hook-isrtl.md create mode 100644 .changeset/tree-focus-rtl-arrows.md diff --git a/.changeset/deprecate-focus-hook-isrtl.md b/.changeset/deprecate-focus-hook-isrtl.md new file mode 100644 index 000000000000..2c37d5994367 --- /dev/null +++ b/.changeset/deprecate-focus-hook-isrtl.md @@ -0,0 +1,6 @@ +--- +'@astryxdesign/core': patch +--- + +[fix] Deprecate the `isRtl` option on `useListFocus` and `useGridFocus`. Right-to-left arrow-key direction is now auto-detected from the container, so the explicit override is redundant and will be removed in an upcoming major — omit it and RTL is handled automatically. +@nynexman4464 diff --git a/.changeset/tree-focus-rtl-arrows.md b/.changeset/tree-focus-rtl-arrows.md new file mode 100644 index 000000000000..381c81aa8152 --- /dev/null +++ b/.changeset/tree-focus-rtl-arrows.md @@ -0,0 +1,6 @@ +--- +'@astryxdesign/core': patch +--- + +[fix] TreeList arrow-key navigation now follows visual direction in RTL: ArrowLeft expands and ArrowRight collapses under `dir="rtl"` (mirrored from LTR). Detected automatically; LTR is unchanged. +@nynexman4464 diff --git a/packages/core/src/AvatarGroup/AvatarGroup.tsx b/packages/core/src/AvatarGroup/AvatarGroup.tsx index 02c0adc58640..b3086100e52e 100644 --- a/packages/core/src/AvatarGroup/AvatarGroup.tsx +++ b/packages/core/src/AvatarGroup/AvatarGroup.tsx @@ -108,11 +108,9 @@ export function AvatarGroup({ ); // The keyboard hint and roving tab stop only make sense once the group has - // interactive children. Detect their presence (and the writing direction for - // RTL-correct arrow navigation) from the rendered DOM after commit, matching - // how the codebase reads direction elsewhere (`getComputedStyle(el).direction`). + // interactive children. Detect their presence from the rendered DOM after + // commit. (Arrow-key direction is auto-detected inside useListFocus.) const [hasInteractiveItems, setHasInteractiveItems] = useState(false); - const [isRtl, setIsRtl] = useState(false); // Single tab stop + roving arrow focus over the group's interactive items. // `itemSelector` targets the shared `[data-avatar-item]` marker stamped on @@ -123,7 +121,6 @@ export function AvatarGroup({ itemSelector: '[data-avatar-item]', orientation: 'horizontal', hasRovingTabIndex: true, - isRtl, }); useIsomorphicLayoutEffect(() => { @@ -132,7 +129,6 @@ export function AvatarGroup({ return; } setHasInteractiveItems(root.querySelector('[data-avatar-item]') != null); - setIsRtl(getComputedStyle(root).direction === 'rtl'); }); const hintId = useId(); diff --git a/packages/core/src/hooks/useGridFocus.ts b/packages/core/src/hooks/useGridFocus.ts index 0182c5d54a44..0268afdd934d 100644 --- a/packages/core/src/hooks/useGridFocus.ts +++ b/packages/core/src/hooks/useGridFocus.ts @@ -90,13 +90,15 @@ export interface UseGridFocusOptions { onPageDown?: () => void; /** - * Whether the grid is in a right-to-left context. When true, ArrowLeft and - * ArrowRight are swapped so horizontal navigation follows visual direction. + * @deprecated Direction is auto-detected from the container's computed + * `direction` — omit this. The explicit override is redundant (there's no + * valid reason to force RTL arrows in an LTR context) and will be removed in + * an upcoming major. * - * When omitted, the direction is auto-detected from the container's computed - * `direction` (read lazily on keydown, only for horizontal arrow keys), so - * grids inside `dir="rtl"` subtrees flip automatically. Pass an explicit - * boolean to override detection. + * When set, forces whether the grid is right-to-left: ArrowLeft/ArrowRight + * are swapped so horizontal navigation follows visual direction. When + * omitted (preferred), the direction is auto-detected from the container's + * computed `direction` (read lazily on keydown, horizontal arrows only). * @default undefined (auto-detect from the container) */ isRtl?: boolean; diff --git a/packages/core/src/hooks/useListFocus.ts b/packages/core/src/hooks/useListFocus.ts index 3c4fe4aea763..388646bb75a6 100644 --- a/packages/core/src/hooks/useListFocus.ts +++ b/packages/core/src/hooks/useListFocus.ts @@ -82,14 +82,15 @@ export interface UseListFocusOptions { hasHomeEnd?: boolean; /** - * Whether the list is in a right-to-left context. When true, ArrowLeft and - * ArrowRight are swapped so horizontal navigation follows visual direction. - * Only affects horizontal (`'horizontal'`/`'both'`) navigation. + * @deprecated Direction is auto-detected from the container's computed + * `direction` — omit this. The explicit override is redundant (there's no + * valid reason to force RTL arrows in an LTR context) and will be removed in + * an upcoming major. * - * When omitted, the direction is auto-detected from the container's computed - * `direction` (read lazily on keydown, only for horizontal arrow keys), so - * lists inside `dir="rtl"` subtrees flip automatically. Pass an explicit - * boolean to override detection. + * When set, forces whether the list is right-to-left: ArrowLeft/ArrowRight + * are swapped so horizontal navigation follows visual direction. When + * omitted (preferred), the direction is auto-detected from the container's + * computed `direction` (read lazily on keydown, horizontal arrows only). * @default undefined (auto-detect from the container) */ isRtl?: boolean; diff --git a/packages/core/src/hooks/useTreeFocus.test.tsx b/packages/core/src/hooks/useTreeFocus.test.tsx index 13769887746a..e5182fb71131 100644 --- a/packages/core/src/hooks/useTreeFocus.test.tsx +++ b/packages/core/src/hooks/useTreeFocus.test.tsx @@ -35,10 +35,12 @@ function Tree({ collapsed = NO_NODES, expanded = NO_NODES, onActivate, + dir, }: { collapsed?: Node[]; expanded?: Node[]; onActivate?: (id: string | undefined) => boolean | undefined; + dir?: 'ltr' | 'rtl'; }) { const [isOpen, setIsOpen] = useState(false); const nodes = isOpen && expanded.length > 0 ? expanded : collapsed; @@ -47,7 +49,7 @@ function Tree({ onActivate: onActivate ? (_item, id) => onActivate(id) : undefined, }); return ( -