@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.
Files changed (90) hide show
  1. package/components/accordion/accordion.css +101 -102
  2. package/components/agent-feedback-bar/agent-feedback-bar.js +8 -8
  3. package/components/agent-questions/agent-questions.css +2 -1
  4. package/components/agent-questions/agent-questions.js +6 -6
  5. package/components/agent-reasoning/agent-reasoning.js +20 -5
  6. package/components/agent-trace/agent-trace.a2ui.json +5 -5
  7. package/components/agent-trace/agent-trace.js +7 -5
  8. package/components/agent-trace/agent-trace.yaml +2 -2
  9. package/components/alert/alert.a2ui.json +1 -2
  10. package/components/alert/alert.css +4 -4
  11. package/components/alert/alert.yaml +1 -2
  12. package/components/avatar/avatar.a2ui.json +3 -3
  13. package/components/avatar/avatar.js +10 -0
  14. package/components/avatar/avatar.yaml +6 -6
  15. package/components/button/button.a2ui.json +14 -2
  16. package/components/button/button.css +19 -2
  17. package/components/button/button.js +1 -0
  18. package/components/button/button.yaml +20 -2
  19. package/components/calendar-picker/calendar-picker.css +2 -1
  20. package/components/calendar-picker/calendar-picker.js +12 -1
  21. package/components/chart/chart.css +11 -11
  22. package/components/chart/chart.js +26 -18
  23. package/components/chart-legend/chart-legend.a2ui.json +2 -2
  24. package/components/chart-legend/chart-legend.js +4 -1
  25. package/components/chart-legend/chart-legend.yaml +2 -2
  26. package/components/chat/chat-input.js +13 -5
  27. package/components/chat/chat.a2ui.json +2 -2
  28. package/components/chat/chat.js +14 -3
  29. package/components/chat/chat.yaml +2 -2
  30. package/components/code/code.css +16 -6
  31. package/components/command/command.js +9 -1
  32. package/components/field/field.a2ui.json +0 -5
  33. package/components/field/field.css +2 -2
  34. package/components/field/field.js +53 -5
  35. package/components/field/field.yaml +5 -8
  36. package/components/heatmap/heatmap.css +32 -23
  37. package/components/input/input.js +30 -1
  38. package/components/kbd/kbd.a2ui.json +5 -1
  39. package/components/kbd/kbd.yaml +5 -1
  40. package/components/menu/menu.css +20 -8
  41. package/components/menu/menu.js +9 -1
  42. package/components/modal/modal.css +101 -108
  43. package/components/noodles/noodles.js +25 -8
  44. package/components/pipeline-status/pipeline-status.css +4 -4
  45. package/components/pipeline-status/pipeline-status.js +6 -4
  46. package/components/popover/popover.js +4 -0
  47. package/components/progress-row/progress-row.a2ui.json +3 -2
  48. package/components/progress-row/progress-row.yaml +2 -1
  49. package/components/range/range.js +7 -0
  50. package/components/richtext/richtext.css +2 -2
  51. package/components/richtext/richtext.js +4 -1
  52. package/components/segment/segment.css +1 -1
  53. package/components/segmented/segmented.js +7 -1
  54. package/components/select/select.css +7 -4
  55. package/components/slider/slider.js +15 -8
  56. package/components/stepper/stepper.css +181 -144
  57. package/components/stepper/stepper.js +5 -2
  58. package/components/swiper/swiper.a2ui.json +3 -3
  59. package/components/swiper/swiper.css +11 -77
  60. package/components/swiper/swiper.js +6 -5
  61. package/components/swiper/swiper.yaml +3 -3
  62. package/components/switch/switch.a2ui.json +8 -1
  63. package/components/switch/switch.yaml +8 -1
  64. package/components/table/table.js +9 -1
  65. package/components/table-toolbar/table-toolbar.a2ui.json +21 -21
  66. package/components/table-toolbar/table-toolbar.css +32 -91
  67. package/components/table-toolbar/table-toolbar.js +219 -86
  68. package/components/table-toolbar/table-toolbar.yaml +21 -12
  69. package/components/tabs/tabs.css +3 -2
  70. package/components/tabs/tabs.js +7 -1
  71. package/components/tag/tag.a2ui.json +2 -2
  72. package/components/tag/tag.yaml +2 -2
  73. package/components/timeline/timeline.css +244 -204
  74. package/components/timeline/timeline.js +1 -3
  75. package/components/toast/toast.a2ui.json +2 -3
  76. package/components/toast/toast.yaml +5 -3
  77. package/components/toolbar/toolbar.css +6 -1
  78. package/components/toolbar/toolbar.js +10 -2
  79. package/components/tooltip/tooltip.css +8 -2
  80. package/components/tooltip/tooltip.js +12 -14
  81. package/components/tree/tree.css +21 -0
  82. package/core/icons.js +14 -0
  83. package/core/polyfills.js +17 -7
  84. package/package.json +1 -1
  85. package/patterns/a2ui-root/a2ui-root.js +21 -14
  86. package/patterns/app-shell/css/app-shell.main.css +30 -1
  87. package/patterns/app-shell/css/app-shell.tokens.css +1 -0
  88. package/patterns/gen-ui/gen-ui.js +1 -1
  89. package/styles/colors/semantics.css +59 -2
  90. 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
- filterable: { type: Boolean, default: true, reflect: true },
35
- sortable: { type: Boolean, default: true, reflect: true },
36
- columns: { type: Boolean, default: true, reflect: true },
37
- searchable: { type: Boolean, default: true, reflect: true },
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
- clearTimeout(this.#searchTimer);
61
- this.#searchTimer = null;
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
- const search = document.createElement('input-ui');
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.addEventListener('input', this.#onSearchInput);
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"]', !this.filterable);
231
- setHidden('[data-toolbar-btn="sort"]', !this.sortable);
232
- setHidden('[data-toolbar-btn="columns"]', !this.columns);
233
- setHidden('[data-search]', !this.searchable);
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 (['filterable', 'sortable', 'columns', 'searchable'].includes(name)) {
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
- #onSearchInput = (e) => {
250
- const value = e.target?.value ?? '';
251
- clearTimeout(this.#searchTimer);
252
- this.#searchTimer = setTimeout(() => {
253
- if (this.#target) this.#target.search = value;
254
- this.dispatchEvent(new CustomEvent('search', {
255
- bubbles: true,
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
- const head = document.createElement('div');
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
- const row = document.createElement('label');
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
- const head = document.createElement('div');
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
- const row = document.createElement('button');
431
- row.type = 'button';
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
- row.appendChild(indicator);
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
- // Multi-sort with shift: forward to the table by simulating a header click
450
- // (the table already owns the asc / desc / clear cycle). Falls back to no-op
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
- const head = document.createElement('div');
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
- const row = document.createElement('label');
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
- filterable:
28
- description: Show the Filter popover button.
32
+ noFilter:
33
+ description: Hide the Filter popover button. Filter is shown by default; set to opt out.
29
34
  type: boolean
30
- default: true
35
+ default: false
31
36
  reflect: true
32
- sortable:
33
- description: Show the Sort popover button.
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: true
41
+ default: false
36
42
  reflect: true
37
- columns:
38
- description: Show the Columns visibility popover button.
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: true
47
+ default: false
41
48
  reflect: true
42
- searchable:
43
- description: Show the search input.
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: true
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
@@ -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
- --tabs-vertical-strip-min-width: 140px;
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 {
@@ -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": "Visual style variant with semantic colors.",
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
- "error"
51
+ "danger"
52
52
  ],
53
53
  "default": "default"
54
54
  }
@@ -29,7 +29,7 @@ props:
29
29
  type: string
30
30
  default: ""
31
31
  variant:
32
- description: Visual style variant with semantic colors.
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
- - error
40
+ - danger
41
41
  events:
42
42
  remove:
43
43
  description: Fired when the dismiss button is activated.