mnfst 0.5.157 → 0.5.159
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/lib/manifest.combobox.css +14 -25
- package/lib/manifest.combobox.js +189 -16
- package/lib/manifest.css +24 -26
- package/lib/manifest.integrity.json +2 -2
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.table.css +10 -1
- package/lib/manifest.virtual.js +434 -190
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
@layer components {
|
|
4
4
|
|
|
5
|
-
/*
|
|
5
|
+
/* Wrapper */
|
|
6
6
|
:where(.combobox):not(.unstyle) {
|
|
7
7
|
display: flex;
|
|
8
8
|
flex-wrap: wrap;
|
|
@@ -40,16 +40,8 @@
|
|
|
40
40
|
pointer-events: none
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/* Inner typing surface
|
|
44
|
-
manifest.input.css, which sorts after this file in the bundled CSS and
|
|
45
|
-
would otherwise re-apply its own width/background/hover to the editor. */
|
|
43
|
+
/* Inner typing surface */
|
|
46
44
|
&> :where(input:not([type=hidden]), textarea):not(.unstyle) {
|
|
47
|
-
/* flex-basis (not min-width) sets the wrap threshold: the editor fills
|
|
48
|
-
the rest of the row, and once less than 7rem is left it wraps to its
|
|
49
|
-
own line. min-width:0 is REQUIRED — a flex item's default min-width:auto
|
|
50
|
-
is its content size, and WebKit enforces it by crushing the
|
|
51
|
-
flex-shrink:0 chips to an ellipsis. Resetting it to 0 lets the editor
|
|
52
|
-
shrink/wrap instead, so the chips keep their room. */
|
|
53
45
|
flex: 1 1 7rem;
|
|
54
46
|
min-width: 0;
|
|
55
47
|
width: auto;
|
|
@@ -75,7 +67,7 @@
|
|
|
75
67
|
/* Chips */
|
|
76
68
|
:where(.combobox-chip):not(.unstyle) {
|
|
77
69
|
display: inline-flex;
|
|
78
|
-
flex: 0 0 auto;
|
|
70
|
+
flex: 0 0 auto;
|
|
79
71
|
align-items: center;
|
|
80
72
|
gap: var(--spacing, 0.25rem);
|
|
81
73
|
max-width: 100%;
|
|
@@ -117,6 +109,12 @@
|
|
|
117
109
|
}
|
|
118
110
|
}
|
|
119
111
|
|
|
112
|
+
/* Non-removable */
|
|
113
|
+
&[data-locked] {
|
|
114
|
+
padding-inline-end: calc(var(--spacing, 0.25rem) * 2);
|
|
115
|
+
cursor: default
|
|
116
|
+
}
|
|
117
|
+
|
|
120
118
|
/* Failed validation */
|
|
121
119
|
&[aria-invalid="true"] {
|
|
122
120
|
color: var(--color-negative-inverse, oklch(44.4% 0.177 26.899));
|
|
@@ -124,9 +122,8 @@
|
|
|
124
122
|
}
|
|
125
123
|
}
|
|
126
124
|
|
|
127
|
-
/*
|
|
128
|
-
|
|
129
|
-
:where(.combobox) > button:not(.unstyle) {
|
|
125
|
+
/* Button trigger */
|
|
126
|
+
:where(.combobox)>button:not(.unstyle) {
|
|
130
127
|
flex: 1 1 auto;
|
|
131
128
|
align-self: stretch;
|
|
132
129
|
min-width: 7rem;
|
|
@@ -155,10 +152,7 @@
|
|
|
155
152
|
}
|
|
156
153
|
}
|
|
157
154
|
|
|
158
|
-
/* Chip-less
|
|
159
|
-
the field — it fills the whole wrapper and carries the padding, so the click
|
|
160
|
-
target, caret and text selection align with the field edges instead of
|
|
161
|
-
floating inside the wrapper's padding. */
|
|
155
|
+
/* Chip-less */
|
|
162
156
|
:where(.combobox):has(> :where(input:not([type=hidden]), textarea, button):not(.unstyle)):not(:has(.combobox-chip)) {
|
|
163
157
|
padding: 0;
|
|
164
158
|
|
|
@@ -169,15 +163,10 @@
|
|
|
169
163
|
}
|
|
170
164
|
}
|
|
171
165
|
|
|
172
|
-
/* Listbox
|
|
173
|
-
Selectors deliberately AVOID :where() on the option part so they out-specify
|
|
174
|
-
dropdown.css's `menu li { display: inline-flex }` (0,1,0), which sorts after
|
|
175
|
-
this file in the bundle and would otherwise keep filtered options visible. */
|
|
166
|
+
/* Listbox */
|
|
176
167
|
:where(menu[role=listbox]):not(.unstyle) {
|
|
177
168
|
|
|
178
|
-
/* Active descendant
|
|
179
|
-
On a mouse-opened menu the hover state alone highlights, so the first
|
|
180
|
-
option isn't left with a persistent background. */
|
|
169
|
+
/* Active descendant */
|
|
181
170
|
&[data-kbd] [role=option][aria-current="true"] {
|
|
182
171
|
color: var(--color-field-inverse, oklch(43.9% 0 0));
|
|
183
172
|
background-color: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent))
|
package/lib/manifest.combobox.js
CHANGED
|
@@ -81,6 +81,7 @@ function initializeComboboxPlugin() {
|
|
|
81
81
|
value: li.dataset.value != null ? li.dataset.value : li.textContent.trim(),
|
|
82
82
|
label: li.dataset.label || li.textContent.trim(),
|
|
83
83
|
pattern: li.dataset.pattern || null,
|
|
84
|
+
locked: li.hasAttribute('data-locked'),
|
|
84
85
|
html: li.innerHTML
|
|
85
86
|
}));
|
|
86
87
|
}
|
|
@@ -88,11 +89,53 @@ function initializeComboboxPlugin() {
|
|
|
88
89
|
value: o.value || o.textContent.trim(),
|
|
89
90
|
label: o.textContent.trim() || o.value,
|
|
90
91
|
pattern: o.getAttribute('data-pattern') || null,
|
|
92
|
+
locked: o.hasAttribute('data-locked'),
|
|
91
93
|
html: null
|
|
92
94
|
}));
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
// Take ownership of x-model: capture the expression and strip the attribute so
|
|
98
|
+
// Alpine's own model directive never binds the editor. In chips mode the editor is
|
|
99
|
+
// a transient typing buffer — Alpine's x-model would dump the bound array into it
|
|
100
|
+
// and write partial typing back to the model. We read/write the model ourselves.
|
|
101
|
+
function captureModel(el) {
|
|
102
|
+
if (el.__cbModelExpr !== undefined) return;
|
|
103
|
+
let attr = null;
|
|
104
|
+
for (const a of Array.from(el.attributes)) {
|
|
105
|
+
if (a.name === 'x-model' || a.name.indexOf('x-model.') === 0) { attr = a.name; break; }
|
|
106
|
+
}
|
|
107
|
+
el.__cbModelExpr = attr ? el.getAttribute(attr) : null;
|
|
108
|
+
if (attr) el.removeAttribute(attr);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// NB: match by attribute-name prefix, not the `[x-combobox]` CSS selector — the
|
|
112
|
+
// directive is almost always written with modifiers (x-combobox.multiple.chips),
|
|
113
|
+
// a literal attribute name that `[x-combobox]` does NOT match.
|
|
114
|
+
function isComboboxEl(el) {
|
|
115
|
+
if (!el.attributes) return false;
|
|
116
|
+
for (const a of el.attributes) if (a.name === 'x-combobox' || a.name.indexOf('x-combobox.') === 0) return true;
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
function stripModels(root) {
|
|
120
|
+
if (!root || root.nodeType !== 1) return;
|
|
121
|
+
if (isComboboxEl(root)) captureModel(root);
|
|
122
|
+
const all = root.getElementsByTagName ? root.getElementsByTagName('*') : [];
|
|
123
|
+
for (let i = 0; i < all.length; i++) if (isComboboxEl(all[i])) captureModel(all[i]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Strip x-model before Alpine ever binds it. The initial static DOM is handled here
|
|
127
|
+
// (this runs on alpine:init, before the walk); subtrees mounted later — x-markdown,
|
|
128
|
+
// components — are handled by wrapping the public initTree they call to mount, so the
|
|
129
|
+
// attribute is gone before Alpine's model directive looks for it.
|
|
130
|
+
stripModels(document.body);
|
|
131
|
+
if (typeof Alpine.initTree === 'function' && !Alpine.__cbInitTreeWrapped) {
|
|
132
|
+
Alpine.__cbInitTreeWrapped = true;
|
|
133
|
+
const origInitTree = Alpine.initTree.bind(Alpine);
|
|
134
|
+
Alpine.initTree = (node, ...rest) => { stripModels(node); return origInitTree(node, ...rest); };
|
|
135
|
+
}
|
|
136
|
+
|
|
95
137
|
Alpine.directive('combobox', (el, { modifiers, expression }, { cleanup }) => {
|
|
138
|
+
captureModel(el); // fallback for any path that bypasses the above
|
|
96
139
|
// Build after the current tick so sibling sources (datalist/menu) exist.
|
|
97
140
|
setTimeout(() => build(el, modifiers, expression || '', cleanup), 0);
|
|
98
141
|
});
|
|
@@ -101,6 +144,25 @@ function initializeComboboxPlugin() {
|
|
|
101
144
|
if (el.__mnfstCombobox) return;
|
|
102
145
|
el.__mnfstCombobox = true;
|
|
103
146
|
|
|
147
|
+
// If a mount path bypassed the pre-emptive strip (x-if / x-for mount via Alpine's
|
|
148
|
+
// INTERNAL initTree, which our public-initTree wrapper can't see) and Alpine's
|
|
149
|
+
// native x-model already bound the editor, neutralize it here. Otherwise its
|
|
150
|
+
// value-sync would bleed the raw model into the editor ("a,b") and its input
|
|
151
|
+
// listener would write partial typing back to the model. We own read/write below,
|
|
152
|
+
// so make both native paths no-ops. captureModel still recovered the expression.
|
|
153
|
+
if (el._x_model) {
|
|
154
|
+
el._x_forceModelUpdate = function () { }; // kill the model→editor value-sync (no bleed)
|
|
155
|
+
// Remove Alpine's input/change listener that writes the editor back to the model
|
|
156
|
+
// (a local closure — overriding _x_model.set isn't enough). _x_removeModelListeners
|
|
157
|
+
// is Alpine's own removal hook for exactly this.
|
|
158
|
+
try {
|
|
159
|
+
const rm = el._x_removeModelListeners;
|
|
160
|
+
if (rm) Object.keys(rm).forEach(k => { try { rm[k](); } catch (_) { } });
|
|
161
|
+
} catch (_) { }
|
|
162
|
+
el._x_model.set = function () { }; // extra safety for any path that calls it
|
|
163
|
+
if (el.value) el.value = '';
|
|
164
|
+
}
|
|
165
|
+
|
|
104
166
|
// Sweep generated menus left orphaned by a prior render — their controlling
|
|
105
167
|
// editor is gone (a SPA x-markdown re-render) or never hydrated (duplicates
|
|
106
168
|
// an old prerender baked into <body>). Either way, no connected editor points
|
|
@@ -198,6 +260,41 @@ function initializeComboboxPlugin() {
|
|
|
198
260
|
let selected = adopt && seedSelected ? seedSelected : [];
|
|
199
261
|
const isSelected = (v) => selected.some(s => String(s.value).toLowerCase() === String(v).toLowerCase());
|
|
200
262
|
const atCap = () => multiple && selected.length >= max;
|
|
263
|
+
const labelFor = (v) => {
|
|
264
|
+
const o = options.find(o => String(o.value).toLowerCase() === String(v).toLowerCase());
|
|
265
|
+
return o ? o.label : String(v);
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// ----- Locked values (non-removable chips) -----
|
|
269
|
+
// From a `locked: [...]` config list (re-evaluated reactively below) and/or a
|
|
270
|
+
// `data-locked` flag on individual options. Locked chips keep their × hidden and
|
|
271
|
+
// refuse removal while staying selected.
|
|
272
|
+
const lockedFromOptions = options.filter(o => o.locked).map(o => String(o.value).toLowerCase());
|
|
273
|
+
const computeLocked = (cfgObj) => {
|
|
274
|
+
const set = new Set(lockedFromOptions);
|
|
275
|
+
const raw = cfgObj && cfgObj.locked != null ? cfgObj.locked : cfg.locked;
|
|
276
|
+
const list = Array.isArray(raw) ? raw : (raw != null ? String(raw).split(',') : []);
|
|
277
|
+
list.forEach(v => set.add(String(v).trim().toLowerCase()));
|
|
278
|
+
return set;
|
|
279
|
+
};
|
|
280
|
+
let lockedSet = computeLocked();
|
|
281
|
+
const isLocked = (v) => lockedSet.has(String(v).toLowerCase());
|
|
282
|
+
|
|
283
|
+
// ----- Reactive model (x-model) — captured + stripped pre-walk, owned here -----
|
|
284
|
+
// The bound value may be a token array or a CSV string; we preserve whichever the
|
|
285
|
+
// author used (default array for .multiple). Chips render labels; the model carries
|
|
286
|
+
// values/tokens. Read/effect/set are wired near mount, once render() exists.
|
|
287
|
+
const modelExpr = el.__cbModelExpr || null;
|
|
288
|
+
let modelArrayShape = null; // null until first read; true = array, false = CSV
|
|
289
|
+
let modelSet = null;
|
|
290
|
+
const modelToValues = (v) => {
|
|
291
|
+
if (v == null || v === '') return [];
|
|
292
|
+
if (Array.isArray(v)) return v.map(x => (x && typeof x === 'object' && x.value != null) ? x.value : x).map(String);
|
|
293
|
+
if (typeof v === 'string') return v.split(/[,\n;]+/).map(s => s.trim()).filter(Boolean);
|
|
294
|
+
return [String(v)];
|
|
295
|
+
};
|
|
296
|
+
const sameList = (a, b) => a.length === b.length && a.every((x, i) => String(x).toLowerCase() === String(b[i]).toLowerCase());
|
|
297
|
+
function syncOut() { if (modelSet) modelSet(selected.map(s => s.value)); }
|
|
201
298
|
|
|
202
299
|
// ----- Menu -----
|
|
203
300
|
let menu = null, generatedMenu = null, optionEls = [], createEl = null, emptyEl = null, activeIndex = -1;
|
|
@@ -421,18 +518,21 @@ function initializeComboboxPlugin() {
|
|
|
421
518
|
function refocus() { suppressOpen = true; el.focus(); setTimeout(() => { suppressOpen = false; }, 0); }
|
|
422
519
|
|
|
423
520
|
function addValue(value, label) {
|
|
424
|
-
if (!multiple) { selected = [{ value, label }]; render(); announce(label + ' selected'); return; }
|
|
521
|
+
if (!multiple) { selected = [{ value, label }]; render(); syncOut(); announce(label + ' selected'); return; }
|
|
425
522
|
if (isSelected(value)) return;
|
|
426
523
|
if (selected.length >= max) { announce('Maximum of ' + max + ' reached'); return; }
|
|
427
524
|
selected.push({ value, label });
|
|
428
525
|
render();
|
|
526
|
+
syncOut();
|
|
429
527
|
announce(label + ' added');
|
|
430
528
|
}
|
|
431
529
|
function removeValue(value) {
|
|
530
|
+
if (isLocked(value)) return; // locked chips stay put
|
|
432
531
|
const i = selected.findIndex(s => String(s.value).toLowerCase() === String(value).toLowerCase());
|
|
433
532
|
if (i < 0) return;
|
|
434
533
|
const [g] = selected.splice(i, 1);
|
|
435
534
|
render();
|
|
535
|
+
syncOut();
|
|
436
536
|
announce(g.label + ' removed');
|
|
437
537
|
if (menu && !isAsync) filter();
|
|
438
538
|
refocus();
|
|
@@ -444,13 +544,24 @@ function initializeComboboxPlugin() {
|
|
|
444
544
|
chip.dataset.value = s.value;
|
|
445
545
|
const label = document.createElement('span');
|
|
446
546
|
label.textContent = s.label;
|
|
547
|
+
chip.appendChild(label);
|
|
548
|
+
applyLock(chip, s.value, s.label);
|
|
549
|
+
return chip;
|
|
550
|
+
}
|
|
551
|
+
// Add or drop the × to match the value's locked state. Re-run from render() so a
|
|
552
|
+
// reactive `locked` change toggles the affordance on existing chips too.
|
|
553
|
+
function applyLock(chip, value, label) {
|
|
554
|
+
const locked = isLocked(value);
|
|
555
|
+
chip.toggleAttribute('data-locked', locked);
|
|
556
|
+
const btn = chip.querySelector(':scope > button');
|
|
557
|
+
if (locked) { if (btn) btn.remove(); return; }
|
|
558
|
+
if (btn) return;
|
|
447
559
|
const x = document.createElement('button');
|
|
448
560
|
x.type = 'button';
|
|
449
|
-
x.setAttribute('aria-label', 'Remove ' +
|
|
561
|
+
x.setAttribute('aria-label', 'Remove ' + (label || (chip.querySelector(':scope > span') || {}).textContent || value));
|
|
450
562
|
x.textContent = '×';
|
|
451
|
-
x.addEventListener('click', () => removeValue(
|
|
452
|
-
chip.
|
|
453
|
-
return chip;
|
|
563
|
+
x.addEventListener('click', () => removeValue(value));
|
|
564
|
+
chip.appendChild(x);
|
|
454
565
|
}
|
|
455
566
|
function hidden(value) {
|
|
456
567
|
const h = document.createElement('input');
|
|
@@ -463,17 +574,27 @@ function initializeComboboxPlugin() {
|
|
|
463
574
|
|
|
464
575
|
function render() {
|
|
465
576
|
if (chips) {
|
|
466
|
-
// Incremental: keep existing chip nodes, only add new
|
|
467
|
-
// Recreating every chip re-inserts them next to the
|
|
468
|
-
// collapses them to an ellipsis in WebKit; untouched
|
|
577
|
+
// Incremental: keep existing chip nodes, only add new / drop removed /
|
|
578
|
+
// refresh locked state. Recreating every chip re-inserts them next to the
|
|
579
|
+
// focused editor, which collapses them to an ellipsis in WebKit; untouched
|
|
580
|
+
// nodes keep their width.
|
|
581
|
+
const norm = v => String(v).toLowerCase();
|
|
469
582
|
const have = new Map();
|
|
470
|
-
wrap.querySelectorAll('.combobox-chip').forEach(c => have.set(
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
});
|
|
583
|
+
wrap.querySelectorAll('.combobox-chip').forEach(c => have.set(norm(c.dataset.value), c));
|
|
584
|
+
const want = selected.map(s => norm(s.value));
|
|
585
|
+
const wantSet = new Set(want);
|
|
586
|
+
have.forEach((node, key) => { if (!wantSet.has(key)) { node.remove(); have.delete(key); } });
|
|
474
587
|
selected.forEach(s => {
|
|
475
|
-
|
|
588
|
+
const key = norm(s.value);
|
|
589
|
+
const node = have.get(key);
|
|
590
|
+
if (!node) { const n = makeChip(s); have.set(key, n); wrap.insertBefore(n, el); }
|
|
591
|
+
else applyLock(node, s.value, s.label);
|
|
476
592
|
});
|
|
593
|
+
// Reorder to match selection only when it actually differs — needless
|
|
594
|
+
// re-insertion collapses chips in WebKit while the editor is focused, so the
|
|
595
|
+
// common append path (order already matches) skips this.
|
|
596
|
+
const cur = Array.from(wrap.querySelectorAll('.combobox-chip')).map(c => norm(c.dataset.value));
|
|
597
|
+
if (!sameList(cur, want)) selected.forEach(s => wrap.insertBefore(have.get(norm(s.value)), el));
|
|
477
598
|
// Hidden inputs are type=hidden (no layout), so a clean rebuild is harmless.
|
|
478
599
|
wrap.querySelectorAll('input[data-cb]').forEach(n => n.remove());
|
|
479
600
|
if (name) selected.forEach(s => wrap.appendChild(hidden(s.value)));
|
|
@@ -597,9 +718,61 @@ function initializeComboboxPlugin() {
|
|
|
597
718
|
// a stale, sometimes-open duplicate. Authored/adopted menus go with their container.
|
|
598
719
|
if (cleanup && generatedMenu) cleanup(() => { if (generatedMenu.isConnected) generatedMenu.remove(); });
|
|
599
720
|
|
|
600
|
-
// -----
|
|
601
|
-
//
|
|
602
|
-
|
|
721
|
+
// ----- Reactive model (x-model) -----
|
|
722
|
+
// Renders chips from the bound value on init and whenever it changes externally
|
|
723
|
+
// (e.g. switching which record is being edited); writes back on add/remove. The
|
|
724
|
+
// read runs inside an Alpine effect so $x / nested state stay reactive.
|
|
725
|
+
if (modelExpr) {
|
|
726
|
+
const read = Alpine.evaluateLater(el, modelExpr);
|
|
727
|
+
modelSet = (vals) => {
|
|
728
|
+
const out = !multiple ? (vals.length ? vals[0] : '')
|
|
729
|
+
: (modelArrayShape === false ? vals.join(',') : vals.slice());
|
|
730
|
+
try { Alpine.evaluate(el, `${modelExpr} = ${JSON.stringify(out)}`); } catch (_) { }
|
|
731
|
+
};
|
|
732
|
+
Alpine.effect(() => {
|
|
733
|
+
read(raw => {
|
|
734
|
+
if (modelArrayShape === null && raw != null && raw !== '') modelArrayShape = Array.isArray(raw);
|
|
735
|
+
const incoming = modelToValues(raw);
|
|
736
|
+
if (sameList(incoming, selected.map(s => s.value))) return; // unchanged / our own write-back
|
|
737
|
+
selected = incoming.map(v => ({ value: v, label: labelFor(v) }));
|
|
738
|
+
if (!multiple && selected.length > 1) selected = selected.slice(-1);
|
|
739
|
+
render();
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// ----- Reactive locked list (config-object form re-evaluates, so authors can
|
|
745
|
+
// gate it on state, e.g. last-owner: locked: owners.length <= 1 ? ['owner'] : []) -----
|
|
746
|
+
if (expr.startsWith('{')) {
|
|
747
|
+
Alpine.effect(() => {
|
|
748
|
+
let c; try { c = Alpine.evaluate(el, expr); } catch (_) { return; }
|
|
749
|
+
const next = computeLocked(c && typeof c === 'object' && !Array.isArray(c) ? c : null);
|
|
750
|
+
if (next.size === lockedSet.size && [...next].every(v => lockedSet.has(v))) return;
|
|
751
|
+
lockedSet = next;
|
|
752
|
+
render();
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// ----- Dynamic options: re-read when x-for / $x fills the source list after
|
|
757
|
+
// build, so the menu, chip labels, and data-locked flags stay current. -----
|
|
758
|
+
if (src) {
|
|
759
|
+
const reread = () => {
|
|
760
|
+
options = readOptions(src);
|
|
761
|
+
lockedFromOptions.length = 0;
|
|
762
|
+
options.filter(o => o.locked).forEach(o => lockedFromOptions.push(String(o.value).toLowerCase()));
|
|
763
|
+
lockedSet = computeLocked();
|
|
764
|
+
selected = selected.map(s => ({ value: s.value, label: labelFor(s.value) }));
|
|
765
|
+
if (generatedMenu) setOptions(options);
|
|
766
|
+
render();
|
|
767
|
+
};
|
|
768
|
+
const mo = new MutationObserver(reread);
|
|
769
|
+
mo.observe(src, { childList: true, subtree: true, characterData: true });
|
|
770
|
+
if (cleanup) cleanup(() => mo.disconnect());
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// ----- Seed initial chips from value="a, b" — only when there's no x-model
|
|
774
|
+
// (x-model wins) and no adopted wrapper (which already seeded). -----
|
|
775
|
+
if (!modelExpr && !adopt && !editorNone && chips && el.value) {
|
|
603
776
|
const seeds = el.value.split(/[,\n;]+/).map(s => s.trim()).filter(Boolean);
|
|
604
777
|
el.value = '';
|
|
605
778
|
seeds.forEach(s => commitText(s));
|
package/lib/manifest.css
CHANGED
|
@@ -1460,7 +1460,7 @@
|
|
|
1460
1460
|
|
|
1461
1461
|
@layer components {
|
|
1462
1462
|
|
|
1463
|
-
/*
|
|
1463
|
+
/* Wrapper */
|
|
1464
1464
|
:where(.combobox):not(.unstyle) {
|
|
1465
1465
|
display: flex;
|
|
1466
1466
|
flex-wrap: wrap;
|
|
@@ -1498,16 +1498,8 @@
|
|
|
1498
1498
|
pointer-events: none
|
|
1499
1499
|
}
|
|
1500
1500
|
|
|
1501
|
-
/* Inner typing surface
|
|
1502
|
-
manifest.input.css, which sorts after this file in the bundled CSS and
|
|
1503
|
-
would otherwise re-apply its own width/background/hover to the editor. */
|
|
1501
|
+
/* Inner typing surface */
|
|
1504
1502
|
&> :where(input:not([type=hidden]), textarea):not(.unstyle) {
|
|
1505
|
-
/* flex-basis (not min-width) sets the wrap threshold: the editor fills
|
|
1506
|
-
the rest of the row, and once less than 7rem is left it wraps to its
|
|
1507
|
-
own line. min-width:0 is REQUIRED — a flex item's default min-width:auto
|
|
1508
|
-
is its content size, and WebKit enforces it by crushing the
|
|
1509
|
-
flex-shrink:0 chips to an ellipsis. Resetting it to 0 lets the editor
|
|
1510
|
-
shrink/wrap instead, so the chips keep their room. */
|
|
1511
1503
|
flex: 1 1 7rem;
|
|
1512
1504
|
min-width: 0;
|
|
1513
1505
|
width: auto;
|
|
@@ -1533,7 +1525,7 @@
|
|
|
1533
1525
|
/* Chips */
|
|
1534
1526
|
:where(.combobox-chip):not(.unstyle) {
|
|
1535
1527
|
display: inline-flex;
|
|
1536
|
-
flex: 0 0 auto;
|
|
1528
|
+
flex: 0 0 auto;
|
|
1537
1529
|
align-items: center;
|
|
1538
1530
|
gap: var(--spacing, 0.25rem);
|
|
1539
1531
|
max-width: 100%;
|
|
@@ -1575,6 +1567,12 @@
|
|
|
1575
1567
|
}
|
|
1576
1568
|
}
|
|
1577
1569
|
|
|
1570
|
+
/* Non-removable */
|
|
1571
|
+
&[data-locked] {
|
|
1572
|
+
padding-inline-end: calc(var(--spacing, 0.25rem) * 2);
|
|
1573
|
+
cursor: default
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1578
1576
|
/* Failed validation */
|
|
1579
1577
|
&[aria-invalid="true"] {
|
|
1580
1578
|
color: var(--color-negative-inverse, oklch(44.4% 0.177 26.899));
|
|
@@ -1582,9 +1580,8 @@
|
|
|
1582
1580
|
}
|
|
1583
1581
|
}
|
|
1584
1582
|
|
|
1585
|
-
/*
|
|
1586
|
-
|
|
1587
|
-
:where(.combobox) > button:not(.unstyle) {
|
|
1583
|
+
/* Button trigger */
|
|
1584
|
+
:where(.combobox)>button:not(.unstyle) {
|
|
1588
1585
|
flex: 1 1 auto;
|
|
1589
1586
|
align-self: stretch;
|
|
1590
1587
|
min-width: 7rem;
|
|
@@ -1613,10 +1610,7 @@
|
|
|
1613
1610
|
}
|
|
1614
1611
|
}
|
|
1615
1612
|
|
|
1616
|
-
/* Chip-less
|
|
1617
|
-
the field — it fills the whole wrapper and carries the padding, so the click
|
|
1618
|
-
target, caret and text selection align with the field edges instead of
|
|
1619
|
-
floating inside the wrapper's padding. */
|
|
1613
|
+
/* Chip-less */
|
|
1620
1614
|
:where(.combobox):has(> :where(input:not([type=hidden]), textarea, button):not(.unstyle)):not(:has(.combobox-chip)) {
|
|
1621
1615
|
padding: 0;
|
|
1622
1616
|
|
|
@@ -1627,15 +1621,10 @@
|
|
|
1627
1621
|
}
|
|
1628
1622
|
}
|
|
1629
1623
|
|
|
1630
|
-
/* Listbox
|
|
1631
|
-
Selectors deliberately AVOID :where() on the option part so they out-specify
|
|
1632
|
-
dropdown.css's `menu li { display: inline-flex }` (0,1,0), which sorts after
|
|
1633
|
-
this file in the bundle and would otherwise keep filtered options visible. */
|
|
1624
|
+
/* Listbox */
|
|
1634
1625
|
:where(menu[role=listbox]):not(.unstyle) {
|
|
1635
1626
|
|
|
1636
|
-
/* Active descendant
|
|
1637
|
-
On a mouse-opened menu the hover state alone highlights, so the first
|
|
1638
|
-
option isn't left with a persistent background. */
|
|
1627
|
+
/* Active descendant */
|
|
1639
1628
|
&[data-kbd] [role=option][aria-current="true"] {
|
|
1640
1629
|
color: var(--color-field-inverse, oklch(43.9% 0 0));
|
|
1641
1630
|
background-color: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent))
|
|
@@ -3861,8 +3850,13 @@
|
|
|
3861
3850
|
|
|
3862
3851
|
@layer components {
|
|
3863
3852
|
|
|
3853
|
+
:where(.grid-table):not(.unstyle) {
|
|
3854
|
+
display: grid;
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3864
3857
|
:where(table, .grid-table):not(.unstyle) {
|
|
3865
3858
|
table-layout: auto;
|
|
3859
|
+
align-items: start;
|
|
3866
3860
|
width: 100%;
|
|
3867
3861
|
max-width: 100%;
|
|
3868
3862
|
overflow: hidden;
|
|
@@ -3896,7 +3890,6 @@
|
|
|
3896
3890
|
font-size: 0.875rem;
|
|
3897
3891
|
text-align: left;
|
|
3898
3892
|
text-align: start;
|
|
3899
|
-
overflow: hidden;
|
|
3900
3893
|
|
|
3901
3894
|
/* Make elements within cell inline */
|
|
3902
3895
|
&>*:not(template) {
|
|
@@ -3910,6 +3903,11 @@
|
|
|
3910
3903
|
}
|
|
3911
3904
|
}
|
|
3912
3905
|
|
|
3906
|
+
/* Native cells clip */
|
|
3907
|
+
:where(td, th) {
|
|
3908
|
+
overflow: hidden
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3913
3911
|
/* Footer row */
|
|
3914
3912
|
:where(:not(:has(tfoot)) tbody tr:last-child, tfoot tr:last-child, .grid-footer > *) {
|
|
3915
3913
|
border-bottom: 0
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"manifest.code.js": "sha384-BJSRJ7txA6fY660qX+SfEEBrEB0dOChHdHI4/9iNGz3rGqohEZdzs7qA3LuR1GJW",
|
|
7
7
|
"manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
|
|
8
8
|
"manifest.colorpicker.js": "sha384-Wqz0ZIbeIi7KarqqqSLsQk+7E/fMaKhb32hrq5/eWzX1yjqMrpPZKH8y+jZ3mfg+",
|
|
9
|
-
"manifest.combobox.js": "sha384-
|
|
9
|
+
"manifest.combobox.js": "sha384-1Wf4gcfBpct8PjDUScjBFnid7prfcQUZZOftruze2KPqGOVjqsOOWoG1rRDK7pkO",
|
|
10
10
|
"manifest.components.js": "sha384-mzPFoM0vqL9dnTVLMN3OrmO+KCgSqGknM1fd7bM1xzYeCco5OaZi56IMR5RS5oad",
|
|
11
11
|
"manifest.data.js": "sha384-bCYTYyAYNVkg5pSwGcoe07Dgf5B7JDN7GtOIQdS+BtrBStQwvjZtskiQ38Bncvrf",
|
|
12
12
|
"manifest.datepicker.js": "sha384-NEb/H4vuR3CFtRcodHsm3jJjrcYW2JMpDlQKlgwTrzpMMTcDkFKYXzAYJD0gZ7Ov",
|
|
@@ -27,6 +27,6 @@
|
|
|
27
27
|
"manifest.tooltips.js": "sha384-ADzAx9D0HWq2b46mvNG05iOwPmEWdiFZNpEOXONSbBxs4xj1B/bzNL7S3x2R9cS1",
|
|
28
28
|
"manifest.url.parameters.js": "sha384-FIufiClqDx1rJpU/QUc9z/D43qClQ6Qm8rBahipbJl9BDHUvhrOsUDegmTWW7Tuf",
|
|
29
29
|
"manifest.utilities.js": "sha384-HWyVkjQoDRlWFKDBQw4RQOYODkBcU72NHW6l1p4bhQv1RtN0/XtnjwIb+lQK6+zv",
|
|
30
|
-
"manifest.virtual.js": "sha384-
|
|
30
|
+
"manifest.virtual.js": "sha384-klEAlDCSGuzMZmO7NMSckKVeEJRR9UGWCesoPOOypX78EcPJ1/TUBAXz3MeSGx2s",
|
|
31
31
|
"manifest.js": "sha384-zPXrym9jwpYVdg4TJ1XPQVxQhz7uezdDubaO/MZDvLeiTufor+6lNJsTG75cYPXm"
|
|
32
32
|
}
|