@threadlabs/looma 0.10.4 → 0.10.6
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/components/ui-combobox/ui-combobox.html +3 -1
- package/components/ui-combobox/ui-combobox.js +23 -1
- package/components/ui-tree-item/ui-tree-item.html +20 -6
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/styles/ui-tree-item.css +25 -6
- package/vue/UiTreeItem.vue +21 -6
- package/vue/chunks/UiTreeItem.js +6 -2
- package/vue/components.css +81 -68
|
@@ -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"
|
|
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
|
-
|
|
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(); }
|
|
@@ -380,19 +380,23 @@
|
|
|
380
380
|
}
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
+
/* The handle sits beside what it drags. A leaf row has no disclosure, so the handle takes that
|
|
384
|
+
empty column, right before the icon; a branch's disclosure is there, so its handle sits just
|
|
385
|
+
outside the row. It is bare until pointed at: a resting surface behind it read as a faint
|
|
386
|
+
patch rather than as a control. */
|
|
383
387
|
.drag-handle {
|
|
384
388
|
position: absolute;
|
|
385
389
|
z-index: 2;
|
|
386
390
|
inset-block-start: 50%;
|
|
387
|
-
inset-inline-start:
|
|
388
|
-
inline-size:
|
|
389
|
-
block-size:
|
|
391
|
+
inset-inline-start: 0;
|
|
392
|
+
inline-size: var(--_tree-disclosure-size);
|
|
393
|
+
block-size: 24px;
|
|
390
394
|
display: grid;
|
|
391
395
|
place-items: center;
|
|
392
|
-
padding:
|
|
396
|
+
padding: 5px 2px;
|
|
393
397
|
color: var(--ui-text-muted);
|
|
394
|
-
background:
|
|
395
|
-
border:
|
|
398
|
+
background: transparent;
|
|
399
|
+
border: 0;
|
|
396
400
|
border-radius: var(--ui-radius-sm);
|
|
397
401
|
cursor: grab;
|
|
398
402
|
/* Hidden until the row is hovered, but always hittable: the handle sits outside the row, and moving
|
|
@@ -409,6 +413,16 @@
|
|
|
409
413
|
opacity: 1;
|
|
410
414
|
}
|
|
411
415
|
|
|
416
|
+
:host-state([isContainer]) > .row > .drag-handle {
|
|
417
|
+
inset-inline-start: calc(-1 * var(--_tree-disclosure-size) - 2px);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.drag-handle:hover,
|
|
421
|
+
.drag-handle:focus-visible {
|
|
422
|
+
color: var(--ui-text-primary);
|
|
423
|
+
background: var(--ui-surface-hover);
|
|
424
|
+
}
|
|
425
|
+
|
|
412
426
|
.drag-handle:active {
|
|
413
427
|
cursor: grabbing;
|
|
414
428
|
}
|