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.
Files changed (35) hide show
  1. package/README.md +123 -0
  2. package/dist/cjs/combobox/Boolean.attribute.value.normalizer.d.ts +2 -0
  3. package/dist/cjs/combobox/Combobox.markup.d.ts +35 -0
  4. package/dist/cjs/combobox/Combobox.markup.js +178 -102
  5. package/dist/cjs/combobox/HTML.combobox.element.d.ts +60 -0
  6. package/dist/cjs/combobox/HTML.combobox.element.js +262 -113
  7. package/dist/cjs/combobox/HTML.combobox.option.element.d.ts +24 -0
  8. package/dist/cjs/combobox/HTML.combobox.option.element.js +70 -5
  9. package/dist/cjs/combobox/HTML.combobox.tag.element.d.ts +12 -0
  10. package/dist/cjs/combobox/HTML.combobox.tag.element.js +26 -7
  11. package/dist/cjs/combobox/index.d.ts +1 -0
  12. package/dist/cjs/index.d.ts +253 -0
  13. package/dist/cjs/index.js +0 -66
  14. package/dist/cjs/package.json +3 -0
  15. package/dist/esm/combobox/Combobox.markup.js +178 -102
  16. package/dist/esm/combobox/HTML.combobox.element.js +262 -113
  17. package/dist/esm/combobox/HTML.combobox.option.element.js +70 -5
  18. package/dist/esm/combobox/HTML.combobox.tag.element.js +26 -7
  19. package/dist/esm/index.js +0 -66
  20. package/dist/types/combobox/Boolean.attribute.value.normalizer.d.ts +0 -1
  21. package/dist/types/combobox/Combobox.markup.d.ts +13 -9
  22. package/dist/types/combobox/HTML.combobox.element.d.ts +10 -5
  23. package/dist/types/combobox/HTML.combobox.option.element.d.ts +11 -1
  24. package/dist/types/combobox/HTML.combobox.tag.element.d.ts +9 -2
  25. package/dist/types/combobox/index.d.ts +0 -1
  26. package/dist/types/index.d.ts +101 -62
  27. package/package.json +17 -3
  28. package/dist/types/combobox/Boolean.attribute.value.normalizer.d.ts.map +0 -1
  29. package/dist/types/combobox/Combobox.markup.d.ts.map +0 -1
  30. package/dist/types/combobox/HTML.combobox.element.d.ts.map +0 -1
  31. package/dist/types/combobox/HTML.combobox.option.element.d.ts.map +0 -1
  32. package/dist/types/combobox/HTML.combobox.tag.element.d.ts.map +0 -1
  33. package/dist/types/combobox/index.d.ts.map +0 -1
  34. package/dist/types/index.d.ts.map +0 -1
  35. 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, attributes: false, subtree: false };
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: 'closed', delegatesFocus: true });
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.innerHTML = Combobox_markup_js_1.ComboboxMarkup.template;
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.#markup.connect();
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.#onOptionsChanges([{ addedNodes: Array.from(this.children) }]);
34
- this.#observer.observe(this, _a.observerOptions);
35
- this.#markup.clearAllButton.addEventListener('click', this.#onClear);
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.#markup.clearAllButton.removeEventListener('click', this.#onClear);
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.replaceChildren();
48
- this.#setValidityAndFormValue();
49
- this.dispatchEvent(new Event('change'));
140
+ this.#markup.tagsContainer?.replaceChildren();
141
+ this.dispatchEvent(new Event('reset'));
142
+ requestAnimationFrame(() => this.#markup.searchInput?.focus());
50
143
  }
51
- formDisabledCallback(isDisabled) {
52
- this.disabled = isDisabled;
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
- if (this.#markup.connected) {
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
- if (this.#markup.connected) {
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
- super.toggleAttribute('disabled', (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(value));
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 prevValue = new Set(this.#values);
162
- this.#values = new Set;
163
- const values = value.split(',').filter(Boolean);
164
- Promise.resolve(values)
165
- .then(values => {
166
- if (values.length) {
167
- if (!this.multiple) {
168
- if (this.#values.size === 0) {
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.addedNodes.forEach(node => {
245
- if (node instanceof HTML_combobox_option_element_js_1.HTMLComboboxOptionElement) {
246
- node.addEventListener('click', this.#onSelectOption);
247
- if (node.selected) {
248
- if (this.multiple) {
249
- this.#selectOption(node);
250
- }
251
- else if (this.#values.size === 0) {
252
- this.#selectOption(node);
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
- #selectOption(option) {
265
- if (this.#values.has(option.value))
266
- return;
331
+ #onOptionSelectedStateChanges = (event) => {
332
+ const option = event.target;
267
333
  const value = option.value;
268
- this.#values.add(value);
269
- option.toggleAttribute('selected', true);
270
- const control = this.#markup.createAndAppendTag(option);
271
- control?.addEventListener('click', this.#onClearTag);
272
- if (!this.multiple) {
273
- this.#markup.closeDropdown();
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
- #onSelectOption = (event) => {
277
- const target = event.target;
278
- if (target && target instanceof HTMLElement) {
279
- const option = target.closest('box-option');
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 && target instanceof HTMLElement) {
301
- const tag = target.closest('box-tag');
302
- if (tag) {
303
- const value = tag.getAttribute('value');
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
- #onClear = () => {
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', (event) => {
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.textContent;
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
- super.toggleAttribute('selected', (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(value));
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
+ }