@patternfly/pfe-core 3.0.0 → 4.0.1

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 (102) hide show
  1. package/controllers/activedescendant-controller.d.ts +99 -0
  2. package/controllers/activedescendant-controller.js +230 -0
  3. package/controllers/activedescendant-controller.js.map +1 -0
  4. package/controllers/at-focus-controller.d.ts +56 -0
  5. package/controllers/at-focus-controller.js +168 -0
  6. package/controllers/at-focus-controller.js.map +1 -0
  7. package/controllers/cascade-controller.d.ts +7 -2
  8. package/controllers/cascade-controller.js +6 -1
  9. package/controllers/cascade-controller.js.map +1 -1
  10. package/controllers/combobox-controller.d.ts +117 -0
  11. package/controllers/combobox-controller.js +611 -0
  12. package/controllers/combobox-controller.js.map +1 -0
  13. package/controllers/css-variable-controller.js +1 -1
  14. package/controllers/css-variable-controller.js.map +1 -1
  15. package/controllers/floating-dom-controller.d.ts +8 -1
  16. package/controllers/floating-dom-controller.js +12 -5
  17. package/controllers/floating-dom-controller.js.map +1 -1
  18. package/controllers/internals-controller.d.ts +26 -9
  19. package/controllers/internals-controller.js +45 -13
  20. package/controllers/internals-controller.js.map +1 -1
  21. package/controllers/light-dom-controller.js +2 -2
  22. package/controllers/light-dom-controller.js.map +1 -1
  23. package/controllers/listbox-controller.d.ts +118 -33
  24. package/controllers/listbox-controller.js +347 -154
  25. package/controllers/listbox-controller.js.map +1 -1
  26. package/controllers/logger.d.ts +13 -10
  27. package/controllers/logger.js +15 -11
  28. package/controllers/logger.js.map +1 -1
  29. package/controllers/overflow-controller.js +10 -6
  30. package/controllers/overflow-controller.js.map +1 -1
  31. package/controllers/perf-controller.js.map +1 -1
  32. package/controllers/property-observer-controller.d.ts +13 -16
  33. package/controllers/property-observer-controller.js +54 -25
  34. package/controllers/property-observer-controller.js.map +1 -1
  35. package/controllers/roving-tabindex-controller.d.ts +12 -61
  36. package/controllers/roving-tabindex-controller.js +57 -203
  37. package/controllers/roving-tabindex-controller.js.map +1 -1
  38. package/controllers/scroll-spy-controller.d.ts +4 -1
  39. package/controllers/scroll-spy-controller.js +9 -6
  40. package/controllers/scroll-spy-controller.js.map +1 -1
  41. package/controllers/slot-controller.d.ts +12 -16
  42. package/controllers/slot-controller.js +17 -20
  43. package/controllers/slot-controller.js.map +1 -1
  44. package/controllers/style-controller.js +3 -1
  45. package/controllers/style-controller.js.map +1 -1
  46. package/controllers/tabs-aria-controller.d.ts +2 -0
  47. package/controllers/tabs-aria-controller.js +2 -0
  48. package/controllers/tabs-aria-controller.js.map +1 -1
  49. package/controllers/test/combobox-controller.spec.d.ts +1 -0
  50. package/controllers/test/combobox-controller.spec.js +282 -0
  51. package/controllers/test/combobox-controller.spec.js.map +1 -0
  52. package/controllers/timestamp-controller.js +7 -2
  53. package/controllers/timestamp-controller.js.map +1 -1
  54. package/core.d.ts +0 -23
  55. package/core.js +1 -38
  56. package/core.js.map +1 -1
  57. package/custom-elements.json +3862 -1369
  58. package/decorators/bound.d.ts +3 -1
  59. package/decorators/bound.js +3 -1
  60. package/decorators/bound.js.map +1 -1
  61. package/decorators/cascades.d.ts +2 -1
  62. package/decorators/cascades.js +2 -1
  63. package/decorators/cascades.js.map +1 -1
  64. package/decorators/deprecation.d.ts +6 -5
  65. package/decorators/deprecation.js +6 -5
  66. package/decorators/deprecation.js.map +1 -1
  67. package/decorators/initializer.js.map +1 -1
  68. package/decorators/listen.d.ts +8 -0
  69. package/decorators/listen.js +22 -0
  70. package/decorators/listen.js.map +1 -0
  71. package/decorators/observed.d.ts +12 -16
  72. package/decorators/observed.js +39 -44
  73. package/decorators/observed.js.map +1 -1
  74. package/decorators/observes.d.ts +15 -0
  75. package/decorators/observes.js +30 -0
  76. package/decorators/observes.js.map +1 -0
  77. package/decorators/time.d.ts +1 -0
  78. package/decorators/time.js +6 -9
  79. package/decorators/time.js.map +1 -1
  80. package/decorators/trace.d.ts +4 -1
  81. package/decorators/trace.js +4 -1
  82. package/decorators/trace.js.map +1 -1
  83. package/decorators.d.ts +2 -0
  84. package/decorators.js +2 -0
  85. package/decorators.js.map +1 -1
  86. package/functions/arraysAreEquivalent.d.ts +9 -0
  87. package/functions/arraysAreEquivalent.js +28 -0
  88. package/functions/arraysAreEquivalent.js.map +1 -0
  89. package/functions/containsDeep.d.ts +2 -0
  90. package/functions/containsDeep.js +2 -0
  91. package/functions/containsDeep.js.map +1 -1
  92. package/functions/context.d.ts +3 -4
  93. package/functions/context.js +6 -2
  94. package/functions/context.js.map +1 -1
  95. package/functions/debounce.js.map +1 -1
  96. package/functions/isElementInView.d.ts +4 -6
  97. package/functions/isElementInView.js +9 -11
  98. package/functions/isElementInView.js.map +1 -1
  99. package/package.json +12 -4
  100. package/ssr-shims.d.ts +17 -0
  101. package/ssr-shims.js +55 -0
  102. package/ssr-shims.js.map +1 -0
@@ -0,0 +1,99 @@
1
+ import type { ReactiveControllerHost } from 'lit';
2
+ import { type ATFocusControllerOptions, ATFocusController } from './at-focus-controller.js';
3
+ import { nothing } from 'lit';
4
+ export interface ActivedescendantControllerOptions<Item extends HTMLElement> extends ATFocusControllerOptions<Item> {
5
+ /**
6
+ * Returns a reference to the element which acts as the assistive technology container for
7
+ * the items. In the case of a combobox, this is the input element.
8
+ */
9
+ getActiveDescendantContainer(): HTMLElement | null;
10
+ /**
11
+ * Optional callback to control the assistive technology focus behavior of items.
12
+ * By default, ActivedescendantController will not do anything special to items when they receive
13
+ * assistive technology focus, and will only set the `activedescendant` property on the container.
14
+ * If you provide this callback, ActivedescendantController will call it on your item with the
15
+ * active state. You may use this to set active styles.
16
+ */
17
+ setItemActive?(item: Item, active: boolean): void;
18
+ /**
19
+ * Optional callback to retrieve the value from an option element.
20
+ * By default, retrieves the `value` attribute, or the text content.
21
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLOptionElement
22
+ */
23
+ getItemValue?(item: Item): string;
24
+ }
25
+ /**
26
+ * Implements activedescendant pattern, as described in WAI-ARIA practices,
27
+ * [Managing Focus in Composites Using aria-activedescendant][ad]
28
+ *
29
+ * The steps for using the aria-activedescendant method of managing focus are as follows.
30
+ *
31
+ * - When the container element that has a role that supports aria-activedescendant is loaded
32
+ * or created, ensure that:
33
+ * - The container element is included in the tab sequence as described in
34
+ * Keyboard Navigation Between Components or is a focusable element of a composite
35
+ * that implements a roving tabindex.
36
+ * - It has aria-activedescendant="IDREF" where IDREF is the ID of the element within
37
+ * the container that should be identified as active when the widget receives focus.
38
+ * The referenced element needs to meet the DOM relationship requirements described below.
39
+ * - When the container element receives DOM focus, draw a visual focus indicator on the active
40
+ * element and ensure the active element is scrolled into view.
41
+ * - When the composite widget contains focus and the user presses a navigation key that moves
42
+ * focus within the widget, such as an arrow key:
43
+ * - Change the value of aria-activedescendant on the container to refer to the element
44
+ * that should be reported to assistive technologies as active.
45
+ * - Move the visual focus indicator and, if necessary, scrolled the active element into view.
46
+ * - If the design calls for a specific element to be focused the next time a user moves focus
47
+ * into the composite with Tab or Shift+Tab, check if aria-activedescendant is referring to
48
+ * that target element when the container loses focus. If it is not, set aria-activedescendant
49
+ * to refer to the target element.
50
+ *
51
+ * The specification for aria-activedescendant places important restrictions on the
52
+ * DOM relationship between the focused element that has the aria-activedescendant attribute
53
+ * and the element referenced as active by the value of the attribute.
54
+ * One of the following three conditions must be met.
55
+ *
56
+ * 1. The element referenced as active is a DOM descendant of the focused referencing element.
57
+ * 2. The focused referencing element has a value specified for the aria-owns property that
58
+ * includes the ID of the element referenced as active.
59
+ * 3. The focused referencing element has role of combobox, textbox, or searchbox
60
+ * and has aria-controls property referring to an element with a role that supports
61
+ * aria-activedescendant and either:
62
+ * 1. The element referenced as active is a descendant of the controlled element.
63
+ * 2. The controlled element has a value specified for the aria-owns property that includes
64
+ * the ID of the element referenced as active.
65
+ *
66
+ * [ad]: https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant
67
+ */
68
+ export declare class ActivedescendantController<Item extends HTMLElement = HTMLElement> extends ATFocusController<Item> {
69
+ #private;
70
+ host: ReactiveControllerHost;
71
+ protected options: ActivedescendantControllerOptions<Item>;
72
+ /**
73
+ * When true, the browser supports cross-root ARIA such that the controller does not need
74
+ * to copy item nodes into the controlling nodes' root
75
+ */
76
+ static get supportsCrossRootActiveDescendant(): boolean;
77
+ static of<Item extends HTMLElement>(host: ReactiveControllerHost, options: ActivedescendantControllerOptions<Item>): ActivedescendantController<Item>;
78
+ get atFocusedItemIndex(): number;
79
+ /**
80
+ * Rather than setting DOM focus, applies the `aria-activedescendant` attribute,
81
+ * using AriaIDLAttributes for cross-root aria, if supported by the browser
82
+ * @param item item
83
+ */
84
+ set atFocusedItemIndex(index: number);
85
+ protected get controlsElements(): HTMLElement[];
86
+ protected set controlsElements(elements: HTMLElement[]);
87
+ /** All items */
88
+ get items(): Item[];
89
+ /**
90
+ * Sets the list of items and activates the next activatable item after the current one
91
+ * @param items tabindex items
92
+ */
93
+ set items(items: Item[]);
94
+ private constructor();
95
+ protected initItems(): void;
96
+ hostDisconnected(): void;
97
+ protected onKeydown(event: KeyboardEvent): void;
98
+ renderItemsToShadowRoot(): typeof nothing | Node[];
99
+ }
@@ -0,0 +1,230 @@
1
+ var _ActivedescendantController_instances, _ActivedescendantController_lightToShadowMap, _ActivedescendantController_shadowToLightMap, _ActivedescendantController_noCloneSet, _ActivedescendantController_controlsElements, _ActivedescendantController_observing, _ActivedescendantController_listMO, _ActivedescendantController_attrMO, _ActivedescendantController_syncAttr, _ActivedescendantController_onItemsDOMChange, _ActivedescendantController_onItemAttributeChange;
2
+ import { __classPrivateFieldGet, __classPrivateFieldSet, __decorate } from "tslib";
3
+ import { ATFocusController } from './at-focus-controller.js';
4
+ import { isServer, nothing } from 'lit';
5
+ import { getRandomId } from '../functions/random.js';
6
+ import { bound } from '../decorators/bound.js';
7
+ /**
8
+ * Implements activedescendant pattern, as described in WAI-ARIA practices,
9
+ * [Managing Focus in Composites Using aria-activedescendant][ad]
10
+ *
11
+ * The steps for using the aria-activedescendant method of managing focus are as follows.
12
+ *
13
+ * - When the container element that has a role that supports aria-activedescendant is loaded
14
+ * or created, ensure that:
15
+ * - The container element is included in the tab sequence as described in
16
+ * Keyboard Navigation Between Components or is a focusable element of a composite
17
+ * that implements a roving tabindex.
18
+ * - It has aria-activedescendant="IDREF" where IDREF is the ID of the element within
19
+ * the container that should be identified as active when the widget receives focus.
20
+ * The referenced element needs to meet the DOM relationship requirements described below.
21
+ * - When the container element receives DOM focus, draw a visual focus indicator on the active
22
+ * element and ensure the active element is scrolled into view.
23
+ * - When the composite widget contains focus and the user presses a navigation key that moves
24
+ * focus within the widget, such as an arrow key:
25
+ * - Change the value of aria-activedescendant on the container to refer to the element
26
+ * that should be reported to assistive technologies as active.
27
+ * - Move the visual focus indicator and, if necessary, scrolled the active element into view.
28
+ * - If the design calls for a specific element to be focused the next time a user moves focus
29
+ * into the composite with Tab or Shift+Tab, check if aria-activedescendant is referring to
30
+ * that target element when the container loses focus. If it is not, set aria-activedescendant
31
+ * to refer to the target element.
32
+ *
33
+ * The specification for aria-activedescendant places important restrictions on the
34
+ * DOM relationship between the focused element that has the aria-activedescendant attribute
35
+ * and the element referenced as active by the value of the attribute.
36
+ * One of the following three conditions must be met.
37
+ *
38
+ * 1. The element referenced as active is a DOM descendant of the focused referencing element.
39
+ * 2. The focused referencing element has a value specified for the aria-owns property that
40
+ * includes the ID of the element referenced as active.
41
+ * 3. The focused referencing element has role of combobox, textbox, or searchbox
42
+ * and has aria-controls property referring to an element with a role that supports
43
+ * aria-activedescendant and either:
44
+ * 1. The element referenced as active is a descendant of the controlled element.
45
+ * 2. The controlled element has a value specified for the aria-owns property that includes
46
+ * the ID of the element referenced as active.
47
+ *
48
+ * [ad]: https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant
49
+ */
50
+ export class ActivedescendantController extends ATFocusController {
51
+ /**
52
+ * When true, the browser supports cross-root ARIA such that the controller does not need
53
+ * to copy item nodes into the controlling nodes' root
54
+ */
55
+ static get supportsCrossRootActiveDescendant() {
56
+ return !isServer && 'ariaActiveDescendantElement' in HTMLElement.prototype;
57
+ }
58
+ static of(host, options) {
59
+ return new ActivedescendantController(host, options);
60
+ }
61
+ get atFocusedItemIndex() {
62
+ return super.atFocusedItemIndex;
63
+ }
64
+ /**
65
+ * Rather than setting DOM focus, applies the `aria-activedescendant` attribute,
66
+ * using AriaIDLAttributes for cross-root aria, if supported by the browser
67
+ * @param item item
68
+ */
69
+ set atFocusedItemIndex(index) {
70
+ super.atFocusedItemIndex = index;
71
+ const item = this._items.at(this.atFocusedItemIndex);
72
+ for (const _item of this.items) {
73
+ this.options.setItemActive?.(_item, _item === item);
74
+ }
75
+ const container = this.options.getActiveDescendantContainer();
76
+ if (!ActivedescendantController.supportsCrossRootActiveDescendant) {
77
+ container?.setAttribute('aria-activedescendant', item?.id ?? '');
78
+ }
79
+ else if (container) {
80
+ container.ariaActiveDescendantElement = item ?? null;
81
+ }
82
+ this.host.requestUpdate();
83
+ }
84
+ get controlsElements() {
85
+ return __classPrivateFieldGet(this, _ActivedescendantController_controlsElements, "f");
86
+ }
87
+ set controlsElements(elements) {
88
+ for (const old of __classPrivateFieldGet(this, _ActivedescendantController_controlsElements, "f")) {
89
+ old?.removeEventListener('keydown', this.onKeydown);
90
+ }
91
+ __classPrivateFieldSet(this, _ActivedescendantController_controlsElements, elements, "f");
92
+ for (const element of __classPrivateFieldGet(this, _ActivedescendantController_controlsElements, "f")) {
93
+ element.addEventListener('keydown', this.onKeydown);
94
+ }
95
+ }
96
+ /** All items */
97
+ get items() {
98
+ return this._items;
99
+ }
100
+ /**
101
+ * Sets the list of items and activates the next activatable item after the current one
102
+ * @param items tabindex items
103
+ */
104
+ set items(items) {
105
+ const container = this.options.getItemsContainer?.() ?? this.host;
106
+ if (!(container instanceof HTMLElement)) {
107
+ throw new Error('items container must be an HTMLElement');
108
+ }
109
+ this.itemsContainerElement = container;
110
+ const { supportsCrossRootActiveDescendant } = ActivedescendantController;
111
+ if (supportsCrossRootActiveDescendant
112
+ || [container] // all nodes are in the same root
113
+ .concat(this.controlsElements)
114
+ .concat(items)
115
+ .every((node, _, a) => node.getRootNode() === a[0].getRootNode())) {
116
+ this._items = items.map(x => {
117
+ if (!supportsCrossRootActiveDescendant) {
118
+ x.id || (x.id = getRandomId());
119
+ }
120
+ return x;
121
+ });
122
+ }
123
+ else {
124
+ this._items = items?.map((item) => {
125
+ item.removeAttribute('tabindex');
126
+ if (container.contains(item)) {
127
+ item.id || (item.id = getRandomId());
128
+ __classPrivateFieldGet(this, _ActivedescendantController_noCloneSet, "f").add(item);
129
+ __classPrivateFieldGet(this, _ActivedescendantController_shadowToLightMap, "f").set(item, item);
130
+ return item;
131
+ }
132
+ else {
133
+ const clone = item.cloneNode(true);
134
+ clone.id = getRandomId();
135
+ __classPrivateFieldGet(this, _ActivedescendantController_lightToShadowMap, "f").set(item, clone);
136
+ __classPrivateFieldGet(this, _ActivedescendantController_shadowToLightMap, "f").set(clone, item);
137
+ // Though efforts were taken to disconnect
138
+ // this observer, it may still be a memory leak
139
+ __classPrivateFieldGet(this, _ActivedescendantController_attrMO, "f").observe(clone, { attributes: true });
140
+ __classPrivateFieldGet(this, _ActivedescendantController_attrMO, "f").observe(item, { attributes: true });
141
+ return clone;
142
+ }
143
+ });
144
+ }
145
+ }
146
+ constructor(host, options) {
147
+ var _a;
148
+ super(host, options);
149
+ _ActivedescendantController_instances.add(this);
150
+ this.host = host;
151
+ this.options = options;
152
+ /** Maps from original element to shadow DOM clone */
153
+ _ActivedescendantController_lightToShadowMap.set(this, new WeakMap());
154
+ /** Maps from shadow DOM clone to original element */
155
+ _ActivedescendantController_shadowToLightMap.set(this, new WeakMap());
156
+ /** Set of item which should not be cloned */
157
+ _ActivedescendantController_noCloneSet.set(this, new WeakSet());
158
+ /** Element which controls the list i.e. combobox */
159
+ _ActivedescendantController_controlsElements.set(this, []);
160
+ _ActivedescendantController_observing.set(this, false);
161
+ _ActivedescendantController_listMO.set(this, new MutationObserver(records => __classPrivateFieldGet(this, _ActivedescendantController_instances, "m", _ActivedescendantController_onItemsDOMChange).call(this, records)));
162
+ _ActivedescendantController_attrMO.set(this, new MutationObserver(records => __classPrivateFieldGet(this, _ActivedescendantController_instances, "m", _ActivedescendantController_onItemAttributeChange).call(this, records)));
163
+ (_a = this.options).getItemValue ?? (_a.getItemValue = function () {
164
+ return this.value;
165
+ });
166
+ }
167
+ ;
168
+ ;
169
+ initItems() {
170
+ __classPrivateFieldGet(this, _ActivedescendantController_attrMO, "f").disconnect();
171
+ super.initItems();
172
+ this.controlsElements = this.options.getControlsElements?.() ?? [];
173
+ if (!__classPrivateFieldGet(this, _ActivedescendantController_observing, "f") && this.itemsContainerElement && this.itemsContainerElement.isConnected) {
174
+ __classPrivateFieldGet(this, _ActivedescendantController_listMO, "f").observe(this.itemsContainerElement, { childList: true });
175
+ __classPrivateFieldSet(this, _ActivedescendantController_observing, true, "f");
176
+ }
177
+ }
178
+ hostDisconnected() {
179
+ this.controlsElements = [];
180
+ __classPrivateFieldSet(this, _ActivedescendantController_observing, false, "f");
181
+ __classPrivateFieldGet(this, _ActivedescendantController_listMO, "f").disconnect();
182
+ __classPrivateFieldGet(this, _ActivedescendantController_attrMO, "f").disconnect();
183
+ }
184
+ onKeydown(event) {
185
+ if (!event.ctrlKey
186
+ && !event.altKey
187
+ && !event.metaKey
188
+ && !!this.atFocusableItems.length) {
189
+ super.onKeydown(event);
190
+ }
191
+ ;
192
+ }
193
+ renderItemsToShadowRoot() {
194
+ if (ActivedescendantController.supportsCrossRootActiveDescendant) {
195
+ return nothing;
196
+ }
197
+ else {
198
+ return this.items?.filter(x => !__classPrivateFieldGet(this, _ActivedescendantController_noCloneSet, "f").has(x));
199
+ }
200
+ }
201
+ }
202
+ _ActivedescendantController_lightToShadowMap = new WeakMap(), _ActivedescendantController_shadowToLightMap = new WeakMap(), _ActivedescendantController_noCloneSet = new WeakMap(), _ActivedescendantController_controlsElements = new WeakMap(), _ActivedescendantController_observing = new WeakMap(), _ActivedescendantController_listMO = new WeakMap(), _ActivedescendantController_attrMO = new WeakMap(), _ActivedescendantController_instances = new WeakSet(), _ActivedescendantController_syncAttr = function _ActivedescendantController_syncAttr(attributeName, fromNode) {
203
+ const toNode = __classPrivateFieldGet(this, _ActivedescendantController_shadowToLightMap, "f").get(fromNode)
204
+ ?? __classPrivateFieldGet(this, _ActivedescendantController_lightToShadowMap, "f").get(fromNode);
205
+ const newVal = fromNode.getAttribute(attributeName);
206
+ const oldVal = toNode?.getAttribute(attributeName);
207
+ if (!fromNode.hasAttribute(attributeName)) {
208
+ toNode?.removeAttribute(attributeName);
209
+ }
210
+ else if (oldVal !== newVal) {
211
+ toNode?.setAttribute(attributeName, newVal);
212
+ }
213
+ }, _ActivedescendantController_onItemsDOMChange = function _ActivedescendantController_onItemsDOMChange(records) {
214
+ for (const { removedNodes } of records) {
215
+ for (const removed of removedNodes) {
216
+ __classPrivateFieldGet(this, _ActivedescendantController_lightToShadowMap, "f").get(removed)?.remove();
217
+ __classPrivateFieldGet(this, _ActivedescendantController_lightToShadowMap, "f").delete(removed);
218
+ }
219
+ }
220
+ }, _ActivedescendantController_onItemAttributeChange = function _ActivedescendantController_onItemAttributeChange(records) {
221
+ for (const { target, attributeName } of records) {
222
+ if (attributeName) {
223
+ __classPrivateFieldGet(this, _ActivedescendantController_instances, "m", _ActivedescendantController_syncAttr).call(this, attributeName, target);
224
+ }
225
+ }
226
+ };
227
+ __decorate([
228
+ bound
229
+ ], ActivedescendantController.prototype, "onKeydown", null);
230
+ //# sourceMappingURL=activedescendant-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activedescendant-controller.js","sourceRoot":"","sources":["activedescendant-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveControllerHost } from 'lit';\n\nimport { type ATFocusControllerOptions, ATFocusController } from './at-focus-controller.js';\n\nimport { isServer, nothing } from 'lit';\nimport { getRandomId } from '../functions/random.js';\nimport { bound } from '../decorators/bound.js';\n\nexport interface ActivedescendantControllerOptions<\n Item extends HTMLElement\n> extends ATFocusControllerOptions<Item> {\n /**\n * Returns a reference to the element which acts as the assistive technology container for\n * the items. In the case of a combobox, this is the input element.\n */\n getActiveDescendantContainer(): HTMLElement | null;\n /**\n * Optional callback to control the assistive technology focus behavior of items.\n * By default, ActivedescendantController will not do anything special to items when they receive\n * assistive technology focus, and will only set the `activedescendant` property on the container.\n * If you provide this callback, ActivedescendantController will call it on your item with the\n * active state. You may use this to set active styles.\n */\n setItemActive?(item: Item, active: boolean): void;\n /**\n * Optional callback to retrieve the value from an option element.\n * By default, retrieves the `value` attribute, or the text content.\n * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLOptionElement\n */\n getItemValue?(item: Item): string;\n}\n\n/**\n * Implements activedescendant pattern, as described in WAI-ARIA practices,\n * [Managing Focus in Composites Using aria-activedescendant][ad]\n *\n * The steps for using the aria-activedescendant method of managing focus are as follows.\n *\n * - When the container element that has a role that supports aria-activedescendant is loaded\n * or created, ensure that:\n * - The container element is included in the tab sequence as described in\n * Keyboard Navigation Between Components or is a focusable element of a composite\n * that implements a roving tabindex.\n * - It has aria-activedescendant=\"IDREF\" where IDREF is the ID of the element within\n * the container that should be identified as active when the widget receives focus.\n * The referenced element needs to meet the DOM relationship requirements described below.\n * - When the container element receives DOM focus, draw a visual focus indicator on the active\n * element and ensure the active element is scrolled into view.\n * - When the composite widget contains focus and the user presses a navigation key that moves\n * focus within the widget, such as an arrow key:\n * - Change the value of aria-activedescendant on the container to refer to the element\n * that should be reported to assistive technologies as active.\n * - Move the visual focus indicator and, if necessary, scrolled the active element into view.\n * - If the design calls for a specific element to be focused the next time a user moves focus\n * into the composite with Tab or Shift+Tab, check if aria-activedescendant is referring to\n * that target element when the container loses focus. If it is not, set aria-activedescendant\n * to refer to the target element.\n *\n * The specification for aria-activedescendant places important restrictions on the\n * DOM relationship between the focused element that has the aria-activedescendant attribute\n * and the element referenced as active by the value of the attribute.\n * One of the following three conditions must be met.\n *\n * 1. The element referenced as active is a DOM descendant of the focused referencing element.\n * 2. The focused referencing element has a value specified for the aria-owns property that\n * includes the ID of the element referenced as active.\n * 3. The focused referencing element has role of combobox, textbox, or searchbox\n * and has aria-controls property referring to an element with a role that supports\n * aria-activedescendant and either:\n * 1. The element referenced as active is a descendant of the controlled element.\n * 2. The controlled element has a value specified for the aria-owns property that includes\n * the ID of the element referenced as active.\n *\n * [ad]: https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant\n */\nexport class ActivedescendantController<\n Item extends HTMLElement = HTMLElement\n> extends ATFocusController<Item> {\n /**\n * When true, the browser supports cross-root ARIA such that the controller does not need\n * to copy item nodes into the controlling nodes' root\n */\n public static get supportsCrossRootActiveDescendant(): boolean {\n return !isServer && 'ariaActiveDescendantElement' in HTMLElement.prototype;\n }\n\n static of<Item extends HTMLElement>(\n host: ReactiveControllerHost,\n options: ActivedescendantControllerOptions<Item>,\n ): ActivedescendantController<Item> {\n return new ActivedescendantController(host, options);\n }\n\n /** Maps from original element to shadow DOM clone */\n #lightToShadowMap = new WeakMap<Item, Item>();\n\n /** Maps from shadow DOM clone to original element */\n #shadowToLightMap = new WeakMap<Item, Item>();\n\n /** Set of item which should not be cloned */\n #noCloneSet = new WeakSet<Item>();\n\n /** Element which controls the list i.e. combobox */\n #controlsElements: HTMLElement[] = [];\n\n #observing = false;\n\n #listMO = new MutationObserver(records => this.#onItemsDOMChange(records));\n\n #attrMO = new MutationObserver(records => this.#onItemAttributeChange(records));\n\n #syncAttr(attributeName: string, fromNode: Item) {\n const toNode = this.#shadowToLightMap.get(fromNode as Item)\n ?? this.#lightToShadowMap.get(fromNode as Item);\n const newVal = fromNode.getAttribute(attributeName);\n const oldVal = toNode?.getAttribute(attributeName);\n if (!fromNode.hasAttribute(attributeName)) {\n toNode?.removeAttribute(attributeName);\n } else if (oldVal !== newVal) {\n toNode?.setAttribute(attributeName, newVal!);\n }\n }\n\n get atFocusedItemIndex(): number {\n return super.atFocusedItemIndex;\n }\n\n /**\n * Rather than setting DOM focus, applies the `aria-activedescendant` attribute,\n * using AriaIDLAttributes for cross-root aria, if supported by the browser\n * @param item item\n */\n set atFocusedItemIndex(index: number) {\n super.atFocusedItemIndex = index;\n const item = this._items.at(this.atFocusedItemIndex);\n for (const _item of this.items) {\n this.options.setItemActive?.(_item, _item === item);\n }\n const container = this.options.getActiveDescendantContainer();\n if (!ActivedescendantController.supportsCrossRootActiveDescendant) {\n container?.setAttribute('aria-activedescendant', item?.id ?? '');\n } else if (container) {\n container.ariaActiveDescendantElement = item ?? null;\n }\n this.host.requestUpdate();\n }\n\n protected get controlsElements(): HTMLElement[] {\n return this.#controlsElements;\n }\n\n protected set controlsElements(elements: HTMLElement[]) {\n for (const old of this.#controlsElements) {\n old?.removeEventListener('keydown', this.onKeydown);\n }\n this.#controlsElements = elements;\n for (const element of this.#controlsElements) {\n element.addEventListener('keydown', this.onKeydown);\n }\n }\n\n /** All items */\n get items() {\n return this._items;\n }\n\n /**\n * Sets the list of items and activates the next activatable item after the current one\n * @param items tabindex items\n */\n override set items(items: Item[]) {\n const container = this.options.getItemsContainer?.() ?? this.host;\n if (!(container instanceof HTMLElement)) {\n throw new Error('items container must be an HTMLElement');\n }\n this.itemsContainerElement = container;\n const { supportsCrossRootActiveDescendant } = ActivedescendantController;\n if (supportsCrossRootActiveDescendant\n || [container] // all nodes are in the same root\n .concat(this.controlsElements)\n .concat(items)\n .every((node, _, a) => node.getRootNode() === a[0].getRootNode())) {\n this._items = items.map(x => {\n if (!supportsCrossRootActiveDescendant) {\n x.id ||= getRandomId();\n }\n return x;\n });\n } else {\n this._items = items?.map((item: Item) => {\n item.removeAttribute('tabindex');\n if (container.contains(item)) {\n item.id ||= getRandomId();\n this.#noCloneSet.add(item);\n this.#shadowToLightMap.set(item, item);\n return item;\n } else {\n const clone = item.cloneNode(true) as Item;\n clone.id = getRandomId();\n this.#lightToShadowMap.set(item, clone);\n this.#shadowToLightMap.set(clone, item);\n // Though efforts were taken to disconnect\n // this observer, it may still be a memory leak\n this.#attrMO.observe(clone, { attributes: true });\n this.#attrMO.observe(item, { attributes: true });\n return clone;\n }\n });\n }\n }\n\n private constructor(\n public host: ReactiveControllerHost,\n protected options: ActivedescendantControllerOptions<Item>,\n ) {\n super(host, options);\n this.options.getItemValue ??= function(this: Item) {\n return (this as unknown as HTMLOptionElement).value;\n };\n }\n\n #onItemsDOMChange(records: MutationRecord[]) {\n for (const { removedNodes } of records) {\n for (const removed of removedNodes as NodeListOf<Item>) {\n this.#lightToShadowMap.get(removed)?.remove();\n this.#lightToShadowMap.delete(removed);\n }\n }\n };\n\n #onItemAttributeChange(records: MutationRecord[]) {\n for (const { target, attributeName } of records) {\n if (attributeName) {\n this.#syncAttr(attributeName, target as Item);\n }\n }\n };\n\n protected override initItems(): void {\n this.#attrMO.disconnect();\n super.initItems();\n this.controlsElements = this.options.getControlsElements?.() ?? [];\n if (!this.#observing && this.itemsContainerElement && this.itemsContainerElement.isConnected) {\n this.#listMO.observe(this.itemsContainerElement, { childList: true });\n this.#observing = true;\n }\n }\n\n hostDisconnected(): void {\n this.controlsElements = [];\n this.#observing = false;\n this.#listMO.disconnect();\n this.#attrMO.disconnect();\n }\n\n @bound\n protected override onKeydown(event: KeyboardEvent): void {\n if (!event.ctrlKey\n && !event.altKey\n && !event.metaKey\n && !!this.atFocusableItems.length) {\n super.onKeydown(event);\n };\n }\n\n public renderItemsToShadowRoot(): typeof nothing | Node[] {\n if (ActivedescendantController.supportsCrossRootActiveDescendant) {\n return nothing;\n } else {\n return this.items?.filter(x => !this.#noCloneSet.has(x));\n }\n }\n}\n"]}
@@ -0,0 +1,56 @@
1
+ import { type ReactiveControllerHost } from 'lit';
2
+ export interface ATFocusControllerOptions<Item extends HTMLElement> {
3
+ /**
4
+ * Callback to return the list of items
5
+ */
6
+ getItems(): Item[];
7
+ /**
8
+ * Callback to return the listbox container element
9
+ */
10
+ getItemsContainer?(): HTMLElement | null;
11
+ /**
12
+ * Callback to return the direction of navigation in the list box.
13
+ */
14
+ getOrientation?(): 'horizontal' | 'vertical' | 'both' | 'undefined';
15
+ /**
16
+ * Function returning the DOM nodes which are accessibility controllers of item container
17
+ * e.g. the button toggle and combobox input which control a listbox.
18
+ */
19
+ getControlsElements?(): HTMLElement[];
20
+ }
21
+ export declare abstract class ATFocusController<Item extends HTMLElement> {
22
+ #private;
23
+ host: ReactiveControllerHost;
24
+ protected options: ATFocusControllerOptions<Item>;
25
+ protected _items: Item[];
26
+ /** All items */
27
+ abstract items: Item[];
28
+ /**
29
+ * Index of the Item which currently has assistive technology focus
30
+ * Set this to change focus. Setting to an out-of-bounds value will
31
+ * wrap around to the other side of the list.
32
+ */
33
+ get atFocusedItemIndex(): number;
34
+ set atFocusedItemIndex(index: number);
35
+ /** Elements which control the items container e.g. a combobox input */
36
+ protected get controlsElements(): HTMLElement[];
37
+ /** All items which are able to receive assistive technology focus */
38
+ get atFocusableItems(): Item[];
39
+ /** The element containing focusable items, e.g. a listbox */
40
+ get itemsContainerElement(): HTMLElement | null;
41
+ set itemsContainerElement(container: HTMLElement | null);
42
+ constructor(host: ReactiveControllerHost, options: ATFocusControllerOptions<Item>);
43
+ /**
44
+ * Initialize the items and itemsContainerElement fields
45
+ */
46
+ protected initItems(): void;
47
+ hostConnected(): void;
48
+ hostDisconnected(): void;
49
+ hostUpdate(): void;
50
+ /**
51
+ * Override and conditionally call `super.onKeydown` to filter out keyboard events
52
+ * which should not result in a focus change. Ensure that subclass' method is bound
53
+ * @param event keyboard event
54
+ */
55
+ protected onKeydown(event: KeyboardEvent): void;
56
+ }
@@ -0,0 +1,168 @@
1
+ var _ATFocusController_instances, _ATFocusController_itemsContainerElement, _ATFocusController_atFocusedItemIndex, _ATFocusController_initContainer;
2
+ import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib";
3
+ import { isServer } from 'lit';
4
+ import { bound } from '../decorators/bound.js';
5
+ function isATFocusableItem(el) {
6
+ return !!el
7
+ && el.ariaHidden !== 'true'
8
+ && !el.hasAttribute('inert')
9
+ && !el.hasAttribute('hidden');
10
+ }
11
+ export class ATFocusController {
12
+ /**
13
+ * Index of the Item which currently has assistive technology focus
14
+ * Set this to change focus. Setting to an out-of-bounds value will
15
+ * wrap around to the other side of the list.
16
+ */
17
+ get atFocusedItemIndex() {
18
+ return __classPrivateFieldGet(this, _ATFocusController_atFocusedItemIndex, "f");
19
+ }
20
+ set atFocusedItemIndex(index) {
21
+ const previousIndex = __classPrivateFieldGet(this, _ATFocusController_atFocusedItemIndex, "f");
22
+ const direction = index > previousIndex ? 1 : -1;
23
+ const { items, atFocusableItems } = this;
24
+ const itemsIndexOfLastATFocusableItem = items.indexOf(this.atFocusableItems.at(-1));
25
+ let itemToGainFocus = items.at(index);
26
+ let itemToGainFocusIsFocusable = atFocusableItems.includes(itemToGainFocus);
27
+ if (atFocusableItems.length) {
28
+ let count = 0;
29
+ while (!itemToGainFocus || !itemToGainFocusIsFocusable && count++ <= 1000) {
30
+ if (index < 0) {
31
+ index = itemsIndexOfLastATFocusableItem;
32
+ }
33
+ else if (index >= itemsIndexOfLastATFocusableItem) {
34
+ index = 0;
35
+ }
36
+ else {
37
+ index = index + direction;
38
+ }
39
+ itemToGainFocus = items.at(index);
40
+ itemToGainFocusIsFocusable = atFocusableItems.includes(itemToGainFocus);
41
+ }
42
+ if (count >= 1000) {
43
+ throw new Error('Could not atFocusedItemIndex');
44
+ }
45
+ }
46
+ __classPrivateFieldSet(this, _ATFocusController_atFocusedItemIndex, index, "f");
47
+ }
48
+ /** Elements which control the items container e.g. a combobox input */
49
+ get controlsElements() {
50
+ return this.options.getControlsElements?.() ?? [];
51
+ }
52
+ /** All items which are able to receive assistive technology focus */
53
+ get atFocusableItems() {
54
+ return this._items.filter(isATFocusableItem);
55
+ }
56
+ /** The element containing focusable items, e.g. a listbox */
57
+ get itemsContainerElement() {
58
+ return __classPrivateFieldGet(this, _ATFocusController_itemsContainerElement, "f") ?? null;
59
+ }
60
+ set itemsContainerElement(container) {
61
+ if (container !== __classPrivateFieldGet(this, _ATFocusController_itemsContainerElement, "f")) {
62
+ __classPrivateFieldGet(this, _ATFocusController_itemsContainerElement, "f")?.removeEventListener('keydown', this.onKeydown);
63
+ __classPrivateFieldSet(this, _ATFocusController_itemsContainerElement, container, "f");
64
+ __classPrivateFieldGet(this, _ATFocusController_itemsContainerElement, "f")?.addEventListener('keydown', this.onKeydown);
65
+ this.host.requestUpdate();
66
+ }
67
+ }
68
+ constructor(host, options) {
69
+ _ATFocusController_instances.add(this);
70
+ this.host = host;
71
+ this.options = options;
72
+ _ATFocusController_itemsContainerElement.set(this, null);
73
+ _ATFocusController_atFocusedItemIndex.set(this, -1);
74
+ this._items = [];
75
+ this.host.updateComplete.then(() => this.initItems());
76
+ }
77
+ /**
78
+ * Initialize the items and itemsContainerElement fields
79
+ */
80
+ initItems() {
81
+ this.items = this.options.getItems();
82
+ this.itemsContainerElement ?? (this.itemsContainerElement = __classPrivateFieldGet(this, _ATFocusController_instances, "m", _ATFocusController_initContainer).call(this));
83
+ }
84
+ hostConnected() {
85
+ this.hostUpdate();
86
+ }
87
+ hostDisconnected() {
88
+ __classPrivateFieldGet(this, _ATFocusController_itemsContainerElement, "f")?.removeEventListener('keydown', this.onKeydown);
89
+ }
90
+ hostUpdate() {
91
+ this.itemsContainerElement ?? (this.itemsContainerElement = __classPrivateFieldGet(this, _ATFocusController_instances, "m", _ATFocusController_initContainer).call(this));
92
+ }
93
+ /**
94
+ * Override and conditionally call `super.onKeydown` to filter out keyboard events
95
+ * which should not result in a focus change. Ensure that subclass' method is bound
96
+ * @param event keyboard event
97
+ */
98
+ onKeydown(event) {
99
+ const orientation = this.options.getOrientation?.() ?? __classPrivateFieldGet(this, _ATFocusController_itemsContainerElement, "f")
100
+ ?.getAttribute('aria-orientation');
101
+ const item = this._items.at(this.atFocusedItemIndex);
102
+ const horizontalOnly = orientation === 'horizontal'
103
+ || item?.tagName === 'SELECT'
104
+ || item?.getAttribute('role') === 'spinbutton';
105
+ const verticalOnly = orientation === 'vertical';
106
+ switch (event.key) {
107
+ case 'ArrowLeft':
108
+ if (verticalOnly) {
109
+ return;
110
+ }
111
+ this.atFocusedItemIndex--;
112
+ event.stopPropagation();
113
+ event.preventDefault();
114
+ break;
115
+ case 'ArrowRight':
116
+ if (verticalOnly) {
117
+ return;
118
+ }
119
+ this.atFocusedItemIndex++;
120
+ event.stopPropagation();
121
+ event.preventDefault();
122
+ break;
123
+ case 'ArrowUp':
124
+ if (horizontalOnly) {
125
+ return;
126
+ }
127
+ this.atFocusedItemIndex--;
128
+ event.stopPropagation();
129
+ event.preventDefault();
130
+ break;
131
+ case 'ArrowDown':
132
+ if (horizontalOnly) {
133
+ return;
134
+ }
135
+ this.atFocusedItemIndex++;
136
+ event.stopPropagation();
137
+ event.preventDefault();
138
+ break;
139
+ case 'Home':
140
+ if (!(event.target instanceof HTMLElement
141
+ && (event.target.hasAttribute('aria-activedescendant')
142
+ || event.target.ariaActiveDescendantElement))) {
143
+ this.atFocusedItemIndex = 0;
144
+ event.stopPropagation();
145
+ event.preventDefault();
146
+ }
147
+ break;
148
+ case 'End':
149
+ if (!(event.target instanceof HTMLElement
150
+ && (event.target.hasAttribute('aria-activedescendant')
151
+ || event.target.ariaActiveDescendantElement))) {
152
+ this.atFocusedItemIndex = this.items.length - 1;
153
+ event.stopPropagation();
154
+ event.preventDefault();
155
+ }
156
+ break;
157
+ default:
158
+ break;
159
+ }
160
+ this.host.requestUpdate();
161
+ }
162
+ ;
163
+ }
164
+ _ATFocusController_itemsContainerElement = new WeakMap(), _ATFocusController_atFocusedItemIndex = new WeakMap(), _ATFocusController_instances = new WeakSet(), _ATFocusController_initContainer = function _ATFocusController_initContainer() {
165
+ return this.options.getItemsContainer?.()
166
+ ?? (!isServer && this.host instanceof HTMLElement ? this.host : null);
167
+ };
168
+ //# sourceMappingURL=at-focus-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"at-focus-controller.js","sourceRoot":"","sources":["at-focus-controller.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,QAAQ,EAA+B,MAAM,KAAK,CAAC;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAE/C,SAAS,iBAAiB,CAAC,EAAW;IACpC,OAAO,CAAC,CAAC,EAAE;WACJ,EAAE,CAAC,UAAU,KAAK,MAAM;WACxB,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC;WACzB,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;AACpC,CAAC;AAsBD,MAAM,OAAgB,iBAAiB;IAUrC;;;;OAIG;IACH,IAAI,kBAAkB;QACpB,OAAO,uBAAA,IAAI,6CAAoB,CAAC;IAClC,CAAC;IAED,IAAI,kBAAkB,CAAC,KAAa;QAClC,MAAM,aAAa,GAAG,uBAAA,IAAI,6CAAoB,CAAC;QAC/C,MAAM,SAAS,GAAG,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAAC;QACzC,MAAM,+BAA+B,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC;QACrF,IAAI,eAAe,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,0BAA0B,GAAG,gBAAgB,CAAC,QAAQ,CAAC,eAAgB,CAAC,CAAC;QAC7E,IAAI,gBAAgB,CAAC,MAAM,EAAE,CAAC;YAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,OAAO,CAAC,eAAe,IAAI,CAAC,0BAA0B,IAAI,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC;gBAC1E,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;oBACd,KAAK,GAAG,+BAA+B,CAAC;gBAC1C,CAAC;qBAAM,IAAI,KAAK,IAAI,+BAA+B,EAAE,CAAC;oBACpD,KAAK,GAAG,CAAC,CAAC;gBACZ,CAAC;qBAAM,CAAC;oBACN,KAAK,GAAG,KAAK,GAAG,SAAS,CAAC;gBAC5B,CAAC;gBACD,eAAe,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;gBAClC,0BAA0B,GAAG,gBAAgB,CAAC,QAAQ,CAAC,eAAgB,CAAC,CAAC;YAC3E,CAAC;YACD,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;QACD,uBAAA,IAAI,yCAAuB,KAAK,MAAA,CAAC;IACnC,CAAC;IAED,uEAAuE;IACvE,IAAc,gBAAgB;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,CAAC;IACpD,CAAC;IAED,qEAAqE;IACrE,IAAI,gBAAgB;QAClB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC/C,CAAC;IAED,6DAA6D;IAC7D,IAAI,qBAAqB;QACvB,OAAO,uBAAA,IAAI,gDAAuB,IAAI,IAAI,CAAC;IAC7C,CAAC;IAED,IAAI,qBAAqB,CAAC,SAA6B;QACrD,IAAI,SAAS,KAAK,uBAAA,IAAI,gDAAuB,EAAE,CAAC;YAC9C,uBAAA,IAAI,gDAAuB,EAAE,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAC5E,uBAAA,IAAI,4CAA0B,SAAS,MAAA,CAAC;YACxC,uBAAA,IAAI,gDAAuB,EAAE,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YACzE,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,YACS,IAA4B,EACzB,OAAuC;;QAD1C,SAAI,GAAJ,IAAI,CAAwB;QACzB,YAAO,GAAP,OAAO,CAAgC;QAvEnD,mDAA6C,IAAI,EAAC;QAElD,gDAAsB,CAAC,CAAC,EAAC;QAEf,WAAM,GAAW,EAAE,CAAC;QAqE5B,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IACxD,CAAC;IAED;;OAEG;IACO,SAAS;QACjB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrC,IAAI,CAAC,qBAAqB,KAA1B,IAAI,CAAC,qBAAqB,GAAK,uBAAA,IAAI,sEAAe,MAAnB,IAAI,CAAiB,EAAC;IACvD,CAAC;IAED,aAAa;QACX,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IAED,gBAAgB;QACd,uBAAA,IAAI,gDAAuB,EAAE,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC9E,CAAC;IAED,UAAU;QACR,IAAI,CAAC,qBAAqB,KAA1B,IAAI,CAAC,qBAAqB,GAAK,uBAAA,IAAI,sEAAe,MAAnB,IAAI,CAAiB,EAAC;IACvD,CAAC;IAOD;;;;OAIG;IACO,SAAS,CAAC,KAAoB;QACtC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,IAAI,uBAAA,IAAI,gDAChC;YACvB,EAAE,YAAY,CAAC,kBAAkB,CACmB,CAAC;QAEzD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAErD,MAAM,cAAc,GAChB,WAAW,KAAK,YAAY;eACzB,IAAI,EAAE,OAAO,KAAK,QAAQ;eAC1B,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,KAAK,YAAY,CAAC;QAEnD,MAAM,YAAY,GAAG,WAAW,KAAK,UAAU,CAAC;QAEhD,QAAQ,KAAK,CAAC,GAAG,EAAE,CAAC;YAClB,KAAK,WAAW;gBACd,IAAI,YAAY,EAAE,CAAC;oBACjB,OAAO;gBACT,CAAC;gBACD,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC1B,KAAK,CAAC,eAAe,EAAE,CAAC;gBACxB,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,MAAM;YACR,KAAK,YAAY;gBACf,IAAI,YAAY,EAAE,CAAC;oBACjB,OAAO;gBACT,CAAC;gBACD,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC1B,KAAK,CAAC,eAAe,EAAE,CAAC;gBACxB,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,MAAM;YACR,KAAK,SAAS;gBACZ,IAAI,cAAc,EAAE,CAAC;oBACnB,OAAO;gBACT,CAAC;gBACD,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC1B,KAAK,CAAC,eAAe,EAAE,CAAC;gBACxB,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,MAAM;YACR,KAAK,WAAW;gBACd,IAAI,cAAc,EAAE,CAAC;oBACnB,OAAO;gBACT,CAAC;gBACD,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC1B,KAAK,CAAC,eAAe,EAAE,CAAC;gBACxB,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,MAAM;YACR,KAAK,MAAM;gBACT,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,YAAY,WAAW;uBAClC,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,uBAAuB,CAAC;2BAClD,KAAK,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAC,EAAE,CAAC;oBACnD,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAC;oBAC5B,KAAK,CAAC,eAAe,EAAE,CAAC;oBACxB,KAAK,CAAC,cAAc,EAAE,CAAC;gBACzB,CAAC;gBACD,MAAM;YACR,KAAK,KAAK;gBACR,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,YAAY,WAAW;uBAClC,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,uBAAuB,CAAC;2BAClD,KAAK,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAC,EAAE,CAAC;oBACnD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;oBAChD,KAAK,CAAC,eAAe,EAAE,CAAC;oBACxB,KAAK,CAAC,cAAc,EAAE,CAAC;gBACzB,CAAC;gBACD,MAAM;YACR;gBACE,MAAM;QACV,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;IAC5B,CAAC;IAAA,CAAC;CACH;;IAhFG,OAAO,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE;WACpC,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,YAAY,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC1E,CAAC","sourcesContent":["import { isServer, type ReactiveControllerHost } from 'lit';\nimport { bound } from '../decorators/bound.js';\n\nfunction isATFocusableItem(el: Element): el is HTMLElement {\n return !!el\n && el.ariaHidden !== 'true'\n && !el.hasAttribute('inert')\n && !el.hasAttribute('hidden');\n}\n\nexport interface ATFocusControllerOptions<Item extends HTMLElement> {\n /**\n * Callback to return the list of items\n */\n getItems(): Item[];\n /**\n * Callback to return the listbox container element\n */\n getItemsContainer?(): HTMLElement | null;\n /**\n * Callback to return the direction of navigation in the list box.\n */\n getOrientation?(): 'horizontal' | 'vertical' | 'both' | 'undefined';\n /**\n * Function returning the DOM nodes which are accessibility controllers of item container\n * e.g. the button toggle and combobox input which control a listbox.\n */\n getControlsElements?(): HTMLElement[];\n}\n\nexport abstract class ATFocusController<Item extends HTMLElement> {\n #itemsContainerElement: HTMLElement | null = null;\n\n #atFocusedItemIndex = -1;\n\n protected _items: Item[] = [];\n\n /** All items */\n abstract items: Item[];\n\n /**\n * Index of the Item which currently has assistive technology focus\n * Set this to change focus. Setting to an out-of-bounds value will\n * wrap around to the other side of the list.\n */\n get atFocusedItemIndex() {\n return this.#atFocusedItemIndex;\n }\n\n set atFocusedItemIndex(index: number) {\n const previousIndex = this.#atFocusedItemIndex;\n const direction = index > previousIndex ? 1 : -1;\n const { items, atFocusableItems } = this;\n const itemsIndexOfLastATFocusableItem = items.indexOf(this.atFocusableItems.at(-1)!);\n let itemToGainFocus = items.at(index);\n let itemToGainFocusIsFocusable = atFocusableItems.includes(itemToGainFocus!);\n if (atFocusableItems.length) {\n let count = 0;\n while (!itemToGainFocus || !itemToGainFocusIsFocusable && count++ <= 1000) {\n if (index < 0) {\n index = itemsIndexOfLastATFocusableItem;\n } else if (index >= itemsIndexOfLastATFocusableItem) {\n index = 0;\n } else {\n index = index + direction;\n }\n itemToGainFocus = items.at(index);\n itemToGainFocusIsFocusable = atFocusableItems.includes(itemToGainFocus!);\n }\n if (count >= 1000) {\n throw new Error('Could not atFocusedItemIndex');\n }\n }\n this.#atFocusedItemIndex = index;\n }\n\n /** Elements which control the items container e.g. a combobox input */\n protected get controlsElements(): HTMLElement[] {\n return this.options.getControlsElements?.() ?? [];\n }\n\n /** All items which are able to receive assistive technology focus */\n get atFocusableItems(): Item[] {\n return this._items.filter(isATFocusableItem);\n }\n\n /** The element containing focusable items, e.g. a listbox */\n get itemsContainerElement() {\n return this.#itemsContainerElement ?? null;\n }\n\n set itemsContainerElement(container: HTMLElement | null) {\n if (container !== this.#itemsContainerElement) {\n this.#itemsContainerElement?.removeEventListener('keydown', this.onKeydown);\n this.#itemsContainerElement = container;\n this.#itemsContainerElement?.addEventListener('keydown', this.onKeydown);\n this.host.requestUpdate();\n }\n }\n\n constructor(\n public host: ReactiveControllerHost,\n protected options: ATFocusControllerOptions<Item>,\n ) {\n this.host.updateComplete.then(() => this.initItems());\n }\n\n /**\n * Initialize the items and itemsContainerElement fields\n */\n protected initItems(): void {\n this.items = this.options.getItems();\n this.itemsContainerElement ??= this.#initContainer();\n }\n\n hostConnected(): void {\n this.hostUpdate();\n }\n\n hostDisconnected(): void {\n this.#itemsContainerElement?.removeEventListener('keydown', this.onKeydown);\n }\n\n hostUpdate(): void {\n this.itemsContainerElement ??= this.#initContainer();\n }\n\n #initContainer() {\n return this.options.getItemsContainer?.()\n ?? (!isServer && this.host instanceof HTMLElement ? this.host : null);\n }\n\n /**\n * Override and conditionally call `super.onKeydown` to filter out keyboard events\n * which should not result in a focus change. Ensure that subclass' method is bound\n * @param event keyboard event\n */\n protected onKeydown(event: KeyboardEvent): void {\n const orientation = this.options.getOrientation?.() ?? this\n .#itemsContainerElement\n ?.getAttribute('aria-orientation') as\n 'horizontal' | 'vertical' | 'grid' | 'undefined';\n\n const item = this._items.at(this.atFocusedItemIndex);\n\n const horizontalOnly =\n orientation === 'horizontal'\n || item?.tagName === 'SELECT'\n || item?.getAttribute('role') === 'spinbutton';\n\n const verticalOnly = orientation === 'vertical';\n\n switch (event.key) {\n case 'ArrowLeft':\n if (verticalOnly) {\n return;\n }\n this.atFocusedItemIndex--;\n event.stopPropagation();\n event.preventDefault();\n break;\n case 'ArrowRight':\n if (verticalOnly) {\n return;\n }\n this.atFocusedItemIndex++;\n event.stopPropagation();\n event.preventDefault();\n break;\n case 'ArrowUp':\n if (horizontalOnly) {\n return;\n }\n this.atFocusedItemIndex--;\n event.stopPropagation();\n event.preventDefault();\n break;\n case 'ArrowDown':\n if (horizontalOnly) {\n return;\n }\n this.atFocusedItemIndex++;\n event.stopPropagation();\n event.preventDefault();\n break;\n case 'Home':\n if (!(event.target instanceof HTMLElement\n && (event.target.hasAttribute('aria-activedescendant')\n || event.target.ariaActiveDescendantElement))) {\n this.atFocusedItemIndex = 0;\n event.stopPropagation();\n event.preventDefault();\n }\n break;\n case 'End':\n if (!(event.target instanceof HTMLElement\n && (event.target.hasAttribute('aria-activedescendant')\n || event.target.ariaActiveDescendantElement))) {\n this.atFocusedItemIndex = this.items.length - 1;\n event.stopPropagation();\n event.preventDefault();\n }\n break;\n default:\n break;\n }\n this.host.requestUpdate();\n };\n}\n"]}