Skip to content

Commit 70f5ffe

Browse files
authored
FilteredActionList: Remove usingRemoveActiveDescendant logic (#7196)
1 parent f2ba02a commit 70f5ffe

File tree

4 files changed

+96
-77
lines changed

4 files changed

+96
-77
lines changed

.changeset/rotten-goats-stare.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react': minor
3+
---
4+
5+
FilteredActionList: Remove `usingRemoveActiveDescendant` feature flag, add private prop

packages/react/src/FilteredActionList/FilteredActionList.tsx

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {ActionListContainerContext} from '../ActionList/ActionListContainerConte
2222
import {isValidElementType} from 'react-is'
2323
import {useAnnouncements} from './useAnnouncements'
2424
import {clsx} from 'clsx'
25-
import {useFeatureFlag} from '../FeatureFlags'
2625

2726
const menuScrollMargins: ScrollIntoViewOptions = {startMargin: 0, endMargin: 8}
2827

@@ -45,6 +44,26 @@ export interface FilteredActionListProps extends Partial<Omit<GroupedListProps,
4544
announcementsEnabled?: boolean
4645
fullScreenOnNarrow?: boolean
4746
onSelectAllChange?: (checked: boolean) => void
47+
/**
48+
* Private API for use internally only. Adds the ability to switch between
49+
* `active-descendant` and roving tabindex.
50+
*
51+
* By default, FilteredActionList uses `aria-activedescendant` to manage focus.
52+
*
53+
* Roving tabindex is an alternative focus management method that moves
54+
* focus to the list items themselves instead of keeping focus on the input.
55+
*
56+
* Improper usage can lead to inaccessible experiences, so this prop should be used with caution.
57+
*
58+
* For usage, refer to the documentation:
59+
*
60+
* WAI-ARIA `aria-activedescendant`: https://www.w3.org/TR/wai-aria-1.2/#aria-activedescendant
61+
*
62+
* Roving Tabindex: https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex
63+
*
64+
* @default 'active-descendant'
65+
*/
66+
_PrivateFocusManagement?: 'roving-tabindex' | 'active-descendant'
4867
}
4968

5069
export function FilteredActionList({
@@ -67,6 +86,7 @@ export function FilteredActionList({
6786
announcementsEnabled = true,
6887
fullScreenOnNarrow,
6988
onSelectAllChange,
89+
_PrivateFocusManagement = 'active-descendant',
7090
...listProps
7191
}: FilteredActionListProps): JSX.Element {
7292
const [filterValue, setInternalFilterValue] = useProvidedStateOrCreate(externalFilterValue, undefined, '')
@@ -85,7 +105,7 @@ export function FilteredActionList({
85105
const scrollContainerRef = useRef<HTMLDivElement>(null)
86106
const inputRef = useProvidedRefOrCreate<HTMLInputElement>(providedInputRef)
87107

88-
const usingRemoveActiveDescendant = useFeatureFlag('primer_react_select_panel_remove_active_descendant')
108+
const usingRovingTabindex = _PrivateFocusManagement === 'roving-tabindex'
89109
const [listContainerElement, setListContainerElement] = useState<HTMLUListElement | null>(null)
90110
const activeDescendantRef = useRef<HTMLElement>()
91111

@@ -164,7 +184,6 @@ export function FilteredActionList({
164184
[activeDescendantRef],
165185
)
166186

167-
// BEGIN: Todo remove when we remove usingRemoveActiveDescendant
168187
const listContainerRefCallback = useCallback(
169188
(node: HTMLUListElement | null) => {
170189
setListContainerElement(node)
@@ -175,10 +194,9 @@ export function FilteredActionList({
175194
useEffect(() => {
176195
onInputRefChanged?.(inputRef)
177196
}, [inputRef, onInputRefChanged])
178-
//END: Todo remove when we remove usingRemoveActiveDescendant
179197

180198
useFocusZone(
181-
!usingRemoveActiveDescendant
199+
!usingRovingTabindex
182200
? {
183201
containerRef: {current: listContainerElement},
184202
bindKeys: FocusKeys.ArrowVertical | FocusKeys.PageUpDown,
@@ -196,7 +214,7 @@ export function FilteredActionList({
196214
},
197215
}
198216
: undefined,
199-
[listContainerElement, usingRemoveActiveDescendant],
217+
[listContainerElement, usingRovingTabindex],
200218
)
201219

202220
useEffect(() => {
@@ -209,7 +227,7 @@ export function FilteredActionList({
209227
}, [items, inputRef])
210228

211229
useEffect(() => {
212-
if (usingRemoveActiveDescendant) {
230+
if (usingRovingTabindex) {
213231
const inputAndListContainerElement = inputAndListContainerRef.current
214232
if (!inputAndListContainerElement) return
215233
const list = listRef.current
@@ -228,21 +246,22 @@ export function FilteredActionList({
228246
inputAndListContainerElement.removeEventListener('focusin', handleFocusIn)
229247
}
230248
}
231-
}, [items, inputRef, listContainerElement, usingRemoveActiveDescendant]) // Re-run when items change to update active indicators
249+
}, [items, inputRef, listContainerElement, usingRovingTabindex]) // Re-run when items change to update active indicators
232250

233251
useEffect(() => {
234-
if (usingRemoveActiveDescendant && !loading) {
252+
if (usingRovingTabindex && !loading) {
235253
setIsInputFocused(inputRef.current && inputRef.current === document.activeElement ? true : false)
236254
}
237-
}, [loading, inputRef, usingRemoveActiveDescendant])
255+
}, [loading, inputRef, usingRovingTabindex])
238256

239257
useAnnouncements(
240258
items,
241-
usingRemoveActiveDescendant ? listRef : {current: listContainerElement},
259+
usingRovingTabindex ? listRef : {current: listContainerElement},
242260
inputRef,
243261
announcementsEnabled,
244262
loading,
245263
messageText,
264+
_PrivateFocusManagement,
246265
)
247266
useScrollFlash(scrollContainerRef)
248267

@@ -265,7 +284,7 @@ export function FilteredActionList({
265284
let firstGroupIndex = 0
266285
const actionListContent = (
267286
<ActionList
268-
ref={usingRemoveActiveDescendant ? listRef : listContainerRefCallback}
287+
ref={usingRovingTabindex ? listRef : listContainerRefCallback}
269288
showDividers={showItemDividers}
270289
selectionVariant={selectionVariant}
271290
{...listProps}
@@ -316,7 +335,7 @@ export function FilteredActionList({
316335
)
317336

318337
// Use ActionListContainerContext.Provider only for the old behavior (when feature flag is disabled)
319-
if (usingRemoveActiveDescendant) {
338+
if (usingRovingTabindex) {
320339
return (
321340
<ActionListContainerContext.Provider
322341
value={{
@@ -348,7 +367,7 @@ export function FilteredActionList({
348367
value={filterValue}
349368
onChange={onInputChange}
350369
onKeyPress={onInputKeyPress}
351-
onKeyDown={usingRemoveActiveDescendant ? onInputKeyDown : () => {}}
370+
onKeyDown={usingRovingTabindex ? onInputKeyDown : () => {}}
352371
placeholder={placeholderText}
353372
role="combobox"
354373
aria-expanded="true"

packages/react/src/FilteredActionList/useAnnouncements.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {announce as liveRegionAnnounce} from '@primer/live-region-element'
55
import {useCallback, useEffect, useRef} from 'react'
66
import type {FilteredActionListProps} from './index'
77
import type {ItemInput} from '../SelectPanel'
8-
import {useFeatureFlag} from '../FeatureFlags'
98

109
// we add a delay so that it does not interrupt default screen reader announcement and queues after it
1110
const delayMs = 500
@@ -19,7 +18,6 @@ const useFirstRender = () => {
1918
return firstRender.current
2019
}
2120

22-
//TODO remove this when we remove usingRemoveActiveDescendant
2321
const getItemWithActiveDescendant = (
2422
listRef: React.RefObject<HTMLUListElement>,
2523
items: FilteredActionListProps['items'],
@@ -39,7 +37,6 @@ const getItemWithActiveDescendant = (
3937

4038
return {index, text, selected}
4139
}
42-
//TODO remove this when we remove usingRemoveActiveDescendant
4340

4441
export const useAnnouncements = (
4542
items: FilteredActionListProps['items'],
@@ -48,8 +45,10 @@ export const useAnnouncements = (
4845
enabled: boolean = true,
4946
loading: boolean = false,
5047
message?: {title: string; description: string},
48+
focusManagement?: 'active-descendant' | 'roving-tabindex',
5149
) => {
52-
const usingRemoveActiveDescendant = useFeatureFlag('primer_react_select_panel_remove_active_descendant')
50+
const usingRovingTabindex = focusManagement === 'roving-tabindex'
51+
5352
const liveRegion = document.querySelector('live-region')
5453

5554
// Notify user of the number of items available
@@ -67,7 +66,7 @@ export const useAnnouncements = (
6766
useEffect(
6867
function announceInitialFocus() {
6968
const focusHandler = () => {
70-
if (usingRemoveActiveDescendant) {
69+
if (usingRovingTabindex) {
7170
const announcementText = `${items.length} item${items.length > 1 ? 's' : ''} available, ${selectedItems} selected.`
7271
announce(announcementText, {
7372
delayMs,
@@ -98,7 +97,7 @@ export const useAnnouncements = (
9897
inputElement?.addEventListener('focus', focusHandler)
9998
return () => inputElement?.removeEventListener('focus', focusHandler)
10099
},
101-
[listContainerRef, inputRef, items, liveRegion, announce, usingRemoveActiveDescendant, selectedItems],
100+
[listContainerRef, inputRef, items, liveRegion, announce, usingRovingTabindex, selectedItems],
102101
)
103102

104103
const isFirstRender = useFirstRender()
@@ -113,7 +112,7 @@ export const useAnnouncements = (
113112
return
114113
}
115114

116-
if (usingRemoveActiveDescendant) {
115+
if (usingRovingTabindex) {
117116
const announcementText = `${items.length} item${items.length > 1 ? 's' : ''} available, ${selectedItems} selected.`
118117

119118
announce(announcementText, {
@@ -147,7 +146,7 @@ export const useAnnouncements = (
147146
items,
148147
listContainerRef,
149148
liveRegion,
150-
usingRemoveActiveDescendant,
149+
usingRovingTabindex,
151150
message?.title,
152151
message?.description,
153152
loading,

0 commit comments

Comments
 (0)