@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
@@ -1,249 +1,442 @@
1
- var _ListboxController_instances, _ListboxController_shiftStartingItem, _ListboxController_items, _ListboxController_listening, _ListboxController_getEnabledOptions, _ListboxController_getEventOption, _ListboxController_onFocus, _ListboxController_onClick, _ListboxController_onKeyup, _ListboxController_onKeydown, _ListboxController_optionsChanged, _ListboxController_updateSingleselect, _ListboxController_updateMultiselect;
1
+ var _ListboxController_instances, _ListboxController_shiftStartingItem, _ListboxController_options, _ListboxController_items, _ListboxController_selectedItems, _ListboxController_listening, _ListboxController_controlsElements, _ListboxController_removeControlsListeners, _ListboxController_isExpanded_get, _ListboxController_getItemFromEvent, _ListboxController_onClick, _ListboxController_onKeyup, _ListboxController_onKeydown, _ListboxController_selectItem;
2
2
  import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib";
3
+ import { isServer } from 'lit';
4
+ import { arraysAreEquivalent } from '../functions/arraysAreEquivalent.js';
5
+ /**
6
+ * This is the default method for setting the selected state on an item element
7
+ * @param item the item
8
+ * @param selected is this item selected
9
+ */
10
+ function setItemSelected(item, selected) {
11
+ if (selected) {
12
+ item.setAttribute('aria-selected', 'true');
13
+ }
14
+ else {
15
+ item.removeAttribute('aria-selected');
16
+ }
17
+ }
18
+ /**
19
+ * @param item possible disabled item
20
+ * @package do not import this outside of `@patternfly/pfe-core`, it is subject to change at any time
21
+ */
22
+ export function isItem(item) {
23
+ return item instanceof Element
24
+ && item?.parentElement?.role === 'listbox'
25
+ && item?.role !== 'presentation'
26
+ && item?.localName !== 'hr';
27
+ }
28
+ /**
29
+ * This is a fib. aria-disabled might not be present on an element that uses internals,
30
+ * and the `disabled` attribute may not accurately represent the disabled state.
31
+ * short of patching the `attachInternals` constructor, it may not be possible at
32
+ * runtime to know with certainty that an arbitrary custom element is disabled or not.
33
+ * @param item possibly disabled item
34
+ * @package do not import this outside of `@patternfly/pfe-core`, it is subject to change at any time
35
+ */
36
+ export function isItemDisabled(item) {
37
+ return ('disabled' in item && typeof item.disabled === 'boolean' && item.disabled)
38
+ || item.getAttribute('aria-disabled') === 'true'
39
+ || item.hasAttribute('disabled')
40
+ || item.hasAttribute('inert')
41
+ || item.matches(':disabled');
42
+ }
3
43
  let constructingAllowed = false;
4
44
  /**
5
45
  * Implements listbox semantics and accesibility. As there are two recognized
6
46
  * patterns for implementing keyboard interactions with listbox patterns,
7
47
  * provide a secondary controller (either RovingTabindexController or
8
48
  * ActiveDescendantController) to complete the implementation.
49
+ *
50
+ * @see https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_vs_selection
51
+ *
52
+ * > Occasionally, it may appear as if two elements on the page have focus at the same time.
53
+ * > For example, in a multi-select list box, when an option is selected it may be greyed.
54
+ * > Yet, the focus indicator can still be moved to other options, which may also be selected.
55
+ * > Similarly, when a user activates a tab in a tablist, the selected state is set on the tab
56
+ * > and its visual appearance changes. However, the user can still navigate, moving the focus
57
+ * > indicator elsewhere on the page while the tab retains its selected appearance and state.
58
+ * >
59
+ * > Focus and selection are quite different. From the keyboard user's perspective,
60
+ * > focus is a pointer, like a mouse pointer; it tracks the path of navigation.
61
+ * > There is only one point of focus at any time and all operations take place at the
62
+ * > point of focus. On the other hand, selection is an operation that can be performed in
63
+ * > some widgets, such as list boxes, trees, and tablists. If a widget supports only single
64
+ * > selection, then only one item can be selected and very often the selected state will simply
65
+ * > follow the focus when focus is moved inside of the widget.
66
+ * > That is, in some widgets, moving focus may also perform the select operation.
67
+ * > However, if the widget supports multiple selection, then more than one item can be in a
68
+ * > selected state, and keys for moving focus do not perform selection. Some multi-select widgets
69
+ * > do support key commands that both move focus and change selection, but those keys are
70
+ * > different from the normal navigation keys. Finally, when focus leaves a widget that includes
71
+ * > a selected element, the selected state persists.
72
+ * >
73
+ * > From the developer's perspective, the difference is simple -- the focused element is the
74
+ * > active element (document.activeElement). Selected elements are elements that have
75
+ * > aria-selected="true".
76
+ * >
77
+ * > With respect to focus and the selected state, the most important considerations for designers
78
+ * > and developers are:
79
+ * >
80
+ * > - The visual focus indicator must always be visible.
81
+ * > - The selected state must be visually distinct from the focus indicator.
9
82
  */
10
83
  export class ListboxController {
11
84
  static of(host, options) {
12
85
  constructingAllowed = true;
13
- const instance = ListboxController.instances.get(host) ?? new ListboxController(host, options);
86
+ const instance = new ListboxController(host, options);
14
87
  constructingAllowed = false;
15
88
  return instance;
16
89
  }
17
- constructor(host,
18
- // this should ideally be ecma #private, but tsc/esbuild tooling isn't up to scratch yet
19
- // so for now we rely on the underscore convention to avoid compile-time errors
20
- // try refactoring after updating tooling dependencies
21
- _options) {
90
+ get container() {
91
+ return __classPrivateFieldGet(this, _ListboxController_options, "f").getItemsContainer?.() ?? this.host;
92
+ }
93
+ get multi() {
94
+ return !!__classPrivateFieldGet(this, _ListboxController_options, "f").multi;
95
+ }
96
+ set multi(v) {
97
+ __classPrivateFieldGet(this, _ListboxController_options, "f").multi = v;
98
+ this.host.requestUpdate();
99
+ }
100
+ get items() {
101
+ return __classPrivateFieldGet(this, _ListboxController_items, "f");
102
+ }
103
+ /**
104
+ * register's the host's Item elements as listbox controller items
105
+ * sets aria-setsize and aria-posinset on items
106
+ * @param items items
107
+ */
108
+ set items(items) {
109
+ __classPrivateFieldSet(this, _ListboxController_items, items, "f");
110
+ __classPrivateFieldGet(this, _ListboxController_items, "f").forEach((item, index, _items) => {
111
+ item.ariaSetSize = _items.length.toString();
112
+ item.ariaPosInSet = (index + 1).toString();
113
+ });
114
+ }
115
+ /**
116
+ * sets the listbox value based on selected options
117
+ * @param selected item or items
118
+ */
119
+ set selected(selected) {
120
+ if (!arraysAreEquivalent(selected, Array.from(__classPrivateFieldGet(this, _ListboxController_selectedItems, "f")))) {
121
+ __classPrivateFieldSet(this, _ListboxController_selectedItems, new Set(selected), "f");
122
+ for (const item of this.items) {
123
+ __classPrivateFieldGet(this, _ListboxController_options, "f").setItemSelected(item, __classPrivateFieldGet(this, _ListboxController_selectedItems, "f").has(item));
124
+ }
125
+ this.host.requestUpdate();
126
+ }
127
+ }
128
+ /**
129
+ * array of options which are selected
130
+ */
131
+ get selected() {
132
+ return [...__classPrivateFieldGet(this, _ListboxController_selectedItems, "f")];
133
+ }
134
+ constructor(host, options) {
22
135
  _ListboxController_instances.add(this);
23
136
  this.host = host;
24
- this._options = _options;
25
137
  /** Current active descendant when shift key is pressed */
26
138
  _ListboxController_shiftStartingItem.set(this, null);
27
- /** All options that will not be hidden by a filter */
139
+ _ListboxController_options.set(this, void 0);
140
+ /** All items */
28
141
  _ListboxController_items.set(this, []);
142
+ _ListboxController_selectedItems.set(this, new Set);
29
143
  _ListboxController_listening.set(this, false);
30
144
  /** Whether listbox is disabled */
31
145
  this.disabled = false;
32
- /**
33
- * handles focusing on an option:
34
- * updates roving tabindex and active descendant
35
- */
36
- _ListboxController_onFocus.set(this, (event) => {
37
- const target = __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_getEventOption).call(this, event);
38
- if (target && target !== this._options.a11yController.activeItem) {
39
- this._options.a11yController.setActiveItem(target);
40
- }
41
- });
146
+ _ListboxController_controlsElements.set(this, []);
42
147
  /**
43
148
  * handles clicking on a listbox option:
44
149
  * which selects an item by default
45
150
  * or toggles selection if multiselectable
151
+ * @param event click event
46
152
  */
47
153
  _ListboxController_onClick.set(this, (event) => {
48
- const target = __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_getEventOption).call(this, event);
49
- if (target) {
50
- const oldValue = this.value;
51
- if (this._options.multi) {
52
- if (!event.shiftKey) {
53
- this._options.requestSelect(target, !this._options.isSelected(target));
54
- }
55
- else if (__classPrivateFieldGet(this, _ListboxController_shiftStartingItem, "f") && target) {
56
- __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_updateMultiselect).call(this, target, __classPrivateFieldGet(this, _ListboxController_shiftStartingItem, "f"));
57
- }
58
- }
59
- else {
154
+ const item = __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_getItemFromEvent).call(this, event);
155
+ __classPrivateFieldSet(this, _ListboxController_shiftStartingItem, __classPrivateFieldGet(this, _ListboxController_shiftStartingItem, "f") ?? __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_getItemFromEvent).call(this, event), "f");
156
+ if (item && !__classPrivateFieldGet(this, _ListboxController_options, "f").isItemDisabled(item)) {
157
+ // Case: single select?
158
+ // just reset the selected list.
159
+ if (!this.multi) {
60
160
  // select target and deselect all other options
61
- this.options.forEach(option => this._options.requestSelect(option, option === target));
161
+ this.selected = [item];
162
+ // Case: multi select, but no shift key
163
+ // toggle target, keep all other previously selected
62
164
  }
63
- if (target !== this._options.a11yController.activeItem) {
64
- this._options.a11yController.setActiveItem(target);
165
+ else if (!event.shiftKey) {
166
+ this.selected = this.items.filter(possiblySelectedItem => __classPrivateFieldGet(this, _ListboxController_selectedItems, "f").has(possiblySelectedItem) ? possiblySelectedItem !== item
167
+ : possiblySelectedItem === item);
168
+ // Case: multi select, with shift key
169
+ // find all items between previously selected and target,
170
+ // and select them (if reference item is selected) or deselect them (if reference item is deselected)
171
+ // Do not wrap around from end to start, rather, only select withing the range of 0-end
65
172
  }
66
- if (oldValue !== this.value) {
67
- this.host.requestUpdate();
173
+ else {
174
+ const startingItem = __classPrivateFieldGet(this, _ListboxController_shiftStartingItem, "f");
175
+ // whether options will be selected (true) or deselected (false)
176
+ const selecting = __classPrivateFieldGet(this, _ListboxController_selectedItems, "f").has(startingItem);
177
+ const [start, end] = [this.items.indexOf(startingItem), this.items.indexOf(item)].sort();
178
+ // de/select all options between active descendant and target
179
+ this.selected = this.items.filter((item, i) => {
180
+ if (i >= start && i <= end) {
181
+ return selecting;
182
+ }
183
+ else {
184
+ return __classPrivateFieldGet(this, _ListboxController_selectedItems, "f").has(item);
185
+ }
186
+ });
68
187
  }
69
188
  }
189
+ __classPrivateFieldSet(this, _ListboxController_shiftStartingItem, item, "f");
190
+ this.host.requestUpdate();
70
191
  });
71
192
  /**
72
- * handles keyup:
73
193
  * track whether shift key is being used for multiselectable listbox
194
+ * @param event keyup event
74
195
  */
75
196
  _ListboxController_onKeyup.set(this, (event) => {
76
- const target = __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_getEventOption).call(this, event);
77
- if (target && event.shiftKey && this._options.multi) {
78
- if (__classPrivateFieldGet(this, _ListboxController_shiftStartingItem, "f") && target) {
79
- __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_updateMultiselect).call(this, target, __classPrivateFieldGet(this, _ListboxController_shiftStartingItem, "f"));
80
- }
81
- if (event.key === 'Shift') {
82
- __classPrivateFieldSet(this, _ListboxController_shiftStartingItem, null, "f");
83
- }
197
+ if (event.key === 'Shift') {
198
+ __classPrivateFieldSet(this, _ListboxController_shiftStartingItem, null, "f");
84
199
  }
85
200
  });
86
201
  /**
87
- * handles keydown:
88
202
  * filters listbox by keyboard event when slotted option has focus,
89
203
  * or by external element such as a text field
204
+ * @param event keydown event
90
205
  */
91
206
  _ListboxController_onKeydown.set(this, (event) => {
92
- const target = __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_getEventOption).call(this, event);
93
- if (!target || event.altKey || event.metaKey || !this.options.includes(target)) {
207
+ const item = __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_getItemFromEvent).call(this, event);
208
+ if (this.disabled
209
+ || event.altKey
210
+ || event.metaKey
211
+ || !__classPrivateFieldGet(this, _ListboxController_instances, "a", _ListboxController_isExpanded_get)) {
94
212
  return;
95
213
  }
96
- const first = this._options.a11yController.firstItem;
97
- const last = this._options.a11yController.lastItem;
98
214
  // need to set for keyboard support of multiselect
99
- if (event.key === 'Shift' && this._options.multi) {
100
- __classPrivateFieldSet(this, _ListboxController_shiftStartingItem, this.activeItem ?? null, "f");
215
+ if (event.key === 'Shift' && this.multi) {
216
+ __classPrivateFieldSet(this, _ListboxController_shiftStartingItem, __classPrivateFieldGet(this, _ListboxController_shiftStartingItem, "f") ?? (__classPrivateFieldGet(this, _ListboxController_options, "f").getATFocusedItem() ?? null), "f");
101
217
  }
102
218
  switch (event.key) {
219
+ // ctrl+A de/selects all options
103
220
  case 'a':
104
221
  case 'A':
105
- if (event.ctrlKey) {
106
- // ctrl+A selects all options
107
- __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_updateMultiselect).call(this, first, last, true);
222
+ if (event.ctrlKey
223
+ && (event.target === this.container
224
+ || __classPrivateFieldGet(this, _ListboxController_options, "f").isItem(event.target))) {
225
+ const selectableItems = this.items.filter(item => !__classPrivateFieldGet(this, _ListboxController_options, "f").isItemDisabled(item));
226
+ if (arraysAreEquivalent(this.selected, selectableItems)) {
227
+ this.selected = [];
228
+ }
229
+ else {
230
+ this.selected = selectableItems;
231
+ }
108
232
  event.preventDefault();
109
233
  }
110
234
  break;
111
235
  case 'Enter':
236
+ // enter and space are only applicable if a listbox option is clicked
237
+ // an external text input should not trigger multiselect
238
+ if (item && !event.shiftKey) {
239
+ const focused = item;
240
+ __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_selectItem).call(this, focused, event.shiftKey);
241
+ event.preventDefault();
242
+ }
243
+ break;
244
+ case 'ArrowUp':
245
+ if (this.multi && event.shiftKey && __classPrivateFieldGet(this, _ListboxController_options, "f").isItem(event.target)) {
246
+ const item = event.target;
247
+ this.selected = this.items.filter((x, i) => __classPrivateFieldGet(this, _ListboxController_selectedItems, "f").has(x)
248
+ || i === this.items.indexOf(item) - 1)
249
+ .filter(x => !__classPrivateFieldGet(this, _ListboxController_options, "f").isItemDisabled(x));
250
+ }
251
+ break;
252
+ case 'ArrowDown':
253
+ if (this.multi && event.shiftKey && __classPrivateFieldGet(this, _ListboxController_options, "f").isItem(event.target)) {
254
+ const item = event.target;
255
+ this.selected = this.items.filter((x, i) => __classPrivateFieldGet(this, _ListboxController_selectedItems, "f").has(x)
256
+ || i === this.items.indexOf(item) + 1)
257
+ .filter(x => !__classPrivateFieldGet(this, _ListboxController_options, "f").isItemDisabled(x));
258
+ }
259
+ break;
112
260
  case ' ':
113
261
  // enter and space are only applicable if a listbox option is clicked
114
262
  // an external text input should not trigger multiselect
115
- if (this._options.multi) {
116
- if (event.shiftKey) {
117
- __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_updateMultiselect).call(this, target);
118
- }
119
- else if (!this.disabled) {
120
- this._options.requestSelect(target, !this._options.isSelected(target));
121
- }
263
+ if (item && event.target === this.container) {
264
+ __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_selectItem).call(this, item, event.shiftKey);
265
+ event.preventDefault();
122
266
  }
123
- else {
124
- __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_updateSingleselect).call(this);
267
+ else if (__classPrivateFieldGet(this, _ListboxController_options, "f").isItem(event.target)) {
268
+ __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_selectItem).call(this, event.target, event.shiftKey);
269
+ event.preventDefault();
125
270
  }
126
- event.preventDefault();
127
271
  break;
128
272
  default:
129
273
  break;
130
274
  }
275
+ this.host.requestUpdate();
131
276
  });
277
+ __classPrivateFieldSet(this, _ListboxController_options, { setItemSelected, isItemDisabled, isItem, ...options }, "f");
132
278
  if (!constructingAllowed) {
133
279
  throw new Error('ListboxController must be constructed with `ListboxController.of()`');
134
280
  }
135
- if (!(host instanceof HTMLElement) && typeof _options.getHTMLElement !== 'function') {
136
- throw new Error('ListboxController requires the host to be an HTMLElement, or for the initializer to include a `getHTMLElement()` function');
281
+ if (!isServer
282
+ && !(host instanceof HTMLElement)
283
+ && typeof options.getItemsContainer !== 'function') {
284
+ throw new Error([
285
+ 'ListboxController requires the host to be an HTMLElement',
286
+ 'or for the initializer to include a getItemsContainer() function',
287
+ ].join(' '));
137
288
  }
138
- if (!_options.a11yController) {
139
- throw new Error('ListboxController requires an additional keyboard accessibility controller. Provide either a RovingTabindexController or an ActiveDescendantController');
289
+ const instance = ListboxController.instances.get(host);
290
+ if (instance) {
291
+ return instance;
140
292
  }
141
293
  ListboxController.instances.set(host, this);
142
294
  this.host.addController(this);
143
- if (this.element?.isConnected) {
295
+ this.multi = __classPrivateFieldGet(this, _ListboxController_options, "f").multi ?? false;
296
+ if (this.container?.isConnected) {
144
297
  this.hostConnected();
145
298
  }
146
299
  }
147
- /** Current active descendant in listbox */
148
- get activeItem() {
149
- return this.options.find(option => option === this._options.a11yController.activeItem) || this._options.a11yController.firstItem;
300
+ async hostConnected() {
301
+ await this.host.updateComplete;
302
+ this.hostUpdate();
303
+ this.hostUpdated();
150
304
  }
151
- get nextItem() {
152
- return this._options.a11yController.nextItem;
305
+ hostUpdate() {
306
+ const last = __classPrivateFieldGet(this, _ListboxController_controlsElements, "f");
307
+ __classPrivateFieldSet(this, _ListboxController_controlsElements, __classPrivateFieldGet(this, _ListboxController_options, "f").getControlsElements?.() ?? [], "f");
308
+ if (!arraysAreEquivalent(last, __classPrivateFieldGet(this, _ListboxController_controlsElements, "f"))) {
309
+ __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_removeControlsListeners).call(this, last);
310
+ for (const el of __classPrivateFieldGet(this, _ListboxController_controlsElements, "f")) {
311
+ el.addEventListener('keydown', __classPrivateFieldGet(this, _ListboxController_onKeydown, "f"));
312
+ el.addEventListener('keyup', __classPrivateFieldGet(this, _ListboxController_onKeyup, "f"));
313
+ }
314
+ }
153
315
  }
154
- get options() {
155
- return __classPrivateFieldGet(this, _ListboxController_items, "f");
316
+ hostUpdated() {
317
+ if (!__classPrivateFieldGet(this, _ListboxController_listening, "f")) {
318
+ this.container?.addEventListener('click', __classPrivateFieldGet(this, _ListboxController_onClick, "f"));
319
+ this.container?.addEventListener('keydown', __classPrivateFieldGet(this, _ListboxController_onKeydown, "f"));
320
+ this.container?.addEventListener('keyup', __classPrivateFieldGet(this, _ListboxController_onKeyup, "f"));
321
+ __classPrivateFieldSet(this, _ListboxController_listening, true, "f");
322
+ }
323
+ this.container?.setAttribute('role', 'listbox');
324
+ this.container?.setAttribute('aria-disabled', String(!!this.disabled));
325
+ this.container?.setAttribute('aria-multiselectable', String(!!__classPrivateFieldGet(this, _ListboxController_options, "f").multi));
156
326
  }
157
- /**
158
- * array of options which are selected
159
- */
160
- get selectedOptions() {
161
- return this.options.filter(option => this._options.isSelected(option));
327
+ hostDisconnected() {
328
+ this.container?.removeEventListener('click', __classPrivateFieldGet(this, _ListboxController_onClick, "f"));
329
+ this.container?.removeEventListener('keydown', __classPrivateFieldGet(this, _ListboxController_onKeydown, "f"));
330
+ this.container?.removeEventListener('keyup', __classPrivateFieldGet(this, _ListboxController_onKeyup, "f"));
331
+ __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_removeControlsListeners).call(this);
332
+ __classPrivateFieldSet(this, _ListboxController_listening, false, "f");
162
333
  }
163
- get value() {
164
- const [firstItem] = this.selectedOptions;
165
- return this._options.multi ? this.selectedOptions : firstItem;
334
+ isSelected(item) {
335
+ return __classPrivateFieldGet(this, _ListboxController_selectedItems, "f").has(item);
166
336
  }
167
- get element() {
168
- return this._options.getHTMLElement();
337
+ }
338
+ _ListboxController_shiftStartingItem = new WeakMap(), _ListboxController_options = new WeakMap(), _ListboxController_items = new WeakMap(), _ListboxController_selectedItems = new WeakMap(), _ListboxController_listening = new WeakMap(), _ListboxController_controlsElements = new WeakMap(), _ListboxController_onClick = new WeakMap(), _ListboxController_onKeyup = new WeakMap(), _ListboxController_onKeydown = new WeakMap(), _ListboxController_instances = new WeakSet(), _ListboxController_removeControlsListeners = function _ListboxController_removeControlsListeners(els = __classPrivateFieldGet(this, _ListboxController_controlsElements, "f")) {
339
+ for (const el of els) {
340
+ el.removeEventListener('keydown', __classPrivateFieldGet(this, _ListboxController_onKeydown, "f"));
341
+ el.removeEventListener('keyup', __classPrivateFieldGet(this, _ListboxController_onKeyup, "f"));
169
342
  }
170
- async hostConnected() {
171
- if (!__classPrivateFieldGet(this, _ListboxController_listening, "f")) {
172
- await this.host.updateComplete;
173
- this.element?.addEventListener('click', __classPrivateFieldGet(this, _ListboxController_onClick, "f"));
174
- this.element?.addEventListener('focus', __classPrivateFieldGet(this, _ListboxController_onFocus, "f"));
175
- this.element?.addEventListener('keydown', __classPrivateFieldGet(this, _ListboxController_onKeydown, "f"));
176
- this.element?.addEventListener('keyup', __classPrivateFieldGet(this, _ListboxController_onKeyup, "f"));
177
- __classPrivateFieldSet(this, _ListboxController_listening, true, "f");
343
+ }, _ListboxController_isExpanded_get = function _ListboxController_isExpanded_get() {
344
+ return !__classPrivateFieldGet(this, _ListboxController_controlsElements, "f").length ? true
345
+ : __classPrivateFieldGet(this, _ListboxController_controlsElements, "f").every(x => x.ariaExpanded === 'true');
346
+ }, _ListboxController_getItemFromEvent = function _ListboxController_getItemFromEvent(event) {
347
+ // NOTE(bennypowers): I am aware that this function *sucks*
348
+ // you're more than welcome to improve it.
349
+ // make sure there are unit tests first
350
+ const path = event.composedPath();
351
+ const tabindexed = this.items.some(x => x.hasAttribute('tabindex'));
352
+ if (tabindexed) {
353
+ const item = path.find(__classPrivateFieldGet(this, _ListboxController_options, "f").isItem);
354
+ if (item) {
355
+ return item;
178
356
  }
179
357
  }
180
- hostUpdated() {
181
- this.element?.setAttribute('role', 'listbox');
182
- this.element?.setAttribute('aria-disabled', String(!!this.disabled));
183
- this.element?.setAttribute('aria-multi-selectable', String(!!this._options.multi));
184
- for (const option of this._options.a11yController.items) {
185
- if (this._options.a11yController.activeItem === option) {
186
- option.setAttribute('aria-selected', 'true');
358
+ else if (__classPrivateFieldGet(this, _ListboxController_options, "f").isItem(event.target)
359
+ && event.target.getRootNode() !== this.container.getRootNode()
360
+ && 'ariaActiveDescendantElement' in HTMLElement.prototype) {
361
+ return event.target;
362
+ }
363
+ else if (event.target instanceof HTMLElement && event.target.ariaActiveDescendantElement) {
364
+ return event.target.ariaActiveDescendantElement;
365
+ }
366
+ else if (event.type === 'click'
367
+ && __classPrivateFieldGet(this, _ListboxController_options, "f").isItem(event.target)
368
+ && event.target.id) {
369
+ const element = event.target;
370
+ const root = element.getRootNode();
371
+ if (root instanceof ShadowRoot && this.container.getRootNode() === root) {
372
+ const shadowRootListboxElement = this.container;
373
+ const shadowRootItem = element;
374
+ if (shadowRootItem && shadowRootListboxElement) {
375
+ if (this.items.includes(shadowRootItem)) {
376
+ return shadowRootItem;
377
+ }
378
+ else {
379
+ const index = Array.from(shadowRootListboxElement?.children ?? [])
380
+ .filter(__classPrivateFieldGet(this, _ListboxController_options, "f").isItem)
381
+ .filter(x => !x.hidden)
382
+ .indexOf(shadowRootItem);
383
+ return __classPrivateFieldGet(this, _ListboxController_items, "f").filter(x => !x.hidden)[index];
384
+ }
385
+ }
386
+ }
387
+ }
388
+ else {
389
+ // otherwise, query the root (e.g. shadow root) for the associated element
390
+ const element = event.target;
391
+ const root = element.getRootNode();
392
+ const controlsId = element?.getAttribute('aria-controls');
393
+ const shadowRootListboxElement = __classPrivateFieldGet(this, _ListboxController_options, "f").isItem(element) ? this.container
394
+ : controlsId ? root.getElementById(controlsId)
395
+ : null;
396
+ const shadowRootHasActiveDescendantElement = root.querySelector(`[aria-controls="${shadowRootListboxElement?.id}"][aria-activedescendant]`);
397
+ const shadowRootItemId = shadowRootHasActiveDescendantElement?.getAttribute('aria-activedescendant');
398
+ const shadowRootItem = shadowRootItemId && root.getElementById(shadowRootItemId);
399
+ if (shadowRootItem && shadowRootListboxElement) {
400
+ if (this.items.includes(shadowRootItem)) {
401
+ return shadowRootItem;
187
402
  }
188
403
  else {
189
- option.removeAttribute('aria-selected');
404
+ const index = Array.from(shadowRootListboxElement?.children ?? [])
405
+ .filter(__classPrivateFieldGet(this, _ListboxController_options, "f").isItem)
406
+ .filter(x => !x.hidden)
407
+ .indexOf(shadowRootItem);
408
+ return __classPrivateFieldGet(this, _ListboxController_items, "f").filter(x => !x.hidden)[index];
190
409
  }
191
410
  }
192
- }
193
- hostDisconnected() {
194
- this.element?.removeEventListener('click', __classPrivateFieldGet(this, _ListboxController_onClick, "f"));
195
- this.element?.removeEventListener('focus', __classPrivateFieldGet(this, _ListboxController_onFocus, "f"));
196
- this.element?.removeEventListener('keydown', __classPrivateFieldGet(this, _ListboxController_onKeydown, "f"));
197
- this.element?.removeEventListener('keyup', __classPrivateFieldGet(this, _ListboxController_onKeyup, "f"));
198
- __classPrivateFieldSet(this, _ListboxController_listening, false, "f");
199
- }
200
- /**
201
- * sets the listbox value based on selected options
202
- */
203
- setValue(value) {
204
- const selected = Array.isArray(value) ? value : [value];
205
- const [firstItem = null] = selected;
206
- for (const option of this.options) {
207
- this._options.requestSelect(option, (!!this._options.multi && Array.isArray(value) ? value?.includes(option)
208
- : firstItem === option));
411
+ const itemFromEventContainer = shadowRootListboxElement ? shadowRootListboxElement
412
+ : path.find(x => x instanceof HTMLElement && x.role === 'listbox');
413
+ if (itemFromEventContainer) {
414
+ const possiblyShadowRootContainerItems = Array.from(itemFromEventContainer.children)
415
+ .filter(__classPrivateFieldGet(this, _ListboxController_options, "f").isItem);
416
+ const index = possiblyShadowRootContainerItems
417
+ .findIndex(node => path.includes(node));
418
+ if (index >= 0) {
419
+ return this.items[index] ?? null;
420
+ }
209
421
  }
210
422
  }
211
- /**
212
- * register's the host's Item elements as listbox controller items
213
- */
214
- setOptions(options) {
215
- const oldOptions = [...__classPrivateFieldGet(this, _ListboxController_items, "f")];
216
- __classPrivateFieldSet(this, _ListboxController_items, options, "f");
217
- __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_optionsChanged).call(this, oldOptions);
423
+ return null;
424
+ }, _ListboxController_selectItem = function _ListboxController_selectItem(item, shiftDown = false) {
425
+ if (__classPrivateFieldGet(this, _ListboxController_options, "f").isItemDisabled(item)) {
426
+ return;
218
427
  }
219
- }
220
- _ListboxController_shiftStartingItem = new WeakMap(), _ListboxController_items = new WeakMap(), _ListboxController_listening = new WeakMap(), _ListboxController_onFocus = new WeakMap(), _ListboxController_onClick = new WeakMap(), _ListboxController_onKeyup = new WeakMap(), _ListboxController_onKeydown = new WeakMap(), _ListboxController_instances = new WeakSet(), _ListboxController_getEnabledOptions = function _ListboxController_getEnabledOptions(options = this.options) {
221
- return options.filter(option => !option.ariaDisabled && !option.closest('[disabled]'));
222
- }, _ListboxController_getEventOption = function _ListboxController_getEventOption(event) {
223
- return event.composedPath().find(node => __classPrivateFieldGet(this, _ListboxController_items, "f").includes(node));
224
- }, _ListboxController_optionsChanged = function _ListboxController_optionsChanged(oldOptions) {
225
- const setSize = __classPrivateFieldGet(this, _ListboxController_items, "f").length;
226
- if (setSize !== oldOptions.length || !oldOptions.every((element, index) => element === __classPrivateFieldGet(this, _ListboxController_items, "f")[index])) {
227
- this._options.a11yController.updateItems(this.options);
228
- }
229
- }, _ListboxController_updateSingleselect = function _ListboxController_updateSingleselect() {
230
- if (!this._options.multi && !this.disabled) {
231
- __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_getEnabledOptions).call(this)
232
- .forEach(option => this._options.requestSelect(option, option === this._options.a11yController.activeItem));
233
- }
234
- }, _ListboxController_updateMultiselect = function _ListboxController_updateMultiselect(currentItem, referenceItem = this.activeItem, ctrlA = false) {
235
- if (referenceItem && this._options.multi && !this.disabled && currentItem) {
236
- // select all options between active descendant and target
237
- const [start, end] = [this.options.indexOf(referenceItem), this.options.indexOf(currentItem)].sort();
238
- const options = [...this.options].slice(start, end + 1);
239
- // by default CTRL+A will select all options
240
- // if all options are selected, CTRL+A will deselect all options
241
- const allSelected = __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_getEnabledOptions).call(this, options).filter(option => !this._options.isSelected(option))?.length === 0;
242
- // whether options will be selected (true) or deselected (false)
243
- const selected = ctrlA ? !allSelected : this._options.isSelected(referenceItem);
244
- __classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_getEnabledOptions).call(this, options).forEach(option => this._options.requestSelect(option, selected));
428
+ else if (this.multi && shiftDown) {
245
429
  // update starting item for other multiselect
246
- __classPrivateFieldSet(this, _ListboxController_shiftStartingItem, currentItem, "f");
430
+ this.selected = [...this.selected, item];
431
+ }
432
+ else if (this.multi && __classPrivateFieldGet(this, _ListboxController_selectedItems, "f").has(item)) {
433
+ this.selected = this.selected.filter(x => x !== item);
434
+ }
435
+ else if (this.multi) {
436
+ this.selected = this.selected.concat(item);
437
+ }
438
+ else {
439
+ this.selected = [item];
247
440
  }
248
441
  };
249
442
  ListboxController.instances = new WeakMap();