@adia-ai/web-components 0.8.42 → 0.8.44

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 (186) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/MIGRATION.md +130 -80
  3. package/USAGE.md +1 -1
  4. package/color/index.js +1 -1
  5. package/components/action-list/action-item.a2ui.json +3 -8
  6. package/components/action-list/action-item.yaml +10 -16
  7. package/components/action-list/action-list.class.js +5 -61
  8. package/components/action-list/action-list.d.ts +1 -3
  9. package/components/avatar/avatar.class.js +12 -1
  10. package/components/button/button.d.ts +4 -0
  11. package/components/button/button.yaml +1 -1
  12. package/components/calendar-picker/calendar-picker.a2ui.json +1 -1
  13. package/components/calendar-picker/calendar-picker.class.js +2 -3
  14. package/components/calendar-picker/calendar-picker.css +16 -18
  15. package/components/calendar-picker/calendar-picker.yaml +2 -2
  16. package/components/card/card.css +3 -3
  17. package/components/chart/chart.a2ui.json +29 -18
  18. package/components/chart/chart.class.js +1308 -266
  19. package/components/chart/chart.css +151 -1
  20. package/components/chart/chart.d.ts +9 -9
  21. package/components/chart/chart.yaml +63 -31
  22. package/components/chart-legend/chart-legend.a2ui.json +20 -14
  23. package/components/chart-legend/chart-legend.class.js +79 -58
  24. package/components/chart-legend/chart-legend.css +65 -1
  25. package/components/chart-legend/chart-legend.d.ts +5 -5
  26. package/components/chart-legend/chart-legend.examples.md +4 -4
  27. package/components/chart-legend/chart-legend.yaml +50 -20
  28. package/components/check/check.css +9 -0
  29. package/components/color-area/color-area.a2ui.json +1 -1
  30. package/components/color-area/color-area.class.js +2 -2
  31. package/components/color-area/color-area.css +4 -7
  32. package/components/color-area/color-area.d.ts +1 -1
  33. package/components/color-area/color-area.yaml +3 -3
  34. package/components/color-input/color-input.a2ui.json +1 -1
  35. package/components/color-input/color-input.class.js +1 -1
  36. package/components/color-input/color-input.yaml +2 -2
  37. package/components/combobox/combobox.a2ui.json +3 -0
  38. package/components/combobox/combobox.class.js +9 -8
  39. package/components/combobox/combobox.css +25 -4
  40. package/components/combobox/combobox.yaml +6 -0
  41. package/components/command/command.css +17 -1
  42. package/components/context-menu/context-menu.d.ts +2 -0
  43. package/components/drilldown/drilldown.a2ui.json +3 -0
  44. package/components/drilldown/drilldown.css +8 -1
  45. package/components/drilldown/drilldown.yaml +2 -0
  46. package/components/field/field.class.js +5 -5
  47. package/components/heatmap/heatmap.a2ui.json +5 -0
  48. package/components/heatmap/heatmap.d.ts +2 -0
  49. package/components/heatmap/heatmap.yaml +8 -0
  50. package/components/index.js +0 -4
  51. package/components/input/input.a2ui.json +4 -4
  52. package/components/input/input.class.js +20 -7
  53. package/components/input/input.css +6 -0
  54. package/components/input/input.yaml +15 -11
  55. package/components/integration-card/integration-card.d.ts +2 -0
  56. package/components/menu/menu-item.a2ui.json +3 -8
  57. package/components/menu/menu-item.yaml +13 -21
  58. package/components/menu/menu.class.js +5 -27
  59. package/components/menu/menu.d.ts +1 -3
  60. package/components/noodles/noodles.a2ui.json +0 -15
  61. package/components/noodles/noodles.yaml +0 -12
  62. package/components/number-format/number-format.class.js +10 -0
  63. package/components/pagination/pagination.class.js +88 -4
  64. package/components/pane/pane.a2ui.json +2 -12
  65. package/components/pane/pane.class.js +5 -30
  66. package/components/pane/pane.d.ts +3 -5
  67. package/components/pane/pane.yaml +7 -19
  68. package/components/popover/popover.d.ts +2 -0
  69. package/components/progress/progress.a2ui.json +1 -1
  70. package/components/progress/progress.class.js +13 -8
  71. package/components/progress/progress.d.ts +1 -1
  72. package/components/progress/progress.yaml +2 -2
  73. package/components/radio/radio.css +7 -0
  74. package/components/range/range.class.js +9 -1
  75. package/components/search/search.css +10 -1
  76. package/components/segment/segment.a2ui.json +1 -2
  77. package/components/segment/segment.css +10 -0
  78. package/components/segment/segment.yaml +1 -2
  79. package/components/segmented/segmented.a2ui.json +1 -3
  80. package/components/segmented/segmented.class.js +2 -2
  81. package/components/segmented/segmented.yaml +2 -4
  82. package/components/select/select.a2ui.json +5 -1
  83. package/components/select/select.class.js +14 -14
  84. package/components/select/select.css +20 -1
  85. package/components/select/select.yaml +7 -1
  86. package/components/step-progress/step-progress.yaml +1 -1
  87. package/components/stream/stream.d.ts +2 -0
  88. package/components/switch/switch.css +8 -0
  89. package/components/table/table.a2ui.json +6 -1
  90. package/components/table/table.class.js +18 -1
  91. package/components/table/table.css +31 -6
  92. package/components/table/table.d.ts +3 -1
  93. package/components/table/table.examples.md +2 -2
  94. package/components/table/table.yaml +9 -1
  95. package/components/table-toolbar/table-toolbar.a2ui.json +67 -2
  96. package/components/table-toolbar/table-toolbar.class.js +492 -17
  97. package/components/table-toolbar/table-toolbar.css +311 -0
  98. package/components/table-toolbar/table-toolbar.d.ts +17 -1
  99. package/components/table-toolbar/table-toolbar.yaml +220 -15
  100. package/components/tag/tag.class.js +13 -1
  101. package/components/text/text.a2ui.json +3 -3
  102. package/components/text/text.d.ts +5 -3
  103. package/components/text/text.yaml +3 -3
  104. package/components/textarea/textarea.class.js +9 -1
  105. package/components/textarea/textarea.css +4 -0
  106. package/components/time-picker/time-picker.yaml +3 -2
  107. package/components/tour/tour.d.ts +10 -0
  108. package/components/tree/tree.yaml +1 -1
  109. package/components/upload/upload.class.js +16 -0
  110. package/components/upload/upload.d.ts +7 -0
  111. package/components/upload/upload.yaml +7 -3
  112. package/core/a11y.d.ts +22 -0
  113. package/core/a11y.js +64 -0
  114. package/core/anchor.js +9 -5
  115. package/core/element.js +1 -1
  116. package/core/icons.js +2 -5
  117. package/custom-elements.json +142 -594
  118. package/dist/host.min.css +1 -1
  119. package/dist/host.sheet.js +1 -1
  120. package/dist/theme-provider.min.js +1 -1
  121. package/dist/web-components.min.css +1 -1
  122. package/dist/web-components.min.js +122 -119
  123. package/dist/web-components.sheet.js +1 -1
  124. package/index.d.ts +0 -15
  125. package/package.json +1 -1
  126. package/patterns/access-requests/access-requests.examples.html +1 -1
  127. package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +7 -7
  128. package/patterns/agent-cost/agent-cost.examples.html +6 -6
  129. package/patterns/agent-memory/agent-memory.examples.html +3 -3
  130. package/patterns/agent-prompt-library/agent-prompt-library.examples.html +8 -8
  131. package/patterns/agent-tool-call/agent-tool-call.examples.html +1 -1
  132. package/patterns/approvals/approvals.examples.html +6 -6
  133. package/patterns/audit-log/audit-log.examples.html +1 -1
  134. package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +4 -4
  135. package/patterns/chart-in-card/chart-in-card.examples.html +124 -10
  136. package/patterns/chart-in-card/chart-in-card.examples.js +37 -0
  137. package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +5 -5
  138. package/patterns/conversion-funnel/conversion-funnel.html +2 -2
  139. package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +2 -2
  140. package/patterns/diff-review/diff-review.examples.html +6 -6
  141. package/patterns/editor-shell/editor-shell.examples.html +3 -3
  142. package/patterns/export-flow/export-flow.examples.html +2 -2
  143. package/patterns/filter-bar/filter-bar.examples.html +2 -2
  144. package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +2 -2
  145. package/patterns/inline-dialog/inline-dialog.examples.html +2 -2
  146. package/patterns/marketing-engagement/marketing-engagement.examples.html +4 -4
  147. package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +1 -1
  148. package/patterns/permissions-matrix/permissions-matrix.examples.html +1 -1
  149. package/patterns/permissions-sharing/permissions-sharing.examples.html +2 -2
  150. package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +4 -4
  151. package/patterns/search-discovery/search-discovery.examples.html +2 -2
  152. package/styles/colors/semantics/features.css +8 -1
  153. package/styles/components.css +0 -4
  154. package/styles/type/roles.css +29 -9
  155. package/traits/arrow-grid-nav/arrow-grid-nav.js +1 -1
  156. package/traits/resize-observer/resize-observer.js +1 -1
  157. package/components/color-picker/color-picker.a2ui.json +0 -148
  158. package/components/color-picker/color-picker.class.js +0 -46
  159. package/components/color-picker/color-picker.css +0 -14
  160. package/components/color-picker/color-picker.d.ts +0 -99
  161. package/components/color-picker/color-picker.examples.md +0 -19
  162. package/components/color-picker/color-picker.js +0 -17
  163. package/components/color-picker/color-picker.yaml +0 -175
  164. package/components/datetime-picker/datetime-picker.a2ui.json +0 -256
  165. package/components/datetime-picker/datetime-picker.class.js +0 -58
  166. package/components/datetime-picker/datetime-picker.css +0 -11
  167. package/components/datetime-picker/datetime-picker.d.ts +0 -87
  168. package/components/datetime-picker/datetime-picker.examples.md +0 -25
  169. package/components/datetime-picker/datetime-picker.js +0 -17
  170. package/components/datetime-picker/datetime-picker.yaml +0 -254
  171. package/components/progress-row/progress-row.a2ui.json +0 -107
  172. package/components/progress-row/progress-row.class.js +0 -118
  173. package/components/progress-row/progress-row.css +0 -60
  174. package/components/progress-row/progress-row.d.ts +0 -24
  175. package/components/progress-row/progress-row.examples.md +0 -7
  176. package/components/progress-row/progress-row.js +0 -17
  177. package/components/progress-row/progress-row.yaml +0 -85
  178. package/components/toggle-group/toggle-group.a2ui.json +0 -148
  179. package/components/toggle-group/toggle-group.class.js +0 -195
  180. package/components/toggle-group/toggle-group.css +0 -127
  181. package/components/toggle-group/toggle-group.d.ts +0 -50
  182. package/components/toggle-group/toggle-group.examples.md +0 -33
  183. package/components/toggle-group/toggle-group.js +0 -18
  184. package/components/toggle-group/toggle-group.yaml +0 -135
  185. package/components/toggle-group/toggle-option.a2ui.json +0 -86
  186. package/components/toggle-group/toggle-option.yaml +0 -75
@@ -130,7 +130,25 @@
130
130
  NOTE: size="sm|md|lg" used to cap max-width (25/50/100%) which was a bug:
131
131
  a chart in a 160px grid cell would be clamped to 40px wide. Size now
132
132
  only tunes typography/padding/density inside #dims(); layout is the
133
- responsibility of the consumer. */
133
+ responsibility of the consumer.
134
+
135
+ ADR-0074 (Chart 2.0 ratio grammar, gh#1624 plan step 1) — [ratio]
136
+ PINS one of three named buckets (3:2|1:1|2:3), overriding whatever
137
+ height the box would otherwise get; unset auto-snaps by classifying
138
+ the chart's own live rendered aspect against two midpoint boundaries.
139
+ Because this primitive derives height in JS from a measured
140
+ clientWidth/clientHeight rather than from CSS layout (see the note
141
+ above — the SAME reason `size` stopped clamping max-width), the
142
+ ratified "CSS container query" mechanism is realized inside
143
+ chart.class.js's #dims() (the same measurement pass the pre-existing
144
+ ResizeObserver already drives), not as a literal `@container` rule
145
+ here: `container-type: size` (required to query aspect-ratio, not
146
+ just inline-size like the legend query below) would force block-size
147
+ containment on :scope, collapsing any chart with no explicit height
148
+ set today — the common case across this file's own demo gallery.
149
+ Same ratified grammar/snap-boundary/override contract either way;
150
+ `#dims()` reflects the resolved bucket onto the host as
151
+ `data-ratio-resolved` for later waves' per-type CSS to key off. */
134
152
 
135
153
  /* Debug resize handle */
136
154
  :scope[resize] {
@@ -158,6 +176,16 @@
158
176
  fill: none;
159
177
  }
160
178
 
179
+ /* REQ-R-007 (Chart 2.0 color-role plan) — "denser 1:1/2:3 layouts need a
180
+ quieter gridline than spacious 3:2": both buckets already thin the
181
+ gridline COUNT (#gridAndAxes' thinGrid, chart.class.js); this dims the
182
+ remaining lines' CONTRAST tier a step further rather than minting a
183
+ second gridline token. */
184
+ :scope[data-ratio-resolved="1:1"] [data-chart-grid],
185
+ :scope[data-ratio-resolved="2:3"] [data-chart-grid] {
186
+ opacity: 0.72;
187
+ }
188
+
161
189
  /* ── Y-axis labels ── */
162
190
 
163
191
  [data-y-label] {
@@ -387,6 +415,51 @@
387
415
  stroke: none;
388
416
  }
389
417
 
418
+ /* REQ-R-003 (pie, 1:1) — "legend as a ring of labels around the pie":
419
+ one label per slice, positioned OUTSIDE the ring (chart.class.js sizes
420
+ the pie itself smaller at 1:1 to leave this room). Sits on the plain
421
+ chart background, not on a colored slice fill, so it uses the ordinary
422
+ on-surface label token rather than treemap/donut's on-color token. */
423
+ [data-ring-label] {
424
+ fill: var(--chart-label);
425
+ font-weight: var(--a-weight-medium);
426
+ pointer-events: none;
427
+ }
428
+
429
+ /* REQ-R-003 (pie/donut, 1:1) — the ring of labels (pie) / maximized ring
430
+ + center-stat (donut) already function as the legend at 1:1, so the
431
+ always-visible inline `[data-legend]` block below the chart would
432
+ duplicate that information and compete for the same square footprint;
433
+ suppress it at 1:1 only. Tooltip-on-hover and (donut) the center-stat
434
+ text both stay unaffected either way.
435
+
436
+ REQ-R-003 (radial-bar, 1:1 "legend as ring") — same rationale, one
437
+ ring's worth of `[data-ring-label]` text already IS the legend at 1:1
438
+ (chart.class.js).
439
+
440
+ REQ-R-003 (multi-line, 3:2 "all series labeled inline at line end (no
441
+ legend needed)") — same rationale, inverted axis: multi-line's own
442
+ inline end-labels ([data-series-end-label]) only render at 3:2, so
443
+ THAT'S the ratio whose internal legend is redundant and suppressed;
444
+ 1:1/2:3 drop the end-labels and rely on the ordinary legend instead
445
+ (unsuppressed there), matching the matrix's own progressive
446
+ de-emphasis for this type. */
447
+ :scope[type="pie"][data-ratio-resolved="1:1"] [data-legend],
448
+ :scope[type="donut"][data-ratio-resolved="1:1"] [data-legend],
449
+ :scope[type="radial-bar"][data-ratio-resolved="1:1"] [data-legend],
450
+ :scope[type="multi-line"][data-ratio-resolved="3:2"] [data-legend] {
451
+ display: none;
452
+ }
453
+
454
+ /* REQ-R-003 (multi-line, 3:2) — the inline end-of-line series label the
455
+ suppression rule above pairs with. Colored per-series via the same
456
+ `--color-{key}` fallback-chain hook every other series-keyed element
457
+ in this file uses (chart.class.js's `#seriesFill`). */
458
+ [data-series-end-label] {
459
+ font-weight: var(--a-weight-medium);
460
+ pointer-events: none;
461
+ }
462
+
390
463
  /* ── Donut center text ── */
391
464
 
392
465
  [data-donut-total] {
@@ -553,6 +626,15 @@
553
626
  fill: var(--chart-label);
554
627
  }
555
628
 
629
+ /* REQ-R-003 (funnel, 1:1) — "stage labels inline": placed inside the
630
+ colored trapezoid body instead of the plain chart background, so it
631
+ needs the on-color label token (same convention treemap's own
632
+ in-tile label already uses) rather than the ordinary chart-fg. */
633
+ [data-funnel-label-inline] {
634
+ fill: var(--a-canvas-0);
635
+ font-weight: var(--a-weight-semibold);
636
+ }
637
+
556
638
  /* ── Treemap ──
557
639
  Flat tiles — each rect filled by the data palette. Labels +
558
640
  values appear only on tiles large enough to fit them (size
@@ -625,6 +707,22 @@
625
707
  stroke-width: 2;
626
708
  }
627
709
 
710
+ /* REQ-R-003 (composed, 2:3) — the sparkline-inset backing + line. Quiet
711
+ container-low surface (REQ-R-007's own "quiet surfaces" role) so the
712
+ inset reads as a distinct panel without competing with the primary
713
+ bar series for visual weight. */
714
+ [data-composed-inset-bg] {
715
+ fill: var(--md-sys-color-neutral-container-low);
716
+ }
717
+ [data-composed-inset-line] {
718
+ fill: none;
719
+ stroke-width: calc(var(--chart-line-width) - 0.5);
720
+ }
721
+ [data-composed-inset-label] {
722
+ fill: var(--chart-label);
723
+ font-family: var(--chart-font);
724
+ }
725
+
628
726
  /* ── Sparkline container ──
629
727
  `height: 100%` fills an explicit parent height (e.g. when the
630
728
  containing section is a flex-grow item inside card-ui). Falls
@@ -640,6 +738,33 @@
640
738
  :scope[type="segments"] svg {
641
739
  height: 1.5rem;
642
740
  }
741
+
742
+ /* REQ-R-003 (segments, 2:3) — "tall vertical stacked bars": the
743
+ horizontal default's fixed-HEIGHT/fluid-WIDTH strip inverts to a
744
+ fixed-WIDTH/fluid-HEIGHT column — same technique every other type's
745
+ own base `svg { width: 100%; height: auto }` rule already uses
746
+ (a definite dimension on one axis, `auto` on the other so the SVG's
747
+ own intrinsic viewBox aspect ratio solves the rest), just with the
748
+ definite axis swapped. `height: 100%` was tried first and produces a
749
+ wrong render: chart-ui has no definite height of its own in ordinary
750
+ use for `height: 100%` to resolve against, and the actual resolved
751
+ value in testing came from an unrelated ancestor percentage chain —
752
+ visually correct-looking proportions by coincidence, but the wrong
753
+ mechanism (caught by a real browser probe, not the Vitest DOM
754
+ assertions above, which never render CSS at all). */
755
+ :scope[type="segments"][data-ratio-resolved="2:3"] svg {
756
+ width: 2rem;
757
+ height: auto;
758
+ margin-inline: auto;
759
+ }
760
+
761
+ /* REQ-R-003 (segments, 2:3) — "legend stacked": one item per line
762
+ instead of the default wrapped row, matching the vertical strip's own
763
+ tall/narrow footprint. */
764
+ :scope[type="segments"][data-ratio-resolved="2:3"] [data-legend] {
765
+ flex-direction: column;
766
+ align-items: flex-start;
767
+ }
643
768
  [data-sparkline] {
644
769
  display: block;
645
770
  width: 100%;
@@ -694,6 +819,31 @@
694
819
  [data-hit] { cursor: default; }
695
820
  }
696
821
 
822
+ /* ─────────────────────────────────────────────────────────────────
823
+ REQ-R-001/R-007 (Chart 2.0 full-bleed anatomy + color-role plan) —
824
+ full-bleed background depth.
825
+
826
+ Deliberately OUTSIDE `@scope (chart-ui)`: the rule needs to match an
827
+ ANCESTOR (`card-ui[padding="none"]`) of the scoping root, which a
828
+ selector inside the scope block can't express against :scope itself.
829
+ card-ui's own `padding="none"` already zeroes its inset (`--card-inset:
830
+ 0`, card.css) — a chart filling that slot at 100%×100% was already
831
+ edge-to-edge (REQ-R-001's two pre-existing mechanisms), but with a fully
832
+ transparent background it read as "no background" rather than a
833
+ deliberate flush surface. This tints it with a quiet container-low role
834
+ (ADR-0064/0073's ladder) — REQ-P-002's own opt-in requirement is what
835
+ the `card-ui[padding="none"]` ancestor gate enforces: a chart in a
836
+ normal-padding card never matches this selector, so its rendering is
837
+ unchanged (additive, not a new default). --md-sys-color-neutral-
838
+ container-low was chosen over -surface-dim (the SPEC's other named
839
+ option) for a closer value-step to the ambient canvas so the categorical
840
+ series data stays the visual foreground.
841
+ ───────────────────────────────────────────────────────────────── */
842
+ card-ui[padding="none"] chart-ui {
843
+ background: var(--md-sys-color-neutral-container-low);
844
+ border-radius: inherit;
845
+ }
846
+
697
847
  /* ─────────────────────────────────────────────────────────────────
698
848
  Chart tooltip (top-layer, outside @scope)
699
849
 
@@ -30,20 +30,16 @@ export class UIChart extends UIElement {
30
30
  dots: 'all' | 'none' | 'hover' | 'last';
31
31
  /** Number-format mode applied to axis labels + value overlays + donut total + gauge value + treemap value + funnel value + internal tooltip. `abbr` is the legacy 1.2K / 3M format; `decimal` fixes 2 decimals; `currency` prefixes via `--chart-currency-prefix` token (default "$"); `percent` multiplies × 100 and adds a % suffix. */
32
32
  format: 'abbr' | 'decimal' | 'currency' | 'percent';
33
- /** DEPRECATED (ADR-0063, gh#1563) — use `noAverage` instead. Same meaning, same default; kept working as a dual-read compat shim (one- time console.warn when it's the name that resolves). Removed in 0.9.0. */
34
- hideAverage: boolean;
35
- /** DEPRECATED (ADR-0063, gh#1563) — use `noGrid` instead. Same meaning, same default; kept working as a dual-read compat shim (one-time console.warn when it's the name that resolves). Removed in 0.9.0. */
36
- hideGrid: boolean;
37
- /** DEPRECATED (ADR-0063, gh#1563) — use `noValues` instead. Same meaning, same default; kept working as a dual-read compat shim (one- time console.warn when it's the name that resolves). Removed in 0.9.0. */
38
- hideValues: boolean;
39
33
  /** Show a skeleton placeholder instead of the chart body. Set to true while data is being fetched; clear once data arrives. Preserves the element's aspect-ratio dimensions so the skeleton occupies the same space the chart will fill. Parity with stat-ui[loading] and table-ui[loading] (§FB-12, v0.6.27). */
40
34
  loading: boolean;
41
- /** When true, suppress the overlaid average line. Canonical spelling (ADR-0063, gh#1563) replacing `hideAverage`. */
35
+ /** When true, suppress the overlaid average line. Canonical spelling (ADR-0063, gh#1563) the deprecated `hideAverage` dual-read shim was cut in 0.8.43 (gh#1617). */
42
36
  noAverage: boolean;
43
- /** Hide gridlines. Canonical spelling (ADR-0063, gh#1563) replacing `hideGrid`. */
37
+ /** Hide gridlines. Canonical spelling (ADR-0063, gh#1563) the deprecated `hideGrid` dual-read shim was cut in 0.8.43 (gh#1617). */
44
38
  noGrid: boolean;
45
- /** Hide value labels. Canonical spelling (ADR-0063, gh#1563) replacing `hideValues`. */
39
+ /** Hide value labels. Canonical spelling (ADR-0063, gh#1563) the deprecated `hideValues` dual-read shim was cut in 0.8.43 (gh#1617). */
46
40
  noValues: boolean;
41
+ /** Chart 2.0 aspect-ratio bucket (ADR-0074). Unset (default) auto-snaps to the nearest of the three studied ratios by comparing the chart's own live box aspect against two midpoint boundaries (5:4 between 3:2/1:1, 4:5 between 1:1/2:3) — never interpolated; the resolved bucket reflects onto the host as `data-ratio-resolved`. Setting `ratio` explicitly pins that bucket regardless of the box's actual aspect, overriding the auto-snap. Orthogonal to `size` (overall scale) and `type`/`color` (ADR-0064's family axis) — this attribute lands the grammar + snap plumbing only; per-type studied renderings per bucket are later waves (gh#1624 plan steps 2-4). */
42
+ ratio: '3:2' | '1:1' | '2:3';
47
43
  /** Multi-line only. Names one series key (matching a `y` key) to render at full strength — area fill + full-opacity line. Every other series drops its area fill and renders line-only at --chart-deemphasized-opacity. Empty (default) applies full treatment to every series, unchanged from pre-gh#561 behavior. */
48
44
  seriesEmphasis: string;
49
45
  /** Chart size */
@@ -54,6 +50,10 @@ export class UIChart extends UIElement {
54
50
  x: string;
55
51
  /** Y-axis key(s), comma-separated for multi-series */
56
52
  y: string;
53
+ /** Pins the value-axis domain ceiling for cartesian types (bar, line, area, scatter, multi-line, stacked-bar, grouped-bar, composed). Unset (default, null) keeps today's auto-scale ceiling (the data max). Pairs with `yMin` to pin the full domain; either may be set alone. N/A for radial/part-to-whole types — see `yMin` (gh#1690). A datum outside the pinned domain is CLAMPED to the nearest edge for rendering (the value label/tooltip still shows the real value) rather than escaping the plot box. */
54
+ yMax: number | null;
55
+ /** Pins the value-axis domain floor for cartesian types (bar, line, area, scatter, multi-line, stacked-bar, grouped-bar, composed). Unset (default, null) already floors a positive series at 0 (the pre-1690 behavior, unchanged) — a migrating d3 consumer's own "pin the floor to 0" habit is the built-in default, not something to configure. Pin `y-min` explicitly for three OTHER cases: (1) zooming a narrow window — e.g. `y-min="0.9" y-max="1"` magnifies a percent series clustered near 92-95%, which reads flatter against the full auto-scaled [0,1] domain; (2) a mixed-sign series, where the unpinned floor never goes below 0 and negative values clip at the axis; (3) aligning two charts to one shared scale for a fair comparison. N/A for radial/part-to-whole types (pie, donut, radar, radial-bar, gauge, funnel, treemap, sankey, segments, sparkline) — those never read a shared linear value axis, so this prop is a no-op there (gh#1690). A datum outside the pinned domain is CLAMPED to the nearest edge for rendering (the value label/tooltip still shows the real value) rather than escaping the plot box. */
56
+ yMin: number | null;
57
57
 
58
58
  addEventListener(type: 'chart-hover', listener: (ev: ChartHoverEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
59
59
  addEventListener(type: 'chart-leave', listener: (ev: ChartLeaveEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
@@ -61,50 +61,25 @@ props:
61
61
  - warning
62
62
  - danger
63
63
  - info
64
- hideAverage:
65
- description: >-
66
- DEPRECATED (ADR-0063, gh#1563) — use `noAverage` instead. Same
67
- meaning, same default; kept working as a dual-read compat shim (one-
68
- time console.warn when it's the name that resolves). Removed in
69
- 0.9.0.
70
- type: boolean
71
- default: false
72
- attribute: hide-average
73
64
  noAverage:
74
65
  description: >-
75
66
  When true, suppress the overlaid average line. Canonical spelling
76
- (ADR-0063, gh#1563) replacing `hideAverage`.
67
+ (ADR-0063, gh#1563) the deprecated `hideAverage` dual-read shim
68
+ was cut in 0.8.43 (gh#1617).
77
69
  type: boolean
78
70
  default: false
79
71
  attribute: no-average
80
- hideGrid:
81
- description: >-
82
- DEPRECATED (ADR-0063, gh#1563) — use `noGrid` instead. Same meaning,
83
- same default; kept working as a dual-read compat shim (one-time
84
- console.warn when it's the name that resolves). Removed in 0.9.0.
85
- type: boolean
86
- default: false
87
- attribute: hide-grid
88
72
  noGrid:
89
73
  description: >-
90
- Hide gridlines. Canonical spelling (ADR-0063, gh#1563) replacing
91
- `hideGrid`.
74
+ Hide gridlines. Canonical spelling (ADR-0063, gh#1563) — the
75
+ deprecated `hideGrid` dual-read shim was cut in 0.8.43 (gh#1617).
92
76
  type: boolean
93
77
  default: false
94
78
  attribute: no-grid
95
- hideValues:
96
- description: >-
97
- DEPRECATED (ADR-0063, gh#1563) — use `noValues` instead. Same
98
- meaning, same default; kept working as a dual-read compat shim (one-
99
- time console.warn when it's the name that resolves). Removed in
100
- 0.9.0.
101
- type: boolean
102
- default: false
103
- attribute: hide-values
104
79
  noValues:
105
80
  description: >-
106
- Hide value labels. Canonical spelling (ADR-0063, gh#1563) replacing
107
- `hideValues`.
81
+ Hide value labels. Canonical spelling (ADR-0063, gh#1563) — the
82
+ deprecated `hideValues` dual-read shim was cut in 0.8.43 (gh#1617).
108
83
  type: boolean
109
84
  default: false
110
85
  attribute: no-values
@@ -121,6 +96,25 @@ props:
121
96
  - sm
122
97
  - md
123
98
  - lg
99
+ ratio:
100
+ description: >-
101
+ Chart 2.0 aspect-ratio bucket (ADR-0074). Unset (default) auto-snaps
102
+ to the nearest of the three studied ratios by comparing the chart's
103
+ own live box aspect against two midpoint boundaries (5:4 between
104
+ 3:2/1:1, 4:5 between 1:1/2:3) — never interpolated; the resolved
105
+ bucket reflects onto the host as `data-ratio-resolved`. Setting
106
+ `ratio` explicitly pins that bucket regardless of the box's actual
107
+ aspect, overriding the auto-snap. Orthogonal to `size` (overall
108
+ scale) and `type`/`color` (ADR-0064's family axis) — this attribute
109
+ lands the grammar + snap plumbing only; per-type studied renderings
110
+ per bucket are later waves (gh#1624 plan steps 2-4).
111
+ type: string
112
+ default: ""
113
+ enum:
114
+ - "3:2"
115
+ - "1:1"
116
+ - "2:3"
117
+ reflect: true
124
118
  smooth:
125
119
  description: Line smoothing factor
126
120
  type: number
@@ -163,6 +157,44 @@ props:
163
157
  type: boolean
164
158
  default: false
165
159
  reflect: true
160
+ yMin:
161
+ description: >-
162
+ Pins the value-axis domain floor for cartesian types (bar, line,
163
+ area, scatter, multi-line, stacked-bar, grouped-bar, composed).
164
+ Unset (default, null) already floors a positive series at 0 (the
165
+ pre-1690 behavior, unchanged) — a migrating d3 consumer's own
166
+ "pin the floor to 0" habit is the built-in default, not something
167
+ to configure. Pin `y-min` explicitly for three OTHER cases: (1)
168
+ zooming a narrow window — e.g. `y-min="0.9" y-max="1"` magnifies a
169
+ percent series clustered near 92-95%, which reads flatter against
170
+ the full auto-scaled [0,1] domain; (2) a mixed-sign series, where
171
+ the unpinned floor never goes below 0 and negative values clip at
172
+ the axis; (3) aligning two charts to one shared scale for a fair
173
+ comparison. N/A for radial/part-to-whole types (pie, donut, radar,
174
+ radial-bar, gauge, funnel, treemap, sankey, segments, sparkline) —
175
+ those never read a shared linear value axis, so this prop is a
176
+ no-op there (gh#1690).
177
+ A datum outside the pinned domain is CLAMPED to the nearest edge
178
+ for rendering (the value label/tooltip still shows the real value)
179
+ rather than escaping the plot box.
180
+ type: number
181
+ default: null
182
+ attribute: y-min
183
+ reflect: true
184
+ yMax:
185
+ description: >-
186
+ Pins the value-axis domain ceiling for cartesian types (bar, line,
187
+ area, scatter, multi-line, stacked-bar, grouped-bar, composed).
188
+ Unset (default, null) keeps today's auto-scale ceiling (the data
189
+ max). Pairs with `yMin` to pin the full domain; either may be set
190
+ alone. N/A for radial/part-to-whole types — see `yMin` (gh#1690).
191
+ A datum outside the pinned domain is CLAMPED to the nearest edge
192
+ for rendering (the value label/tooltip still shows the real value)
193
+ rather than escaping the plot box.
194
+ type: number
195
+ default: null
196
+ attribute: y-max
197
+ reflect: true
166
198
  x:
167
199
  description: Data key for x-axis (category) values
168
200
  type: string
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "properties": {
16
16
  "items": {
17
- "description": "JSON array of {key, label, slot?, pct?} legend items. Takes precedence over [for] when both are provided.",
17
+ "description": "JSON array of {key, label, slot?, pct?, deemphasized?} legend items. Takes precedence over [for] when both are provided. `deemphasized` (REQ-R-007) mutes the row's swatch/label to the same muted-neutral tier chart-ui's own internal legend uses for a de-emphasized series (e.g. `composed@1:1`'s secondary line) — mirrored automatically from a `[for]`-bound chart's own `.legendData`, or settable explicitly here.",
18
18
  "type": "string",
19
19
  "default": ""
20
20
  },
@@ -27,7 +27,7 @@
27
27
  "default": ""
28
28
  },
29
29
  "interactive": {
30
- "description": "Canonical opt-in spelling (ADR-0063, gh#1563) replacing `static`'s opt-out polarity. Rows are interactive <button>-role badges by default — this default stays `true` in 0.8.42 (deliberately NOT flipped yet; the 0.9.0 cut inverts it to `false` after a full consumer scan of every `<chart-legend-ui>` tag per the ADR).",
30
+ "description": "Canonical opt-in spelling (ADR-0063, gh#1563) the deprecated `static` opt-out alias was cut in 0.8.43 (gh#1617). Rows are interactive <button>-role badges by default — this default stays `true` (deliberately NOT flipped yet; the 0.9.0 cut inverts it to `false` after a full consumer scan of every `<chart-legend-ui>` tag per the ADR).",
31
31
  "type": "boolean",
32
32
  "default": true
33
33
  },
@@ -41,7 +41,7 @@
41
41
  "default": "hide"
42
42
  },
43
43
  "position": {
44
- "description": "Layout hint — drives flex-direction. Actual placement follows DOM order.",
44
+ "description": "Layout hint — drives flex-direction. Actual placement follows DOM order. Superseded by a resolved `ratio` bucket (below) whenever one is in effect; this is the pre-ADR-0074 fallback for a legend with neither an explicit `ratio` nor a `[for]`-bound chart.",
45
45
  "type": "string",
46
46
  "enum": [
47
47
  "top",
@@ -51,6 +51,16 @@
51
51
  ],
52
52
  "default": "bottom"
53
53
  },
54
+ "ratio": {
55
+ "description": "Chart 2.0 legend layout bucket (ADR-0074, gh#1624 plan step 5, REQ-R-005): 3:2 renders a horizontal row, 1:1 a wrapped 2-column grid, 2:3 a vertical stacked list. An explicit value PINS the bucket regardless of any bound chart. Unset (default) auto-mirrors the `[for]`-bound chart's own resolved `data-ratio-resolved` bucket — the legend never runs its own container-query classification, since its own box is typically a thin strip with no studied aspect of its own. With neither a pin nor a bound chart, no bucket resolves and `position`'s flex-direction rules apply unchanged. The resolved bucket reflects read-only onto the host as `data-ratio-resolved`, mirroring chart-ui's own convention.",
56
+ "type": "string",
57
+ "enum": [
58
+ "3:2",
59
+ "1:1",
60
+ "2:3"
61
+ ],
62
+ "default": ""
63
+ },
54
64
  "shape": {
55
65
  "description": "Swatch shape. Maps to badge-ui's icon for dot/square variants.",
56
66
  "type": "string",
@@ -61,11 +71,6 @@
61
71
  "dashed"
62
72
  ],
63
73
  "default": "dot"
64
- },
65
- "static": {
66
- "description": "DEPRECATED (ADR-0063, gh#1563) — use `interactive` instead. When set (and `interactive` is absent), rows are non-interactive <span>s (no click, no toggle). Kept working as a dual-read compat shim (one-time console.warn when it's the name that resolves). Removed in 0.9.0.",
67
- "type": "boolean",
68
- "default": false
69
74
  }
70
75
  },
71
76
  "required": [
@@ -81,7 +86,7 @@
81
86
  ],
82
87
  "events": {
83
88
  "toggle": {
84
- "description": "Fires on row click (non-static). Detail: {key, active, mode}. `active` is the new state (true=visible). Consumers (or chart-ui via [for]) wire this to series visibility.",
89
+ "description": "Fires on row click when interactive. Detail: {key, active, mode}. `active` is the new state (true=visible). Consumers (or chart-ui via [for]) wire this to series visibility.",
85
90
  "detail": {
86
91
  "active": {
87
92
  "description": "New visibility state (true = visible).",
@@ -104,6 +109,11 @@
104
109
  "a2ui": "[\n {\n \"id\": \"legend\", \"component\": \"ChartLegend\",\n \"shape\": \"dot\",\n \"items\": \"[{\\\"key\\\":\\\"revenue\\\",\\\"label\\\":\\\"Revenue\\\",\\\"slot\\\":0},{\\\"key\\\":\\\"users\\\",\\\"label\\\":\\\"Users\\\",\\\"slot\\\":1}]\"\n }\n]",
105
110
  "name": "standalone-with-items"
106
111
  },
112
+ {
113
+ "description": "Standalone legend pinned to the 1:1 (2-col grid) layout bucket.",
114
+ "a2ui": "[\n {\n \"id\": \"legend\", \"component\": \"ChartLegend\",\n \"ratio\": \"1:1\",\n \"items\": \"[{\\\"key\\\":\\\"revenue\\\",\\\"label\\\":\\\"Revenue\\\",\\\"slot\\\":0},{\\\"key\\\":\\\"users\\\",\\\"label\\\":\\\"Users\\\",\\\"slot\\\":1},{\\\"key\\\":\\\"churn\\\",\\\"label\\\":\\\"Churn\\\",\\\"slot\\\":2},{\\\"key\\\":\\\"nps\\\",\\\"label\\\":\\\"NPS\\\",\\\"slot\\\":3}]\"\n }\n]",
115
+ "name": "ratio-pinned-grid"
116
+ },
107
117
  {
108
118
  "description": "Legend mirrors a named chart's series data.",
109
119
  "a2ui": "[\n {\n \"id\": \"root\", \"component\": \"Column\",\n \"children\": [\"chart\", \"legend\"]\n },\n {\n \"id\": \"chart\", \"component\": \"Chart\",\n \"type\": \"multi-line\", \"x\": \"month\", \"y\": \"revenue,users\"\n },\n {\n \"id\": \"legend\", \"component\": \"ChartLegend\",\n \"for\": \"chart\",\n \"shape\": \"line\",\n \"position\": \"bottom\"\n }\n]",
@@ -138,12 +148,8 @@
138
148
  "name": "idle"
139
149
  },
140
150
  {
141
- "description": "Rows are <button>s. Default unless [static].",
151
+ "description": "Rows are <button>s (the default). When false, rows are <span>s — no click handler, no toggle affordance.",
142
152
  "name": "interactive"
143
- },
144
- {
145
- "description": "Rows are <span>s. No click handler, no toggle affordance.",
146
- "name": "static"
147
153
  }
148
154
  ],
149
155
  "status": "stable",
@@ -34,22 +34,47 @@
34
34
  * aren't representable as icon glyphs.
35
35
  * position — top | bottom | left | right. Layout hint; actual placement
36
36
  * is where the element is placed in the DOM. Drives
37
- * flex-direction.
37
+ * flex-direction. Superseded by a resolved `ratio` bucket
38
+ * (below) whenever one is in effect — `position` is the
39
+ * pre-ADR-0074 fallback for a legend with neither an
40
+ * explicit `ratio` nor a `[for]`-bound chart.
41
+ * ratio — '' | 3:2 | 1:1 | 2:3 (ADR-0074, gh#1624 plan step 5).
42
+ * Explicit value PINS the legend's own layout bucket
43
+ * (row / 2-col grid / vertical list — REQ-R-005) regardless
44
+ * of any bound chart. Unset (default) auto-mirrors the
45
+ * `[for]`-bound chart's own resolved `data-ratio-resolved`
46
+ * bucket — the legend does not run its own container-query
47
+ * classification (REQ-R-005: "follows the per-type legend
48
+ * position column rather than introducing a separate
49
+ * legend-only ratio contract"); a legend's own box is
50
+ * typically a thin strip whose aspect carries no studied
51
+ * meaning of its own. With neither a pin nor a bound chart,
52
+ * no bucket resolves and `position`'s pre-existing
53
+ * flex-direction rules apply unchanged (zero regression for
54
+ * standalone legends that predate this attribute). The
55
+ * resolved bucket is reflected read-only as
56
+ * `data-ratio-resolved`, mirroring chart-ui's own
57
+ * convention.
38
58
  * interactive — canonical opt-in spelling (ADR-0063, gh#1563). Rows
39
59
  * default interactive (`role="button"` badges that toggle
40
60
  * on click + Enter/Space) until the 0.9.0 default-inversion
41
- * cut. `static` is a deprecated opt-out alias when set
42
- * (and [interactive] is absent), rows render as
43
- * non-interactive badges (no role/tabindex/handlers).
61
+ * cut. The deprecated `static` opt-out alias was cut in
62
+ * 0.8.43 (gh#1617).
44
63
  * on-toggle — hide | opacity. Default hide. Escape hatch per OD-CHART-09.
45
64
  * Reported via the `toggle` event detail; chart-ui reads it
46
65
  * when wired via [for].
47
66
  *
48
67
  * Events:
49
68
  * toggle — detail: {key, active, mode}. Fires on row click when
50
- * not [static]. `active` is the new state (true=visible).
69
+ * interactive. `active` is the new state (true=visible).
51
70
  */
52
71
 
72
+ // ADR-0074's three ratified ratio buckets — same enum chart-ui pins/resolves
73
+ // to (CHART_RATIOS in chart.class.js). Kept as a Set here rather than
74
+ // re-importing chart.class.js: the legend must resolve standalone (no
75
+ // chart-ui ancestor required, per its own "renders standalone" contract).
76
+ const RATIO_BUCKETS = new Set(['3:2', '1:1', '2:3']);
77
+
53
78
  import { UIElement } from '../../core/element.js';
54
79
 
55
80
  export class UIChartLegend extends UIElement {
@@ -61,67 +86,27 @@ export class UIChartLegend extends UIElement {
61
86
  items: { type: String, default: '', reflect: false },
62
87
  shape: { type: String, default: 'dot', reflect: true },
63
88
  position: { type: String, default: 'bottom', reflect: true },
64
- // ADR-0063 (gh#1563) `static` (opt-out) is DEPRECATED; `interactive`
65
- // (opt-in) is the canonical positively-named boolean. `static` stays
66
- // declared (dual-read compat, 0.8.42). Deliberately does NOT flip the
67
- // default here: `interactive` defaults `true` so a consumer naming
68
- // NEITHER attribute keeps today's behavior (rows interactive)
69
- // unchanged the real default-inversion (interactive defaulting to
70
- // `false`) is scheduled for 0.9.0 per the ADR's own consumer-scan
71
- // requirement (every `<chart-legend-ui>` tag needs review first).
72
- static: { type: Boolean, default: false, reflect: true },
89
+ // ADR-0074 (gh#1624 plan step 5) explicit pin; see the class-doc
90
+ // comment above for the full pin > mirror > position-fallback chain.
91
+ ratio: { type: String, default: '', reflect: true },
92
+ // ADR-0063 (gh#1563) `interactive` (opt-in) is the canonical
93
+ // positively-named boolean; the deprecated `static` opt-out alias was
94
+ // cut in 0.8.43 (gh#1617). Deliberately keeps its `true` default:
95
+ // the real default-inversion (interactive defaulting to `false`) is
96
+ // still scheduled for 0.9.0 per the ADR's own consumer-scan
97
+ // requirement (every `<chart-legend-ui>` tag needs review first) —
98
+ // out of this ticket's scope, which only cuts the `static` alias.
73
99
  interactive: { type: Boolean, default: true, reflect: true },
74
100
  onToggle: { type: String, default: 'hide', reflect: true, attribute: 'on-toggle' },
75
101
  };
76
102
 
77
103
  static template = () => null;
78
104
 
79
- // ADR-0063 (gh#1563) one-shot-per-class deprecation warn for [static].
80
- static #warnedStatic = false;
81
-
82
105
  #target = null;
83
106
  #targetListener = null;
84
107
  #hiddenKeys = new Set();
85
108
 
86
- // ADR-0063 (gh#1563) — captured by overriding `connectedCallback()` to run
87
- // BEFORE calling `super.connectedCallback()`. `interactive` defaults
88
- // `true` (a reflected Boolean), and UIElement's own connectedCallback
89
- // pre-sync loop stamps the attribute for any reflected Boolean prop whose
90
- // value is already `true` and whose attribute is absent (gh#961) — that
91
- // stamp happens INSIDE `super.connectedCallback()`, so a check made from
92
- // `connected()` (which UIElement invokes AFTER its pre-sync loop) would
93
- // see `[interactive]` present even when the author never wrote it.
94
- // Capturing here, before the super call, reads only the AUTHORED markup —
95
- // the constructor is too early (elements are constructed attribute-less
96
- // then have each attribute set individually as the parser processes them;
97
- // by connectedCallback time every authored attribute has already landed).
98
- #authoredInteractive = false;
99
- #authoredStatic = false;
100
-
101
- connectedCallback() {
102
- this.#authoredInteractive = this.hasAttribute('interactive');
103
- this.#authoredStatic = this.hasAttribute('static');
104
- super.connectedCallback();
105
- }
106
-
107
109
  connected() {
108
- // ADR-0063 (gh#1563) compat shim — resolve BEFORE the first render/paint
109
- // so there's no wrong-then-right flash. New-name precedence: [interactive]
110
- // wins outright when present, regardless of [static]; only fall back to
111
- // (and mirror) [static] when [interactive] is absent from markup.
112
- if (!this.#authoredInteractive && this.#authoredStatic) {
113
- this.interactive = false;
114
- if (!UIChartLegend.#warnedStatic) {
115
- UIChartLegend.#warnedStatic = true;
116
- // eslint-disable-next-line no-console
117
- console.warn(
118
- '[AdiaUI] <chart-legend-ui [static]> is deprecated — use ' +
119
- '[interactive] instead (ADR-0063, gh#1563). Note: [interactive] ' +
120
- 'still defaults to true until the 0.9.0 default-inversion cut. ' +
121
- 'Kept working as-is; no behavior change.',
122
- );
123
- }
124
- }
125
110
  this.setAttribute('role', 'list');
126
111
  this.#resolveTarget();
127
112
  this.#paint();
@@ -166,6 +151,25 @@ export class UIChartLegend extends UIElement {
166
151
  this.#targetListener = null;
167
152
  }
168
153
 
154
+ /* ── Ratio resolution (ADR-0074, gh#1624 plan step 5) ─────────────── */
155
+
156
+ /**
157
+ * Pin > mirror > none. A pinned `[ratio]` always wins; absent that, a
158
+ * `[for]`-bound chart's own `data-ratio-resolved` is mirrored (set by
159
+ * the SAME `#renderChart()` pass that fires the `legend-update` event
160
+ * this class already listens for — the mirror is always reading a
161
+ * value already fresh by the time `#paint()` runs). Neither present →
162
+ * '' (no bucket resolves; `position`'s pre-existing CSS applies).
163
+ */
164
+ #resolveRatio() {
165
+ if (RATIO_BUCKETS.has(this.ratio)) return this.ratio;
166
+ if (this.#target) {
167
+ const mirrored = this.#target.getAttribute('data-ratio-resolved');
168
+ if (RATIO_BUCKETS.has(mirrored)) return mirrored;
169
+ }
170
+ return '';
171
+ }
172
+
169
173
  /* ── Item source ────────────────────────────────────────────────── */
170
174
 
171
175
  #resolveItems() {
@@ -186,6 +190,15 @@ export class UIChartLegend extends UIElement {
186
190
  /* ── Render ─────────────────────────────────────────────────────── */
187
191
 
188
192
  #paint() {
193
+ const resolved = this.#resolveRatio();
194
+ if (resolved) {
195
+ if (this.getAttribute('data-ratio-resolved') !== resolved) {
196
+ this.setAttribute('data-ratio-resolved', resolved);
197
+ }
198
+ } else if (this.hasAttribute('data-ratio-resolved')) {
199
+ this.removeAttribute('data-ratio-resolved');
200
+ }
201
+
189
202
  const items = this.#resolveItems();
190
203
  this.innerHTML = '';
191
204
  if (!items.length) return;
@@ -209,6 +222,12 @@ export class UIChartLegend extends UIElement {
209
222
  row.setAttribute('text', label);
210
223
  if (key) row.setAttribute('data-key', key);
211
224
  if (active) row.setAttribute('data-active', '');
225
+ // REQ-R-007 (Chart 2.0 ratio-and-surface SPEC) — a de-emphasized
226
+ // series (e.g. composed@1:1's secondary line, Wave C) mutes the
227
+ // mirrored legend row the same way chart-ui's own internal legend
228
+ // already mutes its dot (chart.class.js #buildLegend): the muted
229
+ // neutral token, not the series' categorical color slot.
230
+ if (item.deemphasized) row.setAttribute('data-deemphasized', '');
212
231
  if (this.interactive) {
213
232
  row.setAttribute('tabindex', '0');
214
233
  row.setAttribute('aria-pressed', active ? 'true' : 'false');
@@ -228,9 +247,11 @@ export class UIChartLegend extends UIElement {
228
247
  const swatch = document.createElement('swatch-ui');
229
248
  swatch.setAttribute('shape', swatchShape);
230
249
  swatch.setAttribute('size', 'sm');
231
- const swatchColor = key
232
- ? `var(--color-${key}, var(--a-data-${slot}))`
233
- : `var(--a-data-${slot})`;
250
+ const swatchColor = item.deemphasized
251
+ ? 'var(--md-sys-color-neutral-on-surface-variant)'
252
+ : key
253
+ ? `var(--color-${key}, var(--a-data-${slot}))`
254
+ : `var(--a-data-${slot})`;
234
255
  swatch.style.setProperty('--swatch-color', swatchColor);
235
256
  row.appendChild(swatch);
236
257