@primer/behaviors 0.0.0-20260106160020 → 0.0.0-20260108040347

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,7 +28,6 @@ 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;
32
31
  };
33
32
  export declare const isActiveDescendantAttribute = "data-is-active-descendant";
34
33
  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, _g;
134
+ var _a, _b, _c, _d, _e, _f;
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,10 +140,8 @@ 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;
144
143
  let currentFocusedElement;
145
- let wasDirectlyActivated = false;
146
- const preventScroll = (_f = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _f !== void 0 ? _f : false;
144
+ const preventScroll = (_e = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _e !== void 0 ? _e : false;
147
145
  const preventInitialFocus = focusInStrategy === 'initial' && (settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl);
148
146
  function getFirstFocusableElement() {
149
147
  return focusableElements.get(0);
@@ -154,7 +152,6 @@ function focusZone(container, settings) {
154
152
  function updateFocusedElement(to, directlyActivated = false) {
155
153
  const from = currentFocusedElement;
156
154
  currentFocusedElement = to;
157
- wasDirectlyActivated = directlyActivated;
158
155
  if (activeDescendantControl) {
159
156
  if (to && isActiveDescendantInputFocused()) {
160
157
  setActiveDescendant(from, to, directlyActivated);
@@ -205,16 +202,14 @@ function focusZone(container, settings) {
205
202
  if (filteredElements.length === 0) {
206
203
  return;
207
204
  }
208
- const insertionIndex = findInsertionIndex(filteredElements);
209
- focusableElements.insertAt(insertionIndex, ...filteredElements);
205
+ focusableElements.insertAt(findInsertionIndex(filteredElements), ...filteredElements);
210
206
  for (const element of filteredElements) {
211
207
  if (!savedTabIndex.has(element)) {
212
208
  savedTabIndex.set(element, element.getAttribute('tabindex'));
213
209
  }
214
210
  element.setAttribute('tabindex', '-1');
215
211
  }
216
- const shouldFocusPrepended = focusPrependedElements && insertionIndex === 0 && !wasDirectlyActivated;
217
- if (!preventInitialFocus && (!currentFocusedElement || shouldFocusPrepended)) {
212
+ if (!currentFocusedElement && !preventInitialFocus) {
218
213
  updateFocusedElement(getFirstFocusableElement());
219
214
  }
220
215
  }
@@ -332,7 +327,7 @@ function focusZone(container, settings) {
332
327
  attributeOldValue: true,
333
328
  });
334
329
  const controller = new AbortController();
335
- const signal = (_g = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _g !== void 0 ? _g : controller.signal;
330
+ const signal = (_f = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _f !== void 0 ? _f : controller.signal;
336
331
  signal.addEventListener('abort', () => {
337
332
  observer.disconnect();
338
333
  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,7 +28,6 @@ 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;
32
31
  };
33
32
  export declare const isActiveDescendantAttribute = "data-is-active-descendant";
34
33
  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, _g;
132
+ var _a, _b, _c, _d, _e, _f;
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,10 +138,8 @@ 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;
142
141
  let currentFocusedElement;
143
- let wasDirectlyActivated = false;
144
- const preventScroll = (_f = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _f !== void 0 ? _f : false;
142
+ const preventScroll = (_e = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _e !== void 0 ? _e : false;
145
143
  const preventInitialFocus = focusInStrategy === 'initial' && (settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl);
146
144
  function getFirstFocusableElement() {
147
145
  return focusableElements.get(0);
@@ -152,7 +150,6 @@ function focusZone(container, settings) {
152
150
  function updateFocusedElement(to, directlyActivated = false) {
153
151
  const from = currentFocusedElement;
154
152
  currentFocusedElement = to;
155
- wasDirectlyActivated = directlyActivated;
156
153
  if (activeDescendantControl) {
157
154
  if (to && isActiveDescendantInputFocused()) {
158
155
  setActiveDescendant(from, to, directlyActivated);
@@ -203,16 +200,14 @@ function focusZone(container, settings) {
203
200
  if (filteredElements.length === 0) {
204
201
  return;
205
202
  }
206
- const insertionIndex = findInsertionIndex(filteredElements);
207
- focusableElements.insertAt(insertionIndex, ...filteredElements);
203
+ focusableElements.insertAt(findInsertionIndex(filteredElements), ...filteredElements);
208
204
  for (const element of filteredElements) {
209
205
  if (!savedTabIndex.has(element)) {
210
206
  savedTabIndex.set(element, element.getAttribute('tabindex'));
211
207
  }
212
208
  element.setAttribute('tabindex', '-1');
213
209
  }
214
- const shouldFocusPrepended = focusPrependedElements && insertionIndex === 0 && !wasDirectlyActivated;
215
- if (!preventInitialFocus && (!currentFocusedElement || shouldFocusPrepended)) {
210
+ if (!currentFocusedElement && !preventInitialFocus) {
216
211
  updateFocusedElement(getFirstFocusableElement());
217
212
  }
218
213
  }
@@ -330,7 +325,7 @@ function focusZone(container, settings) {
330
325
  attributeOldValue: true,
331
326
  });
332
327
  const controller = new AbortController();
333
- const signal = (_g = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _g !== void 0 ? _g : controller.signal;
328
+ const signal = (_f = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _f !== void 0 ? _f : controller.signal;
334
329
  signal.addEventListener('abort', () => {
335
330
  observer.disconnect();
336
331
  endFocusManagement(...focusableElements);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/behaviors",
3
- "version": "0.0.0-20260106160020",
3
+ "version": "0.0.0-20260108040347",
4
4
  "description": "Shared behaviors for JavaScript components",
5
5
  "type": "commonjs",
6
6
  "main": "dist/cjs/index.js",