@primer/behaviors 0.0.0-20220188453 → 0.0.0-2022019163715

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 (78) hide show
  1. package/dist/cjs/anchored-position.d.ts +15 -0
  2. package/dist/cjs/anchored-position.js +210 -0
  3. package/dist/cjs/focus-trap.d.ts +2 -0
  4. package/dist/cjs/focus-trap.js +111 -0
  5. package/dist/cjs/focus-zone.d.ts +32 -0
  6. package/dist/cjs/focus-zone.js +410 -0
  7. package/{lib-esm → dist/cjs}/index.d.ts +0 -0
  8. package/dist/cjs/index.js +16 -0
  9. package/{lib-esm → dist/cjs}/polyfills/event-listener-signal.d.ts +0 -0
  10. package/dist/cjs/polyfills/event-listener-signal.js +44 -0
  11. package/{lib-esm → dist/cjs}/scroll-into-view.d.ts +0 -0
  12. package/dist/cjs/scroll-into-view.js +21 -0
  13. package/{lib-esm → dist/cjs}/utils/index.d.ts +0 -0
  14. package/dist/cjs/utils/index.js +15 -0
  15. package/dist/cjs/utils/iterate-focusable-elements.d.ts +8 -0
  16. package/dist/cjs/utils/iterate-focusable-elements.js +63 -0
  17. package/{lib-esm → dist/cjs}/utils/unique-id.d.ts +0 -0
  18. package/dist/cjs/utils/unique-id.js +8 -0
  19. package/{lib-esm → dist/cjs}/utils/user-agent.d.ts +0 -0
  20. package/dist/cjs/utils/user-agent.js +11 -0
  21. package/dist/esm/anchored-position.d.ts +15 -0
  22. package/dist/esm/anchored-position.js +206 -0
  23. package/dist/esm/focus-trap.d.ts +2 -0
  24. package/dist/esm/focus-trap.js +107 -0
  25. package/dist/esm/focus-zone.d.ts +32 -0
  26. package/dist/esm/focus-zone.js +406 -0
  27. package/{lib → dist/esm}/index.d.ts +0 -0
  28. package/{lib-esm → dist/esm}/index.js +1 -1
  29. package/{lib → dist/esm}/polyfills/event-listener-signal.d.ts +0 -0
  30. package/dist/esm/polyfills/event-listener-signal.js +40 -0
  31. package/{lib → dist/esm}/scroll-into-view.d.ts +0 -0
  32. package/dist/esm/scroll-into-view.js +17 -0
  33. package/{lib → dist/esm}/utils/index.d.ts +0 -0
  34. package/{lib-esm → dist/esm}/utils/index.js +1 -1
  35. package/dist/esm/utils/iterate-focusable-elements.d.ts +8 -0
  36. package/dist/esm/utils/iterate-focusable-elements.js +57 -0
  37. package/{lib → dist/esm}/utils/unique-id.d.ts +0 -0
  38. package/dist/esm/utils/unique-id.js +4 -0
  39. package/{lib → dist/esm}/utils/user-agent.d.ts +0 -0
  40. package/dist/esm/utils/user-agent.js +7 -0
  41. package/package.json +18 -35
  42. package/utils/package.json +7 -0
  43. package/lib/__tests__/anchored-position.test.js +0 -388
  44. package/lib/__tests__/focus-trap.test.js +0 -234
  45. package/lib/__tests__/focus-zone.test.js +0 -570
  46. package/lib/__tests__/iterate-focusable-elements.test.js +0 -55
  47. package/lib/__tests__/scroll-into-view.test.js +0 -245
  48. package/lib/anchored-position.d.ts +0 -89
  49. package/lib/anchored-position.js +0 -316
  50. package/lib/focus-trap.d.ts +0 -12
  51. package/lib/focus-trap.js +0 -179
  52. package/lib/focus-zone.d.ts +0 -137
  53. package/lib/focus-zone.js +0 -578
  54. package/lib/index.js +0 -57
  55. package/lib/polyfills/event-listener-signal.js +0 -64
  56. package/lib/scroll-into-view.js +0 -42
  57. package/lib/utils/index.js +0 -44
  58. package/lib/utils/iterate-focusable-elements.d.ts +0 -42
  59. package/lib/utils/iterate-focusable-elements.js +0 -113
  60. package/lib/utils/unique-id.js +0 -12
  61. package/lib/utils/user-agent.js +0 -15
  62. package/lib-esm/__tests__/anchored-position.test.js +0 -386
  63. package/lib-esm/__tests__/focus-trap.test.js +0 -227
  64. package/lib-esm/__tests__/focus-zone.test.js +0 -487
  65. package/lib-esm/__tests__/iterate-focusable-elements.test.js +0 -48
  66. package/lib-esm/__tests__/scroll-into-view.test.js +0 -243
  67. package/lib-esm/anchored-position.d.ts +0 -89
  68. package/lib-esm/anchored-position.js +0 -309
  69. package/lib-esm/focus-trap.d.ts +0 -12
  70. package/lib-esm/focus-trap.js +0 -170
  71. package/lib-esm/focus-zone.d.ts +0 -137
  72. package/lib-esm/focus-zone.js +0 -559
  73. package/lib-esm/polyfills/event-listener-signal.js +0 -57
  74. package/lib-esm/scroll-into-view.js +0 -35
  75. package/lib-esm/utils/iterate-focusable-elements.d.ts +0 -42
  76. package/lib-esm/utils/iterate-focusable-elements.js +0 -102
  77. package/lib-esm/utils/unique-id.js +0 -5
  78. package/lib-esm/utils/user-agent.js +0 -8
@@ -0,0 +1,406 @@
1
+ import { polyfill as eventListenerSignalPolyfill } from './polyfills/event-listener-signal.js';
2
+ import { isMacOS } from './utils/user-agent.js';
3
+ import { iterateFocusableElements } from './utils/iterate-focusable-elements.js';
4
+ import { uniqueId } from './utils/unique-id.js';
5
+ eventListenerSignalPolyfill();
6
+ export var FocusKeys;
7
+ (function (FocusKeys) {
8
+ FocusKeys[FocusKeys["ArrowHorizontal"] = 1] = "ArrowHorizontal";
9
+ FocusKeys[FocusKeys["ArrowVertical"] = 2] = "ArrowVertical";
10
+ FocusKeys[FocusKeys["JK"] = 4] = "JK";
11
+ FocusKeys[FocusKeys["HL"] = 8] = "HL";
12
+ FocusKeys[FocusKeys["HomeAndEnd"] = 16] = "HomeAndEnd";
13
+ FocusKeys[FocusKeys["PageUpDown"] = 256] = "PageUpDown";
14
+ FocusKeys[FocusKeys["WS"] = 32] = "WS";
15
+ FocusKeys[FocusKeys["AD"] = 64] = "AD";
16
+ FocusKeys[FocusKeys["Tab"] = 128] = "Tab";
17
+ FocusKeys[FocusKeys["ArrowAll"] = 3] = "ArrowAll";
18
+ FocusKeys[FocusKeys["HJKL"] = 12] = "HJKL";
19
+ FocusKeys[FocusKeys["WASD"] = 96] = "WASD";
20
+ FocusKeys[FocusKeys["All"] = 511] = "All";
21
+ })(FocusKeys || (FocusKeys = {}));
22
+ const KEY_TO_BIT = {
23
+ ArrowLeft: FocusKeys.ArrowHorizontal,
24
+ ArrowDown: FocusKeys.ArrowVertical,
25
+ ArrowUp: FocusKeys.ArrowVertical,
26
+ ArrowRight: FocusKeys.ArrowHorizontal,
27
+ h: FocusKeys.HL,
28
+ j: FocusKeys.JK,
29
+ k: FocusKeys.JK,
30
+ l: FocusKeys.HL,
31
+ a: FocusKeys.AD,
32
+ s: FocusKeys.WS,
33
+ w: FocusKeys.WS,
34
+ d: FocusKeys.AD,
35
+ Tab: FocusKeys.Tab,
36
+ Home: FocusKeys.HomeAndEnd,
37
+ End: FocusKeys.HomeAndEnd,
38
+ PageUp: FocusKeys.PageUpDown,
39
+ PageDown: FocusKeys.PageUpDown
40
+ };
41
+ const KEY_TO_DIRECTION = {
42
+ ArrowLeft: 'previous',
43
+ ArrowDown: 'next',
44
+ ArrowUp: 'previous',
45
+ ArrowRight: 'next',
46
+ h: 'previous',
47
+ j: 'next',
48
+ k: 'previous',
49
+ l: 'next',
50
+ a: 'previous',
51
+ s: 'next',
52
+ w: 'previous',
53
+ d: 'next',
54
+ Tab: 'next',
55
+ Home: 'start',
56
+ End: 'end',
57
+ PageUp: 'start',
58
+ PageDown: 'end'
59
+ };
60
+ function getDirection(keyboardEvent) {
61
+ const direction = KEY_TO_DIRECTION[keyboardEvent.key];
62
+ if (keyboardEvent.key === 'Tab' && keyboardEvent.shiftKey) {
63
+ return 'previous';
64
+ }
65
+ const isMac = isMacOS();
66
+ if ((isMac && keyboardEvent.metaKey) || (!isMac && keyboardEvent.ctrlKey)) {
67
+ if (keyboardEvent.key === 'ArrowLeft' || keyboardEvent.key === 'ArrowUp') {
68
+ return 'start';
69
+ }
70
+ else if (keyboardEvent.key === 'ArrowRight' || keyboardEvent.key === 'ArrowDown') {
71
+ return 'end';
72
+ }
73
+ }
74
+ return direction;
75
+ }
76
+ function shouldIgnoreFocusHandling(keyboardEvent, activeElement) {
77
+ const key = keyboardEvent.key;
78
+ const keyLength = [...key].length;
79
+ const isTextInput = (activeElement instanceof HTMLInputElement && activeElement.type === 'text') ||
80
+ activeElement instanceof HTMLTextAreaElement;
81
+ if (isTextInput && (keyLength === 1 || key === 'Home' || key === 'End')) {
82
+ return true;
83
+ }
84
+ if (activeElement instanceof HTMLSelectElement) {
85
+ if (keyLength === 1) {
86
+ return true;
87
+ }
88
+ if (key === 'ArrowDown' && isMacOS() && !keyboardEvent.metaKey) {
89
+ return true;
90
+ }
91
+ if (key === 'ArrowDown' && !isMacOS() && keyboardEvent.altKey) {
92
+ return true;
93
+ }
94
+ }
95
+ if (activeElement instanceof HTMLTextAreaElement && (key === 'PageUp' || key === 'PageDown')) {
96
+ return true;
97
+ }
98
+ if (isTextInput) {
99
+ const textInput = activeElement;
100
+ const cursorAtStart = textInput.selectionStart === 0 && textInput.selectionEnd === 0;
101
+ const cursorAtEnd = textInput.selectionStart === textInput.value.length && textInput.selectionEnd === textInput.value.length;
102
+ if (key === 'ArrowLeft' && !cursorAtStart) {
103
+ return true;
104
+ }
105
+ if (key === 'ArrowRight' && !cursorAtEnd) {
106
+ return true;
107
+ }
108
+ if (textInput instanceof HTMLTextAreaElement) {
109
+ if (key === 'ArrowUp' && !cursorAtStart) {
110
+ return true;
111
+ }
112
+ if (key === 'ArrowDown' && !cursorAtEnd) {
113
+ return true;
114
+ }
115
+ }
116
+ }
117
+ return false;
118
+ }
119
+ export const isActiveDescendantAttribute = 'data-is-active-descendant';
120
+ export const activeDescendantActivatedDirectly = 'activated-directly';
121
+ export const activeDescendantActivatedIndirectly = 'activated-indirectly';
122
+ export const hasActiveDescendantAttribute = 'data-has-active-descendant';
123
+ export function focusZone(container, settings) {
124
+ var _a, _b, _c, _d;
125
+ const focusableElements = [];
126
+ const savedTabIndex = new WeakMap();
127
+ const bindKeys = (_a = settings === null || settings === void 0 ? void 0 : settings.bindKeys) !== null && _a !== void 0 ? _a : ((settings === null || settings === void 0 ? void 0 : settings.getNextFocusable) ? FocusKeys.ArrowAll : FocusKeys.ArrowVertical) | FocusKeys.HomeAndEnd;
128
+ const focusOutBehavior = (_b = settings === null || settings === void 0 ? void 0 : settings.focusOutBehavior) !== null && _b !== void 0 ? _b : 'stop';
129
+ const focusInStrategy = (_c = settings === null || settings === void 0 ? void 0 : settings.focusInStrategy) !== null && _c !== void 0 ? _c : 'previous';
130
+ const activeDescendantControl = settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl;
131
+ const activeDescendantCallback = settings === null || settings === void 0 ? void 0 : settings.onActiveDescendantChanged;
132
+ let currentFocusedElement;
133
+ function getFirstFocusableElement() {
134
+ return focusableElements[0];
135
+ }
136
+ function isActiveDescendantInputFocused() {
137
+ return document.activeElement === activeDescendantControl;
138
+ }
139
+ function updateFocusedElement(to, directlyActivated = false) {
140
+ const from = currentFocusedElement;
141
+ currentFocusedElement = to;
142
+ if (activeDescendantControl) {
143
+ if (to && isActiveDescendantInputFocused()) {
144
+ setActiveDescendant(from, to, directlyActivated);
145
+ }
146
+ else {
147
+ clearActiveDescendant();
148
+ }
149
+ return;
150
+ }
151
+ if (from && from !== to && savedTabIndex.has(from)) {
152
+ from.setAttribute('tabindex', '-1');
153
+ }
154
+ to === null || to === void 0 ? void 0 : to.setAttribute('tabindex', '0');
155
+ }
156
+ function setActiveDescendant(from, to, directlyActivated = false) {
157
+ if (!to.id) {
158
+ to.setAttribute('id', uniqueId());
159
+ }
160
+ if (from && from !== to) {
161
+ from.removeAttribute(isActiveDescendantAttribute);
162
+ }
163
+ if (!activeDescendantControl ||
164
+ (!directlyActivated && activeDescendantControl.getAttribute('aria-activedescendant') === to.id)) {
165
+ return;
166
+ }
167
+ activeDescendantControl.setAttribute('aria-activedescendant', to.id);
168
+ container.setAttribute(hasActiveDescendantAttribute, to.id);
169
+ to.setAttribute(isActiveDescendantAttribute, directlyActivated ? activeDescendantActivatedDirectly : activeDescendantActivatedIndirectly);
170
+ activeDescendantCallback === null || activeDescendantCallback === void 0 ? void 0 : activeDescendantCallback(to, from, directlyActivated);
171
+ }
172
+ function clearActiveDescendant(previouslyActiveElement = currentFocusedElement) {
173
+ if (focusInStrategy === 'first') {
174
+ currentFocusedElement = undefined;
175
+ }
176
+ activeDescendantControl === null || activeDescendantControl === void 0 ? void 0 : activeDescendantControl.removeAttribute('aria-activedescendant');
177
+ container.removeAttribute(hasActiveDescendantAttribute);
178
+ previouslyActiveElement === null || previouslyActiveElement === void 0 ? void 0 : previouslyActiveElement.removeAttribute(isActiveDescendantAttribute);
179
+ activeDescendantCallback === null || activeDescendantCallback === void 0 ? void 0 : activeDescendantCallback(undefined, previouslyActiveElement, false);
180
+ }
181
+ function beginFocusManagement(...elements) {
182
+ const filteredElements = elements.filter(e => { var _a, _b; return (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.focusableElementFilter) === null || _a === void 0 ? void 0 : _a.call(settings, e)) !== null && _b !== void 0 ? _b : true; });
183
+ if (filteredElements.length === 0) {
184
+ return;
185
+ }
186
+ const insertIndex = focusableElements.findIndex(e => (e.compareDocumentPosition(filteredElements[0]) & Node.DOCUMENT_POSITION_PRECEDING) > 0);
187
+ focusableElements.splice(insertIndex === -1 ? focusableElements.length : insertIndex, 0, ...filteredElements);
188
+ for (const element of filteredElements) {
189
+ if (!savedTabIndex.has(element)) {
190
+ savedTabIndex.set(element, element.getAttribute('tabindex'));
191
+ }
192
+ element.setAttribute('tabindex', '-1');
193
+ }
194
+ if (!currentFocusedElement) {
195
+ updateFocusedElement(getFirstFocusableElement());
196
+ }
197
+ }
198
+ function endFocusManagement(...elements) {
199
+ for (const element of elements) {
200
+ const focusableElementIndex = focusableElements.indexOf(element);
201
+ if (focusableElementIndex >= 0) {
202
+ focusableElements.splice(focusableElementIndex, 1);
203
+ }
204
+ const savedIndex = savedTabIndex.get(element);
205
+ if (savedIndex !== undefined) {
206
+ if (savedIndex === null) {
207
+ element.removeAttribute('tabindex');
208
+ }
209
+ else {
210
+ element.setAttribute('tabindex', savedIndex);
211
+ }
212
+ savedTabIndex.delete(element);
213
+ }
214
+ if (element === currentFocusedElement) {
215
+ const nextElementToFocus = getFirstFocusableElement();
216
+ updateFocusedElement(nextElementToFocus);
217
+ }
218
+ }
219
+ }
220
+ beginFocusManagement(...iterateFocusableElements(container));
221
+ updateFocusedElement(getFirstFocusableElement());
222
+ const observer = new MutationObserver(mutations => {
223
+ for (const mutation of mutations) {
224
+ for (const removedNode of mutation.removedNodes) {
225
+ if (removedNode instanceof HTMLElement) {
226
+ endFocusManagement(...iterateFocusableElements(removedNode));
227
+ }
228
+ }
229
+ }
230
+ for (const mutation of mutations) {
231
+ for (const addedNode of mutation.addedNodes) {
232
+ if (addedNode instanceof HTMLElement) {
233
+ beginFocusManagement(...iterateFocusableElements(addedNode));
234
+ }
235
+ }
236
+ }
237
+ });
238
+ observer.observe(container, {
239
+ subtree: true,
240
+ childList: true
241
+ });
242
+ const controller = new AbortController();
243
+ const signal = (_d = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _d !== void 0 ? _d : controller.signal;
244
+ signal.addEventListener('abort', () => {
245
+ endFocusManagement(...focusableElements);
246
+ });
247
+ let elementIndexFocusedByClick = undefined;
248
+ container.addEventListener('mousedown', event => {
249
+ if (event.target instanceof HTMLElement && event.target !== document.activeElement) {
250
+ elementIndexFocusedByClick = focusableElements.indexOf(event.target);
251
+ }
252
+ }, { signal });
253
+ if (activeDescendantControl) {
254
+ container.addEventListener('focusin', event => {
255
+ if (event.target instanceof HTMLElement && focusableElements.includes(event.target)) {
256
+ activeDescendantControl.focus();
257
+ updateFocusedElement(event.target);
258
+ }
259
+ });
260
+ container.addEventListener('mousemove', ({ target }) => {
261
+ if (!(target instanceof Node)) {
262
+ return;
263
+ }
264
+ const focusableElement = focusableElements.find(element => element.contains(target));
265
+ if (focusableElement) {
266
+ updateFocusedElement(focusableElement);
267
+ }
268
+ }, { signal, capture: true });
269
+ activeDescendantControl.addEventListener('focusin', () => {
270
+ if (!currentFocusedElement) {
271
+ updateFocusedElement(getFirstFocusableElement());
272
+ }
273
+ else {
274
+ setActiveDescendant(undefined, currentFocusedElement);
275
+ }
276
+ });
277
+ activeDescendantControl.addEventListener('focusout', () => {
278
+ clearActiveDescendant();
279
+ });
280
+ }
281
+ else {
282
+ container.addEventListener('focusin', event => {
283
+ if (event.target instanceof HTMLElement) {
284
+ if (elementIndexFocusedByClick !== undefined) {
285
+ if (elementIndexFocusedByClick >= 0) {
286
+ if (focusableElements[elementIndexFocusedByClick] !== currentFocusedElement) {
287
+ updateFocusedElement(focusableElements[elementIndexFocusedByClick]);
288
+ }
289
+ }
290
+ elementIndexFocusedByClick = undefined;
291
+ }
292
+ else {
293
+ if (focusInStrategy === 'previous') {
294
+ updateFocusedElement(event.target);
295
+ }
296
+ else if (focusInStrategy === 'closest' || focusInStrategy === 'first') {
297
+ if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
298
+ const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.length - 1 : 0;
299
+ const targetElement = focusableElements[targetElementIndex];
300
+ targetElement === null || targetElement === void 0 ? void 0 : targetElement.focus();
301
+ return;
302
+ }
303
+ else {
304
+ updateFocusedElement(event.target);
305
+ }
306
+ }
307
+ else if (typeof focusInStrategy === 'function') {
308
+ if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
309
+ const elementToFocus = focusInStrategy(event.relatedTarget);
310
+ const requestedFocusElementIndex = elementToFocus ? focusableElements.indexOf(elementToFocus) : -1;
311
+ if (requestedFocusElementIndex >= 0 && elementToFocus instanceof HTMLElement) {
312
+ elementToFocus.focus();
313
+ return;
314
+ }
315
+ else {
316
+ console.warn('Element requested is not a known focusable element.');
317
+ }
318
+ }
319
+ else {
320
+ updateFocusedElement(event.target);
321
+ }
322
+ }
323
+ }
324
+ }
325
+ lastKeyboardFocusDirection = undefined;
326
+ }, { signal });
327
+ }
328
+ const keyboardEventRecipient = activeDescendantControl !== null && activeDescendantControl !== void 0 ? activeDescendantControl : container;
329
+ let lastKeyboardFocusDirection = undefined;
330
+ if (focusInStrategy === 'closest') {
331
+ document.addEventListener('keydown', event => {
332
+ if (event.key === 'Tab') {
333
+ lastKeyboardFocusDirection = getDirection(event);
334
+ }
335
+ }, { signal, capture: true });
336
+ }
337
+ function getCurrentFocusedIndex() {
338
+ if (!currentFocusedElement) {
339
+ return 0;
340
+ }
341
+ const focusedIndex = focusableElements.indexOf(currentFocusedElement);
342
+ const fallbackIndex = currentFocusedElement === container ? -1 : 0;
343
+ return focusedIndex !== -1 ? focusedIndex : fallbackIndex;
344
+ }
345
+ keyboardEventRecipient.addEventListener('keydown', event => {
346
+ var _a;
347
+ if (event.key in KEY_TO_DIRECTION) {
348
+ const keyBit = KEY_TO_BIT[event.key];
349
+ if (!event.defaultPrevented &&
350
+ (keyBit & bindKeys) > 0 &&
351
+ !shouldIgnoreFocusHandling(event, document.activeElement)) {
352
+ const direction = getDirection(event);
353
+ let nextElementToFocus = undefined;
354
+ if (settings === null || settings === void 0 ? void 0 : settings.getNextFocusable) {
355
+ nextElementToFocus = settings.getNextFocusable(direction, (_a = document.activeElement) !== null && _a !== void 0 ? _a : undefined, event);
356
+ }
357
+ if (!nextElementToFocus) {
358
+ const lastFocusedIndex = getCurrentFocusedIndex();
359
+ let nextFocusedIndex = lastFocusedIndex;
360
+ if (direction === 'previous') {
361
+ nextFocusedIndex -= 1;
362
+ }
363
+ else if (direction === 'start') {
364
+ nextFocusedIndex = 0;
365
+ }
366
+ else if (direction === 'next') {
367
+ nextFocusedIndex += 1;
368
+ }
369
+ else {
370
+ nextFocusedIndex = focusableElements.length - 1;
371
+ }
372
+ if (nextFocusedIndex < 0) {
373
+ if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
374
+ nextFocusedIndex = focusableElements.length - 1;
375
+ }
376
+ else {
377
+ nextFocusedIndex = 0;
378
+ }
379
+ }
380
+ if (nextFocusedIndex >= focusableElements.length) {
381
+ if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
382
+ nextFocusedIndex = 0;
383
+ }
384
+ else {
385
+ nextFocusedIndex = focusableElements.length - 1;
386
+ }
387
+ }
388
+ if (lastFocusedIndex !== nextFocusedIndex) {
389
+ nextElementToFocus = focusableElements[nextFocusedIndex];
390
+ }
391
+ }
392
+ if (activeDescendantControl) {
393
+ updateFocusedElement(nextElementToFocus || currentFocusedElement, true);
394
+ }
395
+ else if (nextElementToFocus) {
396
+ lastKeyboardFocusDirection = direction;
397
+ nextElementToFocus.focus();
398
+ }
399
+ if (event.key !== 'Tab' || nextElementToFocus) {
400
+ event.preventDefault();
401
+ }
402
+ }
403
+ }
404
+ }, { signal });
405
+ return controller;
406
+ }
File without changes
@@ -1,4 +1,4 @@
1
1
  export * from './anchored-position.js';
2
2
  export * from './focus-trap.js';
3
3
  export * from './focus-zone.js';
4
- export * from './scroll-into-view.js';
4
+ export * from './scroll-into-view.js';
@@ -0,0 +1,40 @@
1
+ let signalSupported = false;
2
+ function noop() { }
3
+ try {
4
+ const options = Object.create({}, {
5
+ signal: {
6
+ get() {
7
+ signalSupported = true;
8
+ }
9
+ }
10
+ });
11
+ window.addEventListener('test', noop, options);
12
+ window.removeEventListener('test', noop, options);
13
+ }
14
+ catch (e) {
15
+ }
16
+ function featureSupported() {
17
+ return signalSupported;
18
+ }
19
+ function monkeyPatch() {
20
+ if (typeof window === 'undefined') {
21
+ return;
22
+ }
23
+ const originalAddEventListener = EventTarget.prototype.addEventListener;
24
+ EventTarget.prototype.addEventListener = function (name, originalCallback, optionsOrCapture) {
25
+ if (typeof optionsOrCapture === 'object' &&
26
+ 'signal' in optionsOrCapture &&
27
+ optionsOrCapture.signal instanceof AbortSignal) {
28
+ originalAddEventListener.call(optionsOrCapture.signal, 'abort', () => {
29
+ this.removeEventListener(name, originalCallback, optionsOrCapture);
30
+ });
31
+ }
32
+ return originalAddEventListener.call(this, name, originalCallback, optionsOrCapture);
33
+ };
34
+ }
35
+ export function polyfill() {
36
+ if (!featureSupported()) {
37
+ monkeyPatch();
38
+ signalSupported = true;
39
+ }
40
+ }
File without changes
@@ -0,0 +1,17 @@
1
+ export function scrollIntoView(child, viewingArea, { direction = 'vertical', startMargin = 0, endMargin = 0, behavior = 'smooth' } = {}) {
2
+ const startSide = direction === 'vertical' ? 'top' : 'left';
3
+ const endSide = direction === 'vertical' ? 'bottom' : 'right';
4
+ const scrollSide = direction === 'vertical' ? 'scrollTop' : 'scrollLeft';
5
+ const { [startSide]: childStart, [endSide]: childEnd } = child.getBoundingClientRect();
6
+ const { [startSide]: viewingAreaStart, [endSide]: viewingAreaEnd } = viewingArea.getBoundingClientRect();
7
+ const isChildStartAboveViewingArea = childStart < viewingAreaStart + startMargin;
8
+ const isChildBottomBelowViewingArea = childEnd > viewingAreaEnd - endMargin;
9
+ if (isChildStartAboveViewingArea) {
10
+ const scrollHeightToChildStart = childStart - viewingAreaStart + viewingArea[scrollSide];
11
+ viewingArea.scrollTo({ behavior, [startSide]: scrollHeightToChildStart - startMargin });
12
+ }
13
+ else if (isChildBottomBelowViewingArea) {
14
+ const scrollHeightToChildBottom = childEnd - viewingAreaEnd + viewingArea[scrollSide];
15
+ viewingArea.scrollTo({ behavior, [startSide]: scrollHeightToChildBottom + endMargin });
16
+ }
17
+ }
File without changes
@@ -1,3 +1,3 @@
1
1
  export * from './iterate-focusable-elements.js';
2
2
  export * from './unique-id.js';
3
- export * from './user-agent.js';
3
+ export * from './user-agent.js';
@@ -0,0 +1,8 @@
1
+ export interface IterateFocusableElements {
2
+ reverse?: boolean;
3
+ strict?: boolean;
4
+ onlyTabbable?: boolean;
5
+ }
6
+ export declare function iterateFocusableElements(container: HTMLElement, options?: IterateFocusableElements): Generator<HTMLElement, undefined, undefined>;
7
+ export declare function isFocusable(elem: HTMLElement, strict?: boolean): boolean;
8
+ export declare function isTabbable(elem: HTMLElement, strict?: boolean): boolean;
@@ -0,0 +1,57 @@
1
+ export function* iterateFocusableElements(container, options = {}) {
2
+ var _a, _b;
3
+ const strict = (_a = options.strict) !== null && _a !== void 0 ? _a : false;
4
+ const acceptFn = ((_b = options.onlyTabbable) !== null && _b !== void 0 ? _b : false) ? isTabbable : isFocusable;
5
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
6
+ acceptNode: node => node instanceof HTMLElement && acceptFn(node, strict) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
7
+ });
8
+ let nextNode = null;
9
+ if (!options.reverse && acceptFn(container, strict)) {
10
+ yield container;
11
+ }
12
+ if (options.reverse) {
13
+ let lastChild = walker.lastChild();
14
+ while (lastChild) {
15
+ nextNode = lastChild;
16
+ lastChild = walker.lastChild();
17
+ }
18
+ }
19
+ else {
20
+ nextNode = walker.firstChild();
21
+ }
22
+ while (nextNode instanceof HTMLElement) {
23
+ yield nextNode;
24
+ nextNode = options.reverse ? walker.previousNode() : walker.nextNode();
25
+ }
26
+ if (options.reverse && acceptFn(container, strict)) {
27
+ yield container;
28
+ }
29
+ return undefined;
30
+ }
31
+ export function isFocusable(elem, strict = false) {
32
+ const disabledAttrInert = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'OPTGROUP', 'OPTION', 'FIELDSET'].includes(elem.tagName) &&
33
+ elem.disabled;
34
+ const hiddenInert = elem.hidden;
35
+ const hiddenInputInert = elem instanceof HTMLInputElement && elem.type === 'hidden';
36
+ if (disabledAttrInert || hiddenInert || hiddenInputInert) {
37
+ return false;
38
+ }
39
+ if (strict) {
40
+ const sizeInert = elem.offsetWidth === 0 || elem.offsetHeight === 0;
41
+ const visibilityInert = ['hidden', 'collapse'].includes(getComputedStyle(elem).visibility);
42
+ const clientRectsInert = elem.getClientRects().length === 0;
43
+ if (sizeInert || visibilityInert || clientRectsInert) {
44
+ return false;
45
+ }
46
+ }
47
+ if (elem.getAttribute('tabindex') != null) {
48
+ return true;
49
+ }
50
+ if (elem instanceof HTMLAnchorElement && elem.getAttribute('href') == null) {
51
+ return false;
52
+ }
53
+ return elem.tabIndex !== -1;
54
+ }
55
+ export function isTabbable(elem, strict = false) {
56
+ return isFocusable(elem, strict) && elem.getAttribute('tabindex') !== '-1';
57
+ }
File without changes
@@ -0,0 +1,4 @@
1
+ let idSeed = 10000;
2
+ export function uniqueId() {
3
+ return `__primer_id_${idSeed++}`;
4
+ }
File without changes
@@ -0,0 +1,7 @@
1
+ let isMac = undefined;
2
+ export function isMacOS() {
3
+ if (isMac === undefined) {
4
+ isMac = /^mac/i.test(window.navigator.platform);
5
+ }
6
+ return isMac;
7
+ }
package/package.json CHANGED
@@ -1,42 +1,42 @@
1
1
  {
2
2
  "name": "@primer/behaviors",
3
- "version": "0.0.0-20220188453",
3
+ "version": "0.0.0-2022019163715",
4
4
  "description": "Shared behaviors for JavaScript components",
5
- "main": "lib/index.js",
6
- "module": "lib-esm/index.js",
5
+ "main": "dist/cjs/index.js",
6
+ "module": "dist/esm/index.js",
7
7
  "exports": {
8
8
  ".": {
9
- "types": "./lib/index.d.ts",
10
- "node": "./lib/index.js",
11
- "default": "./lib-esm/index.js"
9
+ "types": "./dist/index.d.ts",
10
+ "require": "./dist/cjs/index.js",
11
+ "module": "./dist/esm/index.js"
12
12
  },
13
13
  "./utils": {
14
- "types": "./lib/utils/index.d.ts",
15
- "node": "./lib/utils/index.js",
16
- "default": "./lib-esm/utils/index.js"
14
+ "types": "./dist/utils/index.d.ts",
15
+ "require": "./dist/cjs/utils/index.js",
16
+ "module": "./dist/esm/utils/index.js"
17
17
  }
18
18
  },
19
+ "types": "dist/cjs/index.d.ts",
19
20
  "files": [
20
21
  "dist",
21
- "utils",
22
- "lib",
23
- "lib-esm",
24
- "lib/utils"
22
+ "utils"
25
23
  ],
26
24
  "sideEffects": [
27
- "dist/focus-zone.js",
28
- "dist/focus-trap.js"
25
+ "dist/esm/focus-zone.js",
26
+ "dist/esm/focus-trap.js",
27
+ "dist/cjs/focus-zone.js",
28
+ "dist/cjs/focus-trap.js"
29
29
  ],
30
30
  "scripts": {
31
- "build:esm": "cross-env BABEL_ENV=esmUnbundled babel src --extensions '.ts' --out-dir 'lib/esm' --source-maps",
32
- "build:cjs": "cross-env BABEL_ENV=cjs babel src --extensions '.ts' --out-dir 'lib/cjs' --source-maps",
33
31
  "lint": "eslint src/",
34
32
  "test": "npm run jest && npm run lint",
35
33
  "test:watch": "jest --watch",
36
34
  "jest": "jest",
37
35
  "clean": "rm -rf dist",
38
36
  "prebuild": "npm run clean",
39
- "build": "./scripts/build",
37
+ "build": "npm run build:esm && npm run build:cjs",
38
+ "build:esm": "tsc",
39
+ "build:cjs": "tsc --module commonjs --outDir dist/cjs",
40
40
  "size-limit": "npm run build && size-limit",
41
41
  "release": "npm run build && changeset publish"
42
42
  },
@@ -64,22 +64,6 @@
64
64
  }
65
65
  ],
66
66
  "devDependencies": {
67
- "@babel/cli": "7.14.5",
68
- "@babel/core": "7.14.8",
69
- "@babel/eslint-parser": "7.15.7",
70
- "@babel/plugin-proposal-nullish-coalescing-operator": "7.16.0",
71
- "@babel/plugin-proposal-optional-chaining": "7.14.5",
72
- "@babel/plugin-transform-modules-commonjs": "7.14.5",
73
- "@babel/preset-react": "7.14.5",
74
- "@babel/preset-typescript": "7.15.0",
75
- "babel-core": "7.0.0-bridge.0",
76
- "babel-loader": "^8.2.2",
77
- "babel-plugin-add-react-displayname": "0.0.5",
78
- "babel-plugin-macros": "3.1.0",
79
- "babel-plugin-preval": "5.0.0",
80
- "babel-plugin-styled-components": "2.0.2",
81
- "babel-plugin-transform-replace-expressions": "0.2.0",
82
- "babel-polyfill": "6.26.0",
83
67
  "@changesets/changelog-github": "^0.4.2",
84
68
  "@changesets/cli": "^2.18.1",
85
69
  "@github/prettier-config": "0.0.4",
@@ -88,7 +72,6 @@
88
72
  "@testing-library/user-event": "^13.5.0",
89
73
  "@types/jest": "^27.0.3",
90
74
  "@types/react": "^17.0.37",
91
- "cross-env": "^7.0.3",
92
75
  "esbuild": "^0.14.1",
93
76
  "esbuild-jest": "^0.5.0",
94
77
  "eslint": "^8.3.0",