Skip to content

Commit 70e769a

Browse files
authored
Fix FocusScope FocusManager accept node (#3227)
* Fix FocusScope FocusManager accept node
1 parent a162e5b commit 70e769a

File tree

2 files changed

+80
-8
lines changed

2 files changed

+80
-8
lines changed

packages/@react-aria/focus/src/FocusScope.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,10 @@ function createFocusManagerForScope(scopeRef: React.RefObject<HTMLElement[]>): F
147147
return {
148148
focusNext(opts: FocusManagerOptions = {}) {
149149
let scope = scopeRef.current;
150-
let {from, tabbable, wrap} = opts;
150+
let {from, tabbable, wrap, accept} = opts;
151151
let node = from || document.activeElement;
152152
let sentinel = scope[0].previousElementSibling;
153-
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable}, scope);
153+
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable, accept}, scope);
154154
walker.currentNode = isElementInScope(node, scope) ? node : sentinel;
155155
let nextNode = walker.nextNode() as HTMLElement;
156156
if (!nextNode && wrap) {
@@ -164,10 +164,10 @@ function createFocusManagerForScope(scopeRef: React.RefObject<HTMLElement[]>): F
164164
},
165165
focusPrevious(opts: FocusManagerOptions = {}) {
166166
let scope = scopeRef.current;
167-
let {from, tabbable, wrap} = opts;
167+
let {from, tabbable, wrap, accept} = opts;
168168
let node = from || document.activeElement;
169169
let sentinel = scope[scope.length - 1].nextElementSibling;
170-
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable}, scope);
170+
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable, accept}, scope);
171171
walker.currentNode = isElementInScope(node, scope) ? node : sentinel;
172172
let previousNode = walker.previousNode() as HTMLElement;
173173
if (!previousNode && wrap) {
@@ -181,8 +181,8 @@ function createFocusManagerForScope(scopeRef: React.RefObject<HTMLElement[]>): F
181181
},
182182
focusFirst(opts = {}) {
183183
let scope = scopeRef.current;
184-
let {tabbable} = opts;
185-
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable}, scope);
184+
let {tabbable, accept} = opts;
185+
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable, accept}, scope);
186186
walker.currentNode = scope[0].previousElementSibling;
187187
let nextNode = walker.nextNode() as HTMLElement;
188188
if (nextNode) {
@@ -192,8 +192,8 @@ function createFocusManagerForScope(scopeRef: React.RefObject<HTMLElement[]>): F
192192
},
193193
focusLast(opts = {}) {
194194
let scope = scopeRef.current;
195-
let {tabbable} = opts;
196-
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable}, scope);
195+
let {tabbable, accept} = opts;
196+
let walker = getFocusableTreeWalker(getScopeRoot(scope), {tabbable, accept}, scope);
197197
walker.currentNode = scope[scope.length - 1].nextElementSibling;
198198
let previousNode = walker.previousNode() as HTMLElement;
199199
if (previousNode) {

packages/@react-aria/focus/test/FocusScope.test.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,42 @@ describe('FocusScope', function () {
779779
expect(document.activeElement).toBe(item2);
780780
});
781781

782+
it('should move focus forward and allow users to skip certain elements', function () {
783+
function Test() {
784+
return (
785+
<FocusScope>
786+
<Item data-testid="item1" />
787+
<Item data-testid="item2" data-skip />
788+
<Item data-testid="item3" />
789+
</FocusScope>
790+
);
791+
}
792+
793+
function Item(props) {
794+
let focusManager = useFocusManager();
795+
let onClick = () => {
796+
focusManager.focusNext({
797+
wrap: true,
798+
accept: (e) => !e.getAttribute('data-skip')
799+
});
800+
};
801+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
802+
return <div {...props} tabIndex={-1} role="button" onClick={onClick} />;
803+
}
804+
805+
let {getByTestId} = render(<Test />);
806+
let item1 = getByTestId('item1');
807+
let item3 = getByTestId('item3');
808+
809+
act(() => {item1.focus();});
810+
811+
fireEvent.click(item1);
812+
expect(document.activeElement).toBe(item3);
813+
814+
fireEvent.click(item3);
815+
expect(document.activeElement).toBe(item1);
816+
});
817+
782818
it('should move focus backward', function () {
783819
function Test() {
784820
return (
@@ -931,6 +967,42 @@ describe('FocusScope', function () {
931967
// and wrap is false
932968
expect(document.activeElement).toBe(item1);
933969
});
970+
971+
it('should move focus backward and allow users to skip certain elements', function () {
972+
function Test() {
973+
return (
974+
<FocusScope>
975+
<Item data-testid="item1" />
976+
<Item data-testid="item2" data-skip />
977+
<Item data-testid="item3" />
978+
</FocusScope>
979+
);
980+
}
981+
982+
function Item(props) {
983+
let focusManager = useFocusManager();
984+
let onClick = () => {
985+
focusManager.focusPrevious({
986+
wrap: true,
987+
accept: (e) => !e.getAttribute('data-skip')
988+
});
989+
};
990+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
991+
return <div {...props} tabIndex={-1} role="button" onClick={onClick} />;
992+
}
993+
994+
let {getByTestId} = render(<Test />);
995+
let item1 = getByTestId('item1');
996+
let item3 = getByTestId('item3');
997+
998+
act(() => {item1.focus();});
999+
1000+
fireEvent.click(item1);
1001+
expect(document.activeElement).toBe(item3);
1002+
1003+
fireEvent.click(item3);
1004+
expect(document.activeElement).toBe(item1);
1005+
});
9341006
});
9351007

9361008
describe('nested focus scopes', function () {

0 commit comments

Comments
 (0)