@adia-ai/web-components 0.8.33 → 0.8.34

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 (40) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/components/agent-reasoning/agent-reasoning.a2ui.json +1 -1
  3. package/components/agent-reasoning/agent-reasoning.yaml +3 -3
  4. package/components/badge/badge.css +6 -4
  5. package/components/button/button.css +12 -0
  6. package/components/combobox/combobox.a2ui.json +1 -1
  7. package/components/combobox/combobox.yaml +3 -0
  8. package/components/command/command.a2ui.json +4 -0
  9. package/components/command/command.class.js +54 -4
  10. package/components/command/command.d.ts +2 -0
  11. package/components/command/command.yaml +25 -3
  12. package/components/date-range-picker/date-range-picker.a2ui.json +1 -1
  13. package/components/date-range-picker/date-range-picker.class.js +42 -43
  14. package/components/date-range-picker/date-range-picker.css +11 -0
  15. package/components/date-range-picker/date-range-picker.yaml +6 -3
  16. package/components/description-list/description-list.css +16 -4
  17. package/components/modal/modal.class.js +43 -2
  18. package/components/modal/modal.css +23 -0
  19. package/components/nav-group/nav-group.css +15 -4
  20. package/components/nav-item/nav-item.css +11 -3
  21. package/components/richtext/richtext.css +5 -1
  22. package/components/segment/segment.css +10 -0
  23. package/components/step-progress/step-progress.css +16 -3
  24. package/components/table/cell-types.js +8 -0
  25. package/components/table/table.a2ui.json +1 -1
  26. package/components/table/table.class.js +11 -0
  27. package/components/table/table.css +9 -0
  28. package/components/table/table.d.ts +1 -1
  29. package/components/table/table.yaml +1 -1
  30. package/components/tabs/tabs.css +3 -1
  31. package/components/tag/tag.css +7 -4
  32. package/core/element.js +31 -0
  33. package/core/signals.js +3 -1
  34. package/dist/theme-provider.min.js +2 -2
  35. package/dist/web-components.min.css +1 -1
  36. package/dist/web-components.min.js +40 -40
  37. package/dist/web-components.sheet.js +1 -1
  38. package/package.json +1 -1
  39. package/patterns/responsive-shell-sidebar/responsive-shell-sidebar.examples.html +135 -147
  40. package/styles/colors/semantics/core.css +22 -2
@@ -10,13 +10,22 @@
10
10
  --nav-group-font-size-sm: var(--a-ui-sm);
11
11
  --nav-group-font-size-lg: var(--a-ui-lg);
12
12
  --nav-group-font-weight: var(--a-weight-normal);
13
- --nav-group-fg: var(--md-sys-color-neutral-on-surface);
13
+ /* State color spec (gh#968, operator ruling 2026-08-11) — one
14
+ language with nav-item-ui:
15
+ idle — bg transparent · icon NEUTRAL · text NEUTRAL-HIGH
16
+ hover — bg container-low · icon + text ON-SURFACE
17
+ selected — bg container · icon + text ON-SURFACE
18
+ The selected icon no longer takes primary (supersedes gh#522's
19
+ glyph ruling); Figma's neutral/surfaceBright was considered for the
20
+ selected fill and RULED OUT — container stays (gh#795's mirror). */
21
+ --nav-group-fg: var(--md-sys-color-neutral-high);
14
22
  --nav-group-fg-hover: var(--a-fg-strong);
15
23
  --nav-group-fg-muted: var(--a-fg-muted);
16
24
  --nav-group-fg-selected: var(--a-fg-strong);
17
- --nav-group-icon-fg-selected: var(--md-sys-color-primary); /* one selection color with --nav-item-accent (gh#522) */
25
+ --nav-group-icon-fg: var(--md-sys-color-neutral);
26
+ --nav-group-icon-fg-selected: var(--a-fg-strong);
18
27
  --nav-group-bg-hover: var(--a-bg-muted);
19
- --nav-group-bg-selected: var(--a-bg-selected); /* mirrors --nav-item-bg-selected — the missing container fill (gh#795) */
28
+ --nav-group-bg-selected: var(--a-bg-selected); /* mirrors --nav-item-bg-selected (gh#795) */
20
29
  --nav-group-icon-size: calc(var(--nav-group-row-height) - var(--a-space-2));
21
30
  --nav-group-badge-size: var(--a-ui-sm);
22
31
 
@@ -119,7 +128,9 @@
119
128
  width: var(--nav-group-icon-size);
120
129
  height: var(--nav-group-icon-size);
121
130
  flex-shrink: 0;
122
- color: var(--nav-group-fg-muted);
131
+ /* idle icon = NEUTRAL (gh#968 state spec), one step quieter than the
132
+ header's neutral-high text. */
133
+ color: var(--nav-group-icon-fg);
123
134
  transition: color var(--nav-duration-fast) var(--nav-easing);
124
135
  display: flex;
125
136
  align-items: center;
@@ -40,11 +40,19 @@ nav-item-ui[selected] [slot="icon"]:empty::before {
40
40
  --nav-item-font-size-sm: var(--a-ui-sm);
41
41
  --nav-item-font-size-lg: var(--a-ui-lg);
42
42
  --nav-item-font-weight: var(--a-weight-normal);
43
- --nav-item-fg: var(--a-ui-text-subtle);
43
+ /* State color spec (gh#968, operator ruling 2026-08-11):
44
+ idle — bg transparent · icon NEUTRAL · text NEUTRAL-HIGH
45
+ hover — bg container-low · icon + text ON-SURFACE
46
+ selected — bg container · icon + text ON-SURFACE
47
+ Idle sits two quiet steps down (icon quieter than text); hover and
48
+ selected converge on on-surface. The selected ICON no longer takes
49
+ the primary accent (supersedes gh#522 for the glyph) — the accent
50
+ BAR remains the primary selection marker. */
51
+ --nav-item-fg: var(--md-sys-color-neutral-high);
44
52
  --nav-item-fg-hover: var(--a-ui-text-hover);
45
53
  --nav-item-fg-selected: var(--a-ui-text-selected);
46
- --nav-item-icon-fg: var(--a-ui-text-muted);
47
- --nav-item-icon-fg-selected: var(--nav-item-accent);
54
+ --nav-item-icon-fg: var(--md-sys-color-neutral);
55
+ --nav-item-icon-fg-selected: var(--a-ui-text-selected);
48
56
  /* Two real container roles, one mechanism — no opacity synth.
49
57
  hover = --a-bg-muted (--md-sys-color-neutral-container-low, 10%),
50
58
  matching nav-group-ui's own --nav-group-bg-hover exactly.
@@ -15,7 +15,11 @@
15
15
  --richtext-fg: var(--md-sys-color-neutral-on-surface);
16
16
  --richtext-fg-muted: var(--a-fg-muted);
17
17
  --richtext-fg-subtle: var(--md-sys-color-neutral-on-surface-variant);
18
- --richtext-code-bg: var(--a-canvas-2);
18
+ /* Container-low (10% alpha), not an opaque canvas tier (gh#972): the
19
+ inline chip must RECEDE behind prose and composite consistently on
20
+ any stacked surface — an opaque canvas-2 chip read heavy on the
21
+ page and inverted inside cards. Same tier as --a-bg-muted/hover. */
22
+ --richtext-code-bg: var(--md-sys-color-neutral-container-low);
19
23
  --richtext-border: var(--md-sys-color-neutral-outline-variant);
20
24
  --richtext-link: var(--a-primary-bg);
21
25
  --richtext-link-hover: var(--md-sys-color-primary);
@@ -19,6 +19,13 @@ segment-ui[selected] {
19
19
  --segment-font-size: var(--a-ui-size);
20
20
  --segment-font-weight: var(--a-ui-weight);
21
21
  --segment-font-family: var(--a-font-family-ui);
22
+ /* Leading for the [text] label box ONLY — never the host. The host's
23
+ `line-height: 1` is deliberate, but the label's ::after clips to its
24
+ own line box, so inheriting 1 makes half-leading negative and shears
25
+ descenders (g/j/p/q/y, parentheses) by ~0.93px at the default size —
26
+ gh#906. Snug (1.3) contains the font's content box at every [size];
27
+ mirrors --button-label-line-height. */
28
+ --segment-label-line-height: var(--a-leading-snug);
22
29
 
23
30
  /* ── Colors ── */
24
31
  --segment-fg: var(--a-ui-text-subtle);
@@ -92,6 +99,9 @@ segment-ui[selected] {
92
99
  overflow: hidden;
93
100
  text-overflow: ellipsis;
94
101
  white-space: nowrap;
102
+ /* `overflow: hidden` (needed for the ellipsis) makes this line box the
103
+ clip box — give it leading tall enough to contain the descenders. */
104
+ line-height: var(--segment-label-line-height);
95
105
  }
96
106
 
97
107
  /* States — hover + [selected] rules moved outside @scope; see Safari 17.x bug note at top. */
@@ -14,9 +14,15 @@
14
14
  --step-progress-caption-fg: var(--a-fg-muted);
15
15
  --step-progress-caption-size: var(--a-ui-sm);
16
16
 
17
- /* Sizing — match the bespoke flex shape in registration + onboarding
18
- (flex:0 1 18rem; min-width:10rem). Consumers can override. */
19
- --step-progress-flex: 0 1 18rem;
17
+ /* Sizing — 18rem of track width, shrinkable to 10rem, matching the
18
+ bespoke shape in registration + onboarding. Expressed as inline-size
19
+ rather than flex-basis (gh#942): a flex-basis of 18rem is
20
+ direction-fragile — inside a COLUMN-direction flex parent (col-ui,
21
+ any stacked wizard) the same basis constrains HEIGHT, inflating the
22
+ ~30px component to 288px. inline-size constrains width in both flex
23
+ directions. Consumers can override. */
24
+ --step-progress-flex: 0 1 auto;
25
+ --step-progress-inline-size: 18rem;
20
26
  --step-progress-min-width: 10rem;
21
27
  }
22
28
 
@@ -25,6 +31,13 @@
25
31
  flex-direction: column;
26
32
  gap: var(--step-progress-gap);
27
33
  flex: var(--step-progress-flex);
34
+ /* Deliberate SCOPE-EXTENT exception: this component has always shipped
35
+ a default extent (previously flex-basis 18rem + min-width 10rem —
36
+ extent by another name, just direction-fragile). Both remain
37
+ token-overridable; going fully size-agnostic would silently reflow
38
+ the registration/onboarding row-header consumers. */
39
+ inline-size: var(--step-progress-inline-size);
40
+ max-inline-size: 100%;
28
41
  min-width: var(--step-progress-min-width);
29
42
  }
30
43
 
@@ -282,10 +282,18 @@ registerCellType('progress', {
282
282
  wrapper = document.createElement('row-ui');
283
283
  wrapper.setAttribute('gap', '2');
284
284
  wrapper.setAttribute('align', 'center');
285
+ // The gridcell is a flex container; without an explicit flex the
286
+ // wrapper shrinks to content — and a progress bar has no intrinsic
287
+ // width, so the whole cell collapsed to ~33px and the value span
288
+ // ellipsized to "7.." (gh#940). Fill the cell; keep the numeric
289
+ // label un-shrinkable so the BAR absorbs the flexing instead.
290
+ wrapper.style.flex = '1';
291
+ wrapper.style.minWidth = '0';
285
292
  const prog = document.createElement('progress-ui');
286
293
  prog.setAttribute('size', 'sm');
287
294
  prog.style.flex = '1';
288
295
  const span = document.createElement('span');
296
+ span.style.flexShrink = '0';
289
297
  wrapper.appendChild(prog);
290
298
  wrapper.appendChild(span);
291
299
  cell.appendChild(wrapper);
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "properties": {
16
16
  "columns": {
17
- "description": "Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign=\"top\">).",
17
+ "description": "Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign=\"top\">); col-def also accepts `meta` as a JSON attribute (gh#925) — e.g. <col-def type=\"badge\" meta='{\"variants\":{\"Online\":\"success\"}}'> — so cell-type meta (badge variant maps, currency codes) is expressible in static HTML.",
18
18
  "$ref": "common_types.json#/$defs/DynamicStringList"
19
19
  },
20
20
  "component": {
@@ -432,6 +432,17 @@ export class UITable extends UIElement {
432
432
  const flex = el.getAttribute('flex');
433
433
  if (flex) col.flex = Number(flex);
434
434
 
435
+ /* gh#925 — cell-type meta (e.g. the badge type's per-value variant
436
+ map, currency's currency code) had no declarative form: columns[]
437
+ carries `meta` but col-def never parsed it, so a static-HTML badge
438
+ column was structurally stuck at the unmapped tone="muted"
439
+ fallback. JSON attribute, same silent-on-malformed discipline as
440
+ the host's `data` attribute (gh#288). */
441
+ const meta = el.getAttribute('meta');
442
+ if (meta) {
443
+ try { col.meta = JSON.parse(meta); } catch (_) { /* malformed JSON — ignored */ }
444
+ }
445
+
435
446
  cols.push(col);
436
447
  el.remove();
437
448
  }
@@ -313,8 +313,17 @@
313
313
  white-space: nowrap;
314
314
  }
315
315
 
316
+ /* Must match the span/a rule above: without flex:1, row-ui (a flex
317
+ child with default flex:0 1 auto) sizes to its own shrink-to-fit
318
+ content instead of filling the cell — avatar/progress composite
319
+ types then split that tiny intrinsic width between their
320
+ progress-ui/avatar-ui and label span, clipping the label (gh#940:
321
+ stock "71%" rendered as "7.."). Generalizes the per-renderer inline
322
+ flex fix from PR #945 to every composite cell type; adopted from
323
+ PR #943's analysis. */
316
324
  [data-body] [role="gridcell"] > row-ui {
317
325
  min-width: 0;
326
+ flex: 1;
318
327
  overflow: hidden;
319
328
  }
320
329
 
@@ -86,7 +86,7 @@ export interface TableSortEventDetail {
86
86
  export type TableSortEvent = CustomEvent<TableSortEventDetail>;
87
87
 
88
88
  export class UITable extends UIElement {
89
- /** Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign="top">). */
89
+ /** Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign="top">); col-def also accepts `meta` as a JSON attribute (gh#925) — e.g. <col-def type="badge" meta='{"variants":{"Online":"success"}}'> — so cell-type meta (badge variant maps, currency codes) is expressible in static HTML. */
90
90
  columns: string;
91
91
  /** JS property (set programmatically — `el.data = [...]`) — row records, an array of plain objects keyed to columns[].key. May also be supplied declaratively as a JSON-array `data="[…]"` attribute, hydrated once at connect (gh#288) — the same pattern chart-ui's `data` attribute uses. Custom accessor on the element class, not a reflected attribute. */
92
92
  data: string;
@@ -20,7 +20,7 @@ composes:
20
20
  - badge-ui
21
21
  props:
22
22
  columns:
23
- description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign="top">).
23
+ description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign="top">); col-def also accepts `meta` as a JSON attribute (gh#925) — e.g. <col-def type="badge" meta='{"variants":{"Online":"success"}}'> — so cell-type meta (badge variant maps, currency codes) is expressible in static HTML.
24
24
  type: array
25
25
  default: []
26
26
  dynamic: true # JS-set collection prop with a custom setter — deliberately not in static properties
@@ -5,7 +5,9 @@
5
5
  --tabs-bg: transparent;
6
6
  --tabs-radius: var(--a-radius-md);
7
7
  --tabs-button-height: var(--a-size);
8
- --tabs-font-size: var(--a-ui-md);
8
+ /* One rung UP the --a-ui-* scale (gh#965): tab labels are primary
9
+ navigation, not control chrome. */
10
+ --tabs-font-size: var(--a-ui-lg);
9
11
  --tabs-font-family: var(--a-font-family-ui);
10
12
  /* Strip owns the button-to-button spacing — 4px base gap + one
11
13
  padding unit (8px) folded in, since tab-button carries no
@@ -30,7 +30,10 @@ tag-ui[removable]:not([disabled]):hover {
30
30
  --tag-px: var(--a-space-1-5);
31
31
  --tag-py: var(--a-space-0-5);
32
32
  --tag-gap: var(--a-space-1);
33
- --tag-font: var(--a-ui-sm);
33
+ /* One rung down the --a-ui-* scale (gh#965): tags read as metadata
34
+ chrome, not body copy. Whole tier ladder shifts with it (sm=2xs,
35
+ lg=sm) so the tiers stay one rung apart. */
36
+ --tag-font: var(--a-ui-xs);
34
37
 
35
38
  /* ── Size tiers (gh#779) — md (above) is the pixel-identical default;
36
39
  [size="sm"|"lg"] below re-point the active tokens. Component-owned
@@ -45,8 +48,8 @@ tag-ui[removable]:not([disabled]):hover {
45
48
  --tag-px-lg: var(--a-space-2);
46
49
  --tag-py-sm: var(--a-space-px);
47
50
  --tag-py-lg: var(--a-space-1);
48
- --tag-font-sm: var(--a-ui-xs);
49
- --tag-font-lg: var(--a-ui-md);
51
+ --tag-font-sm: var(--a-ui-2xs);
52
+ --tag-font-lg: var(--a-ui-sm);
50
53
  --tag-font-family: var(--a-font-family-ui);
51
54
  --tag-dismiss-size: 1rem;
52
55
  --tag-bg-hover: var(--a-bg-hover);
@@ -292,7 +295,7 @@ tag-ui[removable]:not([disabled]):hover {
292
295
  :scope[size="md"] {
293
296
  --tag-px: var(--a-space-1-5);
294
297
  --tag-py: var(--a-space-0-5);
295
- --tag-font: var(--a-ui-sm);
298
+ --tag-font: var(--a-ui-xs);
296
299
  }
297
300
  :scope[size="lg"] {
298
301
  --tag-px: var(--tag-px-lg);
package/core/element.js CHANGED
@@ -176,12 +176,43 @@ export class UIElement extends HTMLElement {
176
176
  // compliant environments: the value already matches (the property
177
177
  // setter's Object.is check short-circuits), so this never causes an
178
178
  // extra render pass on browsers/environments that already got it right.
179
+ // The whole pre-sync loop runs UNTRACKED: connectedCallback commonly
180
+ // fires synchronously inside a PARENT's render effect (template-created
181
+ // children connect during stamp()), and the gh#961 branch below READS
182
+ // `this[key]` — a tracked read there subscribes the parent's effect to
183
+ // this child's signals, so any later child-prop write (a `.checked`
184
+ // template apply) re-dirties the parent, which re-stamps fresh
185
+ // children, whose own mount-reads re-subscribe: the unbounded
186
+ // parent↔child oscillation the signals drain-loop guard traps (the
187
+ // admin-settings/switch-ui/button-ui loop on gh#961). Same rationale
188
+ // as the untracked(connected()) wrapper below.
189
+ untracked(() => {
179
190
  for (const [key, cfg] of Object.entries(ctor.properties)) {
180
191
  const attr = cfg.attribute ?? key.toLowerCase();
181
192
  if (this.hasAttribute(attr)) {
182
193
  this[key] = parseAttr(this.getAttribute(attr), cfg.type ?? String);
194
+ } else if (cfg.reflect && cfg.type === Boolean && this[key] === true) {
195
+ // gh#961 — stamp the initial attribute for a TRUE-default reflected
196
+ // boolean. installProps seeds the signal with the default but can't
197
+ // touch attributes (constructor restriction), and the property
198
+ // setter's Object.is short-circuit means a later same-value set
199
+ // (e.g. chat-thread's #syncEmptyFromChildren recomputing `empty:
200
+ // true` on first connect) never reflects either — so CSS gates like
201
+ // `chat-thread:not([empty]) > chat-empty` and
202
+ // `editor-canvas:not([empty]) > editor-canvas-empty` read the
203
+ // wrong state forever. Deliberately scoped to Boolean-true ONLY:
204
+ // reflecting string/number defaults here stamps `text=""`-shaped
205
+ // attributes framework-wide, flipping `[attr]` CSS gates and
206
+ // cascading re-renders into the signals drain-loop guard (the
207
+ // full-reflect attempt documented on gh#961). A false default is
208
+ // already a DOM no-op (removeAttribute on an absent attribute).
209
+ // The setAttribute replays attributeChangedCallback → parseAttr →
210
+ // same value → Object.is short-circuit: no signal write, no
211
+ // render pass.
212
+ this.setAttribute(attr, '');
183
213
  }
184
214
  }
215
+ });
185
216
  // connected() commonly reads this element's reactive properties via
186
217
  // template-literal interpolation (`${this.label}` etc.) to stamp its
187
218
  // inner DOM. If the outer call path is inside another effect (e.g.
package/core/signals.js CHANGED
@@ -32,7 +32,9 @@ function flushSigs() {
32
32
  while ((q = pending)) {
33
33
  if (++loops > 100) {
34
34
  pending = null;
35
- console.error(`signals: drain loop exceeded 100 iterations`);
35
+ // Name the still-dirty effect hosts — a bare count is undiagnosable
36
+ // (gh#961's oscillation took host attribution to even locate).
37
+ console.error(`signals: drain loop exceeded 100 iterations`, JSON.stringify([...q].map(s => s.host || '?')));
36
38
  break;
37
39
  }
38
40
  pending = null;