@primer/behaviors 0.0.0-2022927173910 → 0.0.0-20230925221012

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.
@@ -5,12 +5,12 @@ const alternateOrders = {
5
5
  'outside-top': ['outside-bottom', 'outside-right', 'outside-left', 'outside-bottom'],
6
6
  'outside-bottom': ['outside-top', 'outside-right', 'outside-left', 'outside-bottom'],
7
7
  'outside-left': ['outside-right', 'outside-bottom', 'outside-top', 'outside-bottom'],
8
- 'outside-right': ['outside-left', 'outside-bottom', 'outside-top', 'outside-bottom']
8
+ 'outside-right': ['outside-left', 'outside-bottom', 'outside-top', 'outside-bottom'],
9
9
  };
10
10
  const alternateAlignments = {
11
11
  start: ['end', 'center'],
12
12
  end: ['start', 'center'],
13
- center: ['end', 'start']
13
+ center: ['end', 'start'],
14
14
  };
15
15
  function getAnchoredPosition(floatingElement, anchorElement, settings = {}) {
16
16
  const parentElement = getPositionedParent(floatingElement);
@@ -20,12 +20,14 @@ function getAnchoredPosition(floatingElement, anchorElement, settings = {}) {
20
20
  const [borderTop, borderLeft] = [parentElementStyle.borderTopWidth, parentElementStyle.borderLeftWidth].map(v => parseInt(v, 10) || 0);
21
21
  const relativeRect = {
22
22
  top: parentElementRect.top + borderTop,
23
- left: parentElementRect.left + borderLeft
23
+ left: parentElementRect.left + borderLeft,
24
24
  };
25
25
  return pureCalculateAnchoredPosition(clippingRect, relativeRect, floatingElement.getBoundingClientRect(), anchorElement instanceof Element ? anchorElement.getBoundingClientRect() : anchorElement, getDefaultSettings(settings));
26
26
  }
27
27
  exports.getAnchoredPosition = getAnchoredPosition;
28
28
  function getPositionedParent(element) {
29
+ if (isOnTopLayer(element))
30
+ return document.body;
29
31
  let parentNode = element.parentNode;
30
32
  while (parentNode !== null) {
31
33
  if (parentNode instanceof HTMLElement && getComputedStyle(parentNode).position !== 'static') {
@@ -35,6 +37,21 @@ function getPositionedParent(element) {
35
37
  }
36
38
  return document.body;
37
39
  }
40
+ function isOnTopLayer(element) {
41
+ var _a;
42
+ if (element.tagName === 'DIALOG') {
43
+ return true;
44
+ }
45
+ try {
46
+ if (element.matches(':popover-open') && /native code/.test((_a = document.body.showPopover) === null || _a === void 0 ? void 0 : _a.toString())) {
47
+ return true;
48
+ }
49
+ }
50
+ catch (_b) {
51
+ return false;
52
+ }
53
+ return false;
54
+ }
38
55
  function getClippingRect(element) {
39
56
  let parentNode = element;
40
57
  while (parentNode !== null) {
@@ -54,13 +71,13 @@ function getClippingRect(element) {
54
71
  elemStyle.borderTopWidth,
55
72
  elemStyle.borderLeftWidth,
56
73
  elemStyle.borderRightWidth,
57
- elemStyle.borderBottomWidth
74
+ elemStyle.borderBottomWidth,
58
75
  ].map(v => parseInt(v, 10) || 0);
59
76
  return {
60
77
  top: elemRect.top + borderTop,
61
78
  left: elemRect.left + borderLeft,
62
79
  width: elemRect.width - borderRight - borderLeft,
63
- height: Math.max(elemRect.height - borderTop - borderBottom, clippingNode === document.body ? window.innerHeight : -Infinity)
80
+ height: Math.max(elemRect.height - borderTop - borderBottom, clippingNode === document.body ? window.innerHeight : -Infinity),
64
81
  };
65
82
  }
66
83
  const positionDefaults = {
@@ -68,7 +85,7 @@ const positionDefaults = {
68
85
  align: 'start',
69
86
  anchorOffset: 4,
70
87
  alignmentOffset: 4,
71
- allowOutOfBounds: false
88
+ allowOutOfBounds: false,
72
89
  };
73
90
  function getDefaultSettings(settings = {}) {
74
91
  var _a, _b, _c, _d, _e;
@@ -79,7 +96,7 @@ function getDefaultSettings(settings = {}) {
79
96
  align,
80
97
  anchorOffset: (_c = settings.anchorOffset) !== null && _c !== void 0 ? _c : (side === 'inside-center' ? 0 : positionDefaults.anchorOffset),
81
98
  alignmentOffset: (_d = settings.alignmentOffset) !== null && _d !== void 0 ? _d : (align !== 'center' && side.startsWith('inside') ? positionDefaults.alignmentOffset : 0),
82
- allowOutOfBounds: (_e = settings.allowOutOfBounds) !== null && _e !== void 0 ? _e : positionDefaults.allowOutOfBounds
99
+ allowOutOfBounds: (_e = settings.allowOutOfBounds) !== null && _e !== void 0 ? _e : positionDefaults.allowOutOfBounds,
83
100
  };
84
101
  }
85
102
  function pureCalculateAnchoredPosition(viewportRect, relativePosition, floatingRect, anchorRect, { side, align, allowOutOfBounds, anchorOffset, alignmentOffset }) {
@@ -87,7 +104,7 @@ function pureCalculateAnchoredPosition(viewportRect, relativePosition, floatingR
87
104
  top: viewportRect.top - relativePosition.top,
88
105
  left: viewportRect.left - relativePosition.left,
89
106
  width: viewportRect.width,
90
- height: viewportRect.height
107
+ height: viewportRect.height,
91
108
  };
92
109
  let pos = calculatePosition(floatingRect, anchorRect, side, align, anchorOffset, alignmentOffset);
93
110
  let anchorSide = side;
@@ -5,7 +5,7 @@ function offset(element) {
5
5
  const rect = element.getBoundingClientRect();
6
6
  return {
7
7
  top: rect.top + window.pageYOffset,
8
- left: rect.left + window.pageXOffset
8
+ left: rect.left + window.pageXOffset,
9
9
  };
10
10
  }
11
11
  exports.offset = offset;
@@ -62,11 +62,11 @@ function overflowOffset(element, targetContainer) {
62
62
  const scroll = container === document.documentElement && document.defaultView
63
63
  ? {
64
64
  top: document.defaultView.pageYOffset,
65
- left: document.defaultView.pageXOffset
65
+ left: document.defaultView.pageXOffset,
66
66
  }
67
67
  : {
68
68
  top: container.scrollTop,
69
- left: container.scrollLeft
69
+ left: container.scrollLeft,
70
70
  };
71
71
  const top = elementOffset.top - scroll.top;
72
72
  const left = elementOffset.left - scroll.left;
@@ -94,7 +94,7 @@ function focusTrap(container, initialFocus, abortSignal) {
94
94
  container,
95
95
  controller: wrappingController,
96
96
  initialFocus,
97
- originalSignal: signal
97
+ originalSignal: signal,
98
98
  };
99
99
  const suspendedTrapIndex = suspendedTrapStack.findIndex(t => t.container === container);
100
100
  if (suspendedTrapIndex >= 0) {
@@ -25,6 +25,7 @@ export interface FocusZoneSettings {
25
25
  activeDescendantControl?: HTMLElement;
26
26
  onActiveDescendantChanged?: (newActiveDescendant: HTMLElement | undefined, previousActiveDescendant: HTMLElement | undefined, directlyActivated: boolean) => void;
27
27
  focusInStrategy?: 'first' | 'closest' | 'previous' | ((previousFocusedElement: Element) => HTMLElement | undefined);
28
+ preventScroll?: boolean;
28
29
  }
29
30
  export declare const isActiveDescendantAttribute = "data-is-active-descendant";
30
31
  export declare const activeDescendantActivatedDirectly = "activated-directly";
@@ -41,7 +41,7 @@ const KEY_TO_BIT = {
41
41
  End: FocusKeys.HomeAndEnd,
42
42
  PageUp: FocusKeys.PageUpDown,
43
43
  PageDown: FocusKeys.PageUpDown,
44
- Backspace: FocusKeys.Backspace
44
+ Backspace: FocusKeys.Backspace,
45
45
  };
46
46
  const KEY_TO_DIRECTION = {
47
47
  ArrowLeft: 'previous',
@@ -61,7 +61,7 @@ const KEY_TO_DIRECTION = {
61
61
  End: 'end',
62
62
  PageUp: 'start',
63
63
  PageDown: 'end',
64
- Backspace: 'previous'
64
+ Backspace: 'previous',
65
65
  };
66
66
  function getDirection(keyboardEvent) {
67
67
  const direction = KEY_TO_DIRECTION[keyboardEvent.key];
@@ -127,7 +127,7 @@ exports.activeDescendantActivatedDirectly = 'activated-directly';
127
127
  exports.activeDescendantActivatedIndirectly = 'activated-indirectly';
128
128
  exports.hasActiveDescendantAttribute = 'data-has-active-descendant';
129
129
  function focusZone(container, settings) {
130
- var _a, _b, _c, _d;
130
+ var _a, _b, _c, _d, _e;
131
131
  const focusableElements = [];
132
132
  const savedTabIndex = new WeakMap();
133
133
  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;
@@ -136,6 +136,7 @@ function focusZone(container, settings) {
136
136
  const activeDescendantControl = settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl;
137
137
  const activeDescendantCallback = settings === null || settings === void 0 ? void 0 : settings.onActiveDescendantChanged;
138
138
  let currentFocusedElement;
139
+ const preventScroll = (_d = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _d !== void 0 ? _d : false;
139
140
  function getFirstFocusableElement() {
140
141
  return focusableElements[0];
141
142
  }
@@ -189,8 +190,7 @@ function focusZone(container, settings) {
189
190
  if (filteredElements.length === 0) {
190
191
  return;
191
192
  }
192
- const insertIndex = focusableElements.findIndex(e => (e.compareDocumentPosition(filteredElements[0]) & Node.DOCUMENT_POSITION_PRECEDING) > 0);
193
- focusableElements.splice(insertIndex === -1 ? focusableElements.length : insertIndex, 0, ...filteredElements);
193
+ focusableElements.splice(findInsertionIndex(filteredElements), 0, ...filteredElements);
194
194
  for (const element of filteredElements) {
195
195
  if (!savedTabIndex.has(element)) {
196
196
  savedTabIndex.set(element, element.getAttribute('tabindex'));
@@ -201,6 +201,27 @@ function focusZone(container, settings) {
201
201
  updateFocusedElement(getFirstFocusableElement());
202
202
  }
203
203
  }
204
+ function findInsertionIndex(elementsToInsert) {
205
+ const firstElementToInsert = elementsToInsert[0];
206
+ if (focusableElements.length === 0)
207
+ return 0;
208
+ let iMin = 0;
209
+ let iMax = focusableElements.length - 1;
210
+ while (iMin <= iMax) {
211
+ const i = Math.floor((iMin + iMax) / 2);
212
+ const element = focusableElements[i];
213
+ if (followsInDocument(firstElementToInsert, element)) {
214
+ iMax = i - 1;
215
+ }
216
+ else {
217
+ iMin = i + 1;
218
+ }
219
+ }
220
+ return iMin;
221
+ }
222
+ function followsInDocument(first, second) {
223
+ return (second.compareDocumentPosition(first) & Node.DOCUMENT_POSITION_PRECEDING) > 0;
224
+ }
204
225
  function endFocusManagement(...elements) {
205
226
  for (const element of elements) {
206
227
  const focusableElementIndex = focusableElements.indexOf(element);
@@ -224,7 +245,8 @@ function focusZone(container, settings) {
224
245
  }
225
246
  }
226
247
  beginFocusManagement(...(0, iterate_focusable_elements_js_1.iterateFocusableElements)(container));
227
- updateFocusedElement(getFirstFocusableElement());
248
+ const initialElement = typeof focusInStrategy === 'function' ? focusInStrategy(document.body) : getFirstFocusableElement();
249
+ updateFocusedElement(initialElement);
228
250
  const observer = new MutationObserver(mutations => {
229
251
  for (const mutation of mutations) {
230
252
  for (const removedNode of mutation.removedNodes) {
@@ -243,10 +265,10 @@ function focusZone(container, settings) {
243
265
  });
244
266
  observer.observe(container, {
245
267
  subtree: true,
246
- childList: true
268
+ childList: true,
247
269
  });
248
270
  const controller = new AbortController();
249
- const signal = (_d = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _d !== void 0 ? _d : controller.signal;
271
+ const signal = (_e = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _e !== void 0 ? _e : controller.signal;
250
272
  signal.addEventListener('abort', () => {
251
273
  endFocusManagement(...focusableElements);
252
274
  });
@@ -259,7 +281,7 @@ function focusZone(container, settings) {
259
281
  if (activeDescendantControl) {
260
282
  container.addEventListener('focusin', event => {
261
283
  if (event.target instanceof HTMLElement && focusableElements.includes(event.target)) {
262
- activeDescendantControl.focus();
284
+ activeDescendantControl.focus({ preventScroll });
263
285
  updateFocusedElement(event.target);
264
286
  }
265
287
  });
@@ -303,7 +325,7 @@ function focusZone(container, settings) {
303
325
  if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
304
326
  const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.length - 1 : 0;
305
327
  const targetElement = focusableElements[targetElementIndex];
306
- targetElement === null || targetElement === void 0 ? void 0 : targetElement.focus();
328
+ targetElement === null || targetElement === void 0 ? void 0 : targetElement.focus({ preventScroll });
307
329
  return;
308
330
  }
309
331
  else {
@@ -315,7 +337,7 @@ function focusZone(container, settings) {
315
337
  const elementToFocus = focusInStrategy(event.relatedTarget);
316
338
  const requestedFocusElementIndex = elementToFocus ? focusableElements.indexOf(elementToFocus) : -1;
317
339
  if (requestedFocusElementIndex >= 0 && elementToFocus instanceof HTMLElement) {
318
- elementToFocus.focus();
340
+ elementToFocus.focus({ preventScroll });
319
341
  return;
320
342
  }
321
343
  else {
@@ -400,7 +422,7 @@ function focusZone(container, settings) {
400
422
  }
401
423
  else if (nextElementToFocus) {
402
424
  lastKeyboardFocusDirection = direction;
403
- nextElementToFocus.focus();
425
+ nextElementToFocus.focus({ preventScroll });
404
426
  }
405
427
  if (event.key !== 'Tab' || nextElementToFocus) {
406
428
  event.preventDefault();
@@ -8,8 +8,8 @@ try {
8
8
  signal: {
9
9
  get() {
10
10
  signalSupported = true;
11
- }
12
- }
11
+ },
12
+ },
13
13
  });
14
14
  window.addEventListener('test', noop, options);
15
15
  window.removeEventListener('test', noop, options);
@@ -6,7 +6,7 @@ function* iterateFocusableElements(container, options = {}) {
6
6
  const strict = (_a = options.strict) !== null && _a !== void 0 ? _a : false;
7
7
  const acceptFn = ((_b = options.onlyTabbable) !== null && _b !== void 0 ? _b : false) ? isTabbable : isFocusable;
8
8
  const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
9
- acceptNode: node => node instanceof HTMLElement && acceptFn(node, strict) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
9
+ acceptNode: node => node instanceof HTMLElement && acceptFn(node, strict) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP,
10
10
  });
11
11
  let nextNode = null;
12
12
  if (!options.reverse && acceptFn(container, strict)) {
@@ -2,12 +2,12 @@ const alternateOrders = {
2
2
  'outside-top': ['outside-bottom', 'outside-right', 'outside-left', 'outside-bottom'],
3
3
  'outside-bottom': ['outside-top', 'outside-right', 'outside-left', 'outside-bottom'],
4
4
  'outside-left': ['outside-right', 'outside-bottom', 'outside-top', 'outside-bottom'],
5
- 'outside-right': ['outside-left', 'outside-bottom', 'outside-top', 'outside-bottom']
5
+ 'outside-right': ['outside-left', 'outside-bottom', 'outside-top', 'outside-bottom'],
6
6
  };
7
7
  const alternateAlignments = {
8
8
  start: ['end', 'center'],
9
9
  end: ['start', 'center'],
10
- center: ['end', 'start']
10
+ center: ['end', 'start'],
11
11
  };
12
12
  export function getAnchoredPosition(floatingElement, anchorElement, settings = {}) {
13
13
  const parentElement = getPositionedParent(floatingElement);
@@ -17,11 +17,13 @@ export function getAnchoredPosition(floatingElement, anchorElement, settings = {
17
17
  const [borderTop, borderLeft] = [parentElementStyle.borderTopWidth, parentElementStyle.borderLeftWidth].map(v => parseInt(v, 10) || 0);
18
18
  const relativeRect = {
19
19
  top: parentElementRect.top + borderTop,
20
- left: parentElementRect.left + borderLeft
20
+ left: parentElementRect.left + borderLeft,
21
21
  };
22
22
  return pureCalculateAnchoredPosition(clippingRect, relativeRect, floatingElement.getBoundingClientRect(), anchorElement instanceof Element ? anchorElement.getBoundingClientRect() : anchorElement, getDefaultSettings(settings));
23
23
  }
24
24
  function getPositionedParent(element) {
25
+ if (isOnTopLayer(element))
26
+ return document.body;
25
27
  let parentNode = element.parentNode;
26
28
  while (parentNode !== null) {
27
29
  if (parentNode instanceof HTMLElement && getComputedStyle(parentNode).position !== 'static') {
@@ -31,6 +33,21 @@ function getPositionedParent(element) {
31
33
  }
32
34
  return document.body;
33
35
  }
36
+ function isOnTopLayer(element) {
37
+ var _a;
38
+ if (element.tagName === 'DIALOG') {
39
+ return true;
40
+ }
41
+ try {
42
+ if (element.matches(':popover-open') && /native code/.test((_a = document.body.showPopover) === null || _a === void 0 ? void 0 : _a.toString())) {
43
+ return true;
44
+ }
45
+ }
46
+ catch (_b) {
47
+ return false;
48
+ }
49
+ return false;
50
+ }
34
51
  function getClippingRect(element) {
35
52
  let parentNode = element;
36
53
  while (parentNode !== null) {
@@ -50,13 +67,13 @@ function getClippingRect(element) {
50
67
  elemStyle.borderTopWidth,
51
68
  elemStyle.borderLeftWidth,
52
69
  elemStyle.borderRightWidth,
53
- elemStyle.borderBottomWidth
70
+ elemStyle.borderBottomWidth,
54
71
  ].map(v => parseInt(v, 10) || 0);
55
72
  return {
56
73
  top: elemRect.top + borderTop,
57
74
  left: elemRect.left + borderLeft,
58
75
  width: elemRect.width - borderRight - borderLeft,
59
- height: Math.max(elemRect.height - borderTop - borderBottom, clippingNode === document.body ? window.innerHeight : -Infinity)
76
+ height: Math.max(elemRect.height - borderTop - borderBottom, clippingNode === document.body ? window.innerHeight : -Infinity),
60
77
  };
61
78
  }
62
79
  const positionDefaults = {
@@ -64,7 +81,7 @@ const positionDefaults = {
64
81
  align: 'start',
65
82
  anchorOffset: 4,
66
83
  alignmentOffset: 4,
67
- allowOutOfBounds: false
84
+ allowOutOfBounds: false,
68
85
  };
69
86
  function getDefaultSettings(settings = {}) {
70
87
  var _a, _b, _c, _d, _e;
@@ -75,7 +92,7 @@ function getDefaultSettings(settings = {}) {
75
92
  align,
76
93
  anchorOffset: (_c = settings.anchorOffset) !== null && _c !== void 0 ? _c : (side === 'inside-center' ? 0 : positionDefaults.anchorOffset),
77
94
  alignmentOffset: (_d = settings.alignmentOffset) !== null && _d !== void 0 ? _d : (align !== 'center' && side.startsWith('inside') ? positionDefaults.alignmentOffset : 0),
78
- allowOutOfBounds: (_e = settings.allowOutOfBounds) !== null && _e !== void 0 ? _e : positionDefaults.allowOutOfBounds
95
+ allowOutOfBounds: (_e = settings.allowOutOfBounds) !== null && _e !== void 0 ? _e : positionDefaults.allowOutOfBounds,
79
96
  };
80
97
  }
81
98
  function pureCalculateAnchoredPosition(viewportRect, relativePosition, floatingRect, anchorRect, { side, align, allowOutOfBounds, anchorOffset, alignmentOffset }) {
@@ -83,7 +100,7 @@ function pureCalculateAnchoredPosition(viewportRect, relativePosition, floatingR
83
100
  top: viewportRect.top - relativePosition.top,
84
101
  left: viewportRect.left - relativePosition.left,
85
102
  width: viewportRect.width,
86
- height: viewportRect.height
103
+ height: viewportRect.height,
87
104
  };
88
105
  let pos = calculatePosition(floatingRect, anchorRect, side, align, anchorOffset, alignmentOffset);
89
106
  let anchorSide = side;
@@ -2,7 +2,7 @@ export function offset(element) {
2
2
  const rect = element.getBoundingClientRect();
3
3
  return {
4
4
  top: rect.top + window.pageYOffset,
5
- left: rect.left + window.pageXOffset
5
+ left: rect.left + window.pageXOffset,
6
6
  };
7
7
  }
8
8
  export function overflowParent(targetElement) {
@@ -57,11 +57,11 @@ export function overflowOffset(element, targetContainer) {
57
57
  const scroll = container === document.documentElement && document.defaultView
58
58
  ? {
59
59
  top: document.defaultView.pageYOffset,
60
- left: document.defaultView.pageXOffset
60
+ left: document.defaultView.pageXOffset,
61
61
  }
62
62
  : {
63
63
  top: container.scrollTop,
64
- left: container.scrollLeft
64
+ left: container.scrollLeft,
65
65
  };
66
66
  const top = elementOffset.top - scroll.top;
67
67
  const left = elementOffset.left - scroll.left;
@@ -91,7 +91,7 @@ export function focusTrap(container, initialFocus, abortSignal) {
91
91
  container,
92
92
  controller: wrappingController,
93
93
  initialFocus,
94
- originalSignal: signal
94
+ originalSignal: signal,
95
95
  };
96
96
  const suspendedTrapIndex = suspendedTrapStack.findIndex(t => t.container === container);
97
97
  if (suspendedTrapIndex >= 0) {
@@ -25,6 +25,7 @@ export interface FocusZoneSettings {
25
25
  activeDescendantControl?: HTMLElement;
26
26
  onActiveDescendantChanged?: (newActiveDescendant: HTMLElement | undefined, previousActiveDescendant: HTMLElement | undefined, directlyActivated: boolean) => void;
27
27
  focusInStrategy?: 'first' | 'closest' | 'previous' | ((previousFocusedElement: Element) => HTMLElement | undefined);
28
+ preventScroll?: boolean;
28
29
  }
29
30
  export declare const isActiveDescendantAttribute = "data-is-active-descendant";
30
31
  export declare const activeDescendantActivatedDirectly = "activated-directly";
@@ -38,7 +38,7 @@ const KEY_TO_BIT = {
38
38
  End: FocusKeys.HomeAndEnd,
39
39
  PageUp: FocusKeys.PageUpDown,
40
40
  PageDown: FocusKeys.PageUpDown,
41
- Backspace: FocusKeys.Backspace
41
+ Backspace: FocusKeys.Backspace,
42
42
  };
43
43
  const KEY_TO_DIRECTION = {
44
44
  ArrowLeft: 'previous',
@@ -58,7 +58,7 @@ const KEY_TO_DIRECTION = {
58
58
  End: 'end',
59
59
  PageUp: 'start',
60
60
  PageDown: 'end',
61
- Backspace: 'previous'
61
+ Backspace: 'previous',
62
62
  };
63
63
  function getDirection(keyboardEvent) {
64
64
  const direction = KEY_TO_DIRECTION[keyboardEvent.key];
@@ -124,7 +124,7 @@ export const activeDescendantActivatedDirectly = 'activated-directly';
124
124
  export const activeDescendantActivatedIndirectly = 'activated-indirectly';
125
125
  export const hasActiveDescendantAttribute = 'data-has-active-descendant';
126
126
  export function focusZone(container, settings) {
127
- var _a, _b, _c, _d;
127
+ var _a, _b, _c, _d, _e;
128
128
  const focusableElements = [];
129
129
  const savedTabIndex = new WeakMap();
130
130
  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;
@@ -133,6 +133,7 @@ export function focusZone(container, settings) {
133
133
  const activeDescendantControl = settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl;
134
134
  const activeDescendantCallback = settings === null || settings === void 0 ? void 0 : settings.onActiveDescendantChanged;
135
135
  let currentFocusedElement;
136
+ const preventScroll = (_d = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _d !== void 0 ? _d : false;
136
137
  function getFirstFocusableElement() {
137
138
  return focusableElements[0];
138
139
  }
@@ -186,8 +187,7 @@ export function focusZone(container, settings) {
186
187
  if (filteredElements.length === 0) {
187
188
  return;
188
189
  }
189
- const insertIndex = focusableElements.findIndex(e => (e.compareDocumentPosition(filteredElements[0]) & Node.DOCUMENT_POSITION_PRECEDING) > 0);
190
- focusableElements.splice(insertIndex === -1 ? focusableElements.length : insertIndex, 0, ...filteredElements);
190
+ focusableElements.splice(findInsertionIndex(filteredElements), 0, ...filteredElements);
191
191
  for (const element of filteredElements) {
192
192
  if (!savedTabIndex.has(element)) {
193
193
  savedTabIndex.set(element, element.getAttribute('tabindex'));
@@ -198,6 +198,27 @@ export function focusZone(container, settings) {
198
198
  updateFocusedElement(getFirstFocusableElement());
199
199
  }
200
200
  }
201
+ function findInsertionIndex(elementsToInsert) {
202
+ const firstElementToInsert = elementsToInsert[0];
203
+ if (focusableElements.length === 0)
204
+ return 0;
205
+ let iMin = 0;
206
+ let iMax = focusableElements.length - 1;
207
+ while (iMin <= iMax) {
208
+ const i = Math.floor((iMin + iMax) / 2);
209
+ const element = focusableElements[i];
210
+ if (followsInDocument(firstElementToInsert, element)) {
211
+ iMax = i - 1;
212
+ }
213
+ else {
214
+ iMin = i + 1;
215
+ }
216
+ }
217
+ return iMin;
218
+ }
219
+ function followsInDocument(first, second) {
220
+ return (second.compareDocumentPosition(first) & Node.DOCUMENT_POSITION_PRECEDING) > 0;
221
+ }
201
222
  function endFocusManagement(...elements) {
202
223
  for (const element of elements) {
203
224
  const focusableElementIndex = focusableElements.indexOf(element);
@@ -221,7 +242,8 @@ export function focusZone(container, settings) {
221
242
  }
222
243
  }
223
244
  beginFocusManagement(...iterateFocusableElements(container));
224
- updateFocusedElement(getFirstFocusableElement());
245
+ const initialElement = typeof focusInStrategy === 'function' ? focusInStrategy(document.body) : getFirstFocusableElement();
246
+ updateFocusedElement(initialElement);
225
247
  const observer = new MutationObserver(mutations => {
226
248
  for (const mutation of mutations) {
227
249
  for (const removedNode of mutation.removedNodes) {
@@ -240,10 +262,10 @@ export function focusZone(container, settings) {
240
262
  });
241
263
  observer.observe(container, {
242
264
  subtree: true,
243
- childList: true
265
+ childList: true,
244
266
  });
245
267
  const controller = new AbortController();
246
- const signal = (_d = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _d !== void 0 ? _d : controller.signal;
268
+ const signal = (_e = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _e !== void 0 ? _e : controller.signal;
247
269
  signal.addEventListener('abort', () => {
248
270
  endFocusManagement(...focusableElements);
249
271
  });
@@ -256,7 +278,7 @@ export function focusZone(container, settings) {
256
278
  if (activeDescendantControl) {
257
279
  container.addEventListener('focusin', event => {
258
280
  if (event.target instanceof HTMLElement && focusableElements.includes(event.target)) {
259
- activeDescendantControl.focus();
281
+ activeDescendantControl.focus({ preventScroll });
260
282
  updateFocusedElement(event.target);
261
283
  }
262
284
  });
@@ -300,7 +322,7 @@ export function focusZone(container, settings) {
300
322
  if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
301
323
  const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.length - 1 : 0;
302
324
  const targetElement = focusableElements[targetElementIndex];
303
- targetElement === null || targetElement === void 0 ? void 0 : targetElement.focus();
325
+ targetElement === null || targetElement === void 0 ? void 0 : targetElement.focus({ preventScroll });
304
326
  return;
305
327
  }
306
328
  else {
@@ -312,7 +334,7 @@ export function focusZone(container, settings) {
312
334
  const elementToFocus = focusInStrategy(event.relatedTarget);
313
335
  const requestedFocusElementIndex = elementToFocus ? focusableElements.indexOf(elementToFocus) : -1;
314
336
  if (requestedFocusElementIndex >= 0 && elementToFocus instanceof HTMLElement) {
315
- elementToFocus.focus();
337
+ elementToFocus.focus({ preventScroll });
316
338
  return;
317
339
  }
318
340
  else {
@@ -397,7 +419,7 @@ export function focusZone(container, settings) {
397
419
  }
398
420
  else if (nextElementToFocus) {
399
421
  lastKeyboardFocusDirection = direction;
400
- nextElementToFocus.focus();
422
+ nextElementToFocus.focus({ preventScroll });
401
423
  }
402
424
  if (event.key !== 'Tab' || nextElementToFocus) {
403
425
  event.preventDefault();
@@ -5,8 +5,8 @@ try {
5
5
  signal: {
6
6
  get() {
7
7
  signalSupported = true;
8
- }
9
- }
8
+ },
9
+ },
10
10
  });
11
11
  window.addEventListener('test', noop, options);
12
12
  window.removeEventListener('test', noop, options);
@@ -3,7 +3,7 @@ export function* iterateFocusableElements(container, options = {}) {
3
3
  const strict = (_a = options.strict) !== null && _a !== void 0 ? _a : false;
4
4
  const acceptFn = ((_b = options.onlyTabbable) !== null && _b !== void 0 ? _b : false) ? isTabbable : isFocusable;
5
5
  const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
6
- acceptNode: node => node instanceof HTMLElement && acceptFn(node, strict) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
6
+ acceptNode: node => node instanceof HTMLElement && acceptFn(node, strict) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP,
7
7
  });
8
8
  let nextNode = null;
9
9
  if (!options.reverse && acceptFn(container, strict)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/behaviors",
3
- "version": "0.0.0-2022927173910",
3
+ "version": "0.0.0-20230925221012",
4
4
  "description": "Shared behaviors for JavaScript components",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -68,21 +68,22 @@
68
68
  "devDependencies": {
69
69
  "@changesets/changelog-github": "^0.4.2",
70
70
  "@changesets/cli": "^2.18.1",
71
- "@github/prettier-config": "0.0.4",
72
- "@size-limit/preset-small-lib": "^7.0.3",
71
+ "@github/prettier-config": "^0.0.6",
72
+ "@size-limit/preset-small-lib": "^8.2.4",
73
73
  "@testing-library/react": "^12.1.2",
74
74
  "@testing-library/user-event": "^13.5.0",
75
75
  "@types/jest": "^27.0.3",
76
76
  "@types/react": "^17.0.37",
77
- "esbuild": "^0.14.1",
77
+ "esbuild": "^0.15.16",
78
78
  "esbuild-jest": "^0.5.0",
79
- "eslint": "^8.3.0",
80
- "eslint-plugin-github": "^4.3.5",
79
+ "eslint": "^8.50.0",
80
+ "eslint-plugin-github": "^4.10.0",
81
+ "eslint-plugin-prettier": "^5.0.0",
81
82
  "jest": "^27.4.3",
82
- "prettier": "^2.5.0",
83
+ "prettier": "^3.0.3",
83
84
  "react": "^17.0.2",
84
85
  "react-dom": "^17.0.2",
85
- "size-limit": "^7.0.3",
86
+ "size-limit": "^8.2.4",
86
87
  "typescript": "^4.5.2"
87
88
  }
88
89
  }