html-combobox-element 0.0.2-beta.9 → 0.0.3

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.
@@ -77,10 +77,12 @@ class HTMLComboboxOptionElement extends HTMLElement {
77
77
  set selected(value) {
78
78
  const prev = this.selected;
79
79
  const force = (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(value);
80
- if (prev === force)
80
+ if (prev === force) {
81
81
  return;
82
+ }
82
83
  super.toggleAttribute('selected', force);
83
84
  this.part.toggle('selected', force);
85
+ // console.log('this.isConnected', this.isConnected)
84
86
  if (this.isConnected) {
85
87
  this.dispatchEvent(new Event('optionselectedstatechange', _a.EventInit));
86
88
  }
@@ -96,13 +98,11 @@ class HTMLComboboxOptionElement extends HTMLElement {
96
98
  }
97
99
  #initialAttributesSynchronization() {
98
100
  for (const key of _a.OWN_IDL) {
99
- // @ts-ignore
100
101
  this[key] = this.getAttribute(key);
101
102
  }
102
103
  }
103
104
  setAttribute(name, value) {
104
105
  if (_a.OWN_IDL.has(name)) {
105
- // @ts-ignore
106
106
  this[name] = value;
107
107
  }
108
108
  else {
@@ -111,7 +111,6 @@ class HTMLComboboxOptionElement extends HTMLElement {
111
111
  }
112
112
  removeAttribute(name) {
113
113
  if (_a.OWN_IDL.has(name)) {
114
- // @ts-ignore
115
114
  this[name] = null;
116
115
  }
117
116
  else {
@@ -9,7 +9,7 @@ class HTMLComboboxTagElement extends HTMLElement {
9
9
  connectedCallback() {
10
10
  this.part.add('tag');
11
11
  const control = this.querySelector('[part*="clear-tag"]');
12
- if (!control?.checkVisibility()) {
12
+ if (!this.hasAttribute('visible')) {
13
13
  super.setAttribute('tabindex', '0');
14
14
  }
15
15
  control?.addEventListener('focusout', HTMLComboboxTagElement.captureFocus);
package/dist/cjs/index.js CHANGED
@@ -1,8 +1,4 @@
1
1
  "use strict";
2
- /// <reference types="solid-js" />
3
- /// <reference types="preact" />
4
- /// <reference types="react" />
5
- /// <reference types="vue" />
6
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
3
  if (k2 === undefined) k2 = k;
8
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -19,10 +19,7 @@ export class ComboboxMarkup {
19
19
  window.addEventListener('click', this.onClickOutside);
20
20
  this.#shadowRoot.host.addEventListener('focusin', this.onFocusIn);
21
21
  this.#shadowRoot.host.addEventListener('focusout', this.onFocusOut);
22
- // document.addEventListener('scroll', this.#onDimensionsChanges);
23
- // document.addEventListener('resize', this.#onDimensionsChanges);
24
22
  this.#rso = new ResizeObserver(this.#onDimensionsChanges);
25
- // document.addEventListener('scrollend', this.onConnected);
26
23
  }
27
24
  connect() {
28
25
  const placeholder = this.#shadowRoot.host.getAttribute('placeholder') || `Выбрать`;
@@ -41,6 +38,11 @@ export class ComboboxMarkup {
41
38
  const doc = document.importNode(innerTemplate.content, true);
42
39
  this.tagTemplate = doc.querySelector('box-tag');
43
40
  }
41
+ onHostConnected() {
42
+ const host = this.#shadowRoot.host;
43
+ const clearable = host.hasAttribute('clearable') || host.hasAttribute('multiple');
44
+ this.tagTemplate.toggleAttribute('visible', clearable);
45
+ }
44
46
  disconnect() {
45
47
  this.#shadowRoot.host.removeEventListener('focusin', this.onFocusIn);
46
48
  this.#shadowRoot.host.removeEventListener('focusout', this.onFocusOut);
@@ -79,7 +81,6 @@ export class ComboboxMarkup {
79
81
  sort(query) {
80
82
  clearTimeout(this.#timer);
81
83
  this.#timer = setTimeout(() => {
82
- // console.log(this.options)
83
84
  const regex = new RegExp(query.trim(), 'i');
84
85
  this.options.forEach((option) => {
85
86
  if (!regex.test(option.textContent)) {
@@ -129,8 +130,6 @@ export class ComboboxMarkup {
129
130
  return;
130
131
  this.#hostTop = this.#shadowRoot.host.getBoundingClientRect().top;
131
132
  try {
132
- this.dropdown.style.display = 'flex';
133
- // @ts-ignore
134
133
  this.dropdown.showPopover();
135
134
  this.#internals.ariaExpanded = "true";
136
135
  }
@@ -140,9 +139,7 @@ export class ComboboxMarkup {
140
139
  };
141
140
  closeDropdown() {
142
141
  try {
143
- // @ts-ignore
144
142
  this.dropdown.hidePopover();
145
- this.dropdown.style.display = 'none';
146
143
  this.#internals.ariaExpanded = "false";
147
144
  }
148
145
  catch (e) {
@@ -178,14 +175,12 @@ export class ComboboxMarkup {
178
175
  }
179
176
  else {
180
177
  const template = this.tagTemplate;
181
- // console.log(this)
182
178
  tag = template.cloneNode(true);
183
179
  const label = tag.querySelector('[part="tag-label"]');
184
180
  label.textContent = option.label || option.textContent;
185
181
  button = tag.querySelector('[part="clear-tag"]');
186
182
  }
187
183
  button?.setAttribute('value', value);
188
- // button.addEventListener('blur', e => e.stopPropagation());
189
184
  tag.setAttribute('value', value);
190
185
  this.tagsContainer.appendChild(tag);
191
186
  this.clearAllButton?.removeAttribute('disabled');
@@ -233,6 +228,7 @@ ${urls.map(url => `@import "${url}";`)}
233
228
  }
234
229
 
235
230
  #dropdown {
231
+ contain: layout style paint;
236
232
  inset: unset;
237
233
  margin: 0;
238
234
  /*position: absolute;*/
@@ -244,29 +240,30 @@ ${urls.map(url => `@import "${url}";`)}
244
240
  border-width: inherit;
245
241
  background-color: Canvas;
246
242
  outline: none;
247
-
248
- top: anchor(bottom);
243
+ display: none;
244
+ top: calc(anchor(bottom) + 2px);
249
245
  left: anchor(left);
250
246
  position-anchor: --position;
251
247
  position-try: normal flip-block;
252
248
  }
253
249
 
250
+ #dropdown:popover-open {
251
+ display: flex;
252
+ }
253
+
254
254
  #dropdown::-webkit-scrollbar {
255
255
  display: none;
256
256
  }
257
257
 
258
- [part="options"] {
258
+ #options {
259
259
  display: flex;
260
260
  flex-direction: column;
261
261
  justify-content: start;
262
262
  gap: 2px;
263
- /*padding: inherit;*/
264
- /*padding-block: .5rem;*/
265
- border-radius: inherit;
266
-
263
+ border-radius: inherit;
267
264
  }
268
265
 
269
- :host([filterable]) [part="options"], :host([searchable]) [part="options"] {
266
+ :host([filterable]) #options, :host([searchable]) #options {
270
267
  padding-top: .5rem;
271
268
  }
272
269
 
@@ -277,8 +274,6 @@ ${urls.map(url => `@import "${url}";`)}
277
274
  cursor: pointer;
278
275
  padding: inherit;
279
276
  padding-inline: .5lh;
280
- /*padding-inline: 2px;*/
281
- /*padding-block: 1px;*/
282
277
  }
283
278
 
284
279
  box-option:hover {
@@ -291,26 +286,17 @@ ${urls.map(url => `@import "${url}";`)}
291
286
  pointer-events: none;
292
287
  }
293
288
 
294
- [part="search-input"] {
289
+ #dropdown > [part="search-input"] {
295
290
  display: none;
296
291
  position: sticky;
297
292
  top: 0;
298
293
  z-index: 2;
299
- /*border-radius: inherit;*/
300
- /*border-style: inherit;*/
301
- /*border-width: inherit;*/
302
- /*border-color: inherit;*/
303
- /*padding: inherit;*/
304
- /*padding-inline: .4lh;*/
305
294
  font-size: inherit;
306
295
  outline: inherit;
307
- &::placeholder {
308
- color: var(--search-input-placeholder-color);
309
- }
310
296
  }
311
297
 
312
- :host([searchable]) [part="search-input"],
313
- :host([filterable]) [part="search-input"] {
298
+ :host([searchable]) #dropdown > [part="search-input"],
299
+ :host([filterable]) #dropdown > [part="search-input"] {
314
300
  display: flow-root;
315
301
  }
316
302
 
@@ -318,7 +304,6 @@ ${urls.map(url => `@import "${url}";`)}
318
304
  text-align: left;
319
305
  overflow: hidden;
320
306
  padding-inline-start: 2px;
321
- /*font-size: smaller;*/
322
307
  color: dimgrey;
323
308
  outline: none;
324
309
  user-select: none;
@@ -332,15 +317,12 @@ ${urls.map(url => `@import "${url}";`)}
332
317
  grid-column: 1 / span 2;
333
318
  width: 100%;
334
319
  }
335
-
336
-
337
320
 
338
321
  #tags {
339
322
  display: flex;
340
323
  flex-wrap: wrap;
341
324
  overflow: hidden;
342
325
  gap: 2px;
343
- /*border-radius: inherit;*/
344
326
  }
345
327
 
346
328
  box-tag {
@@ -349,13 +331,10 @@ ${urls.map(url => `@import "${url}";`)}
349
331
  box-sizing: border-box;
350
332
  display: flex;
351
333
  align-items: center;
352
- /*border-radius: inherit;*/
353
334
  padding-inline-start: 0.4lh;
354
335
  padding-inline-end: 0.2lh;
355
336
  background-color: transparent;
356
337
  gap: 5px;
357
- /*font-size: medium;*/
358
- text-transform: uppercase;
359
338
  border-radius: 1lh;
360
339
  }
361
340
 
@@ -365,31 +344,28 @@ ${urls.map(url => `@import "${url}";`)}
365
344
  max-width: 100%;
366
345
  }
367
346
 
368
- box-tag [part*="tag-label"] {
347
+ [part*="tag-label"] {
369
348
  white-space: nowrap;
370
349
  text-overflow: ellipsis;
371
350
  overflow: hidden;
372
351
  user-select: none;
373
- /*font-size: 95%;*/
374
352
  flex-grow: 1;
375
353
  }
376
354
 
377
- :host([multiple]) box-tag[part*="tag-label"] {
355
+ :host([multiple]) [part*="tag-label"] {
378
356
  flex-grow: unset;
379
357
  }
380
358
 
381
- [part*="clear-tag"], [part*="clear-all"] {
359
+ [part*="clear-tag"], #internal > [part*="clear-all"] {
382
360
  border-radius: 100%;
383
361
  border: none;
384
362
  aspect-ratio: 1;
385
- /*line-height: 0;*/
386
363
  padding: 0!important;
387
364
  user-select: none;
388
365
  background-color: transparent;
389
366
  }
390
367
 
391
368
  [part*="clear-tag"] {
392
- /*inline-size: 1lh;*/
393
369
  block-size: 1lh;
394
370
  font-size: .7lh;
395
371
  text-transform: uppercase;
@@ -401,7 +377,7 @@ ${urls.map(url => `@import "${url}";`)}
401
377
  outline-width: 1px;
402
378
  }
403
379
 
404
- :host([multiple]) [part*="clear-all"] {
380
+ :host([multiple]) #internal > [part*="clear-all"] {
405
381
  display: block;
406
382
  }
407
383
 
@@ -411,13 +387,10 @@ ${urls.map(url => `@import "${url}";`)}
411
387
  place-content: center;
412
388
  }
413
389
 
414
- [part*="clear-all"] {
390
+ #internal > [part*="clear-all"] {
415
391
  font-size: inherit;
416
392
  text-transform: uppercase;
417
- /*inline-size: 1lh;*/
418
393
  block-size: 1lh;
419
- /*inline-size: 1.2em;*/
420
- /*block-size: 1.2em;*/
421
394
  display: none;
422
395
  }
423
396
 
@@ -425,28 +398,20 @@ ${urls.map(url => `@import "${url}";`)}
425
398
  outline: none;
426
399
  }
427
400
 
428
- [part*="clear-all"]:hover,
401
+ #internal > [part*="clear-all"]:hover,
429
402
  [part*="clear-tag"]:hover {
430
403
  color: ActiveText;
431
404
  cursor: pointer;
432
405
  }
433
406
 
434
- [part*="clear-all"]:hover {
407
+ #internal >[part*="clear-all"]:hover {
435
408
  background-color: ButtonFace;
436
409
  }
437
410
 
438
- :host:has(#tags:empty) [part*="clear-all"] {
411
+ #tags:empty + [part*="clear-all"] {
439
412
  pointer-events: none;
440
413
  outline: none;
441
414
  }
442
-
443
- [part*="not-found"] {
444
- visibility: visible;
445
- }
446
-
447
- /*:host:has(#dropdown:popover-open) #dropdown {*/
448
- /* border-top-left-radius: 0;*/
449
- /*}*/
450
415
 
451
416
  </style>
452
417
  <div id='internal' part='internal'>
@@ -457,8 +422,9 @@ ${urls.map(url => `@import "${url}";`)}
457
422
 
458
423
  <div id="dropdown" popover="manual" part='dropdown' >
459
424
  <input placeholder='Поиск' name="search-input" part="search-input" inputmode='search' autofocus />
460
- <div part="options"></div>
425
+ <div part="options" id='options'></div>
461
426
  </div>
427
+
462
428
  <template id='tag-template'>
463
429
  <box-tag>
464
430
  <span part="tag-label"></span>
@@ -466,4 +432,10 @@ ${urls.map(url => `@import "${url}";`)}
466
432
  </box-tag>
467
433
  </template>
468
434
  `;
435
+ static markup;
436
+ static {
437
+ const template = document.createElement('template');
438
+ template.innerHTML = ComboboxMarkup.template;
439
+ ComboboxMarkup.markup = template;
440
+ }
469
441
  }
@@ -3,7 +3,7 @@ import { HTMLComboboxOptionElement } from './HTML.combobox.option.element.js';
3
3
  import { toBoolean } from './Boolean.attribute.value.normalizer.js';
4
4
  export class HTMLComboboxElement extends HTMLElement {
5
5
  static OWN_IDL = new Set(['required', 'disabled', 'clearable', 'multiple', 'filterable', 'searchable', 'value', 'placeholder', 'query']);
6
- static observerOptions = { childList: true, attributes: false, subtree: false };
6
+ static observerOptions = { childList: true };
7
7
  static styleSheet = [new CSSStyleSheet];
8
8
  static formAssociated = true;
9
9
  shadowRoot;
@@ -20,32 +20,51 @@ export class HTMLComboboxElement extends HTMLElement {
20
20
  this.#internals.ariaHasPopup = "dialog";
21
21
  this.shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); //delegatesFocus: true
22
22
  this.#markup = new ComboboxMarkup(this.shadowRoot, this.#internals);
23
- this.shadowRoot.innerHTML = ComboboxMarkup.template;
23
+ this.shadowRoot.appendChild(ComboboxMarkup.markup.content.cloneNode(true));
24
24
  this.shadowRoot.adoptedStyleSheets = HTMLComboboxElement.styleSheet;
25
25
  this.#observer = new MutationObserver(this.#onOptionsChanges);
26
26
  this.#markup.connect();
27
27
  }
28
28
  // Lifecycle callbacks
29
29
  connectedCallback() {
30
- // super.setAttribute('tabindex', '0');
30
+ for (const child of Array.from(this.children)) {
31
+ if (HTMLComboboxElement.#isOptionLike(child)) {
32
+ this.#markup.optionsContainer?.appendChild(child);
33
+ }
34
+ }
35
+ this.#markup.onHostConnected();
31
36
  this.shadowRoot.addEventListener('optionselectedstatechange', this.#onOptionSelectedStateChanges);
32
37
  this.shadowRoot.addEventListener('tagcleared', this.#onClearTag);
33
- this.#onOptionsChanges([{ addedNodes: Array.from(this.children) }]);
34
38
  this.#initialAttributesSynchronization();
35
- this.#observer.observe(this, HTMLComboboxElement.observerOptions);
39
+ this.#observer.observe(this.#markup.optionsContainer, HTMLComboboxElement.observerOptions);
36
40
  this.addEventListener('keydown', this.#onKeyDown);
37
41
  this.#markup.clearAllButton.addEventListener('click', this.#onClearAll);
38
42
  this.#markup.searchInput.addEventListener('input', this.#onInput);
39
43
  this.#markup.dropdown.addEventListener('toggle', this.#onPickerToggle);
40
44
  this.#setValidityAndFormValue();
41
- }
45
+ // Options/optgroups can end up as direct light-DOM children of <combo-box> without ever
46
+ // going through insertBefore/appendChild below: parsed straight from static HTML markup,
47
+ // or - notably - Solid's compiler, which renders static JSX subtrees via a single
48
+ // <template> + cloneNode(true), never calling any child-mutation method at all. Sweep
49
+ // whatever is already here once on connect and move it into the real options container.
50
+ }
51
+ static #isOptionLike(node) {
52
+ return node instanceof HTMLComboboxOptionElement || node instanceof HTMLOptGroupElement;
53
+ }
54
+ // Overwritten child-mutation methods.
55
+ //
56
+ // <box-option>/<optgroup> children don't actually live under <combo-box> itself - they're
57
+ // redirected into #markup.optionsContainer inside the shadow root, which is what the
58
+ // dropdown renders from and what #observer watches. Every native method a framework's
59
+ // renderer or plain user code might use to add/move/remove children needs to funnel through
60
+ // the same redirect, since browsers implement each of these independently (overriding one,
61
+ // e.g. insertBefore, does not make the others call it under the hood):
62
+ // - React calls appendChild for a plain append and insertBefore only when reordering.
63
+ // - Vue and Preact always call insertBefore, with a null reference for a plain append.
64
+ // - Solid mostly bypasses all of them for static subtrees (see connectedCallback above),
65
+ // but dynamic option lists still go through insert()/insertBefore().
42
66
  insertBefore = (node, referenceNode) => {
43
- // Since we just move options to the #markup.optionsContainer, they are no more part of this children
44
- // next time, when someone is calling this(combo-box).insertBefore, an error will be thrown:
45
- // Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
46
- // Here we overwrite this method, and if new node is HTMLComboboxOptionElement,
47
- // we just delegate this job to the real option parent: optionsContainer
48
- if (node instanceof HTMLComboboxOptionElement || node instanceof HTMLOptGroupElement) {
67
+ if (HTMLComboboxElement.#isOptionLike(node)) {
49
68
  this.#markup.optionsContainer?.insertBefore(node, referenceNode);
50
69
  }
51
70
  else {
@@ -53,6 +72,52 @@ export class HTMLComboboxElement extends HTMLElement {
53
72
  }
54
73
  return node;
55
74
  };
75
+ appendChild = (node) => {
76
+ if (HTMLComboboxElement.#isOptionLike(node)) {
77
+ this.#markup.optionsContainer?.appendChild(node);
78
+ }
79
+ else {
80
+ super.appendChild(node);
81
+ }
82
+ return node;
83
+ };
84
+ append = (...nodes) => {
85
+ nodes.forEach(node => this.appendChild(typeof node === 'string' ? new Text(node) : node));
86
+ };
87
+ prepend = (...nodes) => {
88
+ [...nodes].reverse().forEach(node => {
89
+ const n = typeof node === 'string' ? new Text(node) : node;
90
+ if (HTMLComboboxElement.#isOptionLike(n)) {
91
+ this.#markup.optionsContainer?.insertBefore(n, this.#markup.optionsContainer.firstChild);
92
+ }
93
+ else {
94
+ super.insertBefore(n, this.firstChild);
95
+ }
96
+ });
97
+ };
98
+ replaceChildren = (...nodes) => {
99
+ this.#markup.optionsContainer?.replaceChildren();
100
+ super.replaceChildren();
101
+ this.append(...nodes);
102
+ };
103
+ removeChild = (child) => {
104
+ if (child.parentNode === this.#markup.optionsContainer) {
105
+ this.#markup.optionsContainer.removeChild(child);
106
+ }
107
+ else if (child.parentNode === this) {
108
+ super.removeChild(child);
109
+ }
110
+ return child;
111
+ };
112
+ replaceChild = (newChild, oldChild) => {
113
+ if (oldChild.parentNode === this.#markup.optionsContainer) {
114
+ this.#markup.optionsContainer.replaceChild(newChild, oldChild);
115
+ }
116
+ else {
117
+ super.replaceChild(newChild, oldChild);
118
+ }
119
+ return oldChild;
120
+ };
56
121
  get changed() {
57
122
  return this.#changed;
58
123
  }
@@ -68,6 +133,7 @@ export class HTMLComboboxElement extends HTMLElement {
68
133
  }
69
134
  formResetCallback() {
70
135
  this.selectedOptions.forEach(option => option.selected = false);
136
+ this.#markup.tagsContainer?.replaceChildren();
71
137
  this.dispatchEvent(new Event('reset'));
72
138
  requestAnimationFrame(() => this.#markup.searchInput?.focus());
73
139
  }
@@ -180,11 +246,14 @@ export class HTMLComboboxElement extends HTMLElement {
180
246
  set value(value) {
181
247
  if (this.value === value || typeof value !== 'string')
182
248
  return;
183
- const values = value.split(',').filter(Boolean);
184
- this.#values = new Set(values);
249
+ const nextValues = new Set(value.split(',').filter(Boolean));
185
250
  this.#markup.options.forEach(option => {
186
- const value = option.value;
187
- option.selected = this.#values.has(value);
251
+ const optionValue = option.value;
252
+ // toggling `selected` drives #onOptionSelectedStateChanges, which is the
253
+ // single source of truth for #values and tags - don't pre-assign #values
254
+ // here, or the dedup logic there ends up deselecting the option it's in
255
+ // the middle of selecting (see: stale tag left behind on next selection)
256
+ option.selected = nextValues.has(optionValue);
188
257
  });
189
258
  }
190
259
  // Instance methods
@@ -233,9 +302,24 @@ export class HTMLComboboxElement extends HTMLElement {
233
302
  // Internal
234
303
  #onOptionsChanges = (records) => {
235
304
  records.forEach(record => {
236
- record.addedNodes.forEach(node => {
237
- if (node instanceof HTMLComboboxOptionElement || node instanceof HTMLOptGroupElement) {
238
- this.#markup.optionsContainer.append(node);
305
+ record.removedNodes.forEach(option => {
306
+ if (option instanceof HTMLComboboxOptionElement) {
307
+ const value = option.value;
308
+ // mode is searchable,
309
+ // user type "a", select a result,
310
+ // then types "b". Here previous options will be replaced with new ones,
311
+ // this.#values may contain "a", but there is no relevant option yet,
312
+ // so just wait
313
+ if (this.searchable && this.#internals.ariaExpanded === 'true') {
314
+ return;
315
+ }
316
+ // options was removed, but values still include this value,
317
+ // just clear, this is like a reset was called
318
+ if (this.#values.has(value) && !this.#markup.optionsContainer?.querySelector(`[value="${value}"]`)) {
319
+ this.#values.delete(value);
320
+ const control = this.#markup.getTagByValue(value);
321
+ control?.remove();
322
+ }
239
323
  }
240
324
  });
241
325
  });
@@ -247,11 +331,17 @@ export class HTMLComboboxElement extends HTMLElement {
247
331
  if (!this.multiple) {
248
332
  this.#values.forEach(value => {
249
333
  const prevOption = this.#markup.getOptionByValue(value);
250
- if (prevOption)
334
+ if (prevOption) {
251
335
  prevOption.selected = false;
336
+ }
337
+ else {
338
+ const tag = this.#markup.getTagByValue(value);
339
+ this.#values.delete(value);
340
+ tag?.remove();
341
+ }
252
342
  });
253
343
  }
254
- if (!this.#values.has(value)) {
344
+ if (!this.#values.has(value) || !this.#markup.getTagByValue(value)) {
255
345
  this.#values.add(value);
256
346
  this.#markup.createAndAppendTag(option);
257
347
  this.#setValidityAndFormValue();
@@ -266,14 +356,24 @@ export class HTMLComboboxElement extends HTMLElement {
266
356
  this.#setValidityAndFormValue();
267
357
  this.dispatchEvent(new Event('change'));
268
358
  }
359
+ else {
360
+ const tag = this.#markup.getTagByValue(value);
361
+ tag?.remove();
362
+ }
269
363
  }
270
364
  };
271
365
  #onClearTag = (event) => {
272
366
  const target = event.target;
273
367
  if (target) {
274
368
  const option = this.#markup.getOptionByValue(target.value);
275
- if (option)
369
+ if (option) {
276
370
  option.selected = false;
371
+ }
372
+ else {
373
+ const tag = this.#markup.getTagByValue(target.value);
374
+ this.#values.delete(target.value);
375
+ tag?.remove();
376
+ }
277
377
  requestAnimationFrame(() => {
278
378
  if (this.#values.size) {
279
379
  this.focus();
@@ -315,6 +415,7 @@ export class HTMLComboboxElement extends HTMLElement {
315
415
  if (activeElement === this.#markup.searchInput && event.key === 'Enter') {
316
416
  event.stopPropagation();
317
417
  if (!this.searchable && this.filterable) {
418
+ console.log('sss');
318
419
  this.#markup.sort(this.#markup.searchInput.value);
319
420
  }
320
421
  if (this.searchable) {
@@ -328,8 +429,14 @@ export class HTMLComboboxElement extends HTMLElement {
328
429
  return;
329
430
  }
330
431
  }
331
- if (event.key === 'Enter' && activeElement instanceof HTMLElement && activeElement.part.contains('clear-tag')) {
432
+ if (event.key === 'Enter' && activeElement.part.contains('clear-tag')) {
433
+ // @ts-ignore
434
+ // this.#onClearTag(event);
435
+ // event.stopPropagation();
436
+ // event.preventDefault()
437
+ // @ts-ignore
332
438
  activeElement.click();
439
+ // event.stopPropagation();
333
440
  return;
334
441
  }
335
442
  if (event.key === 'Escape' || event.key === 'Enter') {
@@ -362,7 +469,6 @@ export class HTMLComboboxElement extends HTMLElement {
362
469
  }
363
470
  #initialAttributesSynchronization() {
364
471
  for (const key of HTMLComboboxElement.OWN_IDL) {
365
- // @ts-ignore
366
472
  this[key] = this.getAttribute(key);
367
473
  }
368
474
  }
@@ -389,7 +495,7 @@ export class HTMLComboboxElement extends HTMLElement {
389
495
  }
390
496
  }
391
497
  }
392
- document.addEventListener('keypress', (event) => {
498
+ document.addEventListener('keypress', () => {
393
499
  if (document.activeElement instanceof HTMLComboboxElement) {
394
500
  const maybeHost = document.activeElement.shadowRoot.activeElement;
395
501
  if (maybeHost instanceof HTMLComboboxOptionElement) {
@@ -73,10 +73,12 @@ export class HTMLComboboxOptionElement extends HTMLElement {
73
73
  set selected(value) {
74
74
  const prev = this.selected;
75
75
  const force = toBoolean(value);
76
- if (prev === force)
76
+ if (prev === force) {
77
77
  return;
78
+ }
78
79
  super.toggleAttribute('selected', force);
79
80
  this.part.toggle('selected', force);
81
+ // console.log('this.isConnected', this.isConnected)
80
82
  if (this.isConnected) {
81
83
  this.dispatchEvent(new Event('optionselectedstatechange', HTMLComboboxOptionElement.EventInit));
82
84
  }
@@ -92,13 +94,11 @@ export class HTMLComboboxOptionElement extends HTMLElement {
92
94
  }
93
95
  #initialAttributesSynchronization() {
94
96
  for (const key of HTMLComboboxOptionElement.OWN_IDL) {
95
- // @ts-ignore
96
97
  this[key] = this.getAttribute(key);
97
98
  }
98
99
  }
99
100
  setAttribute(name, value) {
100
101
  if (HTMLComboboxOptionElement.OWN_IDL.has(name)) {
101
- // @ts-ignore
102
102
  this[name] = value;
103
103
  }
104
104
  else {
@@ -107,7 +107,6 @@ export class HTMLComboboxOptionElement extends HTMLElement {
107
107
  }
108
108
  removeAttribute(name) {
109
109
  if (HTMLComboboxOptionElement.OWN_IDL.has(name)) {
110
- // @ts-ignore
111
110
  this[name] = null;
112
111
  }
113
112
  else {
@@ -6,7 +6,7 @@ export class HTMLComboboxTagElement extends HTMLElement {
6
6
  connectedCallback() {
7
7
  this.part.add('tag');
8
8
  const control = this.querySelector('[part*="clear-tag"]');
9
- if (!control?.checkVisibility()) {
9
+ if (!this.hasAttribute('visible')) {
10
10
  super.setAttribute('tabindex', '0');
11
11
  }
12
12
  control?.addEventListener('focusout', HTMLComboboxTagElement.captureFocus);
package/dist/esm/index.js CHANGED
@@ -1,5 +1 @@
1
- /// <reference types="solid-js" />
2
- /// <reference types="preact" />
3
- /// <reference types="react" />
4
- /// <reference types="vue" />
5
1
  export * from './combobox/index.js';
@@ -11,6 +11,7 @@ export declare class ComboboxMarkup {
11
11
  tagTemplate: HTMLComboboxTagElement | null;
12
12
  constructor(shadowRoot: ShadowRoot, internals: ElementInternals);
13
13
  connect(): void;
14
+ onHostConnected(): void;
14
15
  disconnect(): void;
15
16
  setPlaceholder(value: string): void;
16
17
  setInputValue(value: string): void;
@@ -30,5 +31,6 @@ export declare class ComboboxMarkup {
30
31
  get selectedOptions(): NodeListOf<HTMLComboboxOptionElement>;
31
32
  static importCSS(urls: string[]): void;
32
33
  static template: string;
34
+ static markup: HTMLTemplateElement;
33
35
  }
34
36
  //# sourceMappingURL=Combobox.markup.d.ts.map