@adia-ai/web-components 0.0.18 → 0.0.20
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/accordion/accordion.css +101 -102
- package/components/agent-feedback-bar/agent-feedback-bar.js +8 -8
- package/components/agent-questions/agent-questions.css +2 -1
- package/components/agent-questions/agent-questions.js +6 -6
- package/components/agent-reasoning/agent-reasoning.js +20 -5
- package/components/agent-trace/agent-trace.a2ui.json +5 -5
- package/components/agent-trace/agent-trace.js +7 -5
- package/components/agent-trace/agent-trace.yaml +2 -2
- package/components/alert/alert.a2ui.json +1 -2
- package/components/alert/alert.css +4 -4
- package/components/alert/alert.yaml +1 -2
- package/components/avatar/avatar.a2ui.json +3 -3
- package/components/avatar/avatar.js +10 -0
- package/components/avatar/avatar.yaml +6 -6
- package/components/button/button.a2ui.json +14 -2
- package/components/button/button.css +19 -2
- package/components/button/button.js +1 -0
- package/components/button/button.yaml +20 -2
- package/components/calendar-picker/calendar-picker.css +2 -1
- package/components/calendar-picker/calendar-picker.js +12 -1
- package/components/chart/chart.css +11 -11
- package/components/chart/chart.js +26 -18
- package/components/chart-legend/chart-legend.a2ui.json +2 -2
- package/components/chart-legend/chart-legend.js +4 -1
- package/components/chart-legend/chart-legend.yaml +2 -2
- package/components/chat/chat-input.js +13 -5
- package/components/chat/chat.a2ui.json +2 -2
- package/components/chat/chat.js +14 -3
- package/components/chat/chat.yaml +2 -2
- package/components/code/code.css +16 -6
- package/components/command/command.js +9 -1
- package/components/field/field.a2ui.json +0 -5
- package/components/field/field.css +2 -2
- package/components/field/field.js +53 -5
- package/components/field/field.yaml +5 -8
- package/components/heatmap/heatmap.css +32 -23
- package/components/input/input.js +30 -1
- package/components/kbd/kbd.a2ui.json +5 -1
- package/components/kbd/kbd.yaml +5 -1
- package/components/menu/menu.css +20 -8
- package/components/menu/menu.js +9 -1
- package/components/modal/modal.css +101 -108
- package/components/noodles/noodles.js +25 -8
- package/components/pipeline-status/pipeline-status.css +4 -4
- package/components/pipeline-status/pipeline-status.js +6 -4
- package/components/popover/popover.js +4 -0
- package/components/progress-row/progress-row.a2ui.json +3 -2
- package/components/progress-row/progress-row.yaml +2 -1
- package/components/range/range.js +7 -0
- package/components/richtext/richtext.css +2 -2
- package/components/richtext/richtext.js +4 -1
- package/components/segment/segment.css +1 -1
- package/components/segmented/segmented.js +7 -1
- package/components/select/select.css +7 -4
- package/components/slider/slider.js +15 -8
- package/components/stepper/stepper.css +181 -144
- package/components/stepper/stepper.js +5 -2
- package/components/swiper/swiper.a2ui.json +3 -3
- package/components/swiper/swiper.css +11 -77
- package/components/swiper/swiper.js +6 -5
- package/components/swiper/swiper.yaml +3 -3
- package/components/switch/switch.a2ui.json +8 -1
- package/components/switch/switch.yaml +8 -1
- package/components/table/table.js +9 -1
- package/components/table-toolbar/table-toolbar.a2ui.json +21 -21
- package/components/table-toolbar/table-toolbar.css +32 -91
- package/components/table-toolbar/table-toolbar.js +219 -86
- package/components/table-toolbar/table-toolbar.yaml +21 -12
- package/components/tabs/tabs.css +3 -2
- package/components/tabs/tabs.js +7 -1
- package/components/tag/tag.a2ui.json +2 -2
- package/components/tag/tag.yaml +2 -2
- package/components/timeline/timeline.css +244 -204
- package/components/timeline/timeline.js +1 -3
- package/components/toast/toast.a2ui.json +2 -3
- package/components/toast/toast.yaml +5 -3
- package/components/toolbar/toolbar.css +6 -1
- package/components/toolbar/toolbar.js +10 -2
- package/components/tooltip/tooltip.css +8 -2
- package/components/tooltip/tooltip.js +12 -14
- package/components/tree/tree.css +21 -0
- package/core/icons.js +14 -0
- package/core/polyfills.js +17 -7
- package/package.json +1 -1
- package/patterns/a2ui-root/a2ui-root.js +21 -14
- package/patterns/app-shell/css/app-shell.main.css +30 -1
- package/patterns/app-shell/css/app-shell.tokens.css +1 -0
- package/patterns/gen-ui/gen-ui.js +1 -1
- package/styles/colors/semantics.css +59 -2
- package/styles/tokens.css +16 -12
|
@@ -31,10 +31,10 @@ class AdiaTableToolbar extends AdiaElement {
|
|
|
31
31
|
for: { type: String, default: '', reflect: true },
|
|
32
32
|
text: { type: String, default: '', reflect: false },
|
|
33
33
|
count: { type: String, default: '', reflect: false },
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
noFilter: { type: Boolean, default: false, reflect: true, attribute: 'no-filter' },
|
|
35
|
+
noSort: { type: Boolean, default: false, reflect: true, attribute: 'no-sort' },
|
|
36
|
+
noColumns: { type: Boolean, default: false, reflect: true, attribute: 'no-columns' },
|
|
37
|
+
noSearch: { type: Boolean, default: false, reflect: true, attribute: 'no-search' },
|
|
38
38
|
placeholder: { type: String, default: 'Search...', reflect: false },
|
|
39
39
|
variant: { type: String, default: 'default', reflect: true },
|
|
40
40
|
};
|
|
@@ -43,9 +43,10 @@ class AdiaTableToolbar extends AdiaElement {
|
|
|
43
43
|
|
|
44
44
|
#target = null;
|
|
45
45
|
#targetListeners = [];
|
|
46
|
-
#searchTimer = null;
|
|
47
46
|
#activePopover = null; // { btn, panel, cleanup }
|
|
48
47
|
#docListenersBound = false;
|
|
48
|
+
#docListenerRaf = null;
|
|
49
|
+
#sortIndicatorRafs = new Set();
|
|
49
50
|
|
|
50
51
|
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
51
52
|
|
|
@@ -57,8 +58,12 @@ class AdiaTableToolbar extends AdiaElement {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
disconnected() {
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
if (this.#docListenerRaf != null) {
|
|
62
|
+
cancelAnimationFrame(this.#docListenerRaf);
|
|
63
|
+
this.#docListenerRaf = null;
|
|
64
|
+
}
|
|
65
|
+
for (const id of this.#sortIndicatorRafs) cancelAnimationFrame(id);
|
|
66
|
+
this.#sortIndicatorRafs.clear();
|
|
62
67
|
this.#closePopover();
|
|
63
68
|
this.#detachTarget();
|
|
64
69
|
}
|
|
@@ -139,13 +144,16 @@ class AdiaTableToolbar extends AdiaElement {
|
|
|
139
144
|
controls.appendChild(this.#mkButton('sort', 'Sort', 'arrows-down-up'));
|
|
140
145
|
controls.appendChild(this.#mkButton('columns', 'Columns', 'columns'));
|
|
141
146
|
|
|
142
|
-
// Search
|
|
143
|
-
|
|
147
|
+
// Search — compose <search-ui>, which already stamps input-ui with
|
|
148
|
+
// the magnifying-glass prefix + clear suffix and debounces a `search`
|
|
149
|
+
// event. Rolling our own from input-ui would re-derive that wiring
|
|
150
|
+
// and (as discovered) hit a first-paint timing race where the icon
|
|
151
|
+
// name renders as literal text before the icon registry resolves.
|
|
152
|
+
const search = document.createElement('search-ui');
|
|
144
153
|
search.setAttribute('data-search', '');
|
|
145
|
-
search.setAttribute('type', 'search');
|
|
146
|
-
search.setAttribute('prefix', 'magnifying-glass');
|
|
147
154
|
search.setAttribute('placeholder', this.placeholder);
|
|
148
|
-
search.
|
|
155
|
+
search.setAttribute('debounce', String(SEARCH_DEBOUNCE));
|
|
156
|
+
search.addEventListener('search', this.#onSearch);
|
|
149
157
|
|
|
150
158
|
// Actions slot passthrough — we move any pre-existing [slot="actions"] children here
|
|
151
159
|
const actionsSlot = document.createElement('div');
|
|
@@ -227,15 +235,15 @@ class AdiaTableToolbar extends AdiaElement {
|
|
|
227
235
|
if (el) el.hidden = hidden;
|
|
228
236
|
};
|
|
229
237
|
|
|
230
|
-
setHidden('[data-toolbar-btn="filter"]',
|
|
231
|
-
setHidden('[data-toolbar-btn="sort"]',
|
|
232
|
-
setHidden('[data-toolbar-btn="columns"]',
|
|
233
|
-
setHidden('[data-search]',
|
|
238
|
+
setHidden('[data-toolbar-btn="filter"]', this.noFilter);
|
|
239
|
+
setHidden('[data-toolbar-btn="sort"]', this.noSort);
|
|
240
|
+
setHidden('[data-toolbar-btn="columns"]', this.noColumns);
|
|
241
|
+
setHidden('[data-search]', this.noSearch);
|
|
234
242
|
}
|
|
235
243
|
|
|
236
244
|
// Re-run on attribute changes for boolean flags.
|
|
237
245
|
attributeChanged(name) {
|
|
238
|
-
if (['
|
|
246
|
+
if (['no-filter', 'no-sort', 'no-columns', 'no-search'].includes(name)) {
|
|
239
247
|
this.#updateControlVisibility();
|
|
240
248
|
}
|
|
241
249
|
if (name === 'placeholder') {
|
|
@@ -246,16 +254,13 @@ class AdiaTableToolbar extends AdiaElement {
|
|
|
246
254
|
|
|
247
255
|
// ── Search ───────────────────────────────────────────────────────────────
|
|
248
256
|
|
|
249
|
-
#
|
|
250
|
-
const value = e.
|
|
251
|
-
|
|
252
|
-
this
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
detail: { value },
|
|
257
|
-
}));
|
|
258
|
-
}, SEARCH_DEBOUNCE);
|
|
257
|
+
#onSearch = (e) => {
|
|
258
|
+
const value = e.detail?.value ?? '';
|
|
259
|
+
if (this.#target) this.#target.search = value;
|
|
260
|
+
this.dispatchEvent(new CustomEvent('search', {
|
|
261
|
+
bubbles: true,
|
|
262
|
+
detail: { value },
|
|
263
|
+
}));
|
|
259
264
|
};
|
|
260
265
|
|
|
261
266
|
// ── Sync from target (initial paint) ─────────────────────────────────────
|
|
@@ -296,7 +301,9 @@ class AdiaTableToolbar extends AdiaElement {
|
|
|
296
301
|
|
|
297
302
|
if (!this.#docListenersBound) {
|
|
298
303
|
this.#docListenersBound = true;
|
|
299
|
-
requestAnimationFrame(() => {
|
|
304
|
+
this.#docListenerRaf = requestAnimationFrame(() => {
|
|
305
|
+
this.#docListenerRaf = null;
|
|
306
|
+
if (!this.isConnected || !this.#docListenersBound) return;
|
|
300
307
|
document.addEventListener('pointerdown', this.#onDocDown, true);
|
|
301
308
|
document.addEventListener('keydown', this.#onDocKey, true);
|
|
302
309
|
});
|
|
@@ -344,40 +351,78 @@ class AdiaTableToolbar extends AdiaElement {
|
|
|
344
351
|
}
|
|
345
352
|
const filters = target.filters || {};
|
|
346
353
|
|
|
347
|
-
|
|
348
|
-
head.setAttribute('data-popover-head', '');
|
|
349
|
-
head.textContent = 'Filter rows';
|
|
350
|
-
panel.appendChild(head);
|
|
354
|
+
panel.appendChild(popoverHead('Filter rows'));
|
|
351
355
|
|
|
352
356
|
const list = document.createElement('div');
|
|
353
357
|
list.setAttribute('data-popover-list', '');
|
|
354
358
|
|
|
359
|
+
const data = target.data || [];
|
|
355
360
|
for (const col of target.columns) {
|
|
356
361
|
if (col.hidden) continue;
|
|
357
|
-
|
|
362
|
+
// <field-ui inline label="…"><…control…></…></field-ui> — canonical
|
|
363
|
+
// label-binds-to-control pair, mints id + [for]. The CONTROL is
|
|
364
|
+
// chosen per-column from the column's filter shape:
|
|
365
|
+
// • 'select' → <select-ui multiple searchable> populated from data
|
|
366
|
+
// • 'text' → <input-ui type="text"> (contains match)
|
|
367
|
+
// Shape comes from col.filter when set, otherwise auto-detected
|
|
368
|
+
// from the data (id-like keys → text; ≤ 50 distinct values → select).
|
|
369
|
+
const row = document.createElement('field-ui');
|
|
358
370
|
row.setAttribute('data-filter-row', '');
|
|
371
|
+
row.setAttribute('inline', '');
|
|
372
|
+
row.setAttribute('label', col.label || col.key);
|
|
373
|
+
|
|
374
|
+
const shape = detectFilterShape(col, data);
|
|
375
|
+
const current = filters[col.key];
|
|
376
|
+
|
|
377
|
+
if (shape === 'select') {
|
|
378
|
+
const sel = document.createElement('select-ui');
|
|
379
|
+
sel.setAttribute('data-filter-input', '');
|
|
380
|
+
sel.setAttribute('multiple', '');
|
|
381
|
+
sel.setAttribute('placeholder', '—');
|
|
382
|
+
|
|
383
|
+
const uniqueValues = collectUniqueValues(col, data);
|
|
384
|
+
// Searchable kicks in at 12+ options — below that the listbox is
|
|
385
|
+
// fully scannable and a search field is overhead.
|
|
386
|
+
if (uniqueValues.length >= 12) sel.setAttribute('searchable', '');
|
|
387
|
+
|
|
388
|
+
for (const val of uniqueValues) {
|
|
389
|
+
const opt = document.createElement('option');
|
|
390
|
+
opt.setAttribute('value', val);
|
|
391
|
+
opt.textContent = val;
|
|
392
|
+
sel.appendChild(opt);
|
|
393
|
+
}
|
|
394
|
+
if (current?.op === 'select' && current.value) {
|
|
395
|
+
sel.value = current.value;
|
|
396
|
+
}
|
|
397
|
+
sel.addEventListener('change', () => {
|
|
398
|
+
const v = sel.value || '';
|
|
399
|
+
if (v) target.setFilter(col.key, v, 'select');
|
|
400
|
+
else target.setFilter(col.key, null);
|
|
401
|
+
this.dispatchEvent(new CustomEvent('filter-change', {
|
|
402
|
+
bubbles: true,
|
|
403
|
+
detail: { filters: target.filters },
|
|
404
|
+
}));
|
|
405
|
+
});
|
|
406
|
+
row.appendChild(sel);
|
|
407
|
+
} else {
|
|
408
|
+
const input = document.createElement('input-ui');
|
|
409
|
+
input.setAttribute('type', 'text');
|
|
410
|
+
input.setAttribute('size', 'sm');
|
|
411
|
+
input.setAttribute('data-filter-input', '');
|
|
412
|
+
input.setAttribute('placeholder', '—');
|
|
413
|
+
if (current?.op === 'contains') input.value = current.value ?? '';
|
|
414
|
+
input.addEventListener('input', () => {
|
|
415
|
+
const v = input.value;
|
|
416
|
+
if (v) target.setFilter(col.key, v, 'contains');
|
|
417
|
+
else target.setFilter(col.key, null);
|
|
418
|
+
this.dispatchEvent(new CustomEvent('filter-change', {
|
|
419
|
+
bubbles: true,
|
|
420
|
+
detail: { filters: target.filters },
|
|
421
|
+
}));
|
|
422
|
+
});
|
|
423
|
+
row.appendChild(input);
|
|
424
|
+
}
|
|
359
425
|
|
|
360
|
-
const labelEl = document.createElement('span');
|
|
361
|
-
labelEl.setAttribute('data-filter-label', '');
|
|
362
|
-
labelEl.textContent = col.label || col.key;
|
|
363
|
-
row.appendChild(labelEl);
|
|
364
|
-
|
|
365
|
-
const input = document.createElement('input-ui');
|
|
366
|
-
input.setAttribute('type', 'text');
|
|
367
|
-
input.setAttribute('size', 'sm');
|
|
368
|
-
input.setAttribute('data-filter-input', '');
|
|
369
|
-
input.setAttribute('placeholder', '—');
|
|
370
|
-
input.value = filters[col.key]?.value ?? '';
|
|
371
|
-
input.addEventListener('input', () => {
|
|
372
|
-
const v = input.value;
|
|
373
|
-
if (v) target.setFilter(col.key, v, 'contains');
|
|
374
|
-
else target.setFilter(col.key, null);
|
|
375
|
-
this.dispatchEvent(new CustomEvent('filter-change', {
|
|
376
|
-
bubbles: true,
|
|
377
|
-
detail: { filters: target.filters },
|
|
378
|
-
}));
|
|
379
|
-
});
|
|
380
|
-
row.appendChild(input);
|
|
381
426
|
list.appendChild(row);
|
|
382
427
|
}
|
|
383
428
|
|
|
@@ -412,10 +457,7 @@ class AdiaTableToolbar extends AdiaElement {
|
|
|
412
457
|
return;
|
|
413
458
|
}
|
|
414
459
|
|
|
415
|
-
|
|
416
|
-
head.setAttribute('data-popover-head', '');
|
|
417
|
-
head.textContent = 'Sort by';
|
|
418
|
-
panel.appendChild(head);
|
|
460
|
+
panel.appendChild(popoverHead('Sort by'));
|
|
419
461
|
|
|
420
462
|
const sortState = target.sortState || [];
|
|
421
463
|
const dirByKey = new Map(sortState.map((s) => [s.key, s.dir]));
|
|
@@ -427,29 +469,33 @@ class AdiaTableToolbar extends AdiaElement {
|
|
|
427
469
|
if (col.hidden) continue;
|
|
428
470
|
if (col.sortable === false) continue;
|
|
429
471
|
|
|
430
|
-
|
|
431
|
-
|
|
472
|
+
// <menu-item-ui> is the canonical action-row primitive (role="menuitem",
|
|
473
|
+
// built-in icon + text slots, hover/focus tokens, danger variant). Append
|
|
474
|
+
// a trailing <icon-ui> after the auto-stamped [slot="text"] — its flex:1
|
|
475
|
+
// pushes any later child to the trailing edge.
|
|
476
|
+
const dir = dirByKey.get(col.key);
|
|
477
|
+
const row = document.createElement('menu-item-ui');
|
|
432
478
|
row.setAttribute('data-sort-row', '');
|
|
479
|
+
row.setAttribute('text', col.label || col.key);
|
|
433
480
|
row.dataset.key = col.key;
|
|
434
|
-
|
|
435
|
-
const labelEl = document.createElement('span');
|
|
436
|
-
labelEl.setAttribute('data-sort-label', '');
|
|
437
|
-
labelEl.textContent = col.label || col.key;
|
|
438
|
-
row.appendChild(labelEl);
|
|
439
|
-
|
|
440
|
-
const dir = dirByKey.get(col.key);
|
|
441
|
-
const indicator = document.createElement('icon-ui');
|
|
442
|
-
indicator.setAttribute('data-sort-indicator', '');
|
|
443
|
-
indicator.setAttribute('size', 'xs');
|
|
444
|
-
indicator.setAttribute('name', dir === 'asc' ? 'arrow-up' : dir === 'desc' ? 'arrow-down' : 'caret-up-down');
|
|
445
481
|
if (dir) row.dataset.active = dir;
|
|
446
|
-
|
|
482
|
+
|
|
483
|
+
// Trailing direction indicator — append AFTER menu-item-ui's connected()
|
|
484
|
+
// has run its #stamp() (defers via rAF so the [slot="text"] span exists).
|
|
485
|
+
const rafId = requestAnimationFrame(() => {
|
|
486
|
+
this.#sortIndicatorRafs.delete(rafId);
|
|
487
|
+
if (!this.isConnected) return;
|
|
488
|
+
const indicator = document.createElement('icon-ui');
|
|
489
|
+
indicator.setAttribute('data-sort-indicator', '');
|
|
490
|
+
indicator.setAttribute('size', 'xs');
|
|
491
|
+
indicator.setAttribute('name', dir === 'asc' ? 'arrow-up' : dir === 'desc' ? 'arrow-down' : 'caret-up-down');
|
|
492
|
+
row.appendChild(indicator);
|
|
493
|
+
});
|
|
494
|
+
this.#sortIndicatorRafs.add(rafId);
|
|
447
495
|
|
|
448
496
|
row.addEventListener('click', (e) => {
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
// when the table is not present — same defensive shape as chart-legend's
|
|
452
|
-
// [for] target check.
|
|
497
|
+
// Forward to the bound table by simulating a header click — the table
|
|
498
|
+
// already owns the asc / desc / clear cycle, so we don't duplicate state.
|
|
453
499
|
const headerCell = target.querySelector(`:scope > [data-header] [data-sort-key="${col.key}"]`);
|
|
454
500
|
if (!headerCell) return;
|
|
455
501
|
const evt = new MouseEvent('click', { bubbles: true, cancelable: true, shiftKey: e.shiftKey });
|
|
@@ -512,17 +558,19 @@ class AdiaTableToolbar extends AdiaElement {
|
|
|
512
558
|
return;
|
|
513
559
|
}
|
|
514
560
|
|
|
515
|
-
|
|
516
|
-
head.setAttribute('data-popover-head', '');
|
|
517
|
-
head.textContent = 'Visible columns';
|
|
518
|
-
panel.appendChild(head);
|
|
561
|
+
panel.appendChild(popoverHead('Visible columns'));
|
|
519
562
|
|
|
520
563
|
const list = document.createElement('div');
|
|
521
564
|
list.setAttribute('data-popover-list', '');
|
|
522
565
|
|
|
523
566
|
for (const col of target.columns) {
|
|
524
|
-
|
|
567
|
+
// <field-ui inline label="…"><check-ui></check-ui></field-ui> —
|
|
568
|
+
// canonical label-binds-to-control pair, mints id + [for] so clicking
|
|
569
|
+
// the label toggles the check. Same primitive used for filter rows.
|
|
570
|
+
const row = document.createElement('field-ui');
|
|
525
571
|
row.setAttribute('data-columns-row', '');
|
|
572
|
+
row.setAttribute('inline', '');
|
|
573
|
+
row.setAttribute('label', col.label || col.key);
|
|
526
574
|
|
|
527
575
|
const check = document.createElement('check-ui');
|
|
528
576
|
if (!col.hidden) check.setAttribute('checked', '');
|
|
@@ -539,11 +587,6 @@ class AdiaTableToolbar extends AdiaElement {
|
|
|
539
587
|
});
|
|
540
588
|
row.appendChild(check);
|
|
541
589
|
|
|
542
|
-
const labelEl = document.createElement('span');
|
|
543
|
-
labelEl.setAttribute('data-columns-label', '');
|
|
544
|
-
labelEl.textContent = col.label || col.key;
|
|
545
|
-
row.appendChild(labelEl);
|
|
546
|
-
|
|
547
590
|
list.appendChild(row);
|
|
548
591
|
}
|
|
549
592
|
|
|
@@ -560,6 +603,96 @@ function emptyHint(text) {
|
|
|
560
603
|
return el;
|
|
561
604
|
}
|
|
562
605
|
|
|
606
|
+
function popoverHead(text) {
|
|
607
|
+
// <text-ui variant="kicker"> renders the uppercase + tracked + muted-color
|
|
608
|
+
// section-marker typography role — exactly what a popover head needs. No
|
|
609
|
+
// bespoke styling required, no [data-popover-head] CSS rule.
|
|
610
|
+
const el = document.createElement('text-ui');
|
|
611
|
+
el.setAttribute('data-popover-head', '');
|
|
612
|
+
el.setAttribute('variant', 'kicker');
|
|
613
|
+
el.setAttribute('color', 'subtle');
|
|
614
|
+
el.textContent = text;
|
|
615
|
+
return el;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ── Filter shape detection ─────────────────────────────────────────────────
|
|
619
|
+
|
|
620
|
+
const SELECT_THRESHOLD = 50; // ≤ N distinct values → multi-select
|
|
621
|
+
const SAMPLE_LIMIT = 500; // cap data scan for cardinality detection
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Resolve a cell value for a column. Mirrors table.js getCellValue —
|
|
625
|
+
* supports col.accessor (function) and dot-notation col.key paths.
|
|
626
|
+
*/
|
|
627
|
+
function getCellValue(row, col) {
|
|
628
|
+
if (typeof col?.accessor === 'function') return col.accessor(row);
|
|
629
|
+
const path = col?.key;
|
|
630
|
+
if (!path || row == null) return undefined;
|
|
631
|
+
const parts = String(path).split('.');
|
|
632
|
+
let cur = row;
|
|
633
|
+
for (const p of parts) {
|
|
634
|
+
if (cur == null) return undefined;
|
|
635
|
+
cur = cur[p];
|
|
636
|
+
}
|
|
637
|
+
return cur;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Heuristic: id-like keys (`id`, `userId`, `worker_id`, `uuid`, `guid`)
|
|
642
|
+
* are searched as free text — picking from a 10k-entry listbox is worse
|
|
643
|
+
* UX than typing a known value.
|
|
644
|
+
*/
|
|
645
|
+
function looksLikeIdKey(key) {
|
|
646
|
+
if (!key) return false;
|
|
647
|
+
const k = String(key);
|
|
648
|
+
if (k === 'id' || k === 'ID' || k === 'uuid' || k === 'guid') return true;
|
|
649
|
+
if (/[a-z](Id|ID)$/.test(k)) return true; // camelCase: workerId, userId
|
|
650
|
+
if (/_id$/i.test(k)) return true; // snake_case: worker_id
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Choose the right filter primitive for a column.
|
|
656
|
+
* col.filter = 'select' | 'text' — explicit override (table-ui contract)
|
|
657
|
+
* col.type = 'number' | 'currency' | 'percent' → 'text' (numeric range
|
|
658
|
+
* support is a future addition; falls back to contains for now)
|
|
659
|
+
* id-like key → 'text'
|
|
660
|
+
* ≤ SELECT_THRESHOLD distinct values in the sample → 'select'
|
|
661
|
+
* otherwise → 'text'
|
|
662
|
+
*/
|
|
663
|
+
function detectFilterShape(col, data) {
|
|
664
|
+
if (col?.filter === 'select' || col?.filter === 'text') return col.filter;
|
|
665
|
+
if (looksLikeIdKey(col?.key)) return 'text';
|
|
666
|
+
|
|
667
|
+
const values = [];
|
|
668
|
+
const limit = Math.min(data?.length ?? 0, SAMPLE_LIMIT);
|
|
669
|
+
const unique = new Set();
|
|
670
|
+
for (let i = 0; i < limit; i++) {
|
|
671
|
+
const v = getCellValue(data[i], col);
|
|
672
|
+
if (v == null || v === '') continue;
|
|
673
|
+
values.push(v);
|
|
674
|
+
unique.add(String(v));
|
|
675
|
+
if (unique.size > SELECT_THRESHOLD) return 'text';
|
|
676
|
+
}
|
|
677
|
+
if (!values.length) return 'text';
|
|
678
|
+
return 'select';
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Collect distinct stringified values for the column from data, sorted.
|
|
683
|
+
* Used to populate select-ui options when filter shape === 'select'.
|
|
684
|
+
*/
|
|
685
|
+
function collectUniqueValues(col, data) {
|
|
686
|
+
const set = new Set();
|
|
687
|
+
const limit = Math.min(data?.length ?? 0, SAMPLE_LIMIT);
|
|
688
|
+
for (let i = 0; i < limit; i++) {
|
|
689
|
+
const v = getCellValue(data[i], col);
|
|
690
|
+
if (v == null || v === '') continue;
|
|
691
|
+
set.add(String(v));
|
|
692
|
+
}
|
|
693
|
+
return [...set].sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
|
|
694
|
+
}
|
|
695
|
+
|
|
563
696
|
customElements.define('table-toolbar-ui', AdiaTableToolbar);
|
|
564
697
|
|
|
565
698
|
export { AdiaTableToolbar };
|
|
@@ -10,6 +10,11 @@ description: >-
|
|
|
10
10
|
target table via an [for] id-ref. Modeled on chart-legend-ui's [for] binding
|
|
11
11
|
pattern. Drop next to (or above) any table-ui to add the standard data-grid
|
|
12
12
|
toolbar without re-implementing search, filter, sort, or column visibility.
|
|
13
|
+
Filter rows auto-pick a primitive per column: ≤ 50 distinct values → multi-
|
|
14
|
+
select (searchable above 12 options), id-like keys → free-text contains. The
|
|
15
|
+
column descriptor's `filter` field overrides the auto-detect: `'select'`
|
|
16
|
+
forces multi-select even on high-cardinality columns; `'text'` forces a
|
|
17
|
+
contains input even on small enums.
|
|
13
18
|
props:
|
|
14
19
|
for:
|
|
15
20
|
description: id-ref of the table-ui to control. Falls back to the first sibling table-ui within the same parent when omitted.
|
|
@@ -24,26 +29,30 @@ props:
|
|
|
24
29
|
description: Optional count badge value shown next to the title. When unset, falls back to the row count of the bound table.
|
|
25
30
|
type: string
|
|
26
31
|
default: ""
|
|
27
|
-
|
|
28
|
-
description:
|
|
32
|
+
noFilter:
|
|
33
|
+
description: Hide the Filter popover button. Filter is shown by default; set to opt out.
|
|
29
34
|
type: boolean
|
|
30
|
-
default:
|
|
35
|
+
default: false
|
|
31
36
|
reflect: true
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
attribute: no-filter
|
|
38
|
+
noSort:
|
|
39
|
+
description: Hide the Sort popover button. Sort is shown by default; set to opt out.
|
|
34
40
|
type: boolean
|
|
35
|
-
default:
|
|
41
|
+
default: false
|
|
36
42
|
reflect: true
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
attribute: no-sort
|
|
44
|
+
noColumns:
|
|
45
|
+
description: Hide the Columns visibility popover button. Columns control is shown by default; set to opt out.
|
|
39
46
|
type: boolean
|
|
40
|
-
default:
|
|
47
|
+
default: false
|
|
41
48
|
reflect: true
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
attribute: no-columns
|
|
50
|
+
noSearch:
|
|
51
|
+
description: Hide the search input. Search is shown by default; set to opt out.
|
|
44
52
|
type: boolean
|
|
45
|
-
default:
|
|
53
|
+
default: false
|
|
46
54
|
reflect: true
|
|
55
|
+
attribute: no-search
|
|
47
56
|
placeholder:
|
|
48
57
|
description: Placeholder text for the search input.
|
|
49
58
|
type: string
|
package/components/tabs/tabs.css
CHANGED
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
--tabs-focus-radius: var(--a-radius-sm);
|
|
31
31
|
|
|
32
32
|
/* ── Vertical strip min-width ── */
|
|
33
|
-
/* Component-intrinsic visual constant; no --a-space-* equivalent
|
|
34
|
-
|
|
33
|
+
/* Component-intrinsic visual constant; no --a-space-* equivalent
|
|
34
|
+
(8.75rem ≈ 140px at 1rem=16px). */
|
|
35
|
+
--tabs-vertical-strip-min-width: 8.75rem;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
:scope {
|
package/components/tabs/tabs.js
CHANGED
|
@@ -27,6 +27,7 @@ class AdiaTabs extends AdiaElement {
|
|
|
27
27
|
|
|
28
28
|
#strip = null;
|
|
29
29
|
#indicator = null;
|
|
30
|
+
#transitionRaf = null;
|
|
30
31
|
|
|
31
32
|
connected() {
|
|
32
33
|
this.setAttribute('role', 'tablist');
|
|
@@ -42,6 +43,10 @@ class AdiaTabs extends AdiaElement {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
disconnected() {
|
|
46
|
+
if (this.#transitionRaf != null) {
|
|
47
|
+
cancelAnimationFrame(this.#transitionRaf);
|
|
48
|
+
this.#transitionRaf = null;
|
|
49
|
+
}
|
|
45
50
|
this.removeEventListener('click', this.#handleClick);
|
|
46
51
|
this.removeEventListener('keydown', this.#handleKeydown);
|
|
47
52
|
this.#strip = null;
|
|
@@ -175,7 +180,8 @@ class AdiaTabs extends AdiaElement {
|
|
|
175
180
|
if (isNew) {
|
|
176
181
|
this.#indicator.style.transition = 'none';
|
|
177
182
|
apply();
|
|
178
|
-
requestAnimationFrame(() => {
|
|
183
|
+
this.#transitionRaf = requestAnimationFrame(() => {
|
|
184
|
+
this.#transitionRaf = null;
|
|
179
185
|
if (this.#indicator) this.#indicator.style.transition = '';
|
|
180
186
|
});
|
|
181
187
|
} else {
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"default": ""
|
|
42
42
|
},
|
|
43
43
|
"variant": {
|
|
44
|
-
"description": "
|
|
44
|
+
"description": "Semantic variant — `default | info | success | warning | danger`.",
|
|
45
45
|
"type": "string",
|
|
46
46
|
"enum": [
|
|
47
47
|
"default",
|
|
48
48
|
"info",
|
|
49
49
|
"success",
|
|
50
50
|
"warning",
|
|
51
|
-
"
|
|
51
|
+
"danger"
|
|
52
52
|
],
|
|
53
53
|
"default": "default"
|
|
54
54
|
}
|
package/components/tag/tag.yaml
CHANGED
|
@@ -29,7 +29,7 @@ props:
|
|
|
29
29
|
type: string
|
|
30
30
|
default: ""
|
|
31
31
|
variant:
|
|
32
|
-
description:
|
|
32
|
+
description: Semantic variant — `default | info | success | warning | danger`.
|
|
33
33
|
type: string
|
|
34
34
|
default: default
|
|
35
35
|
enum:
|
|
@@ -37,7 +37,7 @@ props:
|
|
|
37
37
|
- info
|
|
38
38
|
- success
|
|
39
39
|
- warning
|
|
40
|
-
-
|
|
40
|
+
- danger
|
|
41
41
|
events:
|
|
42
42
|
remove:
|
|
43
43
|
description: Fired when the dismiss button is activated.
|