@threadlabs/looma 0.10.4 → 0.10.5

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.
@@ -26,7 +26,9 @@
26
26
  >The input's text, when the consumer controls it. Once set, the consumer owns the text: typing reports query-change, and the input returns to query unless
27
27
  query is updated to match. Leave it unset to let the combobox manage its own text, showing the chosen option's label.</prop>
28
28
  <prop name="allowFreeText" type="boolean" default="false">Allows values that do not match an authored option.</prop>
29
- <prop name="allowCreate" type="boolean" default="false">Offers the current query as a new option.</prop>
29
+ <prop name="allowCreate" type="boolean" default="false"
30
+ >Offers the current query as a new option, reported as create-item. When nothing else matches, the offer is highlighted, so Enter creates it; with nothing
31
+ highlighted, Enter in multiple mode commits the typed text as a token separator does.</prop>
30
32
  <prop name="disabled" type="boolean" default="false"
31
33
  >Disables the input and every button in the field: the chosen-item badges, the clear and disclosure buttons, and the help button. The list closes and cannot
32
34
  be opened, the value cannot be cleared, and a named combobox submits nothing with its form.</prop>
@@ -136,6 +136,9 @@ export default function controller(host) {
136
136
  // iterator row instead of the option objects it contains.
137
137
  host.state.rows = Array.from(groups.values()).flat();
138
138
  host.state.loading = false;
139
+ // When nothing matches what was typed, the offer to create it is the only choice, so it is
140
+ // the one Enter makes, and it is highlighted as such.
141
+ if (query && !host.state.rows.some((row) => !row.disabled) && canCreate()) host.state.active = host.state.rows.length;
139
142
  host.dispatch("options-change", host.state.rows);
140
143
  };
141
144
  applyOptions(current.options);
@@ -385,6 +388,9 @@ export default function controller(host) {
385
388
  else if (["Home", "End"].includes(event.key) && host.state.expanded && host.state.active >= 0) { event.preventDefault(); move(event.key); }
386
389
  else if (event.key === "Enter" && host.state.expanded) {
387
390
  if (host.state.active >= 0) { event.preventDefault(); choose(host.state.active, "keyboard"); }
391
+ // With nothing highlighted, Enter commits what was typed, as a token separator does.
392
+ else if (host.state.multiple && String(host.state.raw).trim()) { event.preventDefault(); commitQuery("keyboard"); }
393
+ else if (canCreate()) { event.preventDefault(); choose((host.state.rows ?? []).length, "keyboard"); }
388
394
  else if (config().allowFreeText) { event.preventDefault(); commit(null, host.state.raw, null, "free-entry", "keyboard"); close(); }
389
395
  }
390
396
  };
@@ -455,7 +461,17 @@ export default function controller(host) {
455
461
  input.value = host.state.display;
456
462
  resetValidation();
457
463
  });
458
- const observer = new MutationObserver(relabel);
464
+ // Options can change while the list is open, as when a consumer adds the option it just created;
465
+ // the open list shows them at once rather than at the next keystroke.
466
+ const optionsKey = () => JSON.stringify((config().options ?? []).map((row) => [row.value, row.label, row.disabled]));
467
+ let lastOptionsKey = optionsKey();
468
+ const observer = new MutationObserver(() => {
469
+ relabel();
470
+ const key = optionsKey();
471
+ if (key === lastOptionsKey) return;
472
+ lastOptionsKey = key;
473
+ if (host.state.expanded) search(fullSet ? "disclosure" : "options");
474
+ });
459
475
  observer.observe(authored, { childList: true, subtree: true, characterData: true, attributes: true });
460
476
  let lastItems = host.state.items;
461
477
  if (Array.isArray(lastItems)) host.state.internalItems = lastItems;
@@ -464,6 +480,12 @@ export default function controller(host) {
464
480
  if (host.state.items !== lastItems) {
465
481
  lastItems = host.state.items;
466
482
  if (Array.isArray(lastItems)) host.state.internalItems = lastItems;
483
+ // The list shows the selection; a consumer that adds or removes an item later (after creating
484
+ // it, say) is reflected at once rather than at the next search.
485
+ if (host.state.multiple && host.state.rows?.length) {
486
+ const selected = selectedValues();
487
+ host.state.rows = host.state.rows.map((row) => ({ ...row, selected: selected.has(row.value) }));
488
+ }
467
489
  }
468
490
  if (host.state.value !== lastValue) { lastValue = host.state.value; syncValue(); }
469
491
  if (host.state.query !== lastQuery) { lastQuery = host.state.query; syncQuery(); }