@adia-ai/web-components 0.6.42 → 0.6.43

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog — @adia-ai/web-components
2
2
 
3
+ ## [0.6.43] — 2026-05-27
4
+
5
+ ### Added — `<list-ui>` `[contained]` prop declared in yaml
6
+
7
+ - **`list/list.yaml` + `list.d.ts` + `list.a2ui.json`** — declares the `[contained]` attribute (already implemented in CSS) as a first-class prop so the gen-UI corpus + A2UI sidecar can emit `<list-ui contained>` deterministically. Closes gen-review cycle-18 task #63.
8
+
9
+ ### Fixed — Cycle-18 Ralph Loop substrate fixes (button + card + alert)
10
+
11
+ - **`button/button.yaml` + `button.class.js` + `button.css` + `button.a2ui.json`** — Ralph-Loop sweep absorbing cycle-18 RL findings (6 visual bugs fixed at SoT).
12
+ - **`card/card.css`** — preserves transpiled `text-ui` slot grammar; matches header-slot patterns from RL2.
13
+ - **`alert/alert.css`** — header-slot polish from cycle-18 RL2 (10 visual bugs across header slot, leaf children, calendar widget).
14
+
15
+ ### Maintenance
16
+
17
+ - **`dist/web-components.min.{js,css}` + `dist/icons-manifest.js`** — bundle rebuild reflecting the `components/` substrate fixes above.
18
+
3
19
  ## [0.6.42] — 2026-05-26
4
20
 
5
21
  ### Changed — yaml/sidecar polish absorbing gen-review cycle 8-17 findings
@@ -116,10 +116,16 @@
116
116
  /* Close button is a `<button-ui icon="x" variant="ghost" size="sm">`
117
117
  stamped by alert.js — it brings its own focus ring, hover state,
118
118
  and transitions. Box height matches the leading slot so X and icon
119
- sit on the same first-line baseline. */
119
+ sit on the same first-line baseline.
120
+ The X color tracks the alert's variant text color (e.g. success-text
121
+ on success, danger-text on danger) instead of the default ghost
122
+ subtle/muted — so the dismiss icon reads as part of the alert chrome
123
+ rather than disconnected grey. */
120
124
  :scope [slot="close"] {
121
125
  flex-shrink: 0;
122
126
  min-height: calc(var(--alert-font, var(--alert-font-default)) * var(--alert-line-height, var(--alert-line-height-default)));
127
+ --button-fg-ghost: var(--alert-fg, var(--alert-fg-default));
128
+ --button-fg-ghost-hover: var(--alert-icon-fg, var(--alert-icon-fg-default));
123
129
  }
124
130
 
125
131
  /* ──────────────────────────────────────────────────────────────
@@ -49,6 +49,11 @@
49
49
  "type": "string",
50
50
  "default": ""
51
51
  },
52
+ "icon-trailing": {
53
+ "description": "Trailing Phosphor icon name (rendered after text). Use for forward affordances like \"Next →\" / wizards / pagination / product tours where the caret points away from the label.",
54
+ "type": "string",
55
+ "default": ""
56
+ },
52
57
  "size": {
53
58
  "description": "Sizing scale (xs/sm/md/lg/xl).",
54
59
  "type": "string",
@@ -43,6 +43,7 @@ export class UIButton extends UIElement {
43
43
  disabled: { type: Boolean, default: false, reflect: true },
44
44
  stretch: { type: Boolean, default: false, reflect: true },
45
45
  icon: { type: String, default: '', reflect: true },
46
+ iconTrailing: { type: String, default: '', reflect: true, attribute: 'icon-trailing' },
46
47
  type: { type: String, default: 'button', reflect: true },
47
48
  };
48
49
 
@@ -61,7 +62,7 @@ export class UIButton extends UIElement {
61
62
  // Only auto-set when we have meaningful text to put there.
62
63
  if (this.text) this.setAttribute('aria-label', this.text);
63
64
  if (this.icon) {
64
- const existing = this.querySelector('icon-ui');
65
+ const existing = this.querySelector(':scope > icon-ui:not([slot])');
65
66
  if (!existing || existing.name !== this.icon) {
66
67
  if (existing) existing.remove();
67
68
  const iconEl = document.createElement('icon-ui');
@@ -70,6 +71,17 @@ export class UIButton extends UIElement {
70
71
  }
71
72
  }
72
73
 
74
+ if (this.iconTrailing) {
75
+ const existing = this.querySelector(':scope > icon-ui[slot="icon-trailing"]');
76
+ if (!existing || existing.name !== this.iconTrailing) {
77
+ if (existing) existing.remove();
78
+ const iconEl = document.createElement('icon-ui');
79
+ iconEl.setAttribute('name', this.iconTrailing);
80
+ iconEl.setAttribute('slot', 'icon-trailing');
81
+ this.appendChild(iconEl);
82
+ }
83
+ }
84
+
73
85
  // FEEDBACK-15: warn when text= begins with a symbol that icon= already
74
86
  // renders (e.g. text="+ New Claim" icon="plus" → a doubled "+"). One-shot
75
87
  // per element via WeakSet; warning-only — the button still renders. The
@@ -113,7 +113,19 @@ button-ui[color="danger"]:not([disabled]):hover {
113
113
  min-width: 0;
114
114
  }
115
115
 
116
- /* Trailing slot */
116
+ /* Trailing icon slot — for forward-affordance carets ("Next →"), distinct
117
+ from `[slot="trailing"]` which is styled as a kbd-pill for shortcut
118
+ indicators. Auto-stamped when `[icon-trailing]` is set. */
119
+ [slot="icon-trailing"] {
120
+ order: 99;
121
+ margin-inline-start: var(--a-space-1);
122
+ display: flex;
123
+ align-items: center;
124
+ flex-shrink: 0;
125
+ }
126
+
127
+ /* Trailing slot — kbd-pill style for `<kbd-ui slot="trailing">⌘K</kbd-ui>`.
128
+ Use `[icon-trailing="<name>"]` (above) for trailing icons instead. */
117
129
  [slot="trailing"] {
118
130
  order: 99;
119
131
  margin-inline-start: auto;
@@ -36,6 +36,10 @@ props:
36
36
  description: Leading Phosphor icon name (rendered before text).
37
37
  type: string
38
38
  default: ""
39
+ icon-trailing:
40
+ description: Trailing Phosphor icon name (rendered after text). Use for forward affordances like "Next →" / wizards / pagination / product tours where the caret points away from the label.
41
+ type: string
42
+ default: ""
39
43
  size:
40
44
  description: "Sizing scale (xs/sm/md/lg/xl)."
41
45
  type: string
@@ -151,6 +151,7 @@
151
151
 
152
152
  & > header {
153
153
  display: block;
154
+ width: stretch;
154
155
  margin: var(--card-inset, var(--card-inset-default));
155
156
  }
156
157
 
@@ -194,8 +195,20 @@
194
195
  /* Unslotted children (zettel fragment injection, etc.) — each on its own row,
195
196
  spanning the full width, stacked ABOVE the heading/description/action grid.
196
197
  This lets compositions inject logos, banners, etc. into a header without
197
- having to know the slot vocabulary. */
198
- & > header > *:not([slot]):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(p):not(small) {
198
+ having to know the slot vocabulary.
199
+
200
+ Guarded by `:has(> [slot])` so it ONLY fires when the header is in grid
201
+ mode. Modern browsers honor `justify-self` on block-level in-flow elements,
202
+ so without this guard, an unslotted `<row-ui>` direct child of header
203
+ would be centered even when no slot grammar is in use — causing the
204
+ role-management center-align bug seen in cycle-18 follow-up.
205
+
206
+ A2UI transpilation note: the corpus harvester transpiles <h1>..<h6>/<p>/<small>
207
+ to <text-ui variant="display|title|heading|subsection|body|caption">. The
208
+ heading/description rules below match those variants so transpiled chunks
209
+ get the same grid placement as hand-authored native heading/paragraph
210
+ elements. text-ui is excluded here to avoid the centered-fallback. */
211
+ & > header:has(> [slot]) > *:not([slot]):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(p):not(small):not(text-ui) {
199
212
  grid-column: 1 / -1;
200
213
  justify-self: center;
201
214
  }
@@ -213,13 +226,19 @@
213
226
  justify-content: center;
214
227
  }
215
228
 
216
- & > header:has(> :is([slot="description"], p, small)) > [slot="icon"] {
229
+ & > header:has(> :is([slot="description"], p, small, text-ui[variant="body"], text-ui[variant="caption"])) > [slot="icon"] {
217
230
  grid-row: 1 / span 2;
218
231
  align-self: start;
219
232
  }
220
233
 
221
- /* Heading — row 1 */
234
+ /* Heading — row 1.
235
+ Matches native h1-h6 AND the text-ui variants the A2UI transpiler emits
236
+ for them (display|title|heading|subsection — see HTML_TAG_MAP in
237
+ packages/a2ui/compose/transpiler/transpiler-maps.js).
238
+ The text-ui variants are matched only when UNSLOTTED so an explicit
239
+ `slot="heading"` / `slot="description"` / `slot="action"` always wins. */
222
240
  & > header > :is([slot="heading"], h1, h2, h3, h4, h5, h6),
241
+ & > header > :is(text-ui[variant="display"], text-ui[variant="title"], text-ui[variant="heading"], text-ui[variant="subsection"]):not([slot]),
223
242
  & > header > [slot="heading"] :is(h1, h2, h3, h4, h5, h6) {
224
243
  grid-row: 1;
225
244
  line-height: 1.3;
@@ -232,16 +251,24 @@
232
251
  gap: var(--card-header-gap, var(--card-header-gap-default));
233
252
  }
234
253
  & > header:has(> [slot="icon"]) > :is([slot="heading"], h1, h2, h3, h4, h5, h6) { grid-column: 2; }
254
+ & > header:has(> [slot="icon"]) > :is(text-ui[variant="display"], text-ui[variant="title"], text-ui[variant="heading"], text-ui[variant="subsection"]):not([slot]) { grid-column: 2; }
235
255
  & > header:not(:has(> [slot="icon"])) > :is([slot="heading"], h1, h2, h3, h4, h5, h6) { grid-column: 1; }
236
-
237
- /* Description — row 2 */
238
- & > header > :is([slot="description"], p, small) {
256
+ & > header:not(:has(> [slot="icon"])) > :is(text-ui[variant="display"], text-ui[variant="title"], text-ui[variant="heading"], text-ui[variant="subsection"]):not([slot]) { grid-column: 1; }
257
+
258
+ /* Description row 2.
259
+ Matches native p/small (with or without slot=description) AND the
260
+ text-ui variants the transpiler emits for them (body|caption) ONLY
261
+ when unslotted — so a `slot="heading"` text-ui doesn't get treated
262
+ as description. */
263
+ & > header > :is([slot="description"], p, small),
264
+ & > header > :is(text-ui[variant="body"], text-ui[variant="caption"]):not([slot]) {
239
265
  grid-row: 2;
240
266
  grid-column: 1 / -1;
241
267
  line-height: 1.4;
242
268
  margin: 0;
243
269
  }
244
270
  & > header:has(> [slot="icon"]) > :is([slot="description"], p, small) { grid-column: 2 / -1; }
271
+ & > header:has(> [slot="icon"]) > :is(text-ui[variant="body"], text-ui[variant="caption"]):not([slot]) { grid-column: 2 / -1; }
245
272
 
246
273
  /* Action — row 1, last column.
247
274
  Flex container so it can hold badge + button + anything inline. */
@@ -262,6 +289,7 @@
262
289
  [bleed] removes all spacing for edge-to-edge content. */
263
290
 
264
291
  & > section {
292
+ width: stretch;
265
293
  margin: var(--card-inset, var(--card-inset-default));
266
294
  }
267
295
 
@@ -299,6 +327,7 @@
299
327
 
300
328
  & > footer {
301
329
  display: block;
330
+ width: stretch;
302
331
  margin: var(--card-inset, var(--card-inset-default));
303
332
  }
304
333
 
@@ -16,6 +16,11 @@
16
16
  "component": {
17
17
  "const": "List"
18
18
  },
19
+ "contained": {
20
+ "description": "Add inline padding to list items so content aligns with surrounding\ncard/section insets. Pairs with [divider] — combining `[divider]\n[contained]` gives full-width borders between items but inset\ncontent. Without contained, [divider] items have `padding-inline: 0`\nso content sits at the list-ui edges (canonical edge-to-edge style).\n",
21
+ "type": "boolean",
22
+ "default": false
23
+ },
19
24
  "divider": {
20
25
  "description": "Show dividers between items",
21
26
  "type": "boolean",
@@ -30,6 +30,13 @@ export interface ListSelectionChangeEventDetail {
30
30
  export type ListSelectionChangeEvent = CustomEvent<ListSelectionChangeEventDetail>;
31
31
 
32
32
  export class UIList extends UIElement {
33
+ /** Add inline padding to list items so content aligns with surrounding
34
+ card/section insets. Pairs with [divider] — combining `[divider]
35
+ [contained]` gives full-width borders between items but inset
36
+ content. Without contained, [divider] items have `padding-inline: 0`
37
+ so content sits at the list-ui edges (canonical edge-to-edge style).
38
+ */
39
+ contained: boolean;
33
40
  /** Show dividers between items */
34
41
  divider: boolean;
35
42
  /** Enable selection on child listitems (roving tabindex + aria-selected). */
@@ -25,6 +25,15 @@ props:
25
25
  description: Show dividers between items
26
26
  type: boolean
27
27
  default: false
28
+ contained:
29
+ description: |
30
+ Add inline padding to list items so content aligns with surrounding
31
+ card/section insets. Pairs with [divider] — combining `[divider]
32
+ [contained]` gives full-width borders between items but inset
33
+ content. Without contained, [divider] items have `padding-inline: 0`
34
+ so content sits at the list-ui edges (canonical edge-to-edge style).
35
+ type: boolean
36
+ default: false
28
37
  selectable:
29
38
  description: Enable selection on child listitems (roving tabindex + aria-selected).
30
39
  type: boolean