@primer/behaviors 1.9.1-rc.b43a642 → 1.9.2-rc.45ff0ed

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.
@@ -12,9 +12,9 @@ const alternateAlignments = {
12
12
  center: ['end', 'start'],
13
13
  };
14
14
  function getAnchoredPosition(floatingElement, anchorElement, settings = {}) {
15
- const parentElement = getPositionedParent(floatingElement);
16
- const clippingRect = getClippingRect(parentElement);
17
- const parentElementStyle = getComputedStyle(parentElement);
15
+ const { positionedParent: parentElement, clippingNode, positionedParentStyle, clippingNodeStyle, } = getPositionedParentAndClippingNode(floatingElement);
16
+ const clippingRect = getClippingRect(clippingNode, clippingNodeStyle);
17
+ const parentElementStyle = positionedParentStyle || getComputedStyle(parentElement);
18
18
  const parentElementRect = parentElement.getBoundingClientRect();
19
19
  const [borderTop, borderLeft] = [parentElementStyle.borderTopWidth, parentElementStyle.borderLeftWidth].map(v => parseInt(v, 10) || 0);
20
20
  const relativeRect = {
@@ -23,19 +23,43 @@ function getAnchoredPosition(floatingElement, anchorElement, settings = {}) {
23
23
  };
24
24
  return pureCalculateAnchoredPosition(clippingRect, relativeRect, floatingElement.getBoundingClientRect(), anchorElement instanceof Element ? anchorElement.getBoundingClientRect() : anchorElement, getDefaultSettings(settings));
25
25
  }
26
- function getPositionedParent(element) {
27
- if (isOnTopLayer(element))
28
- return document.body;
26
+ function getPositionedParentAndClippingNode(element) {
27
+ if (isOnTopLayer(element)) {
28
+ return {
29
+ positionedParent: document.body,
30
+ clippingNode: document.body,
31
+ positionedParentStyle: null,
32
+ clippingNodeStyle: null,
33
+ };
34
+ }
35
+ let positionedParent = null;
36
+ let clippingNode = null;
37
+ let positionedParentStyle = null;
38
+ let clippingNodeStyle = null;
29
39
  let parentNode = element.parentNode;
30
40
  while (parentNode !== null && parentNode !== document.body) {
31
41
  if (parentNode instanceof HTMLElement) {
32
- if (getComputedStyle(parentNode).position !== 'static') {
33
- return parentNode;
42
+ const style = getComputedStyle(parentNode);
43
+ if (!positionedParent && style.position !== 'static') {
44
+ positionedParent = parentNode;
45
+ positionedParentStyle = style;
46
+ }
47
+ if (!clippingNode && style.overflow !== 'visible') {
48
+ clippingNode = parentNode;
49
+ clippingNodeStyle = style;
50
+ }
51
+ if (positionedParent && clippingNode) {
52
+ break;
34
53
  }
35
54
  }
36
55
  parentNode = parentNode.parentNode;
37
56
  }
38
- return document.body;
57
+ return {
58
+ positionedParent: positionedParent !== null && positionedParent !== void 0 ? positionedParent : document.body,
59
+ clippingNode: clippingNode !== null && clippingNode !== void 0 ? clippingNode : document.body,
60
+ positionedParentStyle,
61
+ clippingNodeStyle,
62
+ };
39
63
  }
40
64
  function isOnTopLayer(element) {
41
65
  var _a;
@@ -52,21 +76,9 @@ function isOnTopLayer(element) {
52
76
  }
53
77
  return false;
54
78
  }
55
- function getClippingRect(element) {
56
- let clippingNode = document.body;
57
- let parentNode = element;
58
- while (parentNode !== null && parentNode !== document.body) {
59
- if (parentNode instanceof HTMLElement) {
60
- const overflow = getComputedStyle(parentNode).overflow;
61
- if (overflow !== 'visible') {
62
- clippingNode = parentNode;
63
- break;
64
- }
65
- }
66
- parentNode = parentNode.parentNode;
67
- }
79
+ function getClippingRect(clippingNode, cachedStyle = null) {
68
80
  const elemRect = clippingNode.getBoundingClientRect();
69
- const elemStyle = getComputedStyle(clippingNode);
81
+ const elemStyle = cachedStyle || getComputedStyle(clippingNode);
70
82
  const borderTop = parseInt(elemStyle.borderTopWidth, 10) || 0;
71
83
  const borderLeft = parseInt(elemStyle.borderLeftWidth, 10) || 0;
72
84
  const borderRight = parseInt(elemStyle.borderRightWidth, 10) || 0;
@@ -28,6 +28,7 @@ export type FocusZoneSettings = IterateFocusableElements & {
28
28
  focusInStrategy?: 'first' | 'closest' | 'previous' | 'initial' | ((previousFocusedElement: Element) => HTMLElement | undefined);
29
29
  preventScroll?: boolean;
30
30
  ignoreHoverEvents?: boolean;
31
+ focusPrependedElements?: boolean;
31
32
  };
32
33
  export declare const isActiveDescendantAttribute = "data-is-active-descendant";
33
34
  export declare const activeDescendantActivatedDirectly = "activated-directly";
@@ -131,7 +131,7 @@ const activeDescendantActivatedDirectly = 'activated-directly';
131
131
  const activeDescendantActivatedIndirectly = 'activated-indirectly';
132
132
  const hasActiveDescendantAttribute = 'data-has-active-descendant';
133
133
  function focusZone(container, settings) {
134
- var _a, _b, _c, _d, _e, _f;
134
+ var _a, _b, _c, _d, _e, _f, _g;
135
135
  const focusableElements = new indexedSet.IndexedSet();
136
136
  const savedTabIndex = new WeakMap();
137
137
  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) ? exports.FocusKeys.ArrowAll : exports.FocusKeys.ArrowVertical) | exports.FocusKeys.HomeAndEnd;
@@ -140,8 +140,10 @@ function focusZone(container, settings) {
140
140
  const activeDescendantControl = settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl;
141
141
  const activeDescendantCallback = settings === null || settings === void 0 ? void 0 : settings.onActiveDescendantChanged;
142
142
  const ignoreHoverEvents = (_d = settings === null || settings === void 0 ? void 0 : settings.ignoreHoverEvents) !== null && _d !== void 0 ? _d : false;
143
+ const focusPrependedElements = (_e = settings === null || settings === void 0 ? void 0 : settings.focusPrependedElements) !== null && _e !== void 0 ? _e : false;
143
144
  let currentFocusedElement;
144
- const preventScroll = (_e = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _e !== void 0 ? _e : false;
145
+ let wasDirectlyActivated = false;
146
+ const preventScroll = (_f = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _f !== void 0 ? _f : false;
145
147
  const preventInitialFocus = focusInStrategy === 'initial' && (settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl);
146
148
  function getFirstFocusableElement() {
147
149
  return focusableElements.get(0);
@@ -152,6 +154,7 @@ function focusZone(container, settings) {
152
154
  function updateFocusedElement(to, directlyActivated = false) {
153
155
  const from = currentFocusedElement;
154
156
  currentFocusedElement = to;
157
+ wasDirectlyActivated = directlyActivated;
155
158
  if (activeDescendantControl) {
156
159
  if (to && isActiveDescendantInputFocused()) {
157
160
  setActiveDescendant(from, to, directlyActivated);
@@ -202,14 +205,16 @@ function focusZone(container, settings) {
202
205
  if (filteredElements.length === 0) {
203
206
  return;
204
207
  }
205
- focusableElements.insertAt(findInsertionIndex(filteredElements), ...filteredElements);
208
+ const insertionIndex = findInsertionIndex(filteredElements);
209
+ focusableElements.insertAt(insertionIndex, ...filteredElements);
206
210
  for (const element of filteredElements) {
207
211
  if (!savedTabIndex.has(element)) {
208
212
  savedTabIndex.set(element, element.getAttribute('tabindex'));
209
213
  }
210
214
  element.setAttribute('tabindex', '-1');
211
215
  }
212
- if (!currentFocusedElement && !preventInitialFocus) {
216
+ const shouldFocusPrepended = focusPrependedElements && insertionIndex === 0 && !wasDirectlyActivated;
217
+ if (!preventInitialFocus && (!currentFocusedElement || shouldFocusPrepended)) {
213
218
  updateFocusedElement(getFirstFocusableElement());
214
219
  }
215
220
  }
@@ -327,7 +332,7 @@ function focusZone(container, settings) {
327
332
  attributeOldValue: true,
328
333
  });
329
334
  const controller = new AbortController();
330
- const signal = (_f = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _f !== void 0 ? _f : controller.signal;
335
+ const signal = (_g = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _g !== void 0 ? _g : controller.signal;
331
336
  signal.addEventListener('abort', () => {
332
337
  observer.disconnect();
333
338
  endFocusManagement(...focusableElements);
@@ -10,9 +10,9 @@ const alternateAlignments = {
10
10
  center: ['end', 'start'],
11
11
  };
12
12
  function getAnchoredPosition(floatingElement, anchorElement, settings = {}) {
13
- const parentElement = getPositionedParent(floatingElement);
14
- const clippingRect = getClippingRect(parentElement);
15
- const parentElementStyle = getComputedStyle(parentElement);
13
+ const { positionedParent: parentElement, clippingNode, positionedParentStyle, clippingNodeStyle, } = getPositionedParentAndClippingNode(floatingElement);
14
+ const clippingRect = getClippingRect(clippingNode, clippingNodeStyle);
15
+ const parentElementStyle = positionedParentStyle || getComputedStyle(parentElement);
16
16
  const parentElementRect = parentElement.getBoundingClientRect();
17
17
  const [borderTop, borderLeft] = [parentElementStyle.borderTopWidth, parentElementStyle.borderLeftWidth].map(v => parseInt(v, 10) || 0);
18
18
  const relativeRect = {
@@ -21,19 +21,43 @@ function getAnchoredPosition(floatingElement, anchorElement, settings = {}) {
21
21
  };
22
22
  return pureCalculateAnchoredPosition(clippingRect, relativeRect, floatingElement.getBoundingClientRect(), anchorElement instanceof Element ? anchorElement.getBoundingClientRect() : anchorElement, getDefaultSettings(settings));
23
23
  }
24
- function getPositionedParent(element) {
25
- if (isOnTopLayer(element))
26
- return document.body;
24
+ function getPositionedParentAndClippingNode(element) {
25
+ if (isOnTopLayer(element)) {
26
+ return {
27
+ positionedParent: document.body,
28
+ clippingNode: document.body,
29
+ positionedParentStyle: null,
30
+ clippingNodeStyle: null,
31
+ };
32
+ }
33
+ let positionedParent = null;
34
+ let clippingNode = null;
35
+ let positionedParentStyle = null;
36
+ let clippingNodeStyle = null;
27
37
  let parentNode = element.parentNode;
28
38
  while (parentNode !== null && parentNode !== document.body) {
29
39
  if (parentNode instanceof HTMLElement) {
30
- if (getComputedStyle(parentNode).position !== 'static') {
31
- return parentNode;
40
+ const style = getComputedStyle(parentNode);
41
+ if (!positionedParent && style.position !== 'static') {
42
+ positionedParent = parentNode;
43
+ positionedParentStyle = style;
44
+ }
45
+ if (!clippingNode && style.overflow !== 'visible') {
46
+ clippingNode = parentNode;
47
+ clippingNodeStyle = style;
48
+ }
49
+ if (positionedParent && clippingNode) {
50
+ break;
32
51
  }
33
52
  }
34
53
  parentNode = parentNode.parentNode;
35
54
  }
36
- return document.body;
55
+ return {
56
+ positionedParent: positionedParent !== null && positionedParent !== void 0 ? positionedParent : document.body,
57
+ clippingNode: clippingNode !== null && clippingNode !== void 0 ? clippingNode : document.body,
58
+ positionedParentStyle,
59
+ clippingNodeStyle,
60
+ };
37
61
  }
38
62
  function isOnTopLayer(element) {
39
63
  var _a;
@@ -50,21 +74,9 @@ function isOnTopLayer(element) {
50
74
  }
51
75
  return false;
52
76
  }
53
- function getClippingRect(element) {
54
- let clippingNode = document.body;
55
- let parentNode = element;
56
- while (parentNode !== null && parentNode !== document.body) {
57
- if (parentNode instanceof HTMLElement) {
58
- const overflow = getComputedStyle(parentNode).overflow;
59
- if (overflow !== 'visible') {
60
- clippingNode = parentNode;
61
- break;
62
- }
63
- }
64
- parentNode = parentNode.parentNode;
65
- }
77
+ function getClippingRect(clippingNode, cachedStyle = null) {
66
78
  const elemRect = clippingNode.getBoundingClientRect();
67
- const elemStyle = getComputedStyle(clippingNode);
79
+ const elemStyle = cachedStyle || getComputedStyle(clippingNode);
68
80
  const borderTop = parseInt(elemStyle.borderTopWidth, 10) || 0;
69
81
  const borderLeft = parseInt(elemStyle.borderLeftWidth, 10) || 0;
70
82
  const borderRight = parseInt(elemStyle.borderRightWidth, 10) || 0;
@@ -28,6 +28,7 @@ export type FocusZoneSettings = IterateFocusableElements & {
28
28
  focusInStrategy?: 'first' | 'closest' | 'previous' | 'initial' | ((previousFocusedElement: Element) => HTMLElement | undefined);
29
29
  preventScroll?: boolean;
30
30
  ignoreHoverEvents?: boolean;
31
+ focusPrependedElements?: boolean;
31
32
  };
32
33
  export declare const isActiveDescendantAttribute = "data-is-active-descendant";
33
34
  export declare const activeDescendantActivatedDirectly = "activated-directly";
@@ -129,7 +129,7 @@ const activeDescendantActivatedDirectly = 'activated-directly';
129
129
  const activeDescendantActivatedIndirectly = 'activated-indirectly';
130
130
  const hasActiveDescendantAttribute = 'data-has-active-descendant';
131
131
  function focusZone(container, settings) {
132
- var _a, _b, _c, _d, _e, _f;
132
+ var _a, _b, _c, _d, _e, _f, _g;
133
133
  const focusableElements = new IndexedSet();
134
134
  const savedTabIndex = new WeakMap();
135
135
  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;
@@ -138,8 +138,10 @@ function focusZone(container, settings) {
138
138
  const activeDescendantControl = settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl;
139
139
  const activeDescendantCallback = settings === null || settings === void 0 ? void 0 : settings.onActiveDescendantChanged;
140
140
  const ignoreHoverEvents = (_d = settings === null || settings === void 0 ? void 0 : settings.ignoreHoverEvents) !== null && _d !== void 0 ? _d : false;
141
+ const focusPrependedElements = (_e = settings === null || settings === void 0 ? void 0 : settings.focusPrependedElements) !== null && _e !== void 0 ? _e : false;
141
142
  let currentFocusedElement;
142
- const preventScroll = (_e = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _e !== void 0 ? _e : false;
143
+ let wasDirectlyActivated = false;
144
+ const preventScroll = (_f = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _f !== void 0 ? _f : false;
143
145
  const preventInitialFocus = focusInStrategy === 'initial' && (settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl);
144
146
  function getFirstFocusableElement() {
145
147
  return focusableElements.get(0);
@@ -150,6 +152,7 @@ function focusZone(container, settings) {
150
152
  function updateFocusedElement(to, directlyActivated = false) {
151
153
  const from = currentFocusedElement;
152
154
  currentFocusedElement = to;
155
+ wasDirectlyActivated = directlyActivated;
153
156
  if (activeDescendantControl) {
154
157
  if (to && isActiveDescendantInputFocused()) {
155
158
  setActiveDescendant(from, to, directlyActivated);
@@ -200,14 +203,16 @@ function focusZone(container, settings) {
200
203
  if (filteredElements.length === 0) {
201
204
  return;
202
205
  }
203
- focusableElements.insertAt(findInsertionIndex(filteredElements), ...filteredElements);
206
+ const insertionIndex = findInsertionIndex(filteredElements);
207
+ focusableElements.insertAt(insertionIndex, ...filteredElements);
204
208
  for (const element of filteredElements) {
205
209
  if (!savedTabIndex.has(element)) {
206
210
  savedTabIndex.set(element, element.getAttribute('tabindex'));
207
211
  }
208
212
  element.setAttribute('tabindex', '-1');
209
213
  }
210
- if (!currentFocusedElement && !preventInitialFocus) {
214
+ const shouldFocusPrepended = focusPrependedElements && insertionIndex === 0 && !wasDirectlyActivated;
215
+ if (!preventInitialFocus && (!currentFocusedElement || shouldFocusPrepended)) {
211
216
  updateFocusedElement(getFirstFocusableElement());
212
217
  }
213
218
  }
@@ -325,7 +330,7 @@ function focusZone(container, settings) {
325
330
  attributeOldValue: true,
326
331
  });
327
332
  const controller = new AbortController();
328
- const signal = (_f = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _f !== void 0 ? _f : controller.signal;
333
+ const signal = (_g = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _g !== void 0 ? _g : controller.signal;
329
334
  signal.addEventListener('abort', () => {
330
335
  observer.disconnect();
331
336
  endFocusManagement(...focusableElements);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/behaviors",
3
- "version": "1.9.1-rc.b43a642",
3
+ "version": "1.9.2-rc.45ff0ed",
4
4
  "description": "Shared behaviors for JavaScript components",
5
5
  "type": "commonjs",
6
6
  "main": "dist/cjs/index.js",