@adia-ai/web-components 0.8.38 → 0.8.40

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 (101) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/MIGRATION.md +243 -2
  3. package/components/agent-artifact/agent-artifact.css +1 -1
  4. package/components/agent-reasoning/agent-reasoning.css +2 -2
  5. package/components/agent-trace/agent-trace.css +3 -3
  6. package/components/alert/alert.css +1 -1
  7. package/components/avatar/avatar.css +1 -1
  8. package/components/badge/badge.css +1 -1
  9. package/components/button/button.css +6 -5
  10. package/components/calendar-picker/calendar-picker.css +5 -2
  11. package/components/card/card.css +3 -3
  12. package/components/chart/chart.a2ui.json +5 -5
  13. package/components/chart/chart.class.js +4 -4
  14. package/components/chart/chart.css +6 -6
  15. package/components/chart/chart.d.ts +2 -2
  16. package/components/chart/chart.yaml +3 -2
  17. package/components/chat-thread/chat-thread.css +1 -1
  18. package/components/code/code.css +6 -6
  19. package/components/combobox/combobox.css +8 -2
  20. package/components/command/command.a2ui.json +3 -0
  21. package/components/command/command.class.js +28 -6
  22. package/components/command/command.css +17 -6
  23. package/components/command/command.yaml +5 -0
  24. package/components/date-range-picker/date-range-picker.css +7 -1
  25. package/components/datetime-picker/datetime-picker.css +5 -1
  26. package/components/description-list/description-list.css +2 -2
  27. package/components/display-field/display-field.css +3 -3
  28. package/components/divider/divider.css +1 -1
  29. package/components/drawer/drawer.css +2 -2
  30. package/components/drilldown/drilldown.a2ui.json +3 -1
  31. package/components/drilldown/drilldown.class.js +12 -4
  32. package/components/drilldown/drilldown.css +9 -10
  33. package/components/drilldown/drilldown.yaml +2 -0
  34. package/components/empty-state/empty-state.css +2 -2
  35. package/components/field/field.css +1 -1
  36. package/components/heatmap/heatmap.css +2 -2
  37. package/components/inline-message/inline-message.css +1 -1
  38. package/components/input/input.css +5 -4
  39. package/components/integration-card/integration-card.css +4 -4
  40. package/components/kbd/kbd.css +1 -1
  41. package/components/list/list.css +2 -2
  42. package/components/modal/modal.class.js +68 -4
  43. package/components/nav/nav.a2ui.json +5 -0
  44. package/components/nav/nav.class.js +24 -4
  45. package/components/nav/nav.css +2 -2
  46. package/components/nav/nav.d.ts +2 -0
  47. package/components/nav/nav.yaml +18 -0
  48. package/components/nav-group/nav-group.css +3 -3
  49. package/components/nav-item/nav-item.css +2 -2
  50. package/components/option-card/option-card.css +1 -1
  51. package/components/otp-input/otp-input.css +1 -1
  52. package/components/page/page.css +2 -2
  53. package/components/pagination/pagination.css +1 -1
  54. package/components/pane/pane.css +1 -1
  55. package/components/progress-row/progress-row.css +2 -2
  56. package/components/qr-code/qr-code.a2ui.json +10 -10
  57. package/components/qr-code/qr-code.class.js +3 -3
  58. package/components/qr-code/qr-code.d.ts +10 -7
  59. package/components/qr-code/qr-code.yaml +13 -9
  60. package/components/range/range.css +1 -1
  61. package/components/rating/rating.css +1 -1
  62. package/components/richtext/richtext.css +3 -3
  63. package/components/segment/segment.css +5 -4
  64. package/components/select/select.class.js +30 -12
  65. package/components/select/select.css +14 -5
  66. package/components/spinner/spinner.css +1 -1
  67. package/components/stat/stat.css +3 -3
  68. package/components/step-progress/step-progress.css +1 -1
  69. package/components/stepper/stepper.css +2 -2
  70. package/components/swatch/swatch.css +8 -6
  71. package/components/table/table.a2ui.json +0 -10
  72. package/components/table/table.class.js +4 -4
  73. package/components/table/table.css +6 -9
  74. package/components/table/table.d.ts +0 -2
  75. package/components/table/table.yaml +0 -9
  76. package/components/table-toolbar/table-toolbar.css +1 -1
  77. package/components/tabs/tabs.css +1 -1
  78. package/components/tag/tag.css +2 -2
  79. package/components/tags-input/tags-input.css +1 -1
  80. package/components/textarea/textarea.css +1 -1
  81. package/components/time-picker/time-picker.css +1 -1
  82. package/components/timeline/timeline.css +4 -4
  83. package/components/toc/toc.css +1 -1
  84. package/components/toggle-group/toggle-group.class.js +13 -11
  85. package/components/toggle-group/toggle-group.css +14 -8
  86. package/components/toggle-group/toggle-group.d.ts +5 -0
  87. package/components/toggle-group/toggle-group.yaml +7 -0
  88. package/components/toggle-group/toggle-option.a2ui.json +5 -0
  89. package/components/toggle-group/toggle-option.yaml +14 -2
  90. package/components/tooltip/tooltip.css +1 -1
  91. package/components/tour/tour.css +2 -2
  92. package/components/tree/tree.css +1 -1
  93. package/custom-elements.json +101 -89
  94. package/dist/host.min.css +1 -1
  95. package/dist/host.sheet.js +1 -1
  96. package/dist/theme-provider.min.js +3 -3
  97. package/dist/web-components.min.css +1 -1
  98. package/dist/web-components.min.js +97 -94
  99. package/dist/web-components.sheet.js +1 -1
  100. package/package.json +1 -1
  101. package/styles/type/scale.css +6 -7
@@ -439,8 +439,14 @@ export class UISelect extends UIFormElement {
439
439
  : this.icon
440
440
  ? `<icon-ui slot="leading" data-select-leading name="${escapeHTML(this.icon)}"></icon-ui>`
441
441
  : '';
442
+ // No native <input> wrap (per ADR-0055 / ADR-0025): the searchable
443
+ // trigger is a `contenteditable="plaintext-only"` surface carrying
444
+ // `role="combobox"`, mirroring combobox-ui's editable-surface pattern.
445
+ // Placeholder text renders via the [data-empty]::before pseudo (see
446
+ // select.css), same mechanism as input-ui / combobox-ui.
447
+ const initialDisplayText = this.#displayText() === this.placeholder ? '' : this.#displayText();
442
448
  const displayMarkup = this.searchable
443
- ? `<input slot="display" type="text" role="combobox" aria-autocomplete="list" autocomplete="off" placeholder="${escapeHTML(this.placeholder || '')}" value="${escapeHTML(this.#displayText() === this.placeholder ? '' : this.#displayText())}" />`
449
+ ? `<span slot="display" contenteditable="plaintext-only" role="combobox" aria-autocomplete="list" tabindex="0" data-placeholder="${escapeHTML(this.placeholder || '')}"${initialDisplayText ? '' : ' data-empty'}>${escapeHTML(initialDisplayText)}</span>`
444
450
  : `<span slot="display">${escapeHTML(this.#displayText())}</span>`;
445
451
  // §184 (v0.5.5, FEEDBACK-08 §7): optional hint slot beneath the
446
452
  // trigger. Mirrors slider-ui's hint pattern + matches the
@@ -478,7 +484,7 @@ export class UISelect extends UIFormElement {
478
484
  this.#searchInput.removeEventListener('focus', this.#onSearchFocus);
479
485
  this.#searchInput.removeEventListener('click', this.#onSearchClick);
480
486
  }
481
- this.#searchInput = this.querySelector('input[slot="display"]');
487
+ this.#searchInput = this.querySelector('[slot="display"][contenteditable]');
482
488
  if (this.#searchInput) {
483
489
  this.#searchInput.addEventListener('input', this.#onSearchInput);
484
490
  this.#searchInput.addEventListener('focus', this.#onSearchFocus);
@@ -492,9 +498,13 @@ export class UISelect extends UIFormElement {
492
498
  const display = this.querySelector('[slot="display"]');
493
499
  if (display && !this.multiple) {
494
500
  // Single-select: keep the canonical text-rendering path.
495
- if (display.tagName === 'INPUT') {
496
- // Only update value when not actively editing (no active query)
497
- if (!this.#query) display.value = this.#displayText() === this.placeholder ? '' : this.#displayText();
501
+ if (display.hasAttribute('contenteditable')) {
502
+ // Only update text when not actively editing (no active query)
503
+ if (!this.#query) {
504
+ const text = this.#displayText() === this.placeholder ? '' : this.#displayText();
505
+ display.textContent = text;
506
+ display.toggleAttribute('data-empty', !text);
507
+ }
498
508
  } else {
499
509
  display.textContent = this.#displayText();
500
510
  }
@@ -502,10 +512,13 @@ export class UISelect extends UIFormElement {
502
512
  // Multi-select: display element holds the placeholder ONLY when
503
513
  // no chips are present (otherwise the chip row IS the display).
504
514
  const hasChips = this.#values().length > 0;
505
- if (display.tagName === 'INPUT') {
515
+ if (display.hasAttribute('contenteditable')) {
506
516
  // search input — keep placeholder; never inject the value text.
507
- display.placeholder = hasChips ? '' : (this.placeholder || '');
508
- if (!this.#query) display.value = '';
517
+ display.setAttribute('data-placeholder', hasChips ? '' : (this.placeholder || ''));
518
+ if (!this.#query) {
519
+ display.textContent = '';
520
+ display.toggleAttribute('data-empty', true);
521
+ }
509
522
  } else {
510
523
  display.textContent = hasChips ? '' : (this.placeholder || '');
511
524
  }
@@ -991,7 +1004,7 @@ export class UISelect extends UIFormElement {
991
1004
  // before it would start removing query characters.
992
1005
  if (e.key === 'Backspace' && this.multiple && !this.disabled && !this.readonly) {
993
1006
  const target = e.target;
994
- const fromInput = target && target.tagName === 'INPUT' && target.getAttribute('slot') === 'display';
1007
+ const fromInput = target && target.hasAttribute?.('contenteditable') && target.getAttribute('slot') === 'display';
995
1008
  const queryEmpty = !this.#query || this.#query.length === 0;
996
1009
  if (!fromInput || queryEmpty) {
997
1010
  if (this.#values().length > 0) {
@@ -1005,8 +1018,11 @@ export class UISelect extends UIFormElement {
1005
1018
  if (this.searchable && this.#query) {
1006
1019
  // First Escape: clear query
1007
1020
  this.#query = '';
1008
- const input = this.querySelector('input[slot="display"]');
1009
- if (input) input.value = '';
1021
+ const input = this.querySelector('[slot="display"][contenteditable]');
1022
+ if (input) {
1023
+ input.textContent = '';
1024
+ input.toggleAttribute('data-empty', true);
1025
+ }
1010
1026
  this.#applyFilter();
1011
1027
  return;
1012
1028
  }
@@ -1061,7 +1077,9 @@ export class UISelect extends UIFormElement {
1061
1077
  };
1062
1078
 
1063
1079
  #onSearchInput = (e) => {
1064
- this.#query = e.target.value || '';
1080
+ const text = e.target.textContent || '';
1081
+ this.#query = text;
1082
+ e.target.toggleAttribute('data-empty', !text);
1065
1083
  if (!this.open) this.open = true;
1066
1084
  this.#applyFilter();
1067
1085
  };
@@ -91,7 +91,7 @@
91
91
  border: 1px solid var(--select-border);
92
92
  border-radius: var(--select-radius);
93
93
  background: var(--select-bg);
94
- line-height: 1;
94
+ line-height: var(--a-font-leading-none);
95
95
  cursor: pointer;
96
96
  /* Match every property the hover / focus / invalid states change. */
97
97
  transition:
@@ -130,7 +130,7 @@
130
130
  display: inline-flex;
131
131
  align-items: center;
132
132
  flex-shrink: 0;
133
- line-height: 1;
133
+ line-height: var(--a-font-leading-none);
134
134
  --a-icon-size: var(--select-font-size);
135
135
  color: var(--select-fg-muted);
136
136
  }
@@ -149,7 +149,7 @@
149
149
  white-space: nowrap;
150
150
  color: var(--select-fg);
151
151
  }
152
- input[slot="display"] {
152
+ [slot="display"][contenteditable] {
153
153
  border: none;
154
154
  outline: none;
155
155
  background: transparent;
@@ -159,9 +159,18 @@
159
159
  color: var(--select-fg);
160
160
  min-width: 0;
161
161
  width: 100%;
162
+ /* Positioning context for the [data-empty]::before placeholder pseudo
163
+ below — out-of-flow, same rationale as input-ui/combobox-ui: an
164
+ in-flow pseudo would render the caret after the placeholder text
165
+ instead of at content-start. */
166
+ position: relative;
162
167
  }
163
- input[slot="display"]::placeholder {
168
+ [slot="display"][contenteditable][data-empty]::before {
169
+ content: attr(data-placeholder);
164
170
  color: var(--select-placeholder-fg);
171
+ pointer-events: none;
172
+ position: absolute;
173
+ inset: 0;
165
174
  }
166
175
  :scope:not([value]) [slot="display"],
167
176
  :scope[value=""] [slot="display"] {
@@ -405,7 +414,7 @@ select-ui [role="group"] [slot="group-label"] {
405
414
  font-weight: var(--a-ui-weight);
406
415
  color: var(--select-fg-muted);
407
416
  text-transform: uppercase;
408
- letter-spacing: 0.05em;
417
+ letter-spacing: var(--a-font-tracking-wide);
409
418
  white-space: nowrap;
410
419
  }
411
420
 
@@ -178,7 +178,7 @@
178
178
  align-items: center;
179
179
  justify-content: center;
180
180
  font: inherit;
181
- line-height: 1;
181
+ line-height: var(--a-font-leading-none);
182
182
  text-align: center;
183
183
  color: currentColor;
184
184
  }
@@ -171,7 +171,7 @@
171
171
  grid-area: label;
172
172
  font-size: var(--stat-label-size);
173
173
  color: var(--stat-label-fg);
174
- line-height: 1.4;
174
+ line-height: var(--a-font-leading-normal);
175
175
  min-width: 0;
176
176
  overflow: hidden;
177
177
  text-overflow: ellipsis;
@@ -185,7 +185,7 @@
185
185
  align-items: center;
186
186
  gap: 0.25em;
187
187
  font-size: var(--stat-change-size);
188
- line-height: 1;
188
+ line-height: var(--a-font-leading-none);
189
189
  justify-self: start;
190
190
  }
191
191
 
@@ -195,7 +195,7 @@
195
195
  font-size: var(--stat-value-size);
196
196
  font-weight: var(--stat-value-weight);
197
197
  color: var(--stat-value-fg);
198
- line-height: 1.2;
198
+ line-height: var(--a-font-leading-snug);
199
199
  min-width: 0;
200
200
  overflow: hidden;
201
201
  text-overflow: ellipsis;
@@ -46,7 +46,7 @@
46
46
  [slot="caption"] {
47
47
  color: var(--step-progress-caption-fg);
48
48
  font-size: var(--step-progress-caption-size);
49
- line-height: 1.4;
49
+ line-height: var(--a-font-leading-normal);
50
50
  }
51
51
 
52
52
  [slot="caption"][hidden] {
@@ -236,7 +236,7 @@
236
236
  display: block;
237
237
  font-weight: var(--stepper-item-label-weight);
238
238
  font-size: var(--stepper-item-label-size);
239
- line-height: 1.4;
239
+ line-height: var(--a-font-leading-normal);
240
240
  color: var(--stepper-item-label-fg);
241
241
  min-width: 0;
242
242
  overflow-wrap: break-word;
@@ -247,7 +247,7 @@
247
247
  margin-top: var(--stepper-item-offset-xs);
248
248
  color: var(--stepper-item-desc-fg);
249
249
  font-size: var(--stepper-item-desc-size);
250
- line-height: 1.5;
250
+ line-height: var(--a-font-leading-relaxed);
251
251
  }
252
252
 
253
253
  :scope [slot]:empty {
@@ -63,7 +63,7 @@
63
63
  min-width: 0;
64
64
  color: var(--swatch-label-fg);
65
65
  font-size: var(--swatch-label-size);
66
- line-height: 1.2;
66
+ line-height: var(--a-font-leading-snug);
67
67
  vertical-align: baseline;
68
68
  }
69
69
 
@@ -238,7 +238,7 @@
238
238
  background: var(--swatch-badge-bg);
239
239
  color: var(--swatch-badge-fg);
240
240
  font-size: var(--swatch-badge-size);
241
- line-height: 1;
241
+ line-height: var(--a-font-leading-none);
242
242
  border-radius: var(--swatch-badge-radius);
243
243
  box-shadow: 0 0 0 1px color-mix(in oklab, var(--md-sys-color-neutral-outline-variant) 60%, transparent);
244
244
  }
@@ -274,20 +274,22 @@
274
274
  z-index: 1;
275
275
  }
276
276
  :scope[shape="block"][auto-contrast] > [data-label][data-on-dark] {
277
- color: var(--a-chrome-light, #fafafa);
277
+ color: var(--a-chrome-light);
278
278
  }
279
279
  :scope[shape="block"][auto-contrast] > [data-label][data-on-light] {
280
- color: var(--a-chrome-dark, #111);
280
+ color: var(--a-chrome-dark);
281
281
  }
282
282
  /* Detail line follows the same auto-contrast choice. */
283
283
  :scope[shape="block"][auto-contrast] > [data-detail] {
284
284
  z-index: 1;
285
285
  }
286
286
  :scope[shape="block"][auto-contrast] > [data-label][data-on-dark] ~ [data-detail] {
287
- color: color-mix(in oklab, var(--a-chrome-light, #fafafa) 80%, transparent);
287
+ color: var(--a-chrome-light);
288
+ opacity: 0.8;
288
289
  }
289
290
  :scope[shape="block"][auto-contrast] > [data-label][data-on-light] ~ [data-detail] {
290
- color: color-mix(in oklab, var(--a-chrome-dark, #111) 70%, transparent);
291
+ color: var(--a-chrome-dark);
292
+ opacity: 0.7;
291
293
  }
292
294
 
293
295
  /* ═══════ Copy button — a stamped <button-ui variant="ghost" size="sm">
@@ -24,16 +24,6 @@
24
24
  "description": "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.",
25
25
  "$ref": "common_types.json#/$defs/DynamicObjectList"
26
26
  },
27
- "density": {
28
- "description": "Controls cell padding and row height. 'compact' for dense data, 'standard' for default spacing, 'comfortable' for spacious rows.",
29
- "type": "string",
30
- "enum": [
31
- "compact",
32
- "standard",
33
- "comfortable"
34
- ],
35
- "default": "standard"
36
- },
37
27
  "expandable": {
38
28
  "description": "Enable row expansion",
39
29
  "type": "boolean",
@@ -21,8 +21,11 @@
21
21
  * sortable — enable column sorting on header click
22
22
  * selectable — enable row selection via checkboxes
23
23
  * striped — alternate row background
24
- * density — 'compact' | 'standard' | 'comfortable'
25
24
  * valign — 'center' (default) | 'top' — body-cell vertical alignment (gh#505)
25
+ *
26
+ * table-ui has no local `density` prop (ADR-0054) — it converges onto the
27
+ * ambient global [density="compact"|"spacious"] attribute; --table-py/-px
28
+ * already derive from --a-space-* steps, which --a-density scales.
26
29
  * paginate — rows per page (0 = no pagination)
27
30
  * loading — show loading overlay
28
31
  * search — global search filter string
@@ -118,7 +121,6 @@ export class UITable extends UIElement {
118
121
  frameless: { type: Boolean, default: false, reflect: true },
119
122
  wrap: { type: Boolean, default: false, reflect: true },
120
123
  raw: { type: Boolean, default: false, reflect: true },
121
- density: { type: String, default: 'standard', reflect: true },
122
124
  paginate: { type: Number, default: 0, reflect: true },
123
125
  loading: { type: Boolean, default: false, reflect: true },
124
126
  search: { type: String, default: '', reflect: true },
@@ -294,7 +296,6 @@ export class UITable extends UIElement {
294
296
  columnWidths: Object.fromEntries(this.#columnWidths),
295
297
  hiddenColumns: this.#columns.filter(c => c.hidden).map(c => c.key),
296
298
  page: this.#page,
297
- density: this.density,
298
299
  };
299
300
  }
300
301
 
@@ -319,7 +320,6 @@ export class UITable extends UIElement {
319
320
  }
320
321
  }
321
322
  if (state.page != null) this.#page = state.page;
322
- if (state.density) this.density = state.density;
323
323
  this.#requestRender();
324
324
  }
325
325
 
@@ -463,14 +463,11 @@
463
463
 
464
464
  /* ═══════ Density ═══════ */
465
465
 
466
- :scope[density="compact"] {
467
- --table-py: var(--a-space-1);
468
- --table-px: var(--a-space-2);
469
- }
470
-
471
- :scope[density="comfortable"] {
472
- --table-py: var(--a-space-4);
473
- }
466
+ /* No local override (ADR-0054 convergence) — --table-py/-px above already
467
+ derive from --a-space-* steps, which the ambient global
468
+ [density="compact"|"spacious"] attribute already scales via
469
+ --a-density. Do not reintroduce a component-local :scope[density=…]
470
+ block; that would re-shadow the global grammar (ADR-0053). */
474
471
 
475
472
  /* ═══════ Pinned cells ═══════ */
476
473
 
@@ -744,7 +741,7 @@
744
741
  grid-template-columns: subgrid;
745
742
  grid-column: 1 / -1;
746
743
  border-top: 2px solid var(--table-agg-border);
747
- font-weight: 600;
744
+ font-weight: var(--a-weight-semibold);
748
745
  font-size: var(--table-agg-size);
749
746
  color: var(--table-fg-secondary);
750
747
  }
@@ -90,8 +90,6 @@ export class UITable extends UIElement {
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;
93
- /** Controls cell padding and row height. 'compact' for dense data, 'standard' for default spacing, 'comfortable' for spacious rows. */
94
- density: 'compact' | 'standard' | 'comfortable';
95
93
  /** Enable row expansion */
96
94
  expandable: boolean;
97
95
  /** Drop the table's outer 1px perimeter and nothing else (gh#511) — row dividers are untouched (the perimeter is split onto its own --table-perimeter token, defaulting to --table-border). For card-composed tables: the card owns the frame, so the table's square perimeter otherwise renders a doubled edge (flush bleed) or a stray inner box (with content above). Standalone tables keep their chrome by default. Theming path without the attribute: set --table-perimeter to transparent per instance. Note: `<card-ui><section bleed>` already drops a direct-child table-ui's chrome automatically (card.css, gh#796) — `frameless`/`raw` on the instance still applies and is honored, but is no longer required just to avoid the doubled-edge look in that composition. */
@@ -36,15 +36,6 @@ props:
36
36
  type: object
37
37
  default: []
38
38
  dynamic: true # JS-set collection prop with a custom setter — deliberately not in static properties
39
- density:
40
- description: Controls cell padding and row height. 'compact' for dense data, 'standard' for default
41
- spacing, 'comfortable' for spacious rows.
42
- type: string
43
- default: standard
44
- enum:
45
- - compact
46
- - standard
47
- - comfortable
48
39
  expandable:
49
40
  description: Enable row expansion
50
41
  type: boolean
@@ -82,7 +82,7 @@
82
82
  font-size: var(--table-toolbar-title-size);
83
83
  font-weight: var(--table-toolbar-title-weight);
84
84
  color: var(--table-toolbar-title-fg);
85
- line-height: 1.2;
85
+ line-height: var(--a-font-leading-snug);
86
86
  }
87
87
 
88
88
  [data-count-badge][hidden] { display: none; }
@@ -138,7 +138,7 @@
138
138
  /* font-family floor — see segment.css rationale. */
139
139
  font-family: var(--tabs-font-family);
140
140
  font-size: var(--tabs-font-size);
141
- line-height: 1;
141
+ line-height: var(--a-font-leading-none);
142
142
  cursor: pointer;
143
143
  user-select: none;
144
144
  white-space: nowrap;
@@ -154,8 +154,8 @@ tag-ui[removable]:not([disabled]):hover {
154
154
  /* font-family floor — see segment.css rationale. */
155
155
  font-family: var(--tag-font-family);
156
156
  font-size: var(--tag-font);
157
- font-weight: 500;
158
- line-height: 1;
157
+ font-weight: var(--a-weight-medium);
158
+ line-height: var(--a-font-leading-none);
159
159
  color: var(--tag-fg);
160
160
  background: var(--tag-bg);
161
161
  white-space: nowrap;
@@ -109,7 +109,7 @@
109
109
  color: var(--tags-input-fg);
110
110
  font: inherit;
111
111
  font-size: var(--tags-input-font-size);
112
- line-height: 1.4;
112
+ line-height: var(--a-font-leading-normal);
113
113
  white-space: pre-wrap;
114
114
  overflow-wrap: anywhere;
115
115
  cursor: text;
@@ -75,7 +75,7 @@ textarea-ui:not([disabled]) [slot="text"]:hover {
75
75
  /* font-family floor — see segment.css rationale. */
76
76
  font-family: var(--textarea-font-family);
77
77
  font-size: var(--textarea-font-size);
78
- line-height: 1.5;
78
+ line-height: var(--a-font-leading-relaxed);
79
79
  white-space: pre-wrap;
80
80
  word-break: break-word;
81
81
  overflow-y: auto;
@@ -42,7 +42,7 @@
42
42
  font-family: var(--time-picker-font-family, var(--a-font-family-ui));
43
43
  font-size: var(--time-picker-font-size);
44
44
  font-variant-numeric: tabular-nums;
45
- line-height: 1.4;
45
+ line-height: var(--a-font-leading-normal);
46
46
  cursor: text;
47
47
  transition:
48
48
  border-color var(--time-picker-duration) var(--time-picker-easing),
@@ -309,7 +309,7 @@ agent-reasoning-ui timeline-ui:not([orientation="horizontal"]),
309
309
  font-weight: var(--timeline-item-label-weight);
310
310
  font-size: var(--timeline-item-label-size);
311
311
  color: var(--timeline-item-label-fg);
312
- line-height: 1.4;
312
+ line-height: var(--a-font-leading-normal);
313
313
  overflow-wrap: break-word;
314
314
  min-width: 0;
315
315
  }
@@ -323,7 +323,7 @@ agent-reasoning-ui timeline-ui:not([orientation="horizontal"]),
323
323
  grid-row: 2;
324
324
  color: var(--timeline-item-desc-fg);
325
325
  font-size: var(--timeline-item-desc-size);
326
- line-height: 1.5;
326
+ line-height: var(--a-font-leading-relaxed);
327
327
  margin-top: var(--a-space-0-5);
328
328
  }
329
329
 
@@ -333,7 +333,7 @@ agent-reasoning-ui timeline-ui:not([orientation="horizontal"]),
333
333
  color: var(--timeline-item-time-fg);
334
334
  font-size: var(--timeline-item-time-size);
335
335
  font-variant-numeric: tabular-nums;
336
- line-height: 1.4;
336
+ line-height: var(--a-font-leading-normal);
337
337
  white-space: nowrap;
338
338
  justify-self: end;
339
339
  }
@@ -356,7 +356,7 @@ agent-reasoning-ui timeline-ui:not([orientation="horizontal"]),
356
356
  gap: var(--a-space-0-5);
357
357
  color: var(--timeline-item-outcome-fg);
358
358
  font-size: var(--timeline-item-desc-size);
359
- line-height: 1.4;
359
+ line-height: var(--a-font-leading-relaxed);
360
360
  animation: tl-fade var(--timeline-item-duration) var(--timeline-item-easing);
361
361
  }
362
362
 
@@ -59,7 +59,7 @@
59
59
  padding-inline: var(--toc-padding-inline);
60
60
  text-decoration: none;
61
61
  color: inherit;
62
- line-height: 1.4;
62
+ line-height: var(--a-font-leading-normal);
63
63
  border-inline-start: 2px solid var(--toc-rule-inactive);
64
64
  transition:
65
65
  color var(--a-duration-fast) var(--a-easing),
@@ -30,10 +30,12 @@ import { UIElement } from '../../core/element.js';
30
30
  // ── Toggle Option ──
31
31
  //
32
32
  // Parent-managed. Selection state flows through `toggle-group-ui`'s `value`
33
- // (and `single` mode flag). Items don't expose `checked` parent applies
34
- // `data-selected` on the DOM element when the item's `value` is part of the
35
- // group's comma-separated `value` list. Consistent with native `<option>`
36
- // inside `<select>`.
33
+ // (and `single` mode flag) the group's `value` stays the single source of
34
+ // truth. ADR-0056 (gh#1303): the child's current-state surface is the
35
+ // declared, reflected `selected` prop below, converging onto <segment-ui>'s
36
+ // mechanism (yaml → catalog → generative grammar) instead of a private
37
+ // stamp. Don't set `selected` directly on an option; drive the group's
38
+ // `value` instead.
37
39
 
38
40
  export class UIToggleOption extends UIElement {
39
41
  static properties = {
@@ -41,6 +43,7 @@ export class UIToggleOption extends UIElement {
41
43
  text: { type: String, default: '', reflect: true },
42
44
  icon: { type: String, default: '', reflect: true },
43
45
  disabled: { type: Boolean, default: false, reflect: true },
46
+ selected: { type: Boolean, default: false, reflect: true },
44
47
  };
45
48
 
46
49
  static template = () => null;
@@ -59,6 +62,7 @@ export class UIToggleOption extends UIElement {
59
62
  }
60
63
  if (this.disabled) this.setAttribute('aria-disabled', 'true');
61
64
  else this.removeAttribute('aria-disabled');
65
+ this.setAttribute('aria-pressed', this.selected ? 'true' : 'false');
62
66
  }
63
67
 
64
68
  #stamp() {
@@ -100,13 +104,11 @@ export class UIToggleGroup extends UIElement {
100
104
  const selected = this.#valueSet;
101
105
  for (const opt of this.querySelectorAll('toggle-option-ui')) {
102
106
  const optVal = opt.value || opt.getAttribute('value') || '';
103
- if (selected.has(optVal)) {
104
- opt.setAttribute('data-selected', '');
105
- opt.setAttribute('aria-pressed', 'true');
106
- } else {
107
- opt.removeAttribute('data-selected');
108
- opt.setAttribute('aria-pressed', 'false');
109
- }
107
+ const isSelected = selected.has(optVal);
108
+ // ADR-0056 (gh#1303): `selected` is the declared, reflected mechanism —
109
+ // converges onto <segment-ui>'s convention (opt's own render() stamps
110
+ // aria-pressed from it).
111
+ opt.selected = isSelected;
110
112
  }
111
113
  }
112
114
 
@@ -1,12 +1,21 @@
1
- /* Safari 17.x bug: `:scope:not(...):hover` inside `@scope` doesn't match
2
- the scope root. Plain selector outside works. The @scope is
3
- `(toggle-option-ui)` NOT `(toggle-group-ui)` — so the moved-out
4
- selector targets `toggle-option-ui:hover`. See docs/BROWSER-COMPAT.md §3a. */
1
+ /* Safari 17.x bug: `:scope:not(...):hover` (Flavor A) and `:scope[selected]`
2
+ (Flavor B attribute-removal restyle) both fail inside `@scope`.
3
+ Selectors moved out. The @scope is `(toggle-option-ui)` — NOT
4
+ `(toggle-group-ui)` so the moved-out selectors target
5
+ `toggle-option-ui:hover` / `toggle-option-ui[selected]`. See
6
+ docs/BROWSER-COMPAT.md §3a; mirrors segment.css. */
5
7
  toggle-option-ui:not([disabled]):hover {
6
8
  --toggle-option-bg: var(--toggle-option-bg-hover);
7
9
  --toggle-option-fg: var(--toggle-option-fg-hover);
8
10
  }
9
11
 
12
+ /* ADR-0056 (gh#1303): converged onto <segment-ui>'s declared, reflected
13
+ [selected] prop, set by <toggle-group-ui> (toggle-group.class.js). */
14
+ toggle-option-ui[selected] {
15
+ --toggle-option-bg: var(--toggle-option-selected-bg);
16
+ --toggle-option-fg: var(--toggle-option-selected-fg);
17
+ }
18
+
10
19
  @scope (toggle-group-ui) {
11
20
  :where(:scope) {
12
21
  /* ── Tokens ── */
@@ -94,10 +103,7 @@ toggle-option-ui:not([disabled]):hover {
94
103
  z-index: 1;
95
104
  }
96
105
 
97
- :scope[data-selected] {
98
- --toggle-option-bg: var(--toggle-option-selected-bg);
99
- --toggle-option-fg: var(--toggle-option-selected-fg);
100
- }
106
+ /* [selected] state rule moved outside @scope — see Safari 17.x bug note at top. */
101
107
 
102
108
  :scope[disabled] {
103
109
  --toggle-option-fg: var(--toggle-option-fg-disabled);
@@ -38,6 +38,11 @@ export class UIToggleOption extends UIElement {
38
38
  disabled: boolean;
39
39
  /** Optional Phosphor icon name; renders as a leading icon. */
40
40
  icon: string;
41
+ /** Whether this option is currently selected. Managed by the parent
42
+ <toggle-group-ui> container (ADR-0056) — don't set directly; the group's
43
+ `value` is the single source of truth. Converges onto <segment-ui>'s
44
+ declared reflected-prop mechanism. */
45
+ selected: boolean;
41
46
  /** Visible label text. */
42
47
  text: string;
43
48
  /** Stable identifier matched against the parent group's `value`. */
@@ -1,5 +1,10 @@
1
1
  # Generated by scripts/migrate-yamls-to-v1.mjs — migrated to v1 contract.
2
2
  # Edit this file; run `npm run build:components` to regenerate a2ui.json.
3
+ #
4
+ # ADR-0056 (gh#1303): <toggle-option-ui>'s current-state surface converges
5
+ # onto <segment-ui>'s declared, reflected `[selected]` prop (see
6
+ # toggle-option.yaml). This container sets that prop, keeping `value` as the
7
+ # single source of truth.
3
8
  $schema: ../../../../scripts/schemas/component.yaml.schema.json
4
9
  name: UIToggleGroup
5
10
  tag: toggle-group-ui
@@ -80,6 +85,8 @@ a2ui:
80
85
  reason: 'Selection model distinction.'
81
86
  - rule: 'Use for filter chips, multi-flag toggles, day-of-week pickers; for binary on/off use <switch-ui>.'
82
87
  reason: 'Capacity guidance.'
88
+ - rule: 'Sets the child <toggle-option-ui>''s declared [selected] prop to reflect the group value (ADR-0056) — do not author [selected] directly in generated markup; author the group''s value instead.'
89
+ reason: 'Parent-managed declared state — group value stays the single source of truth.'
83
90
  anti_patterns: []
84
91
  examples:
85
92
  - name: basic-toggle-group
@@ -26,6 +26,11 @@
26
26
  "type": "string",
27
27
  "default": ""
28
28
  },
29
+ "selected": {
30
+ "description": "Whether this option is currently selected. Managed by the parent\n<toggle-group-ui> container (ADR-0056) — don't set directly; the group's\n`value` is the single source of truth. Converges onto <segment-ui>'s\ndeclared reflected-prop mechanism.",
31
+ "type": "boolean",
32
+ "default": false
33
+ },
29
34
  "text": {
30
35
  "description": "Visible label text.",
31
36
  "type": "string",