html-combobox-element 0.0.2 → 0.0.4
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 +123 -0
- package/dist/cjs/combobox/Boolean.attribute.value.normalizer.d.ts +2 -0
- package/dist/cjs/combobox/Combobox.markup.d.ts +35 -0
- package/dist/cjs/combobox/Combobox.markup.js +178 -102
- package/dist/cjs/combobox/HTML.combobox.element.d.ts +60 -0
- package/dist/cjs/combobox/HTML.combobox.element.js +262 -113
- package/dist/cjs/combobox/HTML.combobox.option.element.d.ts +24 -0
- package/dist/cjs/combobox/HTML.combobox.option.element.js +70 -5
- package/dist/cjs/combobox/HTML.combobox.tag.element.d.ts +12 -0
- package/dist/cjs/combobox/HTML.combobox.tag.element.js +26 -7
- package/dist/cjs/combobox/index.d.ts +1 -0
- package/dist/cjs/index.d.ts +253 -0
- package/dist/cjs/index.js +0 -66
- package/dist/cjs/package.json +3 -0
- package/dist/esm/combobox/Combobox.markup.js +178 -102
- package/dist/esm/combobox/HTML.combobox.element.js +262 -113
- package/dist/esm/combobox/HTML.combobox.option.element.js +70 -5
- package/dist/esm/combobox/HTML.combobox.tag.element.js +26 -7
- package/dist/esm/index.js +0 -66
- package/dist/types/combobox/Boolean.attribute.value.normalizer.d.ts +0 -1
- package/dist/types/combobox/Combobox.markup.d.ts +13 -9
- package/dist/types/combobox/HTML.combobox.element.d.ts +10 -5
- package/dist/types/combobox/HTML.combobox.option.element.d.ts +11 -1
- package/dist/types/combobox/HTML.combobox.tag.element.d.ts +9 -2
- package/dist/types/combobox/index.d.ts +0 -1
- package/dist/types/index.d.ts +101 -62
- package/package.json +17 -3
- package/dist/types/combobox/Boolean.attribute.value.normalizer.d.ts.map +0 -1
- package/dist/types/combobox/Combobox.markup.d.ts.map +0 -1
- package/dist/types/combobox/HTML.combobox.element.d.ts.map +0 -1
- package/dist/types/combobox/HTML.combobox.option.element.d.ts.map +0 -1
- package/dist/types/combobox/HTML.combobox.tag.element.d.ts.map +0 -1
- package/dist/types/combobox/index.d.ts.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/types.d.ts +0 -198
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { HTMLComboboxOptionElement } from './HTML.combobox.option.element.js';
|
|
2
|
+
export declare class HTMLComboboxElement extends HTMLElement {
|
|
3
|
+
#private;
|
|
4
|
+
static OWN_IDL: Set<string>;
|
|
5
|
+
static observerOptions: {
|
|
6
|
+
childList: boolean;
|
|
7
|
+
};
|
|
8
|
+
static styleSheet: CSSStyleSheet[];
|
|
9
|
+
static formAssociated: boolean;
|
|
10
|
+
shadowRoot: ShadowRoot;
|
|
11
|
+
constructor();
|
|
12
|
+
connectedCallback(): void;
|
|
13
|
+
insertBefore: <T extends Node>(node: T, referenceNode: Node | null) => T;
|
|
14
|
+
appendChild: <T extends Node>(node: T) => T;
|
|
15
|
+
append: (...nodes: (Node | string)[]) => void;
|
|
16
|
+
prepend: (...nodes: (Node | string)[]) => void;
|
|
17
|
+
replaceChildren: (...nodes: (Node | string)[]) => void;
|
|
18
|
+
removeChild: <T extends Node>(child: T) => T;
|
|
19
|
+
replaceChild: <T extends Node>(newChild: Node, oldChild: T) => T;
|
|
20
|
+
get changed(): boolean;
|
|
21
|
+
disconnectedCallback(): void;
|
|
22
|
+
formResetCallback(): void;
|
|
23
|
+
formDisabledCallback(): void;
|
|
24
|
+
get valueAsArray(): string[];
|
|
25
|
+
get query(): string;
|
|
26
|
+
set query(value: string);
|
|
27
|
+
get placeholder(): string;
|
|
28
|
+
set placeholder(value: string);
|
|
29
|
+
get clearable(): boolean;
|
|
30
|
+
set clearable(value: boolean);
|
|
31
|
+
get filterable(): boolean;
|
|
32
|
+
set filterable(value: boolean);
|
|
33
|
+
get searchable(): boolean;
|
|
34
|
+
set searchable(value: boolean);
|
|
35
|
+
get disabled(): boolean;
|
|
36
|
+
set disabled(value: boolean);
|
|
37
|
+
get form(): HTMLFormElement;
|
|
38
|
+
get labels(): NodeList;
|
|
39
|
+
get length(): number;
|
|
40
|
+
get multiple(): boolean;
|
|
41
|
+
set multiple(value: boolean);
|
|
42
|
+
get required(): boolean;
|
|
43
|
+
set required(value: boolean);
|
|
44
|
+
get selectedOptions(): NodeListOf<HTMLComboboxOptionElement>;
|
|
45
|
+
get type(): "select-multiple" | "select-one";
|
|
46
|
+
get validationMessage(): string;
|
|
47
|
+
get validity(): ValidityState;
|
|
48
|
+
get willValidate(): boolean;
|
|
49
|
+
get value(): string;
|
|
50
|
+
set value(value: string);
|
|
51
|
+
checkValidity(): void;
|
|
52
|
+
item(index: number): HTMLComboboxOptionElement;
|
|
53
|
+
reportValidity(): void;
|
|
54
|
+
setCustomValidity(message: string): void;
|
|
55
|
+
showPicker(): void;
|
|
56
|
+
setAttribute(name: string, value: any): void;
|
|
57
|
+
removeAttribute(name: string): void;
|
|
58
|
+
static loadCssFromUrls(urls: string[]): void;
|
|
59
|
+
static loadCssFromDocumentStyleSheets(): void;
|
|
60
|
+
}
|
|
@@ -7,7 +7,7 @@ const HTML_combobox_option_element_js_1 = require("./HTML.combobox.option.elemen
|
|
|
7
7
|
const Boolean_attribute_value_normalizer_js_1 = require("./Boolean.attribute.value.normalizer.js");
|
|
8
8
|
class HTMLComboboxElement extends HTMLElement {
|
|
9
9
|
static OWN_IDL = new Set(['required', 'disabled', 'clearable', 'multiple', 'filterable', 'searchable', 'value', 'placeholder', 'query']);
|
|
10
|
-
static observerOptions = { childList: true
|
|
10
|
+
static observerOptions = { childList: true };
|
|
11
11
|
static styleSheet = [new CSSStyleSheet];
|
|
12
12
|
static formAssociated = true;
|
|
13
13
|
shadowRoot;
|
|
@@ -15,41 +15,136 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
15
15
|
#observer;
|
|
16
16
|
#markup;
|
|
17
17
|
#values = new Set;
|
|
18
|
+
#prevValue = '';
|
|
19
|
+
#changed = false;
|
|
18
20
|
constructor() {
|
|
19
21
|
super();
|
|
20
22
|
this.#internals = this.attachInternals();
|
|
21
23
|
this.#internals.role = "combobox";
|
|
22
24
|
this.#internals.ariaHasPopup = "dialog";
|
|
23
|
-
this.shadowRoot = this.attachShadow({ mode: '
|
|
25
|
+
this.shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); //delegatesFocus: true
|
|
24
26
|
this.#markup = new Combobox_markup_js_1.ComboboxMarkup(this.shadowRoot, this.#internals);
|
|
25
|
-
this.shadowRoot.
|
|
27
|
+
this.shadowRoot.appendChild(Combobox_markup_js_1.ComboboxMarkup.markup.content.cloneNode(true));
|
|
26
28
|
this.shadowRoot.adoptedStyleSheets = _a.styleSheet;
|
|
27
29
|
this.#observer = new MutationObserver(this.#onOptionsChanges);
|
|
30
|
+
this.#markup.connect();
|
|
28
31
|
}
|
|
29
32
|
// Lifecycle callbacks
|
|
30
33
|
connectedCallback() {
|
|
31
|
-
this
|
|
34
|
+
for (const child of Array.from(this.children)) {
|
|
35
|
+
if (_a.#isOptionLike(child)) {
|
|
36
|
+
this.#markup.optionsContainer?.appendChild(child);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
this.#markup.onHostConnected();
|
|
40
|
+
this.shadowRoot.addEventListener('optionselectedstatechange', this.#onOptionSelectedStateChanges);
|
|
41
|
+
this.shadowRoot.addEventListener('tagcleared', this.#onClearTag);
|
|
32
42
|
this.#initialAttributesSynchronization();
|
|
33
|
-
this.#
|
|
34
|
-
this
|
|
35
|
-
this.#markup.clearAllButton.addEventListener('click', this.#
|
|
43
|
+
this.#observer.observe(this.#markup.optionsContainer, _a.observerOptions);
|
|
44
|
+
this.addEventListener('keydown', this.#onKeyDown);
|
|
45
|
+
this.#markup.clearAllButton.addEventListener('click', this.#onClearAll);
|
|
36
46
|
this.#markup.searchInput.addEventListener('input', this.#onInput);
|
|
47
|
+
this.#markup.dropdown.addEventListener('toggle', this.#onPickerToggle);
|
|
48
|
+
this.#setValidityAndFormValue();
|
|
49
|
+
// Options/optgroups can end up as direct light-DOM children of <combo-box> without ever
|
|
50
|
+
// going through insertBefore/appendChild below: parsed straight from static HTML markup,
|
|
51
|
+
// or - notably - Solid's compiler, which renders static JSX subtrees via a single
|
|
52
|
+
// <template> + cloneNode(true), never calling any child-mutation method at all. Sweep
|
|
53
|
+
// whatever is already here once on connect and move it into the real options container.
|
|
54
|
+
}
|
|
55
|
+
static #isOptionLike(node) {
|
|
56
|
+
return node instanceof HTML_combobox_option_element_js_1.HTMLComboboxOptionElement || node instanceof HTMLOptGroupElement;
|
|
57
|
+
}
|
|
58
|
+
// Overwritten child-mutation methods.
|
|
59
|
+
//
|
|
60
|
+
// <box-option>/<optgroup> children don't actually live under <combo-box> itself - they're
|
|
61
|
+
// redirected into #markup.optionsContainer inside the shadow root, which is what the
|
|
62
|
+
// dropdown renders from and what #observer watches. Every native method a framework's
|
|
63
|
+
// renderer or plain user code might use to add/move/remove children needs to funnel through
|
|
64
|
+
// the same redirect, since browsers implement each of these independently (overriding one,
|
|
65
|
+
// e.g. insertBefore, does not make the others call it under the hood):
|
|
66
|
+
// - React calls appendChild for a plain append and insertBefore only when reordering.
|
|
67
|
+
// - Vue and Preact always call insertBefore, with a null reference for a plain append.
|
|
68
|
+
// - Solid mostly bypasses all of them for static subtrees (see connectedCallback above),
|
|
69
|
+
// but dynamic option lists still go through insert()/insertBefore().
|
|
70
|
+
insertBefore = (node, referenceNode) => {
|
|
71
|
+
if (_a.#isOptionLike(node)) {
|
|
72
|
+
this.#markup.optionsContainer?.insertBefore(node, referenceNode);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
super.insertBefore(node, referenceNode);
|
|
76
|
+
}
|
|
77
|
+
return node;
|
|
78
|
+
};
|
|
79
|
+
appendChild = (node) => {
|
|
80
|
+
if (_a.#isOptionLike(node)) {
|
|
81
|
+
this.#markup.optionsContainer?.appendChild(node);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
super.appendChild(node);
|
|
85
|
+
}
|
|
86
|
+
return node;
|
|
87
|
+
};
|
|
88
|
+
append = (...nodes) => {
|
|
89
|
+
nodes.forEach(node => this.appendChild(typeof node === 'string' ? new Text(node) : node));
|
|
90
|
+
};
|
|
91
|
+
prepend = (...nodes) => {
|
|
92
|
+
[...nodes].reverse().forEach(node => {
|
|
93
|
+
const n = typeof node === 'string' ? new Text(node) : node;
|
|
94
|
+
if (_a.#isOptionLike(n)) {
|
|
95
|
+
this.#markup.optionsContainer?.insertBefore(n, this.#markup.optionsContainer.firstChild);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
super.insertBefore(n, this.firstChild);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
replaceChildren = (...nodes) => {
|
|
103
|
+
this.#markup.optionsContainer?.replaceChildren();
|
|
104
|
+
super.replaceChildren();
|
|
105
|
+
this.append(...nodes);
|
|
106
|
+
};
|
|
107
|
+
removeChild = (child) => {
|
|
108
|
+
if (child.parentNode === this.#markup.optionsContainer) {
|
|
109
|
+
this.#markup.optionsContainer.removeChild(child);
|
|
110
|
+
}
|
|
111
|
+
else if (child.parentNode === this) {
|
|
112
|
+
super.removeChild(child);
|
|
113
|
+
}
|
|
114
|
+
return child;
|
|
115
|
+
};
|
|
116
|
+
replaceChild = (newChild, oldChild) => {
|
|
117
|
+
if (oldChild.parentNode === this.#markup.optionsContainer) {
|
|
118
|
+
this.#markup.optionsContainer.replaceChild(newChild, oldChild);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
super.replaceChild(newChild, oldChild);
|
|
122
|
+
}
|
|
123
|
+
return oldChild;
|
|
124
|
+
};
|
|
125
|
+
get changed() {
|
|
126
|
+
return this.#changed;
|
|
37
127
|
}
|
|
38
128
|
disconnectedCallback() {
|
|
39
129
|
this.#observer.disconnect();
|
|
40
130
|
this.#markup.disconnect();
|
|
41
|
-
this
|
|
131
|
+
this.removeEventListener('keydown', this.#onKeyDown);
|
|
132
|
+
this.#markup.clearAllButton.removeEventListener('click', this.#onClearAll);
|
|
42
133
|
this.#markup.searchInput.removeEventListener('input', this.#onInput);
|
|
134
|
+
this.#markup.dropdown.removeEventListener('toggle', this.#onPickerToggle);
|
|
135
|
+
this.shadowRoot.removeEventListener('optionselectedstatechange', this.#onOptionSelectedStateChanges);
|
|
136
|
+
this.shadowRoot.removeEventListener('tagcleared', this.#onClearTag);
|
|
43
137
|
}
|
|
44
138
|
formResetCallback() {
|
|
45
|
-
this.#values = new Set;
|
|
46
139
|
this.selectedOptions.forEach(option => option.selected = false);
|
|
47
|
-
this.#markup.tagsContainer
|
|
48
|
-
this
|
|
49
|
-
this.
|
|
140
|
+
this.#markup.tagsContainer?.replaceChildren();
|
|
141
|
+
this.dispatchEvent(new Event('reset'));
|
|
142
|
+
requestAnimationFrame(() => this.#markup.searchInput?.focus());
|
|
50
143
|
}
|
|
51
|
-
formDisabledCallback(
|
|
52
|
-
|
|
144
|
+
formDisabledCallback() {
|
|
145
|
+
// toDo
|
|
146
|
+
// when inside fieldset, this callback is only invoked for the first time
|
|
147
|
+
// isDisabled: boolean
|
|
53
148
|
}
|
|
54
149
|
// Instance properties
|
|
55
150
|
// <combo-box> specific properties
|
|
@@ -66,22 +161,17 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
66
161
|
value = '';
|
|
67
162
|
value = String(value);
|
|
68
163
|
super.setAttribute('query', value);
|
|
69
|
-
|
|
70
|
-
this.#markup.searchInput.value = value;
|
|
71
|
-
}
|
|
164
|
+
this.#markup?.setInputValue(value);
|
|
72
165
|
}
|
|
73
166
|
get placeholder() {
|
|
74
167
|
return this.getAttribute('placeholder');
|
|
75
168
|
}
|
|
76
169
|
set placeholder(value) {
|
|
77
170
|
if (value == null)
|
|
78
|
-
value = '';
|
|
171
|
+
value = ' ';
|
|
79
172
|
value = String(value);
|
|
80
173
|
super.setAttribute('placeholder', value);
|
|
81
|
-
|
|
82
|
-
this.#markup.placeholder.innerText = value;
|
|
83
|
-
this.#markup.searchInput.placeholder = value;
|
|
84
|
-
}
|
|
174
|
+
this.#markup?.setPlaceholder(value);
|
|
85
175
|
}
|
|
86
176
|
get clearable() {
|
|
87
177
|
return this.hasAttribute('clearable');
|
|
@@ -107,7 +197,9 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
107
197
|
}
|
|
108
198
|
set disabled(value) {
|
|
109
199
|
this.#internals.ariaDisabled = String(value);
|
|
110
|
-
|
|
200
|
+
value = (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(value);
|
|
201
|
+
super.toggleAttribute('disabled', value);
|
|
202
|
+
super.toggleAttribute('inert', value);
|
|
111
203
|
}
|
|
112
204
|
get form() {
|
|
113
205
|
return this.#internals.form;
|
|
@@ -158,34 +250,14 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
158
250
|
set value(value) {
|
|
159
251
|
if (this.value === value || typeof value !== 'string')
|
|
160
252
|
return;
|
|
161
|
-
const
|
|
162
|
-
this.#
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
values.length = 1;
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
values.length = 0;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
for (const key of values) {
|
|
176
|
-
const option = this.#markup.getOptionByValue(key);
|
|
177
|
-
if (option)
|
|
178
|
-
this.#selectOption(option);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
for (const key of prevValue) {
|
|
182
|
-
if (this.#values.has(key))
|
|
183
|
-
continue;
|
|
184
|
-
const option = this.#markup.getOptionByValue(key);
|
|
185
|
-
const tag = this.#markup.getTagByValue(key);
|
|
186
|
-
tag?.remove();
|
|
187
|
-
option?.toggleAttribute('selected', false);
|
|
188
|
-
}
|
|
253
|
+
const nextValues = new Set(value.split(',').filter(Boolean));
|
|
254
|
+
this.#markup.options.forEach(option => {
|
|
255
|
+
const optionValue = option.value;
|
|
256
|
+
// toggling `selected` drives #onOptionSelectedStateChanges, which is the
|
|
257
|
+
// single source of truth for #values and tags - don't pre-assign #values
|
|
258
|
+
// here, or the dedup logic there ends up deselecting the option it's in
|
|
259
|
+
// the middle of selecting (see: stale tag left behind on next selection)
|
|
260
|
+
option.selected = nextValues.has(optionValue);
|
|
189
261
|
});
|
|
190
262
|
}
|
|
191
263
|
// Instance methods
|
|
@@ -232,91 +304,168 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
232
304
|
}
|
|
233
305
|
}
|
|
234
306
|
// Internal
|
|
235
|
-
#onInput = (event) => {
|
|
236
|
-
if (!this.searchable && this.filterable) {
|
|
237
|
-
if (event.target && event.target instanceof HTMLInputElement) {
|
|
238
|
-
this.#markup.sort(event.target.value);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
307
|
#onOptionsChanges = (records) => {
|
|
243
308
|
records.forEach(record => {
|
|
244
|
-
record.
|
|
245
|
-
if (
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
309
|
+
record.removedNodes.forEach(option => {
|
|
310
|
+
if (option instanceof HTML_combobox_option_element_js_1.HTMLComboboxOptionElement) {
|
|
311
|
+
const value = option.value;
|
|
312
|
+
// mode is searchable,
|
|
313
|
+
// user type "a", select a result,
|
|
314
|
+
// then types "b". Here previous options will be replaced with new ones,
|
|
315
|
+
// this.#values may contain "a", but there is no relevant option yet,
|
|
316
|
+
// so just wait
|
|
317
|
+
if (this.searchable && this.#internals.ariaExpanded === 'true') {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
// options was removed, but values still include this value,
|
|
321
|
+
// just clear, this is like a reset was called
|
|
322
|
+
if (this.#values.has(value) && !this.#markup.optionsContainer?.querySelector(`[value="${value}"]`)) {
|
|
323
|
+
this.#values.delete(value);
|
|
324
|
+
const control = this.#markup.getTagByValue(value);
|
|
325
|
+
control?.remove();
|
|
254
326
|
}
|
|
255
|
-
}
|
|
256
|
-
if (node instanceof HTML_combobox_option_element_js_1.HTMLComboboxOptionElement || node instanceof HTMLOptGroupElement) {
|
|
257
|
-
this.#markup.optionsContainer.append(node);
|
|
258
327
|
}
|
|
259
328
|
});
|
|
260
329
|
});
|
|
261
|
-
this.#markup.invalidateOptionsCache();
|
|
262
|
-
this.#setValidityAndFormValue();
|
|
263
330
|
};
|
|
264
|
-
#
|
|
265
|
-
|
|
266
|
-
return;
|
|
331
|
+
#onOptionSelectedStateChanges = (event) => {
|
|
332
|
+
const option = event.target;
|
|
267
333
|
const value = option.value;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
334
|
+
if (option.selected) {
|
|
335
|
+
if (!this.multiple) {
|
|
336
|
+
this.#values.forEach(value => {
|
|
337
|
+
const prevOption = this.#markup.getOptionByValue(value);
|
|
338
|
+
if (prevOption) {
|
|
339
|
+
prevOption.selected = false;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
const tag = this.#markup.getTagByValue(value);
|
|
343
|
+
this.#values.delete(value);
|
|
344
|
+
tag?.remove();
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (!this.#values.has(value) || !this.#markup.getTagByValue(value)) {
|
|
349
|
+
this.#values.add(value);
|
|
350
|
+
this.#markup.createAndAppendTag(option);
|
|
351
|
+
this.#setValidityAndFormValue();
|
|
352
|
+
this.dispatchEvent(new Event('change'));
|
|
353
|
+
}
|
|
274
354
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (option) {
|
|
281
|
-
if (this.#values.has(option.value))
|
|
282
|
-
return;
|
|
283
|
-
if (!this.multiple) {
|
|
284
|
-
event.stopPropagation();
|
|
285
|
-
this.#values.forEach(value => {
|
|
286
|
-
this.#markup.getTagByValue(value)?.remove();
|
|
287
|
-
this.#markup.getOptionByValue(value)?.toggleAttribute('selected', false);
|
|
288
|
-
});
|
|
289
|
-
this.#values.clear();
|
|
290
|
-
this.#markup.tagsContainer.replaceChildren();
|
|
291
|
-
}
|
|
292
|
-
this.#selectOption(option);
|
|
355
|
+
else {
|
|
356
|
+
if (this.#values.has(value)) {
|
|
357
|
+
this.#values.delete(value);
|
|
358
|
+
const control = this.#markup.getTagByValue(value);
|
|
359
|
+
control?.remove();
|
|
293
360
|
this.#setValidityAndFormValue();
|
|
294
361
|
this.dispatchEvent(new Event('change'));
|
|
295
362
|
}
|
|
363
|
+
else {
|
|
364
|
+
const tag = this.#markup.getTagByValue(value);
|
|
365
|
+
tag?.remove();
|
|
366
|
+
}
|
|
296
367
|
}
|
|
297
368
|
};
|
|
298
369
|
#onClearTag = (event) => {
|
|
299
370
|
const target = event.target;
|
|
300
|
-
if (target
|
|
301
|
-
const
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
const option = this.#markup.getOptionByValue(value);
|
|
305
|
-
option.removeAttribute('selected');
|
|
306
|
-
this.#values.delete(value);
|
|
307
|
-
tag.remove();
|
|
308
|
-
this.#setValidityAndFormValue();
|
|
309
|
-
this.dispatchEvent(new Event('change'));
|
|
371
|
+
if (target) {
|
|
372
|
+
const option = this.#markup.getOptionByValue(target.value);
|
|
373
|
+
if (option) {
|
|
374
|
+
option.selected = false;
|
|
310
375
|
}
|
|
376
|
+
else {
|
|
377
|
+
const tag = this.#markup.getTagByValue(target.value);
|
|
378
|
+
this.#values.delete(target.value);
|
|
379
|
+
tag?.remove();
|
|
380
|
+
}
|
|
381
|
+
requestAnimationFrame(() => {
|
|
382
|
+
if (this.#values.size) {
|
|
383
|
+
this.focus();
|
|
384
|
+
}
|
|
385
|
+
else if (this.searchable || this.filterable) {
|
|
386
|
+
this.#markup.searchInput?.focus();
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
this.focus();
|
|
390
|
+
}
|
|
391
|
+
});
|
|
311
392
|
}
|
|
312
393
|
};
|
|
313
|
-
#
|
|
394
|
+
#onClearAll = () => {
|
|
314
395
|
this.formResetCallback();
|
|
315
396
|
};
|
|
397
|
+
#onPickerToggle = (event) => {
|
|
398
|
+
const state = Reflect.get(event, 'newState');
|
|
399
|
+
const oldState = Reflect.get(event, 'oldState');
|
|
400
|
+
if (state === oldState)
|
|
401
|
+
return;
|
|
402
|
+
if (state === 'open') {
|
|
403
|
+
this.#prevValue = this.value;
|
|
404
|
+
this.#changed = true;
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
this.#changed = this.#prevValue !== this.value;
|
|
408
|
+
this.dispatchEvent(new Event('close'));
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
#onKeyDown = (event) => {
|
|
412
|
+
if (this.#internals.ariaExpanded !== 'true')
|
|
413
|
+
return;
|
|
414
|
+
const activeElement = this.shadowRoot.activeElement;
|
|
415
|
+
if (!activeElement) {
|
|
416
|
+
// ??? how we should react ???
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (activeElement === this.#markup.searchInput && event.key === 'Enter') {
|
|
420
|
+
event.stopPropagation();
|
|
421
|
+
if (!this.searchable && this.filterable) {
|
|
422
|
+
console.log('sss');
|
|
423
|
+
this.#markup.sort(this.#markup.searchInput.value);
|
|
424
|
+
}
|
|
425
|
+
if (this.searchable) {
|
|
426
|
+
this.dispatchEvent(new Event('search'));
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (event.key === 'Enter' && activeElement instanceof HTML_combobox_option_element_js_1.HTMLComboboxOptionElement) {
|
|
431
|
+
if (!activeElement.selected) {
|
|
432
|
+
event.stopPropagation();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (event.key === 'Enter' && activeElement.part.contains('clear-tag')) {
|
|
437
|
+
// @ts-ignore
|
|
438
|
+
// this.#onClearTag(event);
|
|
439
|
+
// event.stopPropagation();
|
|
440
|
+
// event.preventDefault()
|
|
441
|
+
// @ts-ignore
|
|
442
|
+
activeElement.click();
|
|
443
|
+
// event.stopPropagation();
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (event.key === 'Escape' || event.key === 'Enter') {
|
|
447
|
+
this.#markup.closeDropdown();
|
|
448
|
+
this.blur();
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
#onInput = () => {
|
|
452
|
+
this.query = this.#markup.searchInput.value;
|
|
453
|
+
if (!this.searchable && this.filterable) {
|
|
454
|
+
this.#markup.sort(this.#markup.searchInput?.value || '');
|
|
455
|
+
}
|
|
456
|
+
};
|
|
316
457
|
#setValidityAndFormValue() {
|
|
458
|
+
this.#markup.clearAllButton?.toggleAttribute('disabled', !Boolean(this.#values.size));
|
|
459
|
+
let empty = false;
|
|
460
|
+
if (this.#values.size === 0)
|
|
461
|
+
empty = true;
|
|
462
|
+
if (this.#values.size > 0 && !this.clearable && !this.multiple && (this.searchable || this.filterable)) {
|
|
463
|
+
empty = true;
|
|
464
|
+
}
|
|
465
|
+
this.#markup.searchInput?.toggleAttribute('autofocus', empty);
|
|
317
466
|
this.#internals.setFormValue(this.value);
|
|
318
467
|
if (this.required && this.value === '') {
|
|
319
|
-
this.#internals.setValidity({ valueMissing: true });
|
|
468
|
+
this.#internals.setValidity({ valueMissing: true }, 'Value missing');
|
|
320
469
|
}
|
|
321
470
|
else {
|
|
322
471
|
this.#internals.setValidity({});
|
|
@@ -352,7 +501,7 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
352
501
|
}
|
|
353
502
|
exports.HTMLComboboxElement = HTMLComboboxElement;
|
|
354
503
|
_a = HTMLComboboxElement;
|
|
355
|
-
document.addEventListener('keypress', (
|
|
504
|
+
document.addEventListener('keypress', () => {
|
|
356
505
|
if (document.activeElement instanceof HTMLComboboxElement) {
|
|
357
506
|
const maybeHost = document.activeElement.shadowRoot.activeElement;
|
|
358
507
|
if (maybeHost instanceof HTML_combobox_option_element_js_1.HTMLComboboxOptionElement) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare class HTMLComboboxOptionElement extends HTMLElement {
|
|
2
|
+
#private;
|
|
3
|
+
static OWN_IDL: Set<string>;
|
|
4
|
+
static EventInit: {
|
|
5
|
+
bubbles: boolean;
|
|
6
|
+
cancelable: boolean;
|
|
7
|
+
composed: boolean;
|
|
8
|
+
};
|
|
9
|
+
static captureFocus(event: FocusEvent): void;
|
|
10
|
+
static onNavigate(event: KeyboardEvent): void;
|
|
11
|
+
connectedCallback(): void;
|
|
12
|
+
disconnectedCallback(): void;
|
|
13
|
+
get value(): string;
|
|
14
|
+
set value(value: string);
|
|
15
|
+
get label(): string;
|
|
16
|
+
set label(value: string);
|
|
17
|
+
get selected(): boolean;
|
|
18
|
+
set selected(value: boolean);
|
|
19
|
+
get disabled(): boolean;
|
|
20
|
+
set disabled(value: boolean);
|
|
21
|
+
setAttribute(name: string, value: string): void;
|
|
22
|
+
removeAttribute(name: string): void;
|
|
23
|
+
toggleAttribute(name: string, force?: boolean): boolean;
|
|
24
|
+
}
|
|
@@ -4,16 +4,57 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.HTMLComboboxOptionElement = void 0;
|
|
5
5
|
const Boolean_attribute_value_normalizer_js_1 = require("./Boolean.attribute.value.normalizer.js");
|
|
6
6
|
class HTMLComboboxOptionElement extends HTMLElement {
|
|
7
|
-
static OWN_IDL = new Set(['value', 'label', 'selected']);
|
|
7
|
+
static OWN_IDL = new Set(['value', 'label', 'selected', 'disabled']);
|
|
8
|
+
static EventInit = { bubbles: true, cancelable: true, composed: true };
|
|
9
|
+
static captureFocus(event) {
|
|
10
|
+
if (event.relatedTarget && event.relatedTarget instanceof _a) {
|
|
11
|
+
event.stopPropagation();
|
|
12
|
+
event.preventDefault();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
static onNavigate(event) {
|
|
16
|
+
if (!event.target ||
|
|
17
|
+
!(event.target instanceof _a) ||
|
|
18
|
+
event.key === 'Tab' ||
|
|
19
|
+
event.key === 'Enter')
|
|
20
|
+
return;
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
if (event.key === 'ArrowDown') {
|
|
23
|
+
const next = event.target.nextElementSibling;
|
|
24
|
+
if (next && next instanceof _a) {
|
|
25
|
+
next.focus();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (event.key === 'ArrowUp') {
|
|
29
|
+
const prev = event.target.previousElementSibling;
|
|
30
|
+
if (prev && prev instanceof _a) {
|
|
31
|
+
prev.focus();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
8
35
|
connectedCallback() {
|
|
36
|
+
this.addEventListener('keydown', _a.onNavigate);
|
|
37
|
+
this.addEventListener('focusout', _a.captureFocus);
|
|
38
|
+
this.addEventListener('click', this.#toggleSelection);
|
|
9
39
|
this.#initialAttributesSynchronization();
|
|
10
40
|
this.part.add('option');
|
|
11
41
|
super.setAttribute('tabindex', "0");
|
|
12
42
|
super.setAttribute('role', "option");
|
|
13
|
-
if (!this.value)
|
|
14
|
-
this.value = this.
|
|
15
|
-
|
|
43
|
+
if (!this.value)
|
|
44
|
+
this.value = this.label;
|
|
45
|
+
if (!this.textContent)
|
|
46
|
+
this.textContent = this.value;
|
|
47
|
+
this.dispatchEvent(new Event('optionselectedstatechange', _a.EventInit));
|
|
16
48
|
}
|
|
49
|
+
disconnectedCallback() {
|
|
50
|
+
this.removeEventListener('click', this.#toggleSelection);
|
|
51
|
+
this.removeEventListener('keydown', _a.onNavigate);
|
|
52
|
+
this.removeEventListener('focusout', _a.captureFocus);
|
|
53
|
+
}
|
|
54
|
+
#toggleSelection = (event) => {
|
|
55
|
+
event.stopPropagation();
|
|
56
|
+
this.selected = !this.selected;
|
|
57
|
+
};
|
|
17
58
|
get value() {
|
|
18
59
|
return this.getAttribute('value');
|
|
19
60
|
}
|
|
@@ -34,7 +75,26 @@ class HTMLComboboxOptionElement extends HTMLElement {
|
|
|
34
75
|
return this.hasAttribute('selected');
|
|
35
76
|
}
|
|
36
77
|
set selected(value) {
|
|
37
|
-
|
|
78
|
+
const prev = this.selected;
|
|
79
|
+
const force = (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(value);
|
|
80
|
+
if (prev === force) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
super.toggleAttribute('selected', force);
|
|
84
|
+
this.part.toggle('selected', force);
|
|
85
|
+
// console.log('this.isConnected', this.isConnected)
|
|
86
|
+
if (this.isConnected) {
|
|
87
|
+
this.dispatchEvent(new Event('optionselectedstatechange', _a.EventInit));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
get disabled() {
|
|
91
|
+
return this.hasAttribute('disabled');
|
|
92
|
+
}
|
|
93
|
+
set disabled(value) {
|
|
94
|
+
const force = (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(value);
|
|
95
|
+
super.toggleAttribute('disabled', force);
|
|
96
|
+
this.part.toggle('disabled', force);
|
|
97
|
+
super.toggleAttribute('inert', force);
|
|
38
98
|
}
|
|
39
99
|
#initialAttributesSynchronization() {
|
|
40
100
|
for (const key of _a.OWN_IDL) {
|
|
@@ -57,6 +117,11 @@ class HTMLComboboxOptionElement extends HTMLElement {
|
|
|
57
117
|
super.removeAttribute(name);
|
|
58
118
|
}
|
|
59
119
|
}
|
|
120
|
+
toggleAttribute(name, force) {
|
|
121
|
+
if (name === 'selected')
|
|
122
|
+
this.part.toggle(name, force);
|
|
123
|
+
return super.toggleAttribute(name, force);
|
|
124
|
+
}
|
|
60
125
|
}
|
|
61
126
|
exports.HTMLComboboxOptionElement = HTMLComboboxOptionElement;
|
|
62
127
|
_a = HTMLComboboxOptionElement;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class HTMLComboboxTagElement extends HTMLElement {
|
|
2
|
+
static EventInit: {
|
|
3
|
+
bubbles: boolean;
|
|
4
|
+
cancelable: boolean;
|
|
5
|
+
composed: boolean;
|
|
6
|
+
};
|
|
7
|
+
get value(): string;
|
|
8
|
+
connectedCallback(): void;
|
|
9
|
+
disconnectedCallback(): void;
|
|
10
|
+
static onClearTag(event: Event): void;
|
|
11
|
+
static captureFocus(event: Event): void;
|
|
12
|
+
}
|