Skip to content

Commit 1bdc64f

Browse files
authored
revert: Brings back single tab stop navigation changes (#157) (#158)
1 parent 75b3106 commit 1bdc64f

File tree

6 files changed

+284
-38
lines changed

6 files changed

+284
-38
lines changed

src/internal/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export { getGlobalFlag } from './global-flags';
3131
export {
3232
SingleTabStopNavigationAPI,
3333
SingleTabStopNavigationProvider,
34+
SingleTabStopNavigationReset,
3435
useSingleTabStopNavigation,
3536
} from './single-tab-stop';
3637
export { isFocusable, getAllFocusables, getFirstFocusable, getLastFocusable } from './focus-lock-utils/utils';

src/internal/single-tab-stop/__tests__/context.test.tsx

Lines changed: 213 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,59 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import React, { useRef } from 'react';
5-
import { render, act } from '@testing-library/react';
4+
import React, { useContext, useEffect, useRef } from 'react';
5+
import { render } from '@testing-library/react';
66

7-
import { SingleTabStopNavigationContext, useSingleTabStopNavigation } from '../';
7+
import {
8+
SingleTabStopNavigationAPI,
9+
SingleTabStopNavigationContext,
10+
SingleTabStopNavigationProvider,
11+
SingleTabStopNavigationReset,
12+
useSingleTabStopNavigation,
13+
} from '../';
814
import { renderWithSingleTabStopNavigation } from './utils';
915

16+
// Simple STSN subscriber component
1017
function Button(props: React.HTMLAttributes<HTMLButtonElement>) {
1118
const buttonRef = useRef<HTMLButtonElement>(null);
1219
const { tabIndex } = useSingleTabStopNavigation(buttonRef, { tabIndex: props.tabIndex });
1320
return <button {...props} ref={buttonRef} tabIndex={tabIndex} />;
1421
}
1522

16-
test('subscribed components can be rendered outside single tab stop navigation context', () => {
17-
render(<Button />);
18-
expect(document.querySelector('button')).not.toHaveAttribute('tabIndex');
19-
});
23+
// Simple STSN provider component
24+
function Group({
25+
id,
26+
navigationActive,
27+
children,
28+
}: {
29+
id: string;
30+
navigationActive: boolean;
31+
children: React.ReactNode;
32+
}) {
33+
const navigationAPI = useRef<SingleTabStopNavigationAPI>(null);
34+
35+
useEffect(() => {
36+
navigationAPI.current?.updateFocusTarget();
37+
});
38+
39+
return (
40+
<SingleTabStopNavigationProvider
41+
ref={navigationAPI}
42+
navigationActive={navigationActive}
43+
getNextFocusTarget={() => document.querySelector(`#${id}`)!.querySelectorAll('button')[0] as HTMLElement}
44+
>
45+
<div id={id}>
46+
<Button>First</Button>
47+
<Button>Second</Button>
48+
{children}
49+
</div>
50+
</SingleTabStopNavigationProvider>
51+
);
52+
}
53+
54+
function findGroupButton(groupId: string, buttonIndex: number) {
55+
return document.querySelector(`#${groupId}`)!.querySelectorAll('button')[buttonIndex] as HTMLElement;
56+
}
2057

2158
test('does not override tab index when keyboard navigation is not active', () => {
2259
renderWithSingleTabStopNavigation(<Button id="button" />, { navigationActive: false });
@@ -34,14 +71,11 @@ test('does not override tab index for suppressed elements', () => {
3471
</div>,
3572
{ navigationActive: true }
3673
);
37-
act(() => {
38-
setCurrentTarget(document.querySelector('#button1'), [
39-
document.querySelector('#button1'),
40-
document.querySelector('#button2'),
41-
document.querySelector('#button3'),
42-
]);
43-
});
44-
74+
setCurrentTarget(document.querySelector('#button1'), [
75+
document.querySelector('#button1'),
76+
document.querySelector('#button2'),
77+
document.querySelector('#button3'),
78+
]);
4579
expect(document.querySelector('#button1')).toHaveAttribute('tabIndex', '0');
4680
expect(document.querySelector('#button2')).toHaveAttribute('tabIndex', '0');
4781
expect(document.querySelector('#button3')).toHaveAttribute('tabIndex', '-1');
@@ -56,9 +90,7 @@ test('overrides tab index when keyboard navigation is active', () => {
5690
<Button id="button2" />
5791
</div>
5892
);
59-
act(() => {
60-
setCurrentTarget(document.querySelector('#button1'));
61-
});
93+
setCurrentTarget(document.querySelector('#button1'));
6294
expect(document.querySelector('#button1')).toHaveAttribute('tabIndex', '0');
6395
expect(document.querySelector('#button2')).toHaveAttribute('tabIndex', '-1');
6496
});
@@ -70,9 +102,7 @@ test('does not override explicit tab index with 0', () => {
70102
<Button id="button2" tabIndex={-2} />
71103
</div>
72104
);
73-
act(() => {
74-
setCurrentTarget(document.querySelector('#button1'));
75-
});
105+
setCurrentTarget(document.querySelector('#button1'));
76106
expect(document.querySelector('#button1')).toHaveAttribute('tabIndex', '-2');
77107
expect(document.querySelector('#button2')).toHaveAttribute('tabIndex', '-2');
78108
});
@@ -84,7 +114,9 @@ test('propagates and suppresses navigation active state', () => {
84114
}
85115
function Test({ navigationActive }: { navigationActive: boolean }) {
86116
return (
87-
<SingleTabStopNavigationContext.Provider value={{ navigationActive, registerFocusable: () => () => {} }}>
117+
<SingleTabStopNavigationContext.Provider
118+
value={{ navigationActive, registerFocusable: () => () => {}, resetFocusTarget: () => {} }}
119+
>
88120
<Component />
89121
</SingleTabStopNavigationContext.Provider>
90122
);
@@ -96,3 +128,162 @@ test('propagates and suppresses navigation active state', () => {
96128
rerender(<Test navigationActive={false} />);
97129
expect(document.querySelector('div')).toHaveTextContent('false');
98130
});
131+
132+
test('subscriber components can be used without provider', () => {
133+
function TestComponent(props: React.HTMLAttributes<HTMLButtonElement>) {
134+
const ref = useRef(null);
135+
const contextResult = useContext(SingleTabStopNavigationContext);
136+
const hookResult = useSingleTabStopNavigation(ref, { tabIndex: props.tabIndex });
137+
useEffect(() => {
138+
contextResult.registerFocusable(ref.current!, () => {});
139+
contextResult.resetFocusTarget();
140+
});
141+
return (
142+
<div ref={ref}>
143+
Context: {`${contextResult.navigationActive}`}, Hook: {`${hookResult.navigationActive}:${hookResult.tabIndex}`}
144+
</div>
145+
);
146+
}
147+
const { container } = render(<TestComponent />);
148+
expect(container.textContent).toBe('Context: false, Hook: false:undefined');
149+
});
150+
151+
describe('nested contexts', () => {
152+
test('tab indices are distributed correctly when switching contexts from inner to outer', () => {
153+
const { rerender } = render(
154+
<Group id="outer-most" navigationActive={false}>
155+
<Group id="outer" navigationActive={false}>
156+
<Group id="inner" navigationActive={true}>
157+
{null}
158+
</Group>
159+
</Group>
160+
</Group>
161+
);
162+
expect(findGroupButton('outer-most', 0)).not.toHaveAttribute('tabindex');
163+
expect(findGroupButton('outer-most', 1)).not.toHaveAttribute('tabindex');
164+
expect(findGroupButton('outer', 0)).not.toHaveAttribute('tabindex');
165+
expect(findGroupButton('outer', 1)).not.toHaveAttribute('tabindex');
166+
expect(findGroupButton('inner', 0)).toHaveAttribute('tabindex', '0');
167+
expect(findGroupButton('inner', 1)).toHaveAttribute('tabindex', '-1');
168+
169+
rerender(
170+
<Group id="outer-most" navigationActive={false}>
171+
<Group id="outer" navigationActive={true}>
172+
<Group id="inner" navigationActive={true}>
173+
{null}
174+
</Group>
175+
</Group>
176+
</Group>
177+
);
178+
expect(findGroupButton('outer-most', 0)).not.toHaveAttribute('tabindex');
179+
expect(findGroupButton('outer-most', 1)).not.toHaveAttribute('tabindex');
180+
expect(findGroupButton('outer', 0)).toHaveAttribute('tabindex', '0');
181+
expect(findGroupButton('outer', 1)).toHaveAttribute('tabindex', '-1');
182+
expect(findGroupButton('inner', 0)).toHaveAttribute('tabindex', '-1');
183+
expect(findGroupButton('inner', 1)).toHaveAttribute('tabindex', '-1');
184+
185+
rerender(
186+
<Group id="outer-most" navigationActive={true}>
187+
<Group id="outer" navigationActive={true}>
188+
<Group id="inner" navigationActive={true}>
189+
{null}
190+
</Group>
191+
</Group>
192+
</Group>
193+
);
194+
expect(findGroupButton('outer-most', 0)).toHaveAttribute('tabindex', '0');
195+
expect(findGroupButton('outer-most', 1)).toHaveAttribute('tabindex', '-1');
196+
expect(findGroupButton('outer', 0)).toHaveAttribute('tabindex', '-1');
197+
expect(findGroupButton('outer', 1)).toHaveAttribute('tabindex', '-1');
198+
expect(findGroupButton('inner', 0)).toHaveAttribute('tabindex', '-1');
199+
expect(findGroupButton('inner', 1)).toHaveAttribute('tabindex', '-1');
200+
});
201+
202+
test('tab indices are distributed correctly when switching contexts from outer to inner', () => {
203+
const { rerender } = render(
204+
<Group id="outer-most" navigationActive={true}>
205+
<Group id="outer" navigationActive={true}>
206+
<Group id="inner" navigationActive={true}>
207+
{null}
208+
</Group>
209+
</Group>
210+
</Group>
211+
);
212+
expect(findGroupButton('outer-most', 0)).toHaveAttribute('tabindex', '0');
213+
expect(findGroupButton('outer-most', 1)).toHaveAttribute('tabindex', '-1');
214+
expect(findGroupButton('outer', 0)).toHaveAttribute('tabindex', '-1');
215+
expect(findGroupButton('outer', 1)).toHaveAttribute('tabindex', '-1');
216+
expect(findGroupButton('inner', 0)).toHaveAttribute('tabindex', '-1');
217+
expect(findGroupButton('inner', 1)).toHaveAttribute('tabindex', '-1');
218+
219+
rerender(
220+
<Group id="outer-most" navigationActive={false}>
221+
<Group id="outer" navigationActive={true}>
222+
<Group id="inner" navigationActive={true}>
223+
{null}
224+
</Group>
225+
</Group>
226+
</Group>
227+
);
228+
expect(findGroupButton('outer-most', 0)).not.toHaveAttribute('tabindex');
229+
expect(findGroupButton('outer-most', 1)).not.toHaveAttribute('tabindex');
230+
expect(findGroupButton('outer', 0)).toHaveAttribute('tabindex', '0');
231+
expect(findGroupButton('outer', 1)).toHaveAttribute('tabindex', '-1');
232+
expect(findGroupButton('inner', 0)).toHaveAttribute('tabindex', '-1');
233+
expect(findGroupButton('inner', 1)).toHaveAttribute('tabindex', '-1');
234+
235+
rerender(
236+
<Group id="outer-most" navigationActive={false}>
237+
<Group id="outer" navigationActive={false}>
238+
<Group id="inner" navigationActive={true}>
239+
{null}
240+
</Group>
241+
</Group>
242+
</Group>
243+
);
244+
expect(findGroupButton('outer-most', 0)).not.toHaveAttribute('tabindex');
245+
expect(findGroupButton('outer-most', 1)).not.toHaveAttribute('tabindex');
246+
expect(findGroupButton('outer', 0)).not.toHaveAttribute('tabindex');
247+
expect(findGroupButton('outer', 1)).not.toHaveAttribute('tabindex');
248+
expect(findGroupButton('inner', 0)).toHaveAttribute('tabindex', '0');
249+
expect(findGroupButton('inner', 1)).toHaveAttribute('tabindex', '-1');
250+
});
251+
252+
test('ignores parent context when reset is used', () => {
253+
const { rerender } = render(
254+
<Group id="outer-most" navigationActive={true}>
255+
<SingleTabStopNavigationReset>
256+
<Group id="outer" navigationActive={true}>
257+
<Group id="inner" navigationActive={true}>
258+
{null}
259+
</Group>
260+
</Group>
261+
</SingleTabStopNavigationReset>
262+
</Group>
263+
);
264+
expect(findGroupButton('outer-most', 0)).toHaveAttribute('tabindex', '0');
265+
expect(findGroupButton('outer-most', 1)).toHaveAttribute('tabindex', '-1');
266+
expect(findGroupButton('outer', 0)).toHaveAttribute('tabindex', '0');
267+
expect(findGroupButton('outer', 1)).toHaveAttribute('tabindex', '-1');
268+
expect(findGroupButton('inner', 0)).toHaveAttribute('tabindex', '-1');
269+
expect(findGroupButton('inner', 1)).toHaveAttribute('tabindex', '-1');
270+
271+
rerender(
272+
<Group id="outer-most" navigationActive={true}>
273+
<Group id="outer" navigationActive={true}>
274+
<SingleTabStopNavigationReset>
275+
<Group id="inner" navigationActive={true}>
276+
{null}
277+
</Group>
278+
</SingleTabStopNavigationReset>
279+
</Group>
280+
</Group>
281+
);
282+
expect(findGroupButton('outer-most', 0)).toHaveAttribute('tabindex', '0');
283+
expect(findGroupButton('outer-most', 1)).toHaveAttribute('tabindex', '-1');
284+
expect(findGroupButton('outer', 0)).toHaveAttribute('tabindex', '-1');
285+
expect(findGroupButton('outer', 1)).toHaveAttribute('tabindex', '-1');
286+
expect(findGroupButton('inner', 0)).toHaveAttribute('tabindex', '0');
287+
expect(findGroupButton('inner', 1)).toHaveAttribute('tabindex', '-1');
288+
});
289+
});

src/internal/single-tab-stop/__tests__/utils.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ const FakeSingleTabStopNavigationProvider = forwardRef(
3636
}));
3737

3838
return (
39-
<SingleTabStopNavigationContext.Provider value={{ registerFocusable, navigationActive }}>
39+
<SingleTabStopNavigationContext.Provider
40+
value={{ navigationActive, registerFocusable, resetFocusTarget: () => {} }}
41+
>
4042
{children}
4143
</SingleTabStopNavigationContext.Provider>
4244
);

0 commit comments

Comments
 (0)