@primer/behaviors 0.0.0-20231221153723 → 0.0.0-20231223202501

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.
@@ -37,22 +37,27 @@ function getPositionedParent(element) {
37
37
  }
38
38
  return document.body;
39
39
  }
40
- function isOnTopLayer(element) {
40
+ function isModalDialog(element) {
41
+ return element.matches('dialog:modal');
42
+ }
43
+ function isFullscreen(element) {
44
+ return element.matches(':fullscreen');
45
+ }
46
+ function isPopover(element) {
41
47
  var _a;
42
- if (element.tagName === 'DIALOG') {
43
- return true;
44
- }
45
48
  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
+ return (element.matches(':popover-open') && /native code/.test((_a = document.body.showPopover) === null || _a === void 0 ? void 0 : _a.toString()));
49
50
  }
50
51
  catch (_b) {
51
52
  return false;
52
53
  }
53
- return false;
54
54
  }
55
- function getClippingRect(element) {
55
+ function isOnTopLayer(element) {
56
+ return isModalDialog(element) || isFullscreen(element) || isPopover(element);
57
+ }
58
+ function getClippingParent(element) {
59
+ if (element === document.body)
60
+ return element;
56
61
  let parentNode = element;
57
62
  while (parentNode !== null) {
58
63
  if (!(parentNode instanceof Element)) {
@@ -64,7 +69,10 @@ function getClippingRect(element) {
64
69
  }
65
70
  parentNode = parentNode.parentNode;
66
71
  }
67
- const clippingNode = parentNode === document.body || !(parentNode instanceof HTMLElement) ? document.body : parentNode;
72
+ return parentNode === document.body || !(parentNode instanceof HTMLElement) ? document.body : parentNode;
73
+ }
74
+ function getClippingRect(element) {
75
+ const clippingNode = getClippingParent(element);
68
76
  const elemRect = clippingNode.getBoundingClientRect();
69
77
  const elemStyle = getComputedStyle(clippingNode);
70
78
  const [borderTop, borderLeft, borderRight, borderBottom] = [
@@ -151,7 +159,7 @@ function pureCalculateAnchoredPosition(viewportRect, relativePosition, floatingR
151
159
  }
152
160
  if (alternateOrder && positionAttempt < alternateOrder.length) {
153
161
  if (pos.top + floatingRect.height > viewportRect.height + relativeViewportRect.top) {
154
- pos.top = Math.max(viewportRect.height + relativeViewportRect.top - floatingRect.height, 0);
162
+ pos.top = viewportRect.height + relativeViewportRect.top - floatingRect.height;
155
163
  }
156
164
  }
157
165
  }
@@ -1,4 +1,3 @@
1
- import { IterateFocusableElements } from './utils/iterate-focusable-elements.js';
2
1
  export type Direction = 'previous' | 'next' | 'start' | 'end';
3
2
  export type FocusMovementKeys = 'ArrowLeft' | 'ArrowDown' | 'ArrowUp' | 'ArrowRight' | 'h' | 'j' | 'k' | 'l' | 'a' | 's' | 'w' | 'd' | 'Tab' | 'Home' | 'End' | 'PageUp' | 'PageDown' | 'Backspace';
4
3
  export declare enum FocusKeys {
@@ -17,7 +16,7 @@ export declare enum FocusKeys {
17
16
  WASD = 96,
18
17
  All = 511
19
18
  }
20
- export type FocusZoneSettings = IterateFocusableElements & {
19
+ export interface FocusZoneSettings {
21
20
  focusOutBehavior?: 'stop' | 'wrap';
22
21
  getNextFocusable?: (direction: Direction, from: Element | undefined, event: KeyboardEvent) => HTMLElement | undefined;
23
22
  focusableElementFilter?: (element: HTMLElement) => boolean;
@@ -27,7 +26,7 @@ export type FocusZoneSettings = IterateFocusableElements & {
27
26
  onActiveDescendantChanged?: (newActiveDescendant: HTMLElement | undefined, previousActiveDescendant: HTMLElement | undefined, directlyActivated: boolean) => void;
28
27
  focusInStrategy?: 'first' | 'closest' | 'previous' | ((previousFocusedElement: Element) => HTMLElement | undefined);
29
28
  preventScroll?: boolean;
30
- };
29
+ }
31
30
  export declare const isActiveDescendantAttribute = "data-is-active-descendant";
32
31
  export declare const activeDescendantActivatedDirectly = "activated-directly";
33
32
  export declare const activeDescendantActivatedIndirectly = "activated-indirectly";
@@ -244,36 +244,21 @@ function focusZone(container, settings) {
244
244
  }
245
245
  }
246
246
  }
247
- const iterateFocusableElementsOptions = {
248
- reverse: settings === null || settings === void 0 ? void 0 : settings.reverse,
249
- strict: settings === null || settings === void 0 ? void 0 : settings.strict,
250
- onlyTabbable: settings === null || settings === void 0 ? void 0 : settings.onlyTabbable,
251
- };
252
- beginFocusManagement(...(0, iterate_focusable_elements_js_1.iterateFocusableElements)(container, iterateFocusableElementsOptions));
247
+ beginFocusManagement(...(0, iterate_focusable_elements_js_1.iterateFocusableElements)(container));
253
248
  const initialElement = typeof focusInStrategy === 'function' ? focusInStrategy(document.body) : getFirstFocusableElement();
254
249
  updateFocusedElement(initialElement);
255
250
  const observer = new MutationObserver(mutations => {
256
251
  for (const mutation of mutations) {
257
252
  for (const removedNode of mutation.removedNodes) {
258
253
  if (removedNode instanceof HTMLElement) {
259
- endFocusManagement(...(0, iterate_focusable_elements_js_1.iterateFocusableElements)(removedNode, iterateFocusableElementsOptions));
260
- }
261
- }
262
- if (mutation.type === 'attributes' && mutation.oldValue === null) {
263
- if (mutation.target instanceof HTMLElement) {
264
- endFocusManagement(mutation.target);
254
+ endFocusManagement(...(0, iterate_focusable_elements_js_1.iterateFocusableElements)(removedNode));
265
255
  }
266
256
  }
267
257
  }
268
258
  for (const mutation of mutations) {
269
259
  for (const addedNode of mutation.addedNodes) {
270
260
  if (addedNode instanceof HTMLElement) {
271
- beginFocusManagement(...(0, iterate_focusable_elements_js_1.iterateFocusableElements)(addedNode, iterateFocusableElementsOptions));
272
- }
273
- }
274
- if (mutation.type === 'attributes' && mutation.oldValue !== null) {
275
- if (mutation.target instanceof HTMLElement) {
276
- beginFocusManagement(mutation.target);
261
+ beginFocusManagement(...(0, iterate_focusable_elements_js_1.iterateFocusableElements)(addedNode));
277
262
  }
278
263
  }
279
264
  }
@@ -281,8 +266,6 @@ function focusZone(container, settings) {
281
266
  observer.observe(container, {
282
267
  subtree: true,
283
268
  childList: true,
284
- attributeFilter: ['hidden', 'disabled'],
285
- attributeOldValue: true,
286
269
  });
287
270
  const controller = new AbortController();
288
271
  const signal = (_e = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _e !== void 0 ? _e : controller.signal;
@@ -46,21 +46,16 @@ function isFocusable(elem, strict = false) {
46
46
  return false;
47
47
  }
48
48
  if (strict) {
49
- const style = getComputedStyle(elem);
50
49
  const sizeInert = elem.offsetWidth === 0 || elem.offsetHeight === 0;
51
- const visibilityInert = ['hidden', 'collapse'].includes(style.visibility);
52
- const displayInert = style.display === 'none' || !elem.offsetParent;
50
+ const visibilityInert = ['hidden', 'collapse'].includes(getComputedStyle(elem).visibility);
53
51
  const clientRectsInert = elem.getClientRects().length === 0;
54
- if (sizeInert || visibilityInert || clientRectsInert || displayInert) {
52
+ if (sizeInert || visibilityInert || clientRectsInert) {
55
53
  return false;
56
54
  }
57
55
  }
58
56
  if (elem.getAttribute('tabindex') != null) {
59
57
  return true;
60
58
  }
61
- if (elem.getAttribute('contenteditable') === 'true' || elem.getAttribute('contenteditable') === 'plaintext-only') {
62
- return true;
63
- }
64
59
  if (elem instanceof HTMLAnchorElement && elem.getAttribute('href') == null) {
65
60
  return false;
66
61
  }
@@ -33,22 +33,27 @@ function getPositionedParent(element) {
33
33
  }
34
34
  return document.body;
35
35
  }
36
- function isOnTopLayer(element) {
36
+ function isModalDialog(element) {
37
+ return element.matches('dialog:modal');
38
+ }
39
+ function isFullscreen(element) {
40
+ return element.matches(':fullscreen');
41
+ }
42
+ function isPopover(element) {
37
43
  var _a;
38
- if (element.tagName === 'DIALOG') {
39
- return true;
40
- }
41
44
  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
+ return (element.matches(':popover-open') && /native code/.test((_a = document.body.showPopover) === null || _a === void 0 ? void 0 : _a.toString()));
45
46
  }
46
47
  catch (_b) {
47
48
  return false;
48
49
  }
49
- return false;
50
50
  }
51
- function getClippingRect(element) {
51
+ function isOnTopLayer(element) {
52
+ return isModalDialog(element) || isFullscreen(element) || isPopover(element);
53
+ }
54
+ function getClippingParent(element) {
55
+ if (element === document.body)
56
+ return element;
52
57
  let parentNode = element;
53
58
  while (parentNode !== null) {
54
59
  if (!(parentNode instanceof Element)) {
@@ -60,7 +65,10 @@ function getClippingRect(element) {
60
65
  }
61
66
  parentNode = parentNode.parentNode;
62
67
  }
63
- const clippingNode = parentNode === document.body || !(parentNode instanceof HTMLElement) ? document.body : parentNode;
68
+ return parentNode === document.body || !(parentNode instanceof HTMLElement) ? document.body : parentNode;
69
+ }
70
+ function getClippingRect(element) {
71
+ const clippingNode = getClippingParent(element);
64
72
  const elemRect = clippingNode.getBoundingClientRect();
65
73
  const elemStyle = getComputedStyle(clippingNode);
66
74
  const [borderTop, borderLeft, borderRight, borderBottom] = [
@@ -147,7 +155,7 @@ function pureCalculateAnchoredPosition(viewportRect, relativePosition, floatingR
147
155
  }
148
156
  if (alternateOrder && positionAttempt < alternateOrder.length) {
149
157
  if (pos.top + floatingRect.height > viewportRect.height + relativeViewportRect.top) {
150
- pos.top = Math.max(viewportRect.height + relativeViewportRect.top - floatingRect.height, 0);
158
+ pos.top = viewportRect.height + relativeViewportRect.top - floatingRect.height;
151
159
  }
152
160
  }
153
161
  }
@@ -1,4 +1,3 @@
1
- import { IterateFocusableElements } from './utils/iterate-focusable-elements.js';
2
1
  export type Direction = 'previous' | 'next' | 'start' | 'end';
3
2
  export type FocusMovementKeys = 'ArrowLeft' | 'ArrowDown' | 'ArrowUp' | 'ArrowRight' | 'h' | 'j' | 'k' | 'l' | 'a' | 's' | 'w' | 'd' | 'Tab' | 'Home' | 'End' | 'PageUp' | 'PageDown' | 'Backspace';
4
3
  export declare enum FocusKeys {
@@ -17,7 +16,7 @@ export declare enum FocusKeys {
17
16
  WASD = 96,
18
17
  All = 511
19
18
  }
20
- export type FocusZoneSettings = IterateFocusableElements & {
19
+ export interface FocusZoneSettings {
21
20
  focusOutBehavior?: 'stop' | 'wrap';
22
21
  getNextFocusable?: (direction: Direction, from: Element | undefined, event: KeyboardEvent) => HTMLElement | undefined;
23
22
  focusableElementFilter?: (element: HTMLElement) => boolean;
@@ -27,7 +26,7 @@ export type FocusZoneSettings = IterateFocusableElements & {
27
26
  onActiveDescendantChanged?: (newActiveDescendant: HTMLElement | undefined, previousActiveDescendant: HTMLElement | undefined, directlyActivated: boolean) => void;
28
27
  focusInStrategy?: 'first' | 'closest' | 'previous' | ((previousFocusedElement: Element) => HTMLElement | undefined);
29
28
  preventScroll?: boolean;
30
- };
29
+ }
31
30
  export declare const isActiveDescendantAttribute = "data-is-active-descendant";
32
31
  export declare const activeDescendantActivatedDirectly = "activated-directly";
33
32
  export declare const activeDescendantActivatedIndirectly = "activated-indirectly";
@@ -241,36 +241,21 @@ export function focusZone(container, settings) {
241
241
  }
242
242
  }
243
243
  }
244
- const iterateFocusableElementsOptions = {
245
- reverse: settings === null || settings === void 0 ? void 0 : settings.reverse,
246
- strict: settings === null || settings === void 0 ? void 0 : settings.strict,
247
- onlyTabbable: settings === null || settings === void 0 ? void 0 : settings.onlyTabbable,
248
- };
249
- beginFocusManagement(...iterateFocusableElements(container, iterateFocusableElementsOptions));
244
+ beginFocusManagement(...iterateFocusableElements(container));
250
245
  const initialElement = typeof focusInStrategy === 'function' ? focusInStrategy(document.body) : getFirstFocusableElement();
251
246
  updateFocusedElement(initialElement);
252
247
  const observer = new MutationObserver(mutations => {
253
248
  for (const mutation of mutations) {
254
249
  for (const removedNode of mutation.removedNodes) {
255
250
  if (removedNode instanceof HTMLElement) {
256
- endFocusManagement(...iterateFocusableElements(removedNode, iterateFocusableElementsOptions));
257
- }
258
- }
259
- if (mutation.type === 'attributes' && mutation.oldValue === null) {
260
- if (mutation.target instanceof HTMLElement) {
261
- endFocusManagement(mutation.target);
251
+ endFocusManagement(...iterateFocusableElements(removedNode));
262
252
  }
263
253
  }
264
254
  }
265
255
  for (const mutation of mutations) {
266
256
  for (const addedNode of mutation.addedNodes) {
267
257
  if (addedNode instanceof HTMLElement) {
268
- beginFocusManagement(...iterateFocusableElements(addedNode, iterateFocusableElementsOptions));
269
- }
270
- }
271
- if (mutation.type === 'attributes' && mutation.oldValue !== null) {
272
- if (mutation.target instanceof HTMLElement) {
273
- beginFocusManagement(mutation.target);
258
+ beginFocusManagement(...iterateFocusableElements(addedNode));
274
259
  }
275
260
  }
276
261
  }
@@ -278,8 +263,6 @@ export function focusZone(container, settings) {
278
263
  observer.observe(container, {
279
264
  subtree: true,
280
265
  childList: true,
281
- attributeFilter: ['hidden', 'disabled'],
282
- attributeOldValue: true,
283
266
  });
284
267
  const controller = new AbortController();
285
268
  const signal = (_e = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _e !== void 0 ? _e : controller.signal;
@@ -41,21 +41,16 @@ export function isFocusable(elem, strict = false) {
41
41
  return false;
42
42
  }
43
43
  if (strict) {
44
- const style = getComputedStyle(elem);
45
44
  const sizeInert = elem.offsetWidth === 0 || elem.offsetHeight === 0;
46
- const visibilityInert = ['hidden', 'collapse'].includes(style.visibility);
47
- const displayInert = style.display === 'none' || !elem.offsetParent;
45
+ const visibilityInert = ['hidden', 'collapse'].includes(getComputedStyle(elem).visibility);
48
46
  const clientRectsInert = elem.getClientRects().length === 0;
49
- if (sizeInert || visibilityInert || clientRectsInert || displayInert) {
47
+ if (sizeInert || visibilityInert || clientRectsInert) {
50
48
  return false;
51
49
  }
52
50
  }
53
51
  if (elem.getAttribute('tabindex') != null) {
54
52
  return true;
55
53
  }
56
- if (elem.getAttribute('contenteditable') === 'true' || elem.getAttribute('contenteditable') === 'plaintext-only') {
57
- return true;
58
- }
59
54
  if (elem instanceof HTMLAnchorElement && elem.getAttribute('href') == null) {
60
55
  return false;
61
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/behaviors",
3
- "version": "0.0.0-20231221153723",
3
+ "version": "0.0.0-20231223202501",
4
4
  "description": "Shared behaviors for JavaScript components",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -72,16 +72,15 @@
72
72
  "@size-limit/preset-small-lib": "^8.2.4",
73
73
  "@testing-library/react": "^14.0.0",
74
74
  "@testing-library/user-event": "^14.5.1",
75
- "@types/jest": "^29.5.11",
76
- "@types/node": "^20.10.5",
75
+ "@types/jest": "^27.0.3",
76
+ "@types/node": "^18.18.0",
77
77
  "@types/react": "^18.2.23",
78
78
  "esbuild": "^0.19.4",
79
79
  "esbuild-jest": "^0.5.0",
80
80
  "eslint": "^8.50.0",
81
81
  "eslint-plugin-github": "^4.10.0",
82
82
  "eslint-plugin-prettier": "^5.0.0",
83
- "jest": "^29.7.0",
84
- "jest-environment-jsdom": "^29.7.0",
83
+ "jest": "^27.4.3",
85
84
  "prettier": "^3.0.3",
86
85
  "react": "^18.2.0",
87
86
  "react-dom": "^18.2.0",