@primer/behaviors 1.0.2-rc.b89e9b4 → 1.1.0-rc.e605114

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 (44) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{anchored-position.d.ts → cjs/anchored-position.d.ts} +0 -0
  3. package/dist/cjs/anchored-position.js +210 -0
  4. package/dist/cjs/focus-trap.d.ts +1 -0
  5. package/dist/cjs/focus-trap.js +107 -0
  6. package/dist/{focus-zone.d.ts → cjs/focus-zone.d.ts} +0 -0
  7. package/dist/cjs/focus-zone.js +410 -0
  8. package/dist/{index.d.ts → cjs/index.d.ts} +0 -0
  9. package/dist/cjs/index.js +16 -0
  10. package/dist/{polyfills → cjs/polyfills}/event-listener-signal.d.ts +0 -0
  11. package/dist/cjs/polyfills/event-listener-signal.js +44 -0
  12. package/dist/{scroll-into-view.d.ts → cjs/scroll-into-view.d.ts} +0 -0
  13. package/dist/cjs/scroll-into-view.js +21 -0
  14. package/dist/{utils → cjs/utils}/index.d.ts +0 -0
  15. package/dist/cjs/utils/index.js +15 -0
  16. package/dist/{utils → cjs/utils}/iterate-focusable-elements.d.ts +1 -0
  17. package/dist/cjs/utils/iterate-focusable-elements.js +68 -0
  18. package/dist/{utils → cjs/utils}/unique-id.d.ts +0 -0
  19. package/dist/cjs/utils/unique-id.js +8 -0
  20. package/dist/{utils → cjs/utils}/user-agent.d.ts +0 -0
  21. package/dist/cjs/utils/user-agent.js +11 -0
  22. package/dist/esm/anchored-position.d.ts +15 -0
  23. package/dist/{anchored-position.js → esm/anchored-position.js} +0 -0
  24. package/dist/esm/focus-trap.d.ts +1 -0
  25. package/dist/{focus-trap.js → esm/focus-trap.js} +24 -28
  26. package/dist/esm/focus-zone.d.ts +32 -0
  27. package/dist/{focus-zone.js → esm/focus-zone.js} +0 -0
  28. package/dist/{index.js → esm/index.d.ts} +0 -0
  29. package/dist/esm/index.js +4 -0
  30. package/dist/esm/polyfills/event-listener-signal.d.ts +6 -0
  31. package/dist/{polyfills → esm/polyfills}/event-listener-signal.js +0 -0
  32. package/dist/esm/scroll-into-view.d.ts +7 -0
  33. package/dist/{scroll-into-view.js → esm/scroll-into-view.js} +0 -0
  34. package/dist/{utils/index.js → esm/utils/index.d.ts} +0 -0
  35. package/dist/esm/utils/index.js +3 -0
  36. package/dist/esm/utils/iterate-focusable-elements.d.ts +9 -0
  37. package/dist/{utils → esm/utils}/iterate-focusable-elements.js +5 -1
  38. package/dist/esm/utils/unique-id.d.ts +1 -0
  39. package/dist/{utils → esm/utils}/unique-id.js +0 -0
  40. package/dist/esm/utils/user-agent.d.ts +1 -0
  41. package/dist/{utils → esm/utils}/user-agent.js +0 -0
  42. package/package.json +24 -8
  43. package/utils/package.json +2 -2
  44. package/dist/focus-trap.d.ts +0 -2
@@ -1,4 +1,4 @@
1
- import { isTabbable, iterateFocusableElements } from './utils/iterate-focusable-elements.js';
1
+ import { getFocusableChild, isTabbable } from './utils/iterate-focusable-elements.js';
2
2
  import { polyfill as eventListenerSignalPolyfill } from './polyfills/event-listener-signal.js';
3
3
  eventListenerSignalPolyfill();
4
4
  const suspendedTrapStack = [];
@@ -16,13 +16,28 @@ function followSignal(signal) {
16
16
  });
17
17
  return controller;
18
18
  }
19
- function getFocusableChild(container, lastChild = false) {
20
- return iterateFocusableElements(container, { reverse: lastChild, strict: true, onlyTabbable: true }).next().value;
21
- }
22
19
  export function focusTrap(container, initialFocus, abortSignal) {
23
20
  const controller = new AbortController();
24
21
  const signal = abortSignal !== null && abortSignal !== void 0 ? abortSignal : controller.signal;
25
22
  container.setAttribute('data-focus-trap', 'active');
23
+ const sentinelStart = document.createElement('span');
24
+ sentinelStart.setAttribute('class', 'sentinel');
25
+ sentinelStart.setAttribute('tabindex', '0');
26
+ sentinelStart.setAttribute('aria-hidden', 'true');
27
+ sentinelStart.onfocus = () => {
28
+ const lastFocusableChild = getFocusableChild(container, true);
29
+ lastFocusableChild === null || lastFocusableChild === void 0 ? void 0 : lastFocusableChild.focus();
30
+ };
31
+ const sentinelEnd = document.createElement('span');
32
+ sentinelEnd.setAttribute('class', 'sentinel');
33
+ sentinelEnd.setAttribute('tabindex', '0');
34
+ sentinelEnd.setAttribute('aria-hidden', 'true');
35
+ sentinelEnd.onfocus = () => {
36
+ const firstFocusableChild = getFocusableChild(container);
37
+ firstFocusableChild === null || firstFocusableChild === void 0 ? void 0 : firstFocusableChild.focus();
38
+ };
39
+ container.prepend(sentinelStart);
40
+ container.append(sentinelEnd);
26
41
  let lastFocusedChild = undefined;
27
42
  function ensureTrapZoneHasFocus(focusedElement) {
28
43
  if (focusedElement instanceof HTMLElement && document.contains(container)) {
@@ -40,36 +55,14 @@ export function focusTrap(container, initialFocus, abortSignal) {
40
55
  return;
41
56
  }
42
57
  else {
43
- const containerNeedsTemporaryTabIndex = container.getAttribute('tabindex') === null;
44
- if (containerNeedsTemporaryTabIndex) {
45
- container.setAttribute('tabindex', '-1');
46
- }
47
- container.focus();
48
- if (containerNeedsTemporaryTabIndex) {
49
- container.addEventListener('blur', () => container.removeAttribute('tabindex'), { once: true });
50
- }
58
+ const firstFocusableChild = getFocusableChild(container);
59
+ firstFocusableChild === null || firstFocusableChild === void 0 ? void 0 : firstFocusableChild.focus();
51
60
  return;
52
61
  }
53
62
  }
54
63
  }
55
64
  }
56
65
  const wrappingController = followSignal(signal);
57
- container.addEventListener('keydown', event => {
58
- if (event.key !== 'Tab' || event.defaultPrevented) {
59
- return;
60
- }
61
- const { target } = event;
62
- const firstFocusableChild = getFocusableChild(container);
63
- const lastFocusableChild = getFocusableChild(container, true);
64
- if (target === firstFocusableChild && event.shiftKey) {
65
- event.preventDefault();
66
- lastFocusableChild === null || lastFocusableChild === void 0 ? void 0 : lastFocusableChild.focus();
67
- }
68
- else if (target === lastFocusableChild && !event.shiftKey) {
69
- event.preventDefault();
70
- firstFocusableChild === null || firstFocusableChild === void 0 ? void 0 : firstFocusableChild.focus();
71
- }
72
- }, { signal: wrappingController.signal });
73
66
  if (activeTrap) {
74
67
  const suspendedTrap = activeTrap;
75
68
  activeTrap.container.setAttribute('data-focus-trap', 'suspended');
@@ -81,6 +74,9 @@ export function focusTrap(container, initialFocus, abortSignal) {
81
74
  });
82
75
  signal.addEventListener('abort', () => {
83
76
  container.removeAttribute('data-focus-trap');
77
+ const sentinels = container.getElementsByClassName('sentinel');
78
+ while (sentinels.length > 0)
79
+ sentinels[0].remove();
84
80
  const suspendedTrapIndex = suspendedTrapStack.findIndex(t => t.container === container);
85
81
  if (suspendedTrapIndex >= 0) {
86
82
  suspendedTrapStack.splice(suspendedTrapIndex, 1);
@@ -0,0 +1,32 @@
1
+ export declare type Direction = 'previous' | 'next' | 'start' | 'end';
2
+ export declare type FocusMovementKeys = 'ArrowLeft' | 'ArrowDown' | 'ArrowUp' | 'ArrowRight' | 'h' | 'j' | 'k' | 'l' | 'a' | 's' | 'w' | 'd' | 'Tab' | 'Home' | 'End' | 'PageUp' | 'PageDown';
3
+ export declare enum FocusKeys {
4
+ ArrowHorizontal = 1,
5
+ ArrowVertical = 2,
6
+ JK = 4,
7
+ HL = 8,
8
+ HomeAndEnd = 16,
9
+ PageUpDown = 256,
10
+ WS = 32,
11
+ AD = 64,
12
+ Tab = 128,
13
+ ArrowAll = 3,
14
+ HJKL = 12,
15
+ WASD = 96,
16
+ All = 511
17
+ }
18
+ export interface FocusZoneSettings {
19
+ focusOutBehavior?: 'stop' | 'wrap';
20
+ getNextFocusable?: (direction: Direction, from: Element | undefined, event: KeyboardEvent) => HTMLElement | undefined;
21
+ focusableElementFilter?: (element: HTMLElement) => boolean;
22
+ bindKeys?: FocusKeys;
23
+ abortSignal?: AbortSignal;
24
+ activeDescendantControl?: HTMLElement;
25
+ onActiveDescendantChanged?: (newActiveDescendant: HTMLElement | undefined, previousActiveDescendant: HTMLElement | undefined, directlyActivated: boolean) => void;
26
+ focusInStrategy?: 'first' | 'closest' | 'previous' | ((previousFocusedElement: Element) => HTMLElement | undefined);
27
+ }
28
+ export declare const isActiveDescendantAttribute = "data-is-active-descendant";
29
+ export declare const activeDescendantActivatedDirectly = "activated-directly";
30
+ export declare const activeDescendantActivatedIndirectly = "activated-indirectly";
31
+ export declare const hasActiveDescendantAttribute = "data-has-active-descendant";
32
+ export declare function focusZone(container: HTMLElement, settings?: FocusZoneSettings): AbortController;
File without changes
File without changes
@@ -0,0 +1,4 @@
1
+ export * from './anchored-position.js';
2
+ export * from './focus-trap.js';
3
+ export * from './focus-zone.js';
4
+ export * from './scroll-into-view.js';
@@ -0,0 +1,6 @@
1
+ export declare function polyfill(): void;
2
+ declare global {
3
+ interface AddEventListenerOptions {
4
+ signal?: AbortSignal;
5
+ }
6
+ }
@@ -0,0 +1,7 @@
1
+ export interface ScrollIntoViewOptions {
2
+ direction?: 'horizontal' | 'vertical';
3
+ startMargin?: number;
4
+ endMargin?: number;
5
+ behavior?: ScrollBehavior;
6
+ }
7
+ export declare function scrollIntoView(child: HTMLElement, viewingArea: HTMLElement, { direction, startMargin, endMargin, behavior }?: ScrollIntoViewOptions): void;
File without changes
@@ -0,0 +1,3 @@
1
+ export * from './iterate-focusable-elements.js';
2
+ export * from './unique-id.js';
3
+ export * from './user-agent.js';
@@ -0,0 +1,9 @@
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 getFocusableChild(container: HTMLElement, lastChild?: boolean): HTMLElement | undefined;
8
+ export declare function isFocusable(elem: HTMLElement, strict?: boolean): boolean;
9
+ export declare function isTabbable(elem: HTMLElement, strict?: boolean): boolean;
@@ -28,12 +28,16 @@ export function* iterateFocusableElements(container, options = {}) {
28
28
  }
29
29
  return undefined;
30
30
  }
31
+ export function getFocusableChild(container, lastChild = false) {
32
+ return iterateFocusableElements(container, { reverse: lastChild, strict: true, onlyTabbable: true }).next().value;
33
+ }
31
34
  export function isFocusable(elem, strict = false) {
32
35
  const disabledAttrInert = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'OPTGROUP', 'OPTION', 'FIELDSET'].includes(elem.tagName) &&
33
36
  elem.disabled;
34
37
  const hiddenInert = elem.hidden;
35
38
  const hiddenInputInert = elem instanceof HTMLInputElement && elem.type === 'hidden';
36
- if (disabledAttrInert || hiddenInert || hiddenInputInert) {
39
+ const sentinelInert = elem.classList.contains('sentinel');
40
+ if (disabledAttrInert || hiddenInert || hiddenInputInert || sentinelInert) {
37
41
  return false;
38
42
  }
39
43
  if (strict) {
@@ -0,0 +1 @@
1
+ export declare function uniqueId(): string;
File without changes
@@ -0,0 +1 @@
1
+ export declare function isMacOS(): boolean;
File without changes
package/package.json CHANGED
@@ -1,17 +1,31 @@
1
1
  {
2
2
  "name": "@primer/behaviors",
3
- "version": "1.0.2-rc.b89e9b4",
3
+ "version": "1.1.0-rc.e605114",
4
4
  "description": "Shared behaviors for JavaScript components",
5
- "main": "./dist/index.js",
6
- "type": "module",
7
- "types": "dist/index.d.ts",
5
+ "main": "dist/cjs/index.js",
6
+ "module": "dist/esm/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "require": "./dist/cjs/index.js",
11
+ "module": "./dist/esm/index.js"
12
+ },
13
+ "./utils": {
14
+ "types": "./dist/utils/index.d.ts",
15
+ "require": "./dist/cjs/utils/index.js",
16
+ "module": "./dist/esm/utils/index.js"
17
+ }
18
+ },
19
+ "types": "dist/cjs/index.d.ts",
8
20
  "files": [
9
21
  "dist",
10
22
  "utils"
11
23
  ],
12
24
  "sideEffects": [
13
- "dist/focus-zone.js",
14
- "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"
15
29
  ],
16
30
  "scripts": {
17
31
  "lint": "eslint src/",
@@ -20,7 +34,9 @@
20
34
  "jest": "jest",
21
35
  "clean": "rm -rf dist",
22
36
  "prebuild": "npm run clean",
23
- "build": "tsc",
37
+ "build": "npm run build:esm && npm run build:cjs",
38
+ "build:esm": "tsc",
39
+ "build:cjs": "tsc --module commonjs --outDir dist/cjs",
24
40
  "size-limit": "npm run build && size-limit",
25
41
  "release": "npm run build && changeset publish"
26
42
  },
@@ -44,7 +60,7 @@
44
60
  "size-limit": [
45
61
  {
46
62
  "limit": "10kb",
47
- "path": "dist/index.js"
63
+ "path": "dist/esm/index.js"
48
64
  }
49
65
  ],
50
66
  "devDependencies": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@primer/behaviors/utils",
3
- "types": "../dist/utils/index.d.ts",
4
- "main": "../dist/utils/index.js",
3
+ "types": "../dist/esm/utils/index.d.ts",
4
+ "main": "../dist/esm/utils/index.js",
5
5
  "type": "module",
6
6
  "sideEffects": false
7
7
  }
@@ -1,2 +0,0 @@
1
- export declare function focusTrap(container: HTMLElement, initialFocus?: HTMLElement): AbortController;
2
- export declare function focusTrap(container: HTMLElement, initialFocus: HTMLElement | undefined, abortSignal: AbortSignal): void;