@adia-ai/web-components 0.8.33 → 0.8.35

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 (118) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/components/agent-questions/agent-questions.class.js +0 -1
  3. package/components/agent-reasoning/agent-reasoning.a2ui.json +1 -1
  4. package/components/agent-reasoning/agent-reasoning.yaml +3 -3
  5. package/components/badge/badge.css +33 -7
  6. package/components/badge/badge.examples.md +4 -4
  7. package/components/button/button.css +12 -0
  8. package/components/card/card.css +44 -1
  9. package/components/check/check.a2ui.json +25 -11
  10. package/components/check/check.class.js +5 -0
  11. package/components/check/check.css +21 -0
  12. package/components/check/check.d.ts +2 -0
  13. package/components/check/check.examples.md +4 -4
  14. package/components/check/check.yaml +26 -7
  15. package/components/combobox/combobox.a2ui.json +1 -1
  16. package/components/combobox/combobox.yaml +3 -0
  17. package/components/command/command.a2ui.json +4 -0
  18. package/components/command/command.class.js +54 -4
  19. package/components/command/command.d.ts +2 -0
  20. package/components/command/command.yaml +25 -3
  21. package/components/date-range-picker/date-range-picker.a2ui.json +1 -1
  22. package/components/date-range-picker/date-range-picker.class.js +42 -43
  23. package/components/date-range-picker/date-range-picker.css +11 -0
  24. package/components/date-range-picker/date-range-picker.yaml +6 -3
  25. package/components/demo-toggle/demo-toggle.css +4 -0
  26. package/components/demo-toggle/demo-toggle.examples.md +4 -4
  27. package/components/description-list/description-list.css +16 -4
  28. package/components/feed/feed-item.a2ui.json +3 -2
  29. package/components/feed/feed-item.yaml +7 -1
  30. package/components/feed/feed.css +28 -5
  31. package/components/field/field.examples.md +4 -4
  32. package/components/image/image.class.js +8 -0
  33. package/components/modal/modal.class.js +43 -2
  34. package/components/modal/modal.css +23 -0
  35. package/components/nav-group/nav-group.css +27 -5
  36. package/components/nav-item/nav-item.css +11 -3
  37. package/components/option-card/option-card.a2ui.json +10 -10
  38. package/components/option-card/option-card.css +32 -16
  39. package/components/option-card/option-card.yaml +19 -15
  40. package/components/radio/radio.examples.md +4 -4
  41. package/components/range/range.css +8 -4
  42. package/components/richtext/richtext.css +5 -1
  43. package/components/segment/segment.css +10 -0
  44. package/components/slider/slider.a2ui.json +6 -3
  45. package/components/slider/slider.css +28 -12
  46. package/components/slider/slider.yaml +11 -3
  47. package/components/stat/stat.examples.md +4 -4
  48. package/components/step-progress/step-progress.css +16 -3
  49. package/components/swatch/swatch.css +14 -6
  50. package/components/switch/switch.examples.md +4 -4
  51. package/components/table/cell-types.js +8 -0
  52. package/components/table/table.a2ui.json +1 -1
  53. package/components/table/table.class.js +11 -0
  54. package/components/table/table.css +9 -0
  55. package/components/table/table.d.ts +1 -1
  56. package/components/table/table.yaml +1 -1
  57. package/components/table-toolbar/table-toolbar.css +10 -1
  58. package/components/tabs/tabs.css +13 -5
  59. package/components/tag/tag.class.js +14 -1
  60. package/components/tag/tag.css +70 -9
  61. package/components/text/text.examples.md +4 -4
  62. package/components/timeline/timeline-item.a2ui.json +2 -2
  63. package/components/timeline/timeline-item.yaml +2 -2
  64. package/components/timeline/timeline.css +33 -5
  65. package/components/timeline/timeline.d.ts +2 -2
  66. package/components/toast/toast.css +28 -5
  67. package/core/element.js +31 -0
  68. package/core/signals.js +3 -1
  69. package/custom-elements.json +469 -46
  70. package/dist/theme-provider.min.js +2 -2
  71. package/dist/web-components.min.css +1 -1
  72. package/dist/web-components.min.js +41 -41
  73. package/dist/web-components.sheet.js +1 -1
  74. package/package.json +1 -1
  75. package/patterns/access-requests/access-requests.examples.html +3 -3
  76. package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +3 -3
  77. package/patterns/admin-shell/admin-shell.examples.html +3 -4
  78. package/patterns/agent-cost/agent-cost.examples.html +3 -3
  79. package/patterns/agent-memory/agent-memory.examples.html +3 -3
  80. package/patterns/agent-prompt-library/agent-prompt-library.examples.html +3 -3
  81. package/patterns/agent-tool-call/agent-tool-call.examples.html +3 -3
  82. package/patterns/alert-dialog/alert-dialog.examples.html +3 -3
  83. package/patterns/app-nav/app-nav.examples.html +4 -4
  84. package/patterns/approvals/approvals.examples.html +3 -3
  85. package/patterns/audit-log/audit-log.examples.html +3 -3
  86. package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +19 -9
  87. package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.js +2 -2
  88. package/patterns/changelog-feed/changelog-feed.examples.html +3 -3
  89. package/patterns/chart-in-card/chart-in-card.examples.html +37 -3
  90. package/patterns/chart-in-card/chart-in-card.examples.js +12 -0
  91. package/patterns/chart-in-card/chart-in-card.html +6 -0
  92. package/patterns/chat-shell/chat-shell.examples.html +3 -4
  93. package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +3 -3
  94. package/patterns/conversion-funnel/conversion-funnel.examples.html +3 -3
  95. package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +3 -3
  96. package/patterns/diff-review/diff-review.examples.html +3 -3
  97. package/patterns/editor-shell/editor-shell.examples.html +3 -4
  98. package/patterns/export-flow/export-flow.examples.html +3 -3
  99. package/patterns/filter-bar/filter-bar.examples.html +3 -3
  100. package/patterns/form-drawer/form-drawer.examples.html +3 -3
  101. package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +3 -3
  102. package/patterns/inline-dialog/inline-dialog.examples.html +3 -3
  103. package/patterns/kanban-board/kanban-board.examples.html +3 -3
  104. package/patterns/layout/layout.examples.html +4 -7
  105. package/patterns/marketing-engagement/marketing-engagement.examples.html +3 -3
  106. package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +3 -3
  107. package/patterns/permissions-matrix/permissions-matrix.examples.html +3 -3
  108. package/patterns/permissions-role-picker/permissions-role-picker.examples.html +3 -3
  109. package/patterns/permissions-sharing/permissions-sharing.examples.html +3 -3
  110. package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +3 -3
  111. package/patterns/record-detail-drawer/record-detail-drawer.examples.html +3 -3
  112. package/patterns/responsive-shell-sidebar/responsive-shell-sidebar.examples.html +135 -147
  113. package/patterns/retention-cohort/retention-cohort.examples.html +3 -3
  114. package/patterns/search-discovery/search-discovery.examples.html +3 -3
  115. package/patterns/system-banners/system-banners.examples.html +3 -3
  116. package/patterns/timezone-selector/timezone-selector.examples.html +3 -3
  117. package/patterns/wizard-drawer/wizard-drawer.examples.html +3 -3
  118. package/styles/colors/semantics/core.css +22 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog — @adia-ai/web-components
2
2
 
3
+ ## [0.8.35] — 2026-08-13
4
+
5
+ ### Maintenance
6
+ - **Source fixes this window are carried per-file below; the narrative CHANGELOG entries live with the substantive packages.** Bumped in lockstep per `scripts/release/check-lockstep.mjs`. Headline v0.8.35 work shipped in factory-DX program (scaffold boot+CSS fixes, class-B npm marketplace, consumer CI recipe, DX sweep) + audit-wave plugin hardening + ~20 component/pattern fixes. See `packages/plugins/adia-ui-factory/CHANGELOG.md#0835--2026-08-13` for details.
7
+ - **`components/` touched in this release window** (58 file(s), e.g. `agent-questions/agent-questions.class.js`) — carried by the entries above.
8
+ - **`dist/` bundles rebuilt** in this cut's window (4 file(s)) — regenerated from the source changes described above, not independent edits.
9
+ - **`patterns/` touched in this release window** (43 file(s), e.g. `access-requests/access-requests.examples.html`) — carried by the entries above.
10
+ - **`traits/` touched in this release window** (5 file(s), e.g. `anchor-positioning/anchor-positioning.examples.html`) — carried by the entries above.
11
+
12
+ ## [0.8.34] — 2026-08-11
13
+
14
+ ### Fixed
15
+
16
+ - **`date-range-picker-ui` — both calendar grids are now ONE click-start-click-end selection surface (gh#948, reported from adiav2 ADIA2-8020).** The change handlers were role-locked per pane (`from`-pane clicks could only ever write the range start), so a same-month range required paging the right pane back a month. First click = start, second = end in either grid; a second click earlier than the pending start restarts there; a click after a committed range starts fresh. Popover content-sizing (`width: max-content` + viewport/token caps) moved into the stylesheet so the JS anchor fallback and anchoring failures can no longer render a viewport-huge panel.
17
+ - **`UIElement` — `reflect:true` booleans with a TRUE default now stamp their attribute on connect (gh#961).** installProps seeds the signal but can't write attributes (constructor restriction), and the setter's `Object.is` short-circuit skips reflection on same-value sets — so attribute-keyed CSS (`chat-thread:not([empty]) > chat-empty`, `editor-canvas:not([empty]) > editor-canvas-empty`, password-strength's `:not([show-label])`) read wrong state forever; 9 components carried the latent shape. The connect-time pre-sync loop now runs inside `untracked()` — its property reads otherwise subscribe a PARENT's render effect to template-created children's signals, the unbounded re-stamp oscillation the signals drain guard traps (the guard now names the still-dirty effect hosts).
18
+ - **`modal-ui` — `aria-labelledby` falls back to an authored `[slot="heading"]` when `text=` is absent (gh#947), and an authored bare `<header>` no longer displaces into the body (gh#941).** Header icon+heading row layout is by tag + DOM order: the last non-button child claims the row, earlier content stays fixed-width.
19
+ - **`table-ui` — composite-cell wrapper flex generalized to CSS (gh#940):** `[data-body] [role="gridcell"] > row-ui { flex: 1 }` covers progress AND avatar cells; the progress bar no longer collapses to ~33px. `<col-def meta>` JSON attribute carries per-column config (badge variant maps) declaratively.
20
+ - **`step-progress-ui` — direction-agnostic sizing (gh#942):** `inline-size: 18rem` + `max-inline-size: 100%` replace the direction-fragile `flex-basis`, so a column-flex parent no longer inflates the component to 288px tall.
21
+ - **`command-ui` / `combobox-ui` — `options` catalog schema corrected to rich objects (`DynamicObjectList`).** Both yamls declare `{value, label, icon?, …}` objects but omitted `items.type`, mapping to the string-list default — the dialect validator rejected every harvested rich-options palette (caught by exit-gate.corpus at release pre-flight).
22
+
23
+ ### Changed
24
+
25
+ - **`richtext` — inline `<code>` chip background softened to container-low (gh#972):** the opaque `--a-canvas-2` chip out-competed body text where several tag references cluster; the 10% alpha tier recedes behind prose and composites consistently on stacked surfaces. The docs site's own `admin-page code` rule moves to the same tier (superseding its earlier well-strong non-text-contrast ruling — inline code is text formatting, not a UI component).
26
+ - **`nav-item-ui` / `nav-group-ui` — state color spec (gh#968, operator ruling):** idle = neutral icons + neutral-high text on transparent; hover = on-surface on container-low; selected = on-surface on container. Selected glyphs drop the primary accent (supersedes gh#522's glyph half — the accent BAR keeps primary); `neutral/surfaceBright` was considered for the selected fill and ruled out.
27
+ - **`tag-ui` / `badge-ui` / `tabs-ui` — font scale steps one rung (gh#965):** tag/badge default `--a-ui-sm` → `--a-ui-xs` with tiers shifting to `--a-ui-2xs`/`--a-ui-sm` (first consumer of the pre-staged 10px token); tabs `--a-ui-md` → `--a-ui-lg`.
28
+ - **`description-list-ui` — `dt` renders as a kicker/data-label** (code family at `--a-label-size`, normal weight); **`dd` is semibold at context-inherited size** (`1em`) so the value leads the pair and tracks the surrounding prose.
29
+ - **Token docs — the `-strong` semantics split documented (gh#969):** `--a-canvas-strong` is a surface-tonal tier while interactive families' `-strong` is the pressed/high-emphasis foreground — noted at the core.css declarations and in component-token-contract.md. No value changes.
30
+
31
+ ### Maintenance
32
+ - **`dist/` bundles rebuilt** in this cut's window (4 file(s)) — regenerated from the source changes described above, not independent edits.
33
+ - **`patterns/` touched in this release window** (1 file(s), e.g. `responsive-shell-sidebar/responsive-shell-sidebar.examples.html`) — carried by the entries above.
34
+ - **`styles/` touched in this release window** (1 file(s), e.g. `semantics/core.css`) — carried by the entries above.
35
+
3
36
  ## [0.8.33] — 2026-08-11
4
37
 
5
38
  ### Added
@@ -133,7 +133,6 @@ export class UIAgentQuestions extends UIElement {
133
133
  this.#submitEl = document.createElement('button-ui');
134
134
  this.#submitEl.setAttribute('text', this.submitLabel || 'Continue');
135
135
  this.#submitEl.setAttribute('variant', 'primary');
136
- this.#submitEl.setAttribute('size', 'sm');
137
136
  this.#submitEl.setAttribute('disabled', '');
138
137
  if (!this.multi) this.#submitEl.hidden = true;
139
138
  this.#submitEl.addEventListener('press', this.#onSubmit);
@@ -111,7 +111,7 @@
111
111
  ],
112
112
  "slots": {
113
113
  "default": {
114
- "description": "Default slot — primary child content."
114
+ "description": "Default slot — NOT a step-authoring surface: the component rebuilds its body from the addStep()/addThought() pipeline and does not consume authored children (gh#938)."
115
115
  }
116
116
  },
117
117
  "states": [
@@ -57,7 +57,7 @@ events:
57
57
  description: Whether the reasoning section is now collapsed.
58
58
  slots:
59
59
  default:
60
- description: "Default slot — primary child content."
60
+ description: "Default slot — NOT a step-authoring surface: the component rebuilds its body from the addStep()/addThought() pipeline and does not consume authored children (gh#938)."
61
61
  states:
62
62
  - name: idle
63
63
  description: Default, ready for interaction.
@@ -72,8 +72,8 @@ a2ui:
72
72
  rules:
73
73
  - rule: 'Agent inner-monologue + pipeline viewer with steps, thoughts, plans, and iterations.'
74
74
  reason: 'Reasoning visualization surface.'
75
- - rule: 'Composes <timeline-ui> internallypass reasoning steps as children rendered as <timeline-item-ui>.'
76
- reason: 'Composition contract.'
75
+ - rule: 'JS-property-driven ONLY — steps arrive via addStep()/addThought(); the component renders them internally as <timeline-item-ui> rows. Authored light-DOM children are NOT consumed (gh#938) for a static/harvestable reasoning-steps surface, author a plain <timeline-ui> with <timeline-item-ui> children instead.'
76
+ reason: 'Composition contract — the previous wording ("pass reasoning steps as children") read as declarative-children support the class never had; a stateful pipeline (timers, spine layout, iterations) has no static form.'
77
77
  - rule: 'noAutocollapse attribute prevents the auto-collapse-on-complete behavior; useful for debug surfaces.'
78
78
  reason: 'Behavior knob.'
79
79
  - rule: 'Different from <agent-trace-ui> (metrics + tool-call summary) — reasoning is narrative, trace is structured-data.'
@@ -20,9 +20,25 @@
20
20
  only radius moved. */
21
21
  --badge-radius: var(--a-radius-full);
22
22
  --badge-px: var(--a-space-1-5);
23
- --badge-py: var(--a-space-0-5);
23
+ /* gh#1084: block padding flattened toward 1px per tier — the
24
+ --badge-min-h floor (below) now owns the chip's vertical size, so
25
+ py no longer has to carry it. Was --a-space-0-5 (2px); mirrors the
26
+ identical tag-ui change (same rationale, tag.css). */
27
+ --badge-py: var(--a-space-px);
24
28
  --badge-gap: var(--a-space-1);
25
- --badge-font-size: var(--a-ui-sm);
29
+ /* gh#1084 — min-height floor, decoupled from padding (mirrors
30
+ tags-input-ui's --tags-input-min-h, and tag-ui's own --tag-min-h
31
+ added in the same change). Component-owned fixed rem constants
32
+ (not density-scaled) calibrated to badge's CURRENT rendered
33
+ height at each tier — measured 12/15/20px sm/md/lg before this
34
+ change — so this is a non-regressing addition: shrinking py
35
+ toward 1px no longer shrinks the badge, the floor holds the line. */
36
+ --badge-min-h: 0.9375rem; /* 15px @ d=1 — md */
37
+ --badge-min-h-sm: 0.75rem; /* 12px @ d=1 */
38
+ --badge-min-h-lg: 1.25rem; /* 20px @ d=1 */
39
+ /* One rung down the --a-ui-* scale (gh#965) — ladder shifts with it
40
+ (sm=2xs, lg=sm) so tiers stay one rung apart. Mirrors tag-ui. */
41
+ --badge-font-size: var(--a-ui-xs);
26
42
  /* ── Typographic register (issue 845; weight revised gh#866) — mono
27
43
  TRIAL taken: kbd-ui's own stack (--a-font-family-code, kbd.css) held
28
44
  up at small badge sizes (roughly a dozen logical pixels) in both
@@ -43,10 +59,13 @@
43
59
  and tag share this md baseline and this exact reasoning). */
44
60
  --badge-px-sm: var(--a-space-1);
45
61
  --badge-px-lg: var(--a-space-2);
62
+ /* gh#1084: py flattened toward 1px/tier now that --badge-min-h
63
+ floors the box — was --a-space-px/--a-space-1 (1px/4px); mirrors
64
+ the identical tag-ui change (tag.css). */
46
65
  --badge-py-sm: var(--a-space-px);
47
- --badge-py-lg: var(--a-space-1);
48
- --badge-font-size-sm: var(--a-ui-xs);
49
- --badge-font-size-lg: var(--a-ui-md);
66
+ --badge-py-lg: var(--a-space-0-5);
67
+ --badge-font-size-sm: var(--a-ui-2xs);
68
+ --badge-font-size-lg: var(--a-ui-sm);
50
69
  text-align: start; /* §text-align-reset — blocks inheritance from centered ancestors */
51
70
  }
52
71
 
@@ -58,6 +77,10 @@
58
77
  justify-content: center;
59
78
  gap: var(--badge-gap);
60
79
  width: fit-content;
80
+ /* gh#1084: floor, not a fixed size — mirrors tags-input-ui's own
81
+ min-height and tag-ui's identical addition (tag.css). Decouples
82
+ the badge's vertical size from --badge-py. */
83
+ min-height: var(--badge-min-h);
61
84
  padding: var(--badge-py) var(--badge-px);
62
85
  background: var(--badge-bg);
63
86
  color: var(--badge-fg);
@@ -237,15 +260,18 @@
237
260
  --badge-px: var(--badge-px-sm);
238
261
  --badge-py: var(--badge-py-sm);
239
262
  --badge-font-size: var(--badge-font-size-sm);
263
+ --badge-min-h: var(--badge-min-h-sm);
240
264
  }
241
265
  :scope[size="md"] {
242
266
  --badge-px: var(--a-space-1-5);
243
- --badge-py: var(--a-space-0-5);
244
- --badge-font-size: var(--a-ui-sm);
267
+ --badge-py: var(--a-space-px);
268
+ --badge-font-size: var(--a-ui-xs);
269
+ --badge-min-h: 0.9375rem; /* 15px @ d=1 — matches the :where(:scope) default */
245
270
  }
246
271
  :scope[size="lg"] {
247
272
  --badge-px: var(--badge-px-lg);
248
273
  --badge-py: var(--badge-py-lg);
249
274
  --badge-font-size: var(--badge-font-size-lg);
275
+ --badge-min-h: var(--badge-min-h-lg);
250
276
  }
251
277
  }
@@ -11,8 +11,8 @@
11
11
  ```html
12
12
  <row-ui gap="6" align="start" wrap>
13
13
  <col-ui gap="2">
14
- <text-ui variant="caption" color="muted">verse</text-ui>
15
- <div verse>
14
+ <text-ui variant="caption" color="muted">scale="ui-sm"</text-ui>
15
+ <div scale="ui-sm">
16
16
  <row-ui gap="2">
17
17
  <badge-ui text="New" variant="success"></badge-ui>
18
18
  <badge-ui text="Beta" variant="info"></badge-ui>
@@ -31,8 +31,8 @@
31
31
  </div>
32
32
  </col-ui>
33
33
  <col-ui gap="2">
34
- <text-ui variant="caption" color="muted">prose</text-ui>
35
- <div prose>
34
+ <text-ui variant="caption" color="muted">scale="content-md"</text-ui>
35
+ <div scale="content-md">
36
36
  <row-ui gap="2">
37
37
  <badge-ui text="New" variant="success"></badge-ui>
38
38
  <badge-ui text="Beta" variant="info"></badge-ui>
@@ -84,6 +84,15 @@ button-ui[variant="ghost"][color="warning"]:not([disabled]):hover { --button-f
84
84
  --button-font-size: var(--a-ui-size);
85
85
  --button-font-weight: var(--a-ui-weight);
86
86
  --button-font-family: var(--a-font-family-ui);
87
+ /* Leading for the [text] label box ONLY — never the host. The host's
88
+ `line-height: 1` is deliberate (it sizes the icon/[slot="trailing"]
89
+ blocks), but the label's ::after clips to its own line box, so
90
+ inheriting 1 makes half-leading negative and shears descenders
91
+ (g/j/p/q/y, parentheses) by ~0.93px at the default size — gh#906.
92
+ Snug (1.3) contains the font's content box at every [size]; the
93
+ precedent is stat.css:174,198 setting leading on the truncating
94
+ box rather than on the component root. */
95
+ --button-label-line-height: var(--a-leading-snug);
87
96
  --button-gap: var(--a-space-1);
88
97
  --button-bg-hover: var(--a-bg-strong); /* neutral fill → neutral -strong surface on hover */
89
98
  --button-fg-hover: var(--a-ui-text-hover);
@@ -167,6 +176,9 @@ button-ui[variant="ghost"][color="warning"]:not([disabled]):hover { --button-f
167
176
  overflow: hidden;
168
177
  text-overflow: ellipsis;
169
178
  min-width: 0;
179
+ /* `overflow: hidden` (needed for the ellipsis) makes this line box the
180
+ clip box — give it leading tall enough to contain the descenders. */
181
+ line-height: var(--button-label-line-height);
170
182
  }
171
183
 
172
184
  /* Display convention (ADR-0037): block-level by default — a text button used
@@ -80,6 +80,18 @@
80
80
  margin-top: auto;
81
81
  }
82
82
 
83
+ /* Flex containers never collapse adjacent margins the way block flow
84
+ does (gh#1098) — once :has(> footer) above switches the card to
85
+ flex-column, a plain header followed by a plain section stacks BOTH
86
+ regions' --card-inset margins into a doubled gap instead of the
87
+ single inset block flow would have collapsed them to. The leading
88
+ region's own margin-bottom already supplies that inset; zero the
89
+ trailing region's margin-top. footer is excluded — its margin-top is
90
+ handled above (bottom-pin `auto`) or by [divider] below. */
91
+ :scope:has(> footer) > :is(header, section) + :is(header, section) {
92
+ margin-block-start: 0;
93
+ }
94
+
83
95
  /* ═══════ Variants — token-only overrides ═══════ */
84
96
 
85
97
  /* `outline` is a documented alias for `outlined` (yaml). */
@@ -385,6 +397,30 @@
385
397
  border-radius: 0;
386
398
  }
387
399
 
400
+ /* Guide/value/legend safety inset (gh#1095) — [bleed]'s zero-inset is
401
+ only collision-free for the bare-marks case: a sparkline (its
402
+ renderer never emits axis ticks, gridlines, value labels, or a
403
+ legend — chart.class.js #renderSparkline), or any other chart-ui
404
+ type with BOTH [hide-grid] and [hide-values] set (the author opted
405
+ out of every text element). A chart that still draws guides or
406
+ value text — or is paired with a <chart-legend-ui> — needs
407
+ clearance from the card's frame, or that text collides with the
408
+ card edge and clips under the corner-rounded `overflow: hidden`
409
+ (:scope, top of file). Restore the card's own inset automatically
410
+ for those cases rather than relying on every author to remember —
411
+ the :has() selector's specificity already beats the plain [bleed]
412
+ reset above regardless of source order. Pairing a legend with a
413
+ genuinely full-bleed chart still works — put the legend in its OWN
414
+ section below, wired via chart-legend-ui[for="<chart id>"], which
415
+ never enters this selector (see chart-in-card.examples.html's
416
+ "Full-bleed chart + legend band" example). */
417
+ & > section[bleed]:has(> chart-ui:not([type="sparkline"]):not([hide-grid])),
418
+ & > section[bleed]:has(> chart-ui:not([type="sparkline"]):not([hide-values])),
419
+ & > section[bleed]:has(> chart-legend-ui) {
420
+ margin: var(--card-inset);
421
+ padding: 0;
422
+ }
423
+
388
424
  /* ═══════ Footer ═══════
389
425
  Margin-based inset. Flex row for actions.
390
426
  [divider] adds top border.
@@ -414,8 +450,15 @@
414
450
  justify-content: flex-end;
415
451
  }
416
452
 
453
+ /* Divider clearance is padding-only (matches header[divider]'s
454
+ padding-bottom pattern) — the space above the line already comes from
455
+ the preceding section's own trailing margin. An explicit margin-top
456
+ here on top of that stacked with the footer's base margin instead of
457
+ collapsing into it (footer/section are flex-column siblings once the
458
+ card has a footer — see the :has(> footer) rule above — so adjacent
459
+ margins never collapse), tripling the gap to the divider vs. the
460
+ content gaps around it (gh#1096). */
417
461
  & > footer[divider] {
418
- margin-top: var(--card-inset);
419
462
  padding-top: var(--card-inset);
420
463
  border-top: 1px solid var(--card-divider);
421
464
  }
@@ -46,6 +46,11 @@
46
46
  "type": "string",
47
47
  "default": ""
48
48
  },
49
+ "labelHidden": {
50
+ "description": "When true, [label] still sets the accessible name (aria-label) but the visible text is suppressed via the canonical sr-only technique (gh#1010). Use when a sibling element already shows the same name visually (e.g. a title next to a check-ui used as a list-item icon) and the plain-visible label would paint it twice.",
51
+ "type": "boolean",
52
+ "default": false
53
+ },
49
54
  "name": {
50
55
  "description": "Form field name",
51
56
  "type": "string",
@@ -123,26 +128,35 @@
123
128
  },
124
129
  "tag": "check-ui",
125
130
  "tokens": {
126
- "--check-box-background": {
131
+ "--check-bg": {
127
132
  "description": "Unchecked background"
128
133
  },
129
- "--check-box-border-color": {
130
- "description": "Unchecked border color"
134
+ "--check-bg-checked": {
135
+ "description": "Checked/indeterminate background"
131
136
  },
132
- "--check-box-radius": {
133
- "description": "Checkbox border radius"
137
+ "--check-bg-checked-hover": {
138
+ "description": "Checked/indeterminate background on hover"
134
139
  },
135
- "--check-box-size": {
136
- "description": "Checkbox indicator size"
140
+ "--check-bg-hover": {
141
+ "description": "Unchecked background on hover"
137
142
  },
138
- "--check-checked-background": {
139
- "description": "Checked/indeterminate background"
143
+ "--check-border": {
144
+ "description": "Unchecked border color"
140
145
  },
141
- "--check-checked-border-color": {
146
+ "--check-border-checked": {
142
147
  "description": "Checked/indeterminate border"
143
148
  },
144
- "--check-checked-foreground": {
149
+ "--check-border-hover": {
150
+ "description": "Unchecked border color on hover"
151
+ },
152
+ "--check-fg-checked": {
145
153
  "description": "Checkmark/dash color"
154
+ },
155
+ "--check-radius": {
156
+ "description": "Checkbox border radius"
157
+ },
158
+ "--check-size": {
159
+ "description": "Checkbox indicator size"
146
160
  }
147
161
  },
148
162
  "traits": [],
@@ -26,6 +26,11 @@ export class UICheck extends UIFormElement {
26
26
  checked: { type: Boolean, default: false, reflect: true },
27
27
  indeterminate: { type: Boolean, default: false, reflect: true },
28
28
  label: { type: String, default: '', reflect: true },
29
+ /* gh#1010: keeps [label] as the accessible name (aria-label, set
30
+ below) while suppressing the VISIBLE `::after` text — for
31
+ compositions that already show the name elsewhere (e.g. a
32
+ sibling title) and would otherwise paint it twice. */
33
+ labelHidden: { type: Boolean, default: false, reflect: true, attribute: 'label-hidden' },
29
34
  };
30
35
 
31
36
  static template = () => html`<span slot="box"></span>`;
@@ -122,6 +122,27 @@ check-ui[indeterminate] {
122
122
  /* Label */
123
123
  :scope[label]::after { content: attr(label); }
124
124
 
125
+ /* Visually-hidden label (gh#1010) — [label-hidden] keeps the a11y name
126
+ (aria-label, set from [label] in check.class.js render()) but drops
127
+ the visible text, for compositions that already show the name
128
+ elsewhere. Same canonical sr-only recipe as <visually-hidden-ui>
129
+ (visually-hidden.css) — clip-path does the real hiding; the rest is
130
+ belt-and-suspenders for cross-UA. More specific than the bare
131
+ `[label]::after` rule above, so it wins whenever both are set. */
132
+ :scope[label][label-hidden]::after {
133
+ content: attr(label);
134
+ position: absolute !important;
135
+ width: 1px !important;
136
+ height: 1px !important;
137
+ padding: 0 !important;
138
+ margin: -1px !important;
139
+ overflow: hidden !important;
140
+ clip: rect(0, 0, 0, 0) !important;
141
+ clip-path: inset(100%) !important;
142
+ white-space: nowrap !important;
143
+ border: 0 !important;
144
+ }
145
+
125
146
  /* Focus */
126
147
  :scope:focus-visible [slot="box"] { box-shadow: var(--check-focus-ring); border-color: transparent; }
127
148
 
@@ -20,6 +20,8 @@ export class UICheck extends UIFormElement {
20
20
  /** Indeterminate (mixed) state — visual only; cleared on click. */
21
21
  indeterminate: boolean;
22
22
  label: string;
23
+ /** When true, [label] still sets the accessible name but the visible text is sr-only hidden (gh#1010). */
24
+ labelHidden: boolean;
23
25
 
24
26
  addEventListener<K extends keyof HTMLElementEventMap>(
25
27
  type: K,
@@ -11,8 +11,8 @@
11
11
  ```html
12
12
  <row-ui gap="6" align="start" wrap>
13
13
  <col-ui gap="2">
14
- <text-ui variant="caption" color="muted">verse</text-ui>
15
- <div verse>
14
+ <text-ui variant="caption" color="muted">scale="ui-sm"</text-ui>
15
+ <div scale="ui-sm">
16
16
  <check-ui label="Notify me" checked></check-ui>
17
17
  </div>
18
18
  </col-ui>
@@ -23,8 +23,8 @@
23
23
  </div>
24
24
  </col-ui>
25
25
  <col-ui gap="2">
26
- <text-ui variant="caption" color="muted">prose</text-ui>
27
- <div prose>
26
+ <text-ui variant="caption" color="muted">scale="content-md"</text-ui>
27
+ <div scale="content-md">
28
28
  <check-ui label="Notify me" checked></check-ui>
29
29
  </div>
30
30
  </col-ui>
@@ -48,6 +48,17 @@ props:
48
48
  required: true
49
49
  type: string
50
50
  default: ""
51
+ labelHidden:
52
+ description: >-
53
+ When true, [label] still sets the accessible name (aria-label) but
54
+ the visible text is suppressed via the canonical sr-only technique
55
+ (gh#1010). Use when a sibling element already shows the same name
56
+ visually (e.g. a title next to a check-ui used as a list-item icon)
57
+ and the plain-visible label would paint it twice.
58
+ type: boolean
59
+ default: false
60
+ reflect: true
61
+ attribute: label-hidden
51
62
  value:
52
63
  description: Form value (submitted as 'on' when checked)
53
64
  type: string
@@ -68,23 +79,31 @@ states:
68
79
  attribute: disabled
69
80
  traits: []
70
81
  tokens:
71
- --check-box-background:
82
+ --check-bg:
72
83
  description: Unchecked background
73
- --check-box-border-color:
84
+ --check-border:
74
85
  description: Unchecked border color
75
- --check-box-radius:
86
+ --check-radius:
76
87
  description: Checkbox border radius
77
- --check-box-size:
88
+ --check-size:
78
89
  description: Checkbox indicator size
79
- --check-checked-background:
90
+ --check-bg-checked:
80
91
  description: Checked/indeterminate background
81
- --check-checked-border-color:
92
+ --check-border-checked:
82
93
  description: Checked/indeterminate border
83
- --check-checked-foreground:
94
+ --check-fg-checked:
84
95
  description: Checkmark/dash color
96
+ --check-border-hover:
97
+ description: Unchecked border color on hover
98
+ --check-bg-hover:
99
+ description: Unchecked background on hover
100
+ --check-bg-checked-hover:
101
+ description: Checked/indeterminate background on hover
85
102
  a2ui:
86
103
  rules:
87
104
  - "Self-labeling widget — use the [label] attribute directly; do NOT wrap in <field-ui>. The widget renders its own label inline via CSS attr() pattern. Wrapping breaks the consent-row layout (see field-ui anti_patterns)."
105
+ - rule: 'When check-ui is used as a list-item icon/indicator beside a separate title element carrying the same name (a title column, a list-item-ui slot="text"), set [label-hidden] alongside [label] so the accessible name is preserved without painting the name twice.'
106
+ reason: 'gh#1010 — the visible ::after label and an adjacent title both showed the step name, and the visible labels varied width, producing a ragged title column.'
88
107
  - rule: 'Three states supported: unchecked (default), checked (via [checked]), and indeterminate (via [indeterminate]); form-associated.'
89
108
  reason: 'Per ADR-0025 — host IS the control, no wrapped <input>.'
90
109
  - rule: 'For binary on/off toggles where visual switch metaphor matters, use <switch-ui> instead; check-ui is for multi-select sets and form-acceptance gates.'
@@ -98,7 +98,7 @@
98
98
  },
99
99
  "options": {
100
100
  "description": "Programmatic option list. Array of {value, label, disabled?, group?} or grouped {label, options:[…]}. Alternative to declarative <option> / <optgroup> children.",
101
- "$ref": "common_types.json#/$defs/DynamicStringList"
101
+ "$ref": "common_types.json#/$defs/DynamicObjectList"
102
102
  },
103
103
  "placeholder": {
104
104
  "description": "Placeholder text in the input when value is empty",
@@ -85,6 +85,9 @@ props:
85
85
  or grouped {label, options:[…]}. Alternative to declarative
86
86
  <option> / <optgroup> children.
87
87
  type: array
88
+ # Rich option objects — same DynamicObjectList mapping fix as command-ui.
89
+ items:
90
+ type: object
88
91
  default: []
89
92
  dynamic: true # JS-set collection prop with a custom setter — deliberately not in static properties
90
93
  open:
@@ -21,6 +21,10 @@
21
21
  "type": "boolean",
22
22
  "default": false
23
23
  },
24
+ "options": {
25
+ "description": "Programmatic item list. Array of {value, label, icon?, shortcut?, disabled?} or grouped {label, options:[…]}. `icon` renders as a leading icon-ui, `shortcut` as a trailing keyboard-shortcut hint — the same fields as the data-icon/data-shortcut attributes on declarative <option> children. Alternative to declarative <option data-icon data-shortcut> / <optgroup label> children.",
26
+ "$ref": "common_types.json#/$defs/DynamicObjectList"
27
+ },
24
28
  "placeholder": {
25
29
  "description": "Placeholder text for the search input",
26
30
  "type": "string",
@@ -85,10 +85,15 @@ export class UICommand extends UIElement {
85
85
  render() {
86
86
  // Stamp internal structure once
87
87
  if (!this.#inputEl) {
88
- // Parse options + capture author-provided slot="empty" content
89
- // before replacing innerHTML (dogfood-status yaml-drift finding:
90
- // yaml documented an overridable empty state that was never wired).
91
- this.#parseOptions();
88
+ // Parse declarative <option>/<optgroup> children once, unless
89
+ // `.options` was already set programmatically before first render
90
+ // (mirrors combobox-ui's connected()-time guard) + capture
91
+ // author-provided slot="empty" content before replacing innerHTML
92
+ // (dogfood-status yaml-drift finding: yaml documented an overridable
93
+ // empty state that was never wired).
94
+ if (this.#items.length === 0) {
95
+ this.#parseOptions();
96
+ }
92
97
  this.#emptyNodes = logicalSlotted(this, 'empty');
93
98
 
94
99
  this.innerHTML = `
@@ -143,6 +148,51 @@ export class UICommand extends UIElement {
143
148
  this.#renderItems();
144
149
  }
145
150
 
151
+ // ── Programmatic options (gh#887) ──
152
+ //
153
+ // Public API mirroring combobox-ui's `options` prop: an array of flat
154
+ // {value, label, icon?, shortcut?, disabled?} entries, or grouped
155
+ // {label, options:[…]} entries — same field names as the data-icon/
156
+ // data-shortcut attributes on declarative <option> children. Normalized
157
+ // into the internal #items shape #renderItems() already consumes (flat
158
+ // items carry `category`; groups carry `.items` not `.options`), so
159
+ // programmatic and declarative authoring render identically.
160
+
161
+ set options(list) {
162
+ this.#items = this.#normalizeOptions(Array.isArray(list) ? list : []);
163
+ // Only re-render if the internal list has already been stamped —
164
+ // otherwise the pending #items feed the first render() pass directly.
165
+ if (this.#listEl) this.#renderItems(this.#inputEl?.value || '');
166
+ }
167
+
168
+ get options() { return this.#items; }
169
+
170
+ #normalizeOptions(list) {
171
+ const out = [];
172
+ for (const entry of list) {
173
+ if (entry && Array.isArray(entry.options)) {
174
+ const label = entry.label || '';
175
+ out.push({ label, items: entry.options.map((opt) => this.#optionObjectToItem(opt, label)) });
176
+ } else if (entry) {
177
+ out.push(this.#optionObjectToItem(entry, ''));
178
+ }
179
+ }
180
+ return out;
181
+ }
182
+
183
+ /** Plain-object counterpart of #optionToItem (which reads a DOM <option>). */
184
+ #optionObjectToItem(opt, category = '') {
185
+ return {
186
+ value: opt.value,
187
+ label: opt.label ?? opt.value,
188
+ icon: opt.icon || '',
189
+ shortcut: opt.shortcut || '',
190
+ keywords: opt.keywords || '',
191
+ disabled: !!opt.disabled,
192
+ category,
193
+ };
194
+ }
195
+
146
196
  // ── Parse declarative <option>/<optgroup> ──
147
197
 
148
198
  // Wrapper-piercing walk for consumer-authored <option>/<optgroup> children.
@@ -35,6 +35,8 @@ export type CommandSelectEvent = CustomEvent<CommandSelectEventDetail>;
35
35
  export class UICommand extends UIElement {
36
36
  /** Whether the command palette is visible */
37
37
  open: boolean;
38
+ /** Programmatic item list. Array of {value, label, icon?, shortcut?, disabled?} or grouped {label, options:[…]}. `icon` renders as a leading icon-ui, `shortcut` as a trailing keyboard-shortcut hint — the same fields as the data-icon/data-shortcut attributes on declarative <option> children. Alternative to declarative <option data-icon data-shortcut> / <optgroup label> children. */
39
+ options: string;
38
40
  /** Placeholder text for the search input */
39
41
  placeholder: string;
40
42
 
@@ -24,6 +24,24 @@ props:
24
24
  description: Placeholder text for the search input
25
25
  type: string
26
26
  default: Type a command...
27
+ options:
28
+ description: >-
29
+ Programmatic item list. Array of {value, label, icon?, shortcut?,
30
+ disabled?} or grouped {label, options:[…]}. `icon` renders as a
31
+ leading icon-ui, `shortcut` as a trailing keyboard-shortcut hint —
32
+ the same fields as the data-icon/data-shortcut attributes on
33
+ declarative <option> children. Alternative to declarative
34
+ <option data-icon data-shortcut> / <optgroup label> children.
35
+ type: array
36
+ # Rich option OBJECTS ({value, label, icon?, …} / grouped) — declares
37
+ # items.type so the a2ui catalog maps to DynamicObjectList, not the
38
+ # string-list default (components.mjs TKT-0010 mapping). Without this
39
+ # the dialect validator rejected every harvested rich-options palette
40
+ # (exit-gate.corpus caught it at release pre-flight).
41
+ items:
42
+ type: object
43
+ default: []
44
+ dynamic: true # JS-set collection prop with a custom setter — mirrors combobox-ui's `options`
27
45
  events:
28
46
  dismiss:
29
47
  description: Fired when Escape is pressed
@@ -69,9 +87,13 @@ a2ui:
69
87
  shortcut. Do NOT hand-roll a <dialog> + Cmd+K key listener.
70
88
  - >-
71
89
  Author items as native <option value data-icon data-shortcut>
72
- elements inside <optgroup label="…"> for grouped sections. The
73
- `select` event's detail.category mirrors the parent optgroup's
74
- label. Detail = { value, label, category }.
90
+ elements inside <optgroup label="…"> for grouped sections, OR set
91
+ `.options` programmatically as an array of
92
+ `{value, label, icon?, shortcut?, disabled?}` (grouped form:
93
+ `{label, options:[…]}`) — same fields, alternative to declarative
94
+ children. The `select` event's detail.category mirrors the parent
95
+ optgroup's label (or the group's `label` when authored via
96
+ `options`). Detail = { value, label, category }.
75
97
  - >-
76
98
  Decision rule vs adjacent surfaces. Use <menu-ui> for small
77
99
  NON-searchable popover menus (≤10 actions, triggered by a
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://adiaui.dev/a2ui/v0_9/components/DateRangePicker.json",
4
4
  "title": "DateRangePicker",
5
- "description": "Compound form primitive for selecting a start + end date pair with optional named preset shortcuts (\"Today\", \"Last 7 days\", \"This month\", ...). Default presentation is a trigger button showing the formatted range; on activation a popover opens with two synchronized calendar panes + a preset side rail. Composes <calendar-picker-ui> twice; emits {from, to} ISO 8601 dates. Optional comparison-range mode emits a secondary range for analytics surfaces.",
5
+ "description": "Compound form primitive for selecting a start + end date pair with optional named preset shortcuts (\"Today\", \"Last 7 days\", \"This month\", ...). Default presentation is a trigger button showing the formatted range; on activation a popover opens with two synchronized calendar panes + a preset side rail. Composes <calendar-grid-ui> twice; emits {from, to} ISO 8601 dates. Both panes are ONE selection surface: first click = start, second click = end, in either grid; a second click earlier than the start restarts the range there. Optional comparison-range mode emits a secondary range for analytics surfaces.",
6
6
  "type": "object",
7
7
  "allOf": [
8
8
  {