@primer/behaviors 0.0.0-202201710334 → 0.0.0-2022017131158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/lib/__tests__/anchored-position.test.d.ts +1 -0
  2. package/lib/__tests__/anchored-position.test.js +388 -0
  3. package/lib/__tests__/focus-trap.test.d.ts +1 -0
  4. package/lib/__tests__/focus-trap.test.js +234 -0
  5. package/lib/__tests__/focus-zone.test.d.ts +1 -0
  6. package/lib/__tests__/focus-zone.test.js +570 -0
  7. package/lib/__tests__/iterate-focusable-elements.test.d.ts +1 -0
  8. package/lib/__tests__/iterate-focusable-elements.test.js +55 -0
  9. package/lib/__tests__/scroll-into-view.test.d.ts +1 -0
  10. package/lib/__tests__/scroll-into-view.test.js +245 -0
  11. package/lib/anchored-position.d.ts +89 -0
  12. package/lib/anchored-position.js +316 -0
  13. package/lib/focus-trap.d.ts +12 -0
  14. package/lib/focus-trap.js +179 -0
  15. package/lib/focus-zone.d.ts +137 -0
  16. package/lib/focus-zone.js +578 -0
  17. package/lib/index.d.ts +4 -0
  18. package/lib/polyfills/event-listener-signal.d.ts +6 -0
  19. package/lib/polyfills/event-listener-signal.js +64 -0
  20. package/lib/scroll-into-view.d.ts +7 -0
  21. package/lib/scroll-into-view.js +42 -0
  22. package/lib/utils/index.d.ts +3 -0
  23. package/lib/utils/index.js +44 -0
  24. package/lib/utils/iterate-focusable-elements.d.ts +42 -0
  25. package/lib/utils/iterate-focusable-elements.js +113 -0
  26. package/lib/utils/unique-id.d.ts +1 -0
  27. package/lib/utils/unique-id.js +12 -0
  28. package/lib/utils/user-agent.d.ts +1 -0
  29. package/lib/utils/user-agent.js +15 -0
  30. package/lib-esm/__tests__/anchored-position.test.d.ts +1 -0
  31. package/lib-esm/__tests__/anchored-position.test.js +386 -0
  32. package/lib-esm/__tests__/focus-trap.test.d.ts +1 -0
  33. package/lib-esm/__tests__/focus-trap.test.js +227 -0
  34. package/lib-esm/__tests__/focus-zone.test.d.ts +1 -0
  35. package/lib-esm/__tests__/focus-zone.test.js +487 -0
  36. package/lib-esm/__tests__/iterate-focusable-elements.test.d.ts +1 -0
  37. package/lib-esm/__tests__/iterate-focusable-elements.test.js +48 -0
  38. package/lib-esm/__tests__/scroll-into-view.test.d.ts +1 -0
  39. package/lib-esm/__tests__/scroll-into-view.test.js +243 -0
  40. package/lib-esm/anchored-position.d.ts +89 -0
  41. package/lib-esm/anchored-position.js +309 -0
  42. package/lib-esm/focus-trap.d.ts +12 -0
  43. package/lib-esm/focus-trap.js +170 -0
  44. package/lib-esm/focus-zone.d.ts +137 -0
  45. package/lib-esm/focus-zone.js +559 -0
  46. package/lib-esm/index.d.ts +4 -0
  47. package/lib-esm/index.js +4 -0
  48. package/lib-esm/polyfills/event-listener-signal.d.ts +6 -0
  49. package/lib-esm/polyfills/event-listener-signal.js +57 -0
  50. package/lib-esm/scroll-into-view.d.ts +7 -0
  51. package/lib-esm/scroll-into-view.js +35 -0
  52. package/lib-esm/utils/index.d.ts +3 -0
  53. package/lib-esm/utils/index.js +3 -0
  54. package/lib-esm/utils/iterate-focusable-elements.d.ts +42 -0
  55. package/lib-esm/utils/iterate-focusable-elements.js +102 -0
  56. package/lib-esm/utils/unique-id.d.ts +1 -0
  57. package/lib-esm/utils/unique-id.js +5 -0
  58. package/lib-esm/utils/user-agent.d.ts +1 -0
  59. package/lib-esm/utils/user-agent.js +8 -0
  60. package/package.json +8 -6
  61. package/utils/package.json +6 -6
@@ -0,0 +1,487 @@
1
+ import { FocusKeys, focusZone } from '../focus-zone.js';
2
+ import { fireEvent, render } from '@testing-library/react';
3
+ import React from 'react';
4
+ import userEvent from '@testing-library/user-event';
5
+
6
+ async function nextTick() {
7
+ return new Promise(resolve => setTimeout(resolve, 0));
8
+ }
9
+
10
+ const moveDown = () => userEvent.type(document.activeElement, '{arrowdown}');
11
+
12
+ const moveUp = () => userEvent.type(document.activeElement, '{arrowup}'); // Since we use strict `isTabbable` checks within focus trap, we need to mock these
13
+ // properties that Jest does not populate.
14
+
15
+
16
+ beforeAll(() => {
17
+ try {
18
+ Object.defineProperties(HTMLElement.prototype, {
19
+ offsetHeight: {
20
+ get: () => 42
21
+ },
22
+ offsetWidth: {
23
+ get: () => 42
24
+ },
25
+ getClientRects: {
26
+ get: () => () => [42]
27
+ }
28
+ });
29
+ } catch {// ignore
30
+ }
31
+ });
32
+ it('Should allow arrow keys to move focus', () => {
33
+ const {
34
+ container
35
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
36
+ tabIndex: 0
37
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("div", {
38
+ id: "focusZone"
39
+ }, /*#__PURE__*/React.createElement("button", {
40
+ tabIndex: 0
41
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
42
+ tabIndex: 0
43
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
44
+ tabIndex: 0
45
+ }, "Cantaloupe"))));
46
+ const focusZoneContainer = container.querySelector('#focusZone');
47
+ const [firstButton, secondButton] = focusZoneContainer.querySelectorAll('button');
48
+ const controller = focusZone(focusZoneContainer);
49
+ firstButton.focus();
50
+ expect(document.activeElement).toEqual(firstButton);
51
+ userEvent.type(firstButton, '{arrowdown}');
52
+ expect(document.activeElement).toEqual(secondButton);
53
+ controller.abort();
54
+ });
55
+ it('Should have one tab-stop inside the focus zone when enabled', () => {
56
+ const {
57
+ container
58
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
59
+ tabIndex: 0
60
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("div", {
61
+ id: "focusZone"
62
+ }, /*#__PURE__*/React.createElement("button", {
63
+ tabIndex: 0
64
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
65
+ tabIndex: 0
66
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
67
+ tabIndex: 0
68
+ }, "Cantaloupe")), /*#__PURE__*/React.createElement("button", {
69
+ tabIndex: 0
70
+ }, "Next Apple")));
71
+ const focusZoneContainer = container.querySelector('#focusZone'); // eslint-disable-next-line @typescript-eslint/no-unused-vars
72
+
73
+ const [one, two, three, four, five] = container.querySelectorAll('button');
74
+ const controller = focusZone(focusZoneContainer);
75
+ one.focus();
76
+ userEvent.tab();
77
+ userEvent.tab();
78
+ expect(document.activeElement).toEqual(five);
79
+ controller.abort();
80
+ one.focus();
81
+ userEvent.tab();
82
+ userEvent.tab();
83
+ expect(document.activeElement).toEqual(three);
84
+ controller.abort();
85
+ });
86
+ it('Should prevent moving focus outside the zone', () => {
87
+ const {
88
+ container
89
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
90
+ tabIndex: 0
91
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("div", {
92
+ id: "focusZone"
93
+ }, /*#__PURE__*/React.createElement("button", {
94
+ tabIndex: 0
95
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
96
+ tabIndex: 0
97
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
98
+ tabIndex: 0
99
+ }, "Cantaloupe"))));
100
+ const focusZoneContainer = container.querySelector('#focusZone');
101
+ const [firstButton, secondButton, thirdButton] = focusZoneContainer.querySelectorAll('button');
102
+ const controller = focusZone(focusZoneContainer);
103
+ firstButton.focus();
104
+ expect(document.activeElement).toEqual(firstButton);
105
+ userEvent.type(firstButton, '{arrowup}');
106
+ expect(document.activeElement).toEqual(firstButton);
107
+ userEvent.type(firstButton, '{arrowdown}');
108
+ expect(document.activeElement).toEqual(secondButton);
109
+ userEvent.type(secondButton, '{arrowdown}');
110
+ expect(document.activeElement).toEqual(thirdButton);
111
+ userEvent.type(thirdButton, '{arrowdown}');
112
+ expect(document.activeElement).toEqual(thirdButton);
113
+ controller.abort();
114
+ });
115
+ it('Should do focus wrapping correctly', () => {
116
+ const {
117
+ container
118
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
119
+ tabIndex: 0
120
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("div", {
121
+ id: "focusZone"
122
+ }, /*#__PURE__*/React.createElement("button", {
123
+ tabIndex: 0
124
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
125
+ tabIndex: 0
126
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
127
+ tabIndex: 0
128
+ }, "Cantaloupe"))));
129
+ const focusZoneContainer = container.querySelector('#focusZone');
130
+ const [firstButton, secondButton, thirdButton] = focusZoneContainer.querySelectorAll('button');
131
+ const controller = focusZone(focusZoneContainer, {
132
+ focusOutBehavior: 'wrap'
133
+ });
134
+ firstButton.focus();
135
+ expect(document.activeElement).toEqual(firstButton);
136
+ userEvent.type(firstButton, '{arrowup}');
137
+ expect(document.activeElement).toEqual(thirdButton);
138
+ userEvent.type(thirdButton, '{arrowup}');
139
+ expect(document.activeElement).toEqual(secondButton);
140
+ userEvent.type(secondButton, '{arrowdown}');
141
+ expect(document.activeElement).toEqual(thirdButton);
142
+ userEvent.type(thirdButton, '{arrowdown}');
143
+ expect(document.activeElement).toEqual(firstButton);
144
+ controller.abort();
145
+ });
146
+ it('Should call custom getNextFocusable callback', () => {
147
+ const {
148
+ container
149
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
150
+ tabIndex: 0
151
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("div", {
152
+ id: "focusZone"
153
+ }, /*#__PURE__*/React.createElement("button", {
154
+ tabIndex: 0
155
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
156
+ tabIndex: 0
157
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
158
+ tabIndex: 0
159
+ }, "Cantaloupe"))));
160
+ const focusZoneContainer = container.querySelector('#focusZone');
161
+ const [firstButton, secondButton] = focusZoneContainer.querySelectorAll('button');
162
+ const getNextFocusableCallback = jest.fn();
163
+ const controller = focusZone(focusZoneContainer, {
164
+ getNextFocusable: getNextFocusableCallback
165
+ });
166
+ firstButton.focus();
167
+ expect(document.activeElement).toEqual(firstButton);
168
+ userEvent.type(firstButton, '{arrowdown}');
169
+ expect(getNextFocusableCallback).toHaveBeenCalledWith('next', firstButton, expect.anything());
170
+ userEvent.type(secondButton, '{home}');
171
+ expect(getNextFocusableCallback).toHaveBeenCalledWith('start', secondButton, expect.anything());
172
+ controller.abort();
173
+ });
174
+ it('Should focus-in to the most recently-focused element', () => {
175
+ const {
176
+ container
177
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
178
+ tabIndex: 0,
179
+ id: "outside"
180
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("div", {
181
+ id: "focusZone"
182
+ }, /*#__PURE__*/React.createElement("button", {
183
+ tabIndex: 0
184
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
185
+ tabIndex: 0
186
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
187
+ tabIndex: 0
188
+ }, "Cantaloupe"))));
189
+ const focusZoneContainer = container.querySelector('#focusZone');
190
+ const outsideButton = container.querySelector('#outside');
191
+ const [firstButton, secondButton] = focusZoneContainer.querySelectorAll('button');
192
+ const controller = focusZone(focusZoneContainer);
193
+ firstButton.focus();
194
+ expect(document.activeElement).toEqual(firstButton);
195
+ userEvent.type(firstButton, '{arrowdown}');
196
+ expect(document.activeElement).toEqual(secondButton);
197
+ outsideButton.focus();
198
+ userEvent.tab();
199
+ expect(document.activeElement).toEqual(secondButton);
200
+ controller.abort();
201
+ });
202
+ it('Should focus-in to the first element when focusInStrategy is "first"', () => {
203
+ const {
204
+ container
205
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
206
+ tabIndex: 0,
207
+ id: "outside"
208
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("div", {
209
+ id: "focusZone"
210
+ }, /*#__PURE__*/React.createElement("button", {
211
+ tabIndex: 0
212
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
213
+ tabIndex: 0
214
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
215
+ tabIndex: 0
216
+ }, "Cantaloupe"))));
217
+ const focusZoneContainer = container.querySelector('#focusZone');
218
+ const outsideButton = container.querySelector('#outside');
219
+ const [firstButton, secondButton] = focusZoneContainer.querySelectorAll('button');
220
+ const controller = focusZone(focusZoneContainer, {
221
+ focusInStrategy: 'first'
222
+ });
223
+ firstButton.focus();
224
+ expect(document.activeElement).toEqual(firstButton);
225
+ userEvent.type(firstButton, '{arrowdown}');
226
+ expect(document.activeElement).toEqual(secondButton);
227
+ outsideButton.focus();
228
+ userEvent.tab();
229
+ expect(document.activeElement).toEqual(firstButton);
230
+ controller.abort();
231
+ });
232
+ it('Should focus-in to the closest element when focusInStrategy is "closest"', () => {
233
+ const {
234
+ container
235
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
236
+ tabIndex: 0,
237
+ id: "outsideBefore"
238
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("div", {
239
+ id: "focusZone"
240
+ }, /*#__PURE__*/React.createElement("button", {
241
+ id: "apple",
242
+ tabIndex: 0
243
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
244
+ id: "banana",
245
+ tabIndex: 0
246
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
247
+ id: "cantaloupe",
248
+ tabIndex: 0
249
+ }, "Cantaloupe")), /*#__PURE__*/React.createElement("button", {
250
+ tabIndex: 0,
251
+ id: "outsideAfter"
252
+ }, "Good Apple")));
253
+ const focusZoneContainer = container.querySelector('#focusZone');
254
+ const outsideBefore = container.querySelector('#outsideBefore');
255
+ const outsideAfter = container.querySelector('#outsideAfter');
256
+ const [firstButton, secondButton, thirdButton] = focusZoneContainer.querySelectorAll('button');
257
+ const controller = focusZone(focusZoneContainer, {
258
+ focusInStrategy: 'closest'
259
+ });
260
+ firstButton.focus();
261
+ expect(document.activeElement).toEqual(firstButton);
262
+ userEvent.type(firstButton, '{arrowdown}');
263
+ expect(document.activeElement).toEqual(secondButton);
264
+ outsideBefore.focus();
265
+ userEvent.tab();
266
+ expect(document.activeElement).toEqual(firstButton);
267
+ outsideAfter.focus();
268
+ userEvent.tab({
269
+ shift: true
270
+ });
271
+ expect(document.activeElement).toEqual(thirdButton);
272
+ controller.abort();
273
+ });
274
+ it('Should call the custom focusInStrategy callback', () => {
275
+ const {
276
+ container
277
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
278
+ tabIndex: 0,
279
+ id: "outside"
280
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("div", {
281
+ id: "focusZone"
282
+ }, /*#__PURE__*/React.createElement("button", {
283
+ tabIndex: 0
284
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
285
+ tabIndex: 0
286
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
287
+ tabIndex: 0
288
+ }, "Cantaloupe"))));
289
+ const focusZoneContainer = container.querySelector('#focusZone');
290
+ const outsideButton = container.querySelector('#outside');
291
+ const [, secondButton] = focusZoneContainer.querySelectorAll('button');
292
+ const focusInCallback = jest.fn().mockReturnValue(secondButton);
293
+ const controller = focusZone(focusZoneContainer, {
294
+ focusInStrategy: focusInCallback
295
+ });
296
+ outsideButton.focus();
297
+ userEvent.tab();
298
+ expect(focusInCallback).toHaveBeenCalledWith(outsideButton);
299
+ expect(document.activeElement).toEqual(secondButton);
300
+ controller.abort();
301
+ });
302
+ it('Should respect inputs by not moving focus if key would have some other effect', () => {
303
+ const {
304
+ container
305
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
306
+ tabIndex: 0,
307
+ id: "outside"
308
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("div", {
309
+ id: "focusZone"
310
+ }, /*#__PURE__*/React.createElement("button", {
311
+ tabIndex: 0
312
+ }, "Apple"), /*#__PURE__*/React.createElement("input", {
313
+ type: "text",
314
+ defaultValue: "Banana",
315
+ tabIndex: 0
316
+ }), /*#__PURE__*/React.createElement("button", {
317
+ tabIndex: 0
318
+ }, "Cantaloupe"))));
319
+ const focusZoneContainer = container.querySelector('#focusZone');
320
+ const [firstButton, secondButton] = focusZoneContainer.querySelectorAll('button');
321
+ const input = focusZoneContainer.querySelector('input');
322
+ const controller = focusZone(focusZoneContainer, {
323
+ bindKeys: FocusKeys.ArrowHorizontal | FocusKeys.HomeAndEnd
324
+ });
325
+ firstButton.focus();
326
+ userEvent.type(firstButton, '{arrowright}');
327
+ expect(document.activeElement).toEqual(input);
328
+ userEvent.type(input, '{arrowleft}');
329
+ expect(document.activeElement).toEqual(input);
330
+ userEvent.type(input, '{arrowright}');
331
+ expect(document.activeElement).toEqual(input);
332
+ userEvent.type(input, '{arrowright}');
333
+ expect(document.activeElement).toEqual(secondButton);
334
+ controller.abort();
335
+ });
336
+ it('Should focus-in to the first element if the last-focused element is removed', async () => {
337
+ const {
338
+ container
339
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
340
+ tabIndex: 0,
341
+ id: "outside"
342
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("div", {
343
+ id: "focusZone"
344
+ }, /*#__PURE__*/React.createElement("button", {
345
+ tabIndex: 0
346
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
347
+ tabIndex: 0
348
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
349
+ tabIndex: 0
350
+ }, "Cantaloupe"))));
351
+ const focusZoneContainer = container.querySelector('#focusZone');
352
+ const [firstButton, secondButton, thirdButton] = focusZoneContainer.querySelectorAll('button');
353
+ const outsideButton = container.querySelector('#outside');
354
+ const controller = focusZone(focusZoneContainer);
355
+ firstButton.focus();
356
+ userEvent.type(firstButton, '{arrowdown}');
357
+ expect(document.activeElement).toEqual(secondButton);
358
+ outsideButton.focus();
359
+ focusZoneContainer.removeChild(secondButton); // The mutation observer fires asynchronously
360
+
361
+ await nextTick();
362
+ userEvent.tab();
363
+ expect(document.activeElement).toEqual(firstButton);
364
+ userEvent.type(firstButton, '{arrowdown}');
365
+ expect(document.activeElement).toEqual(thirdButton);
366
+ controller.abort();
367
+ });
368
+ it('Should call onActiveDescendantChanged properly', () => {
369
+ const {
370
+ container
371
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
372
+ tabIndex: 0,
373
+ id: "outside"
374
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("input", {
375
+ id: "control",
376
+ defaultValue: "control input",
377
+ tabIndex: 0
378
+ }), /*#__PURE__*/React.createElement("div", {
379
+ id: "focusZone"
380
+ }, /*#__PURE__*/React.createElement("button", {
381
+ tabIndex: 0
382
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
383
+ tabIndex: 0
384
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
385
+ tabIndex: 0
386
+ }, "Cantaloupe"))));
387
+ const focusZoneContainer = container.querySelector('#focusZone');
388
+ const [firstButton, secondButton] = focusZoneContainer.querySelectorAll('button');
389
+ const control = container.querySelector('#control');
390
+ const activeDescendantChangedCallback = jest.fn();
391
+ const controller = focusZone(focusZoneContainer, {
392
+ activeDescendantControl: control,
393
+ onActiveDescendantChanged: activeDescendantChangedCallback
394
+ });
395
+ control.focus();
396
+ expect(activeDescendantChangedCallback).toHaveBeenLastCalledWith(firstButton, undefined, false);
397
+ userEvent.type(control, '{arrowdown}');
398
+ expect(activeDescendantChangedCallback).toHaveBeenLastCalledWith(secondButton, firstButton, true);
399
+ userEvent.type(control, '{arrowup}');
400
+ expect(activeDescendantChangedCallback).toHaveBeenLastCalledWith(firstButton, secondButton, true);
401
+ fireEvent.mouseMove(secondButton);
402
+ expect(activeDescendantChangedCallback).toHaveBeenLastCalledWith(secondButton, firstButton, false);
403
+ userEvent.type(control, '{arrowup}');
404
+ expect(activeDescendantChangedCallback).toHaveBeenLastCalledWith(firstButton, secondButton, true);
405
+ userEvent.type(control, '{arrowUp}');
406
+ expect(activeDescendantChangedCallback).toHaveBeenLastCalledWith(firstButton, firstButton, true);
407
+ activeDescendantChangedCallback.mockReset();
408
+ fireEvent.mouseMove(firstButton);
409
+ expect(activeDescendantChangedCallback).not.toBeCalled();
410
+ controller.abort();
411
+ });
412
+ it('Should set aria-activedescendant correctly', () => {
413
+ const {
414
+ container
415
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
416
+ tabIndex: 0,
417
+ id: "outside"
418
+ }, "Bad Apple"), /*#__PURE__*/React.createElement("input", {
419
+ id: "control",
420
+ defaultValue: "control input",
421
+ tabIndex: 0
422
+ }), /*#__PURE__*/React.createElement("div", {
423
+ id: "focusZone"
424
+ }, /*#__PURE__*/React.createElement("button", {
425
+ tabIndex: 0
426
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
427
+ tabIndex: 0
428
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
429
+ tabIndex: 0
430
+ }, "Cantaloupe"))));
431
+ const focusZoneContainer = container.querySelector('#focusZone');
432
+ const [firstButton, secondButton] = focusZoneContainer.querySelectorAll('button');
433
+ const outsideButton = container.querySelector('#outside');
434
+ const control = container.querySelector('#control');
435
+ const controller = focusZone(focusZoneContainer, {
436
+ activeDescendantControl: control
437
+ });
438
+ control.focus();
439
+ expect(control.getAttribute('aria-activedescendant')).toEqual(firstButton.id);
440
+ userEvent.type(control, '{arrowdown}');
441
+ expect(control.getAttribute('aria-activedescendant')).toEqual(secondButton.id);
442
+ userEvent.type(control, '{arrowup}');
443
+ expect(control.getAttribute('aria-activedescendant')).toEqual(firstButton.id);
444
+ expect(document.activeElement).toEqual(control);
445
+ userEvent.type(control, '{arrowup}');
446
+ expect(control.getAttribute('aria-activedescendant')).toEqual(firstButton.id);
447
+ outsideButton.focus();
448
+ expect(control.hasAttribute('aria-activedescendant')).toBeFalsy();
449
+ controller.abort();
450
+ });
451
+ it('Should handle elements being reordered', async () => {
452
+ const {
453
+ container
454
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
455
+ id: "focusZone"
456
+ }, /*#__PURE__*/React.createElement("button", {
457
+ tabIndex: 0
458
+ }, "Apple"), /*#__PURE__*/React.createElement("button", {
459
+ tabIndex: 0
460
+ }, "Banana"), /*#__PURE__*/React.createElement("button", {
461
+ tabIndex: 0
462
+ }, "Cantaloupe"), /*#__PURE__*/React.createElement("button", {
463
+ tabIndex: 0
464
+ }, "Durian"))));
465
+ const focusZoneContainer = container.querySelector('#focusZone');
466
+ const [firstButton, secondButton, thirdButton, fourthButton] = focusZoneContainer.querySelectorAll('button');
467
+ const controller = focusZone(focusZoneContainer);
468
+ firstButton.focus();
469
+ expect(document.activeElement).toEqual(firstButton);
470
+ moveDown();
471
+ expect(document.activeElement).toEqual(secondButton);
472
+ moveUp();
473
+ expect(document.activeElement).toEqual(firstButton); // move secondButton and thirdButton to the end of the zone, in reverse order
474
+
475
+ focusZoneContainer.appendChild(thirdButton);
476
+ focusZoneContainer.appendChild(secondButton); // The mutation observer fires asynchronously
477
+
478
+ await nextTick();
479
+ expect(document.activeElement).toEqual(firstButton);
480
+ moveDown();
481
+ expect(document.activeElement).toEqual(fourthButton);
482
+ moveDown();
483
+ expect(document.activeElement).toEqual(thirdButton);
484
+ moveDown();
485
+ expect(document.activeElement).toEqual(secondButton);
486
+ controller.abort();
487
+ });
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import { iterateFocusableElements } from '../utils/iterate-focusable-elements.js';
3
+ import { render } from '@testing-library/react';
4
+ it('Should iterate through focusable elements only', () => {
5
+ const {
6
+ container
7
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("textarea", null)), /*#__PURE__*/React.createElement("input", null), /*#__PURE__*/React.createElement("button", null, "Hello"), /*#__PURE__*/React.createElement("p", null, "Not focusable"), /*#__PURE__*/React.createElement("div", {
8
+ tabIndex: 0
9
+ }, /*#__PURE__*/React.createElement("a", {
10
+ tabIndex: -1,
11
+ href: "#boo"
12
+ }, "Not focusable"), /*#__PURE__*/React.createElement("a", {
13
+ href: "#yah"
14
+ }, "Focusable"))));
15
+ const focusable = Array.from(iterateFocusableElements(container, {
16
+ onlyTabbable: true
17
+ }));
18
+ expect(focusable.length).toEqual(5);
19
+ expect(focusable[0].tagName.toLowerCase()).toEqual('textarea');
20
+ expect(focusable[1].tagName.toLowerCase()).toEqual('input');
21
+ expect(focusable[2].tagName.toLowerCase()).toEqual('button');
22
+ expect(focusable[3].tagName.toLowerCase()).toEqual('div');
23
+ expect(focusable[4].tagName.toLowerCase()).toEqual('a');
24
+ expect(focusable[4].getAttribute('href')).toEqual('#yah');
25
+ });
26
+ it('Should iterate through focusable elements in reverse', () => {
27
+ const {
28
+ container
29
+ } = render( /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("textarea", null)), /*#__PURE__*/React.createElement("input", null), /*#__PURE__*/React.createElement("button", null, "Hello"), /*#__PURE__*/React.createElement("p", null, "Not focusable"), /*#__PURE__*/React.createElement("div", {
30
+ tabIndex: 0
31
+ }, /*#__PURE__*/React.createElement("a", {
32
+ tabIndex: -1,
33
+ href: "#boo"
34
+ }, "Not focusable"), /*#__PURE__*/React.createElement("a", {
35
+ href: "#yah"
36
+ }, "Focusable"))));
37
+ const focusable = Array.from(iterateFocusableElements(container, {
38
+ reverse: true,
39
+ onlyTabbable: true
40
+ }));
41
+ expect(focusable.length).toEqual(5);
42
+ expect(focusable[0].tagName.toLowerCase()).toEqual('a');
43
+ expect(focusable[0].getAttribute('href')).toEqual('#yah');
44
+ expect(focusable[1].tagName.toLowerCase()).toEqual('div');
45
+ expect(focusable[2].tagName.toLowerCase()).toEqual('button');
46
+ expect(focusable[3].tagName.toLowerCase()).toEqual('input');
47
+ expect(focusable[4].tagName.toLowerCase()).toEqual('textarea');
48
+ });
@@ -0,0 +1 @@
1
+ export {};