@patternfly/pfe-core 2.4.1 → 4.0.0
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.
- package/README.md +0 -1
- package/controllers/activedescendant-controller.d.ts +99 -0
- package/controllers/activedescendant-controller.js +230 -0
- package/controllers/activedescendant-controller.js.map +1 -0
- package/controllers/at-focus-controller.d.ts +56 -0
- package/controllers/at-focus-controller.js +168 -0
- package/controllers/at-focus-controller.js.map +1 -0
- package/controllers/cascade-controller.d.ts +11 -0
- package/controllers/cascade-controller.js +10 -3
- package/controllers/cascade-controller.js.map +1 -1
- package/controllers/combobox-controller.d.ts +117 -0
- package/controllers/combobox-controller.js +611 -0
- package/controllers/combobox-controller.js.map +1 -0
- package/controllers/css-variable-controller.js +1 -1
- package/controllers/css-variable-controller.js.map +1 -1
- package/controllers/floating-dom-controller.d.ts +10 -3
- package/controllers/floating-dom-controller.js +93 -83
- package/controllers/floating-dom-controller.js.map +1 -1
- package/controllers/internals-controller.d.ts +104 -42
- package/controllers/internals-controller.js +354 -36
- package/controllers/internals-controller.js.map +1 -1
- package/controllers/light-dom-controller.js +2 -2
- package/controllers/light-dom-controller.js.map +1 -1
- package/controllers/listbox-controller.d.ts +143 -0
- package/controllers/listbox-controller.js +443 -0
- package/controllers/listbox-controller.js.map +1 -0
- package/controllers/logger.d.ts +33 -6
- package/controllers/logger.js +58 -13
- package/controllers/logger.js.map +1 -1
- package/controllers/overflow-controller.d.ts +10 -3
- package/controllers/overflow-controller.js +79 -44
- package/controllers/overflow-controller.js.map +1 -1
- package/controllers/perf-controller.js.map +1 -1
- package/controllers/property-observer-controller.d.ts +13 -16
- package/controllers/property-observer-controller.js +55 -27
- package/controllers/property-observer-controller.js.map +1 -1
- package/controllers/roving-tabindex-controller.d.ts +19 -50
- package/controllers/roving-tabindex-controller.js +64 -183
- package/controllers/roving-tabindex-controller.js.map +1 -1
- package/controllers/scroll-spy-controller.d.ts +4 -1
- package/controllers/scroll-spy-controller.js +94 -93
- package/controllers/scroll-spy-controller.js.map +1 -1
- package/controllers/slot-controller.d.ts +26 -19
- package/controllers/slot-controller.js +94 -83
- package/controllers/slot-controller.js.map +1 -1
- package/controllers/style-controller.js +3 -1
- package/controllers/style-controller.js.map +1 -1
- package/controllers/tabs-aria-controller.d.ts +31 -0
- package/controllers/tabs-aria-controller.js +97 -0
- package/controllers/tabs-aria-controller.js.map +1 -0
- package/controllers/test/combobox-controller.spec.d.ts +1 -0
- package/controllers/test/combobox-controller.spec.js +282 -0
- package/controllers/test/combobox-controller.spec.js.map +1 -0
- package/controllers/timestamp-controller.js +73 -70
- package/controllers/timestamp-controller.js.map +1 -1
- package/core.d.ts +3 -23
- package/core.js +1 -38
- package/core.js.map +1 -1
- package/custom-elements.json +7302 -2817
- package/decorators/bound.d.ts +3 -1
- package/decorators/bound.js +3 -1
- package/decorators/bound.js.map +1 -1
- package/decorators/cascades.d.ts +2 -0
- package/decorators/cascades.js +2 -0
- package/decorators/cascades.js.map +1 -1
- package/decorators/deprecation.d.ts +6 -5
- package/decorators/deprecation.js +6 -5
- package/decorators/deprecation.js.map +1 -1
- package/decorators/initializer.js.map +1 -1
- package/decorators/listen.d.ts +8 -0
- package/decorators/listen.js +22 -0
- package/decorators/listen.js.map +1 -0
- package/decorators/observed.d.ts +12 -16
- package/decorators/observed.js +39 -44
- package/decorators/observed.js.map +1 -1
- package/decorators/observes.d.ts +15 -0
- package/decorators/observes.js +30 -0
- package/decorators/observes.js.map +1 -0
- package/decorators/time.d.ts +1 -0
- package/decorators/time.js +6 -9
- package/decorators/time.js.map +1 -1
- package/decorators/trace.d.ts +4 -1
- package/decorators/trace.js +4 -1
- package/decorators/trace.js.map +1 -1
- package/decorators.d.ts +2 -0
- package/decorators.js +2 -0
- package/decorators.js.map +1 -1
- package/functions/arraysAreEquivalent.d.ts +9 -0
- package/functions/arraysAreEquivalent.js +28 -0
- package/functions/arraysAreEquivalent.js.map +1 -0
- package/functions/containsDeep.d.ts +8 -0
- package/functions/containsDeep.js +23 -0
- package/functions/containsDeep.js.map +1 -0
- package/functions/context.d.ts +8 -0
- package/functions/context.js +21 -0
- package/functions/context.js.map +1 -0
- package/functions/debounce.js.map +1 -1
- package/functions/isElementInView.d.ts +4 -6
- package/functions/isElementInView.js +9 -11
- package/functions/isElementInView.js.map +1 -1
- package/package.json +10 -4
- package/functions/deprecatedCustomEvent.d.ts +0 -5
- package/functions/deprecatedCustomEvent.js +0 -12
- package/functions/deprecatedCustomEvent.js.map +0 -1
|
@@ -0,0 +1,443 @@
|
|
|
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
|
+
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
|
+
}
|
|
43
|
+
let constructingAllowed = false;
|
|
44
|
+
/**
|
|
45
|
+
* Implements listbox semantics and accesibility. As there are two recognized
|
|
46
|
+
* patterns for implementing keyboard interactions with listbox patterns,
|
|
47
|
+
* provide a secondary controller (either RovingTabindexController or
|
|
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.
|
|
82
|
+
*/
|
|
83
|
+
export class ListboxController {
|
|
84
|
+
static of(host, options) {
|
|
85
|
+
constructingAllowed = true;
|
|
86
|
+
const instance = new ListboxController(host, options);
|
|
87
|
+
constructingAllowed = false;
|
|
88
|
+
return instance;
|
|
89
|
+
}
|
|
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) {
|
|
135
|
+
_ListboxController_instances.add(this);
|
|
136
|
+
this.host = host;
|
|
137
|
+
/** Current active descendant when shift key is pressed */
|
|
138
|
+
_ListboxController_shiftStartingItem.set(this, null);
|
|
139
|
+
_ListboxController_options.set(this, void 0);
|
|
140
|
+
/** All items */
|
|
141
|
+
_ListboxController_items.set(this, []);
|
|
142
|
+
_ListboxController_selectedItems.set(this, new Set);
|
|
143
|
+
_ListboxController_listening.set(this, false);
|
|
144
|
+
/** Whether listbox is disabled */
|
|
145
|
+
this.disabled = false;
|
|
146
|
+
_ListboxController_controlsElements.set(this, []);
|
|
147
|
+
/**
|
|
148
|
+
* handles clicking on a listbox option:
|
|
149
|
+
* which selects an item by default
|
|
150
|
+
* or toggles selection if multiselectable
|
|
151
|
+
* @param event click event
|
|
152
|
+
*/
|
|
153
|
+
_ListboxController_onClick.set(this, (event) => {
|
|
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) {
|
|
160
|
+
// select target and deselect all other options
|
|
161
|
+
this.selected = [item];
|
|
162
|
+
// Case: multi select, but no shift key
|
|
163
|
+
// toggle target, keep all other previously selected
|
|
164
|
+
}
|
|
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
|
|
172
|
+
}
|
|
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
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
__classPrivateFieldSet(this, _ListboxController_shiftStartingItem, item, "f");
|
|
190
|
+
this.host.requestUpdate();
|
|
191
|
+
});
|
|
192
|
+
/**
|
|
193
|
+
* track whether shift key is being used for multiselectable listbox
|
|
194
|
+
* @param event keyup event
|
|
195
|
+
*/
|
|
196
|
+
_ListboxController_onKeyup.set(this, (event) => {
|
|
197
|
+
if (event.key === 'Shift') {
|
|
198
|
+
__classPrivateFieldSet(this, _ListboxController_shiftStartingItem, null, "f");
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
/**
|
|
202
|
+
* filters listbox by keyboard event when slotted option has focus,
|
|
203
|
+
* or by external element such as a text field
|
|
204
|
+
* @param event keydown event
|
|
205
|
+
*/
|
|
206
|
+
_ListboxController_onKeydown.set(this, (event) => {
|
|
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)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// need to set for keyboard support of multiselect
|
|
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");
|
|
217
|
+
}
|
|
218
|
+
switch (event.key) {
|
|
219
|
+
// ctrl+A de/selects all options
|
|
220
|
+
case 'a':
|
|
221
|
+
case 'A':
|
|
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
|
+
}
|
|
232
|
+
event.preventDefault();
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
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;
|
|
260
|
+
case ' ':
|
|
261
|
+
// enter and space are only applicable if a listbox option is clicked
|
|
262
|
+
// an external text input should not trigger multiselect
|
|
263
|
+
if (item && event.target === this.container) {
|
|
264
|
+
__classPrivateFieldGet(this, _ListboxController_instances, "m", _ListboxController_selectItem).call(this, item, event.shiftKey);
|
|
265
|
+
event.preventDefault();
|
|
266
|
+
}
|
|
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();
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
default:
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
this.host.requestUpdate();
|
|
276
|
+
});
|
|
277
|
+
__classPrivateFieldSet(this, _ListboxController_options, { setItemSelected, isItemDisabled, isItem, ...options }, "f");
|
|
278
|
+
if (!constructingAllowed) {
|
|
279
|
+
throw new Error('ListboxController must be constructed with `ListboxController.of()`');
|
|
280
|
+
}
|
|
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(' '));
|
|
288
|
+
}
|
|
289
|
+
const instance = ListboxController.instances.get(host);
|
|
290
|
+
if (instance) {
|
|
291
|
+
return instance;
|
|
292
|
+
}
|
|
293
|
+
ListboxController.instances.set(host, this);
|
|
294
|
+
this.host.addController(this);
|
|
295
|
+
this.multi = __classPrivateFieldGet(this, _ListboxController_options, "f").multi ?? false;
|
|
296
|
+
if (this.container?.isConnected) {
|
|
297
|
+
this.hostConnected();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async hostConnected() {
|
|
301
|
+
await this.host.updateComplete;
|
|
302
|
+
this.hostUpdate();
|
|
303
|
+
this.hostUpdated();
|
|
304
|
+
}
|
|
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
|
+
}
|
|
315
|
+
}
|
|
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));
|
|
326
|
+
}
|
|
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");
|
|
333
|
+
}
|
|
334
|
+
isSelected(item) {
|
|
335
|
+
return __classPrivateFieldGet(this, _ListboxController_selectedItems, "f").has(item);
|
|
336
|
+
}
|
|
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"));
|
|
342
|
+
}
|
|
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;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
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;
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
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];
|
|
409
|
+
}
|
|
410
|
+
}
|
|
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
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return null;
|
|
424
|
+
}, _ListboxController_selectItem = function _ListboxController_selectItem(item, shiftDown = false) {
|
|
425
|
+
if (__classPrivateFieldGet(this, _ListboxController_options, "f").isItemDisabled(item)) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
else if (this.multi && shiftDown) {
|
|
429
|
+
// update starting item for other multiselect
|
|
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];
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
ListboxController.instances = new WeakMap();
|
|
443
|
+
//# sourceMappingURL=listbox-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listbox-controller.js","sourceRoot":"","sources":["listbox-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit';\nimport type { RequireProps } from '../core.ts';\n\nimport { isServer } from 'lit';\nimport { arraysAreEquivalent } from '../functions/arraysAreEquivalent.js';\n\n/**\n * Options for listbox controller\n */\nexport interface ListboxControllerOptions<Item extends HTMLElement> {\n /**\n * Whether the listbox supports multiple selections.\n */\n multi?: boolean;\n /**\n * Optional callback to control the selection behavior of items. By default, ListboxController\n * will set the `aria-selected` attribute. When overriding this option, it will call it on your\n * element with the selected state.\n * Callers **must** ensure that the correct ARIA state is set.\n */\n setItemSelected?(item: Item, selected: boolean): void;\n /**\n * Optional predicate to ascertain whether a custom element item is disabled or not\n * By default, if the item matches any of these conditions, it is considered disabled:\n * 1. it's `disabled` DOM property is `true`\n * 1. it has the `aria-disabled=\"true\"` attribute\n * 2. it has the `disabled` attribute present\n * 3. it matches the `:disabled` pseudo selector\n */\n isItemDisabled?(item: Item): boolean;\n /**\n * Predicate which determines if a given element is in fact an item\n * instead of e.g a presentational divider. By default, elements must meet the following criteria\n * 1. element a child of a listbox role,\n * 2. element does not have role=\"presentation\"\n * 2. element is not an `<hr>`\n * **NB**: When overriding, you must avoid outside references. This predicate must\n * only consider the element itself, without reference to the host element's items array.\n * @example ```js\n * isItem: (item) => item instanceof MyCustomItem\n * ```\n */\n isItem?(item: EventTarget | null): item is Item;\n /**\n * Function returning the item which currently has assistive technology focus.\n * In most cases, this should be the `atFocusedItem` of an ATFocusController\n * i.e. RovingTabindexController or ActivedescendantController.\n *\n */\n getATFocusedItem(): Item | null;\n /**\n * Function returning the DOM node which is the direct parent of the item elements\n * Defaults to the controller host.\n * If the controller host is not an HTMLElement, this *must* be set\n */\n getItemsContainer?(): HTMLElement | null;\n /**\n * Optional function returning an additional DOM node which controls the listbox, e.g.\n * a combobox input.\n */\n getControlsElements?(): HTMLElement[];\n}\n\n/**\n * This is the default method for setting the selected state on an item element\n * @param item the item\n * @param selected is this item selected\n */\nfunction setItemSelected<Item extends HTMLElement>(item: Item, selected: boolean) {\n if (selected) {\n item.setAttribute('aria-selected', 'true');\n } else {\n item.removeAttribute('aria-selected');\n }\n}\n\n/**\n * @param item possible disabled item\n * @package do not import this outside of `@patternfly/pfe-core`, it is subject to change at any time\n */\nexport function isItem<Item extends HTMLElement>(item: EventTarget | null): item is Item {\n return item instanceof Element\n && item?.parentElement?.role === 'listbox'\n && item?.role !== 'presentation'\n && item?.localName !== 'hr';\n}\n\n/**\n * This is a fib. aria-disabled might not be present on an element that uses internals,\n * and the `disabled` attribute may not accurately represent the disabled state.\n * short of patching the `attachInternals` constructor, it may not be possible at\n * runtime to know with certainty that an arbitrary custom element is disabled or not.\n * @param item possibly disabled item\n * @package do not import this outside of `@patternfly/pfe-core`, it is subject to change at any time\n */\nexport function isItemDisabled<Item extends HTMLElement>(item: Item): boolean {\n return ('disabled' in item && typeof item.disabled === 'boolean' && item.disabled)\n || item.getAttribute('aria-disabled') === 'true'\n || item.hasAttribute('disabled')\n || item.hasAttribute('inert')\n || item.matches(':disabled');\n}\n\nlet constructingAllowed = false;\n\n/**\n * Implements listbox semantics and accesibility. As there are two recognized\n * patterns for implementing keyboard interactions with listbox patterns,\n * provide a secondary controller (either RovingTabindexController or\n * ActiveDescendantController) to complete the implementation.\n *\n * @see https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_vs_selection\n *\n * > Occasionally, it may appear as if two elements on the page have focus at the same time.\n * > For example, in a multi-select list box, when an option is selected it may be greyed.\n * > Yet, the focus indicator can still be moved to other options, which may also be selected.\n * > Similarly, when a user activates a tab in a tablist, the selected state is set on the tab\n * > and its visual appearance changes. However, the user can still navigate, moving the focus\n * > indicator elsewhere on the page while the tab retains its selected appearance and state.\n * >\n * > Focus and selection are quite different. From the keyboard user's perspective,\n * > focus is a pointer, like a mouse pointer; it tracks the path of navigation.\n * > There is only one point of focus at any time and all operations take place at the\n * > point of focus. On the other hand, selection is an operation that can be performed in\n * > some widgets, such as list boxes, trees, and tablists. If a widget supports only single\n * > selection, then only one item can be selected and very often the selected state will simply\n * > follow the focus when focus is moved inside of the widget.\n * > That is, in some widgets, moving focus may also perform the select operation.\n * > However, if the widget supports multiple selection, then more than one item can be in a\n * > selected state, and keys for moving focus do not perform selection. Some multi-select widgets\n * > do support key commands that both move focus and change selection, but those keys are\n * > different from the normal navigation keys. Finally, when focus leaves a widget that includes\n * > a selected element, the selected state persists.\n * >\n * > From the developer's perspective, the difference is simple -- the focused element is the\n * > active element (document.activeElement). Selected elements are elements that have\n * > aria-selected=\"true\".\n * >\n * > With respect to focus and the selected state, the most important considerations for designers\n * > and developers are:\n * >\n * > - The visual focus indicator must always be visible.\n * > - The selected state must be visually distinct from the focus indicator.\n */\nexport class ListboxController<Item extends HTMLElement> implements ReactiveController {\n private static instances = new WeakMap<ReactiveControllerHost, ListboxController<HTMLElement>>();\n\n public static of<Item extends HTMLElement>(\n host: ReactiveControllerHost,\n options: ListboxControllerOptions<Item>,\n ): ListboxController<Item> {\n constructingAllowed = true;\n const instance = new ListboxController<Item>(host, options);\n constructingAllowed = false;\n return instance as ListboxController<Item>;\n }\n\n /** Current active descendant when shift key is pressed */\n #shiftStartingItem: Item | null = null;\n\n #options: RequireProps<ListboxControllerOptions<Item>,\n | 'setItemSelected'\n | 'isItemDisabled'\n | 'isItem'\n >;\n\n /** All items */\n #items: Item[] = [];\n\n #selectedItems = new Set<Item>;\n\n #listening = false;\n\n /** Whether listbox is disabled */\n disabled = false;\n\n get container(): HTMLElement {\n return this.#options.getItemsContainer?.() ?? this.host as unknown as HTMLElement;\n }\n\n get multi(): boolean {\n return !!this.#options.multi;\n }\n\n set multi(v: boolean) {\n this.#options.multi = v;\n this.host.requestUpdate();\n }\n\n get items(): Item[] {\n return this.#items;\n }\n\n /**\n * register's the host's Item elements as listbox controller items\n * sets aria-setsize and aria-posinset on items\n * @param items items\n */\n set items(items: Item[]) {\n this.#items = items;\n this.#items.forEach((item, index, _items) => {\n item.ariaSetSize = _items.length.toString();\n item.ariaPosInSet = (index + 1).toString();\n });\n }\n\n /**\n * sets the listbox value based on selected options\n * @param selected item or items\n */\n set selected(selected: Item[]) {\n if (!arraysAreEquivalent(selected, Array.from(this.#selectedItems))) {\n this.#selectedItems = new Set(selected);\n for (const item of this.items) {\n this.#options.setItemSelected(item, this.#selectedItems.has(item));\n }\n this.host.requestUpdate();\n }\n }\n\n /**\n * array of options which are selected\n */\n get selected(): Item[] {\n return [...this.#selectedItems];\n }\n\n private constructor(\n public host: ReactiveControllerHost,\n options: ListboxControllerOptions<Item>,\n ) {\n this.#options = { setItemSelected, isItemDisabled, isItem, ...options };\n if (!constructingAllowed) {\n throw new Error('ListboxController must be constructed with `ListboxController.of()`');\n }\n if (!isServer\n && !(host instanceof HTMLElement)\n && typeof options.getItemsContainer !== 'function') {\n throw new Error([\n 'ListboxController requires the host to be an HTMLElement',\n 'or for the initializer to include a getItemsContainer() function',\n ].join(' '));\n }\n const instance = ListboxController.instances.get(host) as unknown as ListboxController<Item>;\n if (instance) {\n return instance as ListboxController<Item>;\n }\n ListboxController.instances.set(host, this as unknown as ListboxController<HTMLElement>);\n this.host.addController(this);\n this.multi = this.#options.multi ?? false;\n if (this.container?.isConnected) {\n this.hostConnected();\n }\n }\n\n async hostConnected(): Promise<void> {\n await this.host.updateComplete;\n this.hostUpdate();\n this.hostUpdated();\n }\n\n #controlsElements: HTMLElement[] = [];\n\n #removeControlsListeners(els = this.#controlsElements) {\n for (const el of els) {\n el.removeEventListener('keydown', this.#onKeydown);\n el.removeEventListener('keyup', this.#onKeyup);\n }\n }\n\n hostUpdate(): void {\n const last = this.#controlsElements;\n this.#controlsElements = this.#options.getControlsElements?.() ?? [];\n if (!arraysAreEquivalent(last, this.#controlsElements)) {\n this.#removeControlsListeners(last);\n for (const el of this.#controlsElements) {\n el.addEventListener('keydown', this.#onKeydown);\n el.addEventListener('keyup', this.#onKeyup);\n }\n }\n }\n\n hostUpdated(): void {\n if (!this.#listening) {\n this.container?.addEventListener('click', this.#onClick);\n this.container?.addEventListener('keydown', this.#onKeydown);\n this.container?.addEventListener('keyup', this.#onKeyup);\n this.#listening = true;\n }\n this.container?.setAttribute('role', 'listbox');\n this.container?.setAttribute('aria-disabled', String(!!this.disabled));\n this.container?.setAttribute('aria-multiselectable', String(!!this.#options.multi));\n }\n\n hostDisconnected(): void {\n this.container?.removeEventListener('click', this.#onClick);\n this.container?.removeEventListener('keydown', this.#onKeydown);\n this.container?.removeEventListener('keyup', this.#onKeyup);\n this.#removeControlsListeners();\n this.#listening = false;\n }\n\n public isSelected(item: Item): boolean {\n return this.#selectedItems.has(item);\n }\n\n get #isExpanded() {\n return !this.#controlsElements.length ? true\n : this.#controlsElements.every(x => x.ariaExpanded === 'true');\n }\n\n /**\n * In the case where aria IDL attributes are not supported,\n * we need to correlate the item in the event path (i.e. the shadow dom clone)\n * with the item in listbox controller's root (i.e. the hidden light dom original)\n * XXX: as long as there is no DOM preceeding the shadow root clones, this will work\n * @param event click or keyboard event\n */\n #getItemFromEvent(event: Event): Item | null {\n // NOTE(bennypowers): I am aware that this function *sucks*\n // you're more than welcome to improve it.\n // make sure there are unit tests first\n const path = event.composedPath();\n const tabindexed = this.items.some(x => x.hasAttribute('tabindex'));\n if (tabindexed) {\n const item = path.find(this.#options.isItem);\n if (item) {\n return item;\n }\n } else if (this.#options.isItem(event.target)\n && event.target.getRootNode() !== this.container.getRootNode()\n && 'ariaActiveDescendantElement' in HTMLElement.prototype) {\n return event.target;\n } else if (event.target instanceof HTMLElement && event.target.ariaActiveDescendantElement) {\n return event.target.ariaActiveDescendantElement as Item;\n } else if (event.type === 'click'\n && this.#options.isItem(event.target)\n && event.target.id) {\n const element = event.target;\n const root = element.getRootNode();\n if (root instanceof ShadowRoot && this.container.getRootNode() === root) {\n const shadowRootListboxElement = this.container;\n const shadowRootItem = element;\n if (shadowRootItem && shadowRootListboxElement) {\n if (this.items.includes(shadowRootItem)) {\n return shadowRootItem;\n } else {\n const index =\n Array.from(shadowRootListboxElement?.children ?? [])\n .filter(this.#options.isItem)\n .filter(x => !x.hidden)\n .indexOf(shadowRootItem);\n return this.#items.filter(x => !x.hidden)[index];\n }\n }\n }\n } else {\n // otherwise, query the root (e.g. shadow root) for the associated element\n const element = event.target as HTMLElement;\n\n const root = element.getRootNode() as ShadowRoot | Document;\n\n const controlsId = element?.getAttribute('aria-controls');\n const shadowRootListboxElement =\n this.#options.isItem(element) ? this.container\n : controlsId ? root.getElementById(controlsId)\n : null;\n\n const shadowRootHasActiveDescendantElement =\n root.querySelector(`[aria-controls=\"${shadowRootListboxElement?.id}\"][aria-activedescendant]`);\n\n const shadowRootItemId =\n shadowRootHasActiveDescendantElement?.getAttribute('aria-activedescendant');\n\n const shadowRootItem =\n shadowRootItemId && root.getElementById(shadowRootItemId) as Item | null;\n\n if (shadowRootItem && shadowRootListboxElement) {\n if (this.items.includes(shadowRootItem)) {\n return shadowRootItem;\n } else {\n const index =\n Array.from(shadowRootListboxElement?.children ?? [])\n .filter(this.#options.isItem)\n .filter(x => !x.hidden)\n .indexOf(shadowRootItem);\n return this.#items.filter(x => !x.hidden)[index];\n }\n }\n\n const itemFromEventContainer =\n shadowRootListboxElement ? shadowRootListboxElement\n : path.find(x =>\n x instanceof HTMLElement && x.role === 'listbox') as HTMLElement;\n\n if (itemFromEventContainer) {\n const possiblyShadowRootContainerItems = Array.from(itemFromEventContainer.children)\n .filter(this.#options.isItem);\n\n const index = possiblyShadowRootContainerItems\n .findIndex(node => path.includes(node));\n\n if (index >= 0) {\n return this.items[index] ?? null;\n }\n }\n }\n\n return null;\n }\n\n /**\n * handles clicking on a listbox option:\n * which selects an item by default\n * or toggles selection if multiselectable\n * @param event click event\n */\n #onClick = (event: MouseEvent) => {\n const item = this.#getItemFromEvent(event);\n this.#shiftStartingItem ??= this.#getItemFromEvent(event);\n if (item && !this.#options.isItemDisabled(item)) {\n // Case: single select?\n // just reset the selected list.\n if (!this.multi) {\n // select target and deselect all other options\n this.selected = [item];\n // Case: multi select, but no shift key\n // toggle target, keep all other previously selected\n } else if (!event.shiftKey) {\n this.selected = this.items.filter(possiblySelectedItem =>\n this.#selectedItems.has(possiblySelectedItem) ? possiblySelectedItem !== item\n : possiblySelectedItem === item);\n // Case: multi select, with shift key\n // find all items between previously selected and target,\n // and select them (if reference item is selected) or deselect them (if reference item is deselected)\n // Do not wrap around from end to start, rather, only select withing the range of 0-end\n } else {\n const startingItem = this.#shiftStartingItem!;\n // whether options will be selected (true) or deselected (false)\n const selecting = this.#selectedItems.has(startingItem);\n const [start, end] = [this.items.indexOf(startingItem), this.items.indexOf(item)].sort();\n // de/select all options between active descendant and target\n this.selected = this.items.filter((item, i) => {\n if (i >= start && i <= end) {\n return selecting;\n } else {\n return this.#selectedItems.has(item);\n }\n });\n }\n }\n this.#shiftStartingItem = item;\n this.host.requestUpdate();\n };\n\n /**\n * track whether shift key is being used for multiselectable listbox\n * @param event keyup event\n */\n #onKeyup = (event: KeyboardEvent) => {\n if (event.key === 'Shift') {\n this.#shiftStartingItem = null;\n }\n };\n\n /**\n * filters listbox by keyboard event when slotted option has focus,\n * or by external element such as a text field\n * @param event keydown event\n */\n #onKeydown = (event: KeyboardEvent) => {\n const item = this.#getItemFromEvent(event);\n\n if (this.disabled\n || event.altKey\n || event.metaKey\n || !this.#isExpanded) {\n return;\n }\n\n // need to set for keyboard support of multiselect\n if (event.key === 'Shift' && this.multi) {\n this.#shiftStartingItem ??= this.#options.getATFocusedItem() ?? null;\n }\n\n switch (event.key) {\n // ctrl+A de/selects all options\n case 'a':\n case 'A':\n if (event.ctrlKey\n && (event.target === this.container\n || this.#options.isItem(event.target))) {\n const selectableItems = this.items.filter(item => !this.#options.isItemDisabled(item));\n if (arraysAreEquivalent(this.selected, selectableItems)) {\n this.selected = [];\n } else {\n this.selected = selectableItems;\n }\n event.preventDefault();\n }\n break;\n case 'Enter':\n // enter and space are only applicable if a listbox option is clicked\n // an external text input should not trigger multiselect\n if (item && !event.shiftKey) {\n const focused = item;\n this.#selectItem(focused, event.shiftKey);\n event.preventDefault();\n }\n break;\n case 'ArrowUp':\n if (this.multi && event.shiftKey && this.#options.isItem(event.target)) {\n const item = event.target;\n this.selected = this.items.filter((x, i) =>\n this.#selectedItems.has(x)\n || i === this.items.indexOf(item) - 1)\n .filter(x => !this.#options.isItemDisabled(x));\n }\n break;\n case 'ArrowDown':\n if (this.multi && event.shiftKey && this.#options.isItem(event.target)) {\n const item = event.target;\n this.selected = this.items.filter((x, i) =>\n this.#selectedItems.has(x)\n || i === this.items.indexOf(item) + 1)\n .filter(x => !this.#options.isItemDisabled(x));\n }\n break;\n case ' ':\n // enter and space are only applicable if a listbox option is clicked\n // an external text input should not trigger multiselect\n if (item && event.target === this.container) {\n this.#selectItem(item, event.shiftKey);\n event.preventDefault();\n } else if (this.#options.isItem(event.target)) {\n this.#selectItem(event.target, event.shiftKey);\n event.preventDefault();\n }\n break;\n default:\n break;\n }\n this.host.requestUpdate();\n };\n\n #selectItem(item: Item, shiftDown = false) {\n if (this.#options.isItemDisabled(item)) {\n return;\n } else if (this.multi && shiftDown) {\n // update starting item for other multiselect\n this.selected = [...this.selected, item];\n } else if (this.multi && this.#selectedItems.has(item)) {\n this.selected = this.selected.filter(x => x !== item);\n } else if (this.multi) {\n this.selected = this.selected.concat(item);\n } else {\n this.selected = [item];\n }\n }\n}\n"]}
|
package/controllers/logger.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactiveController, type ReactiveControllerHost } from 'lit';
|
|
2
2
|
export declare class Logger implements ReactiveController {
|
|
3
3
|
private host;
|
|
4
4
|
private static logDebug;
|
|
@@ -8,45 +8,72 @@ export declare class Logger implements ReactiveController {
|
|
|
8
8
|
* A boolean value that indicates if the logging should be printed to the console; used for debugging.
|
|
9
9
|
* For use in a JS file or script tag; can also be added in the constructor of a component during development.
|
|
10
10
|
* @example Logger.debugLog(true);
|
|
11
|
-
* @
|
|
11
|
+
* @param [preference=null]
|
|
12
12
|
*/
|
|
13
13
|
static debugLog(preference?: null): boolean;
|
|
14
14
|
/**
|
|
15
15
|
* A logging wrapper which checks the debugLog boolean and prints to the console if true.
|
|
16
|
-
*
|
|
16
|
+
* @example Logger.debug("Hello");
|
|
17
|
+
* @param msgs console.log params
|
|
18
|
+
*/
|
|
19
|
+
static debug(...msgs: unknown[]): void;
|
|
20
|
+
/**
|
|
21
|
+
* A logging wrapper which checks the debugLog boolean and prints to the console if true.
|
|
22
|
+
* @example Logger.info("Hello");
|
|
23
|
+
* @param msgs console.log params
|
|
24
|
+
*/
|
|
25
|
+
static info(...msgs: unknown[]): void;
|
|
26
|
+
/**
|
|
27
|
+
* A logging wrapper which checks the debugLog boolean and prints to the console if true.
|
|
17
28
|
* @example Logger.log("Hello");
|
|
29
|
+
* @param msgs console.log params
|
|
18
30
|
*/
|
|
19
31
|
static log(...msgs: unknown[]): void;
|
|
20
32
|
/**
|
|
21
33
|
* A console warning wrapper which formats your output with useful debugging information.
|
|
22
|
-
*
|
|
23
34
|
* @example Logger.warn("Hello");
|
|
35
|
+
* @param msgs console.log params
|
|
24
36
|
*/
|
|
25
37
|
static warn(...msgs: unknown[]): void;
|
|
26
38
|
/**
|
|
27
39
|
* A console error wrapper which formats your output with useful debugging information.
|
|
28
40
|
* For use inside a component's function.
|
|
29
41
|
* @example Logger.error("Hello");
|
|
42
|
+
* @param msgs console.log params
|
|
30
43
|
*/
|
|
31
44
|
static error(...msgs: unknown[]): void;
|
|
45
|
+
/**
|
|
46
|
+
* Debug logging that outputs the tag name as a prefix automatically
|
|
47
|
+
* @example this.logger.log("Hello");
|
|
48
|
+
* @param msgs console.log params
|
|
49
|
+
*/
|
|
50
|
+
debug(...msgs: unknown[]): void;
|
|
51
|
+
/**
|
|
52
|
+
* Info logging that outputs the tag name as a prefix automatically
|
|
53
|
+
* @example this.logger.log("Hello");
|
|
54
|
+
* @param msgs console.log params
|
|
55
|
+
*/
|
|
56
|
+
info(...msgs: unknown[]): void;
|
|
32
57
|
/**
|
|
33
58
|
* Local logging that outputs the tag name as a prefix automatically
|
|
34
|
-
*
|
|
35
59
|
* @example this.logger.log("Hello");
|
|
60
|
+
* @param msgs console.log params
|
|
36
61
|
*/
|
|
37
62
|
log(...msgs: unknown[]): void;
|
|
38
63
|
/**
|
|
39
64
|
* Local warning wrapper that outputs the tag name as a prefix automatically.
|
|
40
65
|
* For use inside a component's function.
|
|
41
66
|
* @example this.logger.warn("Hello");
|
|
67
|
+
* @param msgs console.log params
|
|
42
68
|
*/
|
|
43
69
|
warn(...msgs: unknown[]): void;
|
|
44
70
|
/**
|
|
45
71
|
* Local error wrapper that outputs the tag name as a prefix automatically.
|
|
46
72
|
* For use inside a component's function.
|
|
47
73
|
* @example this.logger.error("Hello");
|
|
74
|
+
* @param msgs console.log params
|
|
48
75
|
*/
|
|
49
76
|
error(...msgs: unknown[]): void;
|
|
50
|
-
constructor(host:
|
|
77
|
+
constructor(host: ReactiveControllerHost);
|
|
51
78
|
hostConnected(): void;
|
|
52
79
|
}
|