@agent-ui-kit/components 0.0.3

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 (271) hide show
  1. package/dist/base-styles.css +8 -0
  2. package/dist/component-styles.css +147 -0
  3. package/dist/controls/_base/index.d.ts +3 -0
  4. package/dist/controls/_base/index.js +9 -0
  5. package/dist/controls/_base/indicator-element.d.ts +132 -0
  6. package/dist/controls/_base/indicator-element.js +158 -0
  7. package/dist/controls/_base/listbox-element.d.ts +70 -0
  8. package/dist/controls/_base/listbox-element.js +74 -0
  9. package/dist/controls/_base/range-element.d.ts +120 -0
  10. package/dist/controls/_base/range-element.js +158 -0
  11. package/dist/controls/_surface/container-box.css +301 -0
  12. package/dist/controls/_surface/container.css +156 -0
  13. package/dist/controls/_token-surface/token-surface.d.ts +35 -0
  14. package/dist/controls/_token-surface/token-surface.js +97 -0
  15. package/dist/controls/attachment/attachment-meta.d.ts +8 -0
  16. package/dist/controls/attachment/attachment-meta.js +84 -0
  17. package/dist/controls/attachment/attachment.css +91 -0
  18. package/dist/controls/attachment/attachment.d.ts +40 -0
  19. package/dist/controls/attachment/attachment.js +70 -0
  20. package/dist/controls/avatar/avatar-initials.d.ts +4 -0
  21. package/dist/controls/avatar/avatar-initials.js +30 -0
  22. package/dist/controls/avatar/avatar.css +87 -0
  23. package/dist/controls/avatar/avatar.d.ts +40 -0
  24. package/dist/controls/avatar/avatar.js +77 -0
  25. package/dist/controls/badge/badge.css +175 -0
  26. package/dist/controls/badge/badge.d.ts +36 -0
  27. package/dist/controls/badge/badge.js +71 -0
  28. package/dist/controls/bar-chart/bar-chart.css +110 -0
  29. package/dist/controls/bar-chart/bar-chart.d.ts +26 -0
  30. package/dist/controls/bar-chart/bar-chart.js +80 -0
  31. package/dist/controls/bar-chart/bar-math.d.ts +31 -0
  32. package/dist/controls/bar-chart/bar-math.js +106 -0
  33. package/dist/controls/button/button.css +263 -0
  34. package/dist/controls/button/button.d.ts +61 -0
  35. package/dist/controls/button/button.js +115 -0
  36. package/dist/controls/calendar/calendar.css +495 -0
  37. package/dist/controls/calendar/calendar.d.ts +161 -0
  38. package/dist/controls/calendar/calendar.js +1004 -0
  39. package/dist/controls/card/card-content.d.ts +24 -0
  40. package/dist/controls/card/card-content.js +133 -0
  41. package/dist/controls/card/card-footer.d.ts +3 -0
  42. package/dist/controls/card/card-footer.js +16 -0
  43. package/dist/controls/card/card-header.d.ts +3 -0
  44. package/dist/controls/card/card-header.js +18 -0
  45. package/dist/controls/card/card.css +445 -0
  46. package/dist/controls/card/card.d.ts +51 -0
  47. package/dist/controls/card/card.js +61 -0
  48. package/dist/controls/checkbox/checkbox.css +204 -0
  49. package/dist/controls/checkbox/checkbox.d.ts +114 -0
  50. package/dist/controls/checkbox/checkbox.js +130 -0
  51. package/dist/controls/code/code.css +72 -0
  52. package/dist/controls/code/code.d.ts +23 -0
  53. package/dist/controls/code/code.js +40 -0
  54. package/dist/controls/color-picker/color-picker.css +209 -0
  55. package/dist/controls/color-picker/color-picker.d.ts +84 -0
  56. package/dist/controls/color-picker/color-picker.js +545 -0
  57. package/dist/controls/color-picker/color.d.ts +40 -0
  58. package/dist/controls/color-picker/color.js +159 -0
  59. package/dist/controls/column/column.css +151 -0
  60. package/dist/controls/column/column.d.ts +107 -0
  61. package/dist/controls/column/column.js +57 -0
  62. package/dist/controls/combo-box/combo-box.css +397 -0
  63. package/dist/controls/combo-box/combo-box.d.ts +133 -0
  64. package/dist/controls/combo-box/combo-box.js +680 -0
  65. package/dist/controls/command-modal/command-modal.css +226 -0
  66. package/dist/controls/command-modal/command-modal.d.ts +42 -0
  67. package/dist/controls/command-modal/command-modal.js +315 -0
  68. package/dist/controls/disclosure/disclosure.css +162 -0
  69. package/dist/controls/disclosure/disclosure.d.ts +37 -0
  70. package/dist/controls/disclosure/disclosure.js +207 -0
  71. package/dist/controls/field/field.css +140 -0
  72. package/dist/controls/field/field.d.ts +27 -0
  73. package/dist/controls/field/field.js +228 -0
  74. package/dist/controls/form-provider/form-provider.css +40 -0
  75. package/dist/controls/form-provider/form-provider.d.ts +42 -0
  76. package/dist/controls/form-provider/form-provider.js +105 -0
  77. package/dist/controls/grid/grid.css +72 -0
  78. package/dist/controls/grid/grid.d.ts +60 -0
  79. package/dist/controls/grid/grid.js +56 -0
  80. package/dist/controls/icon/icon.css +37 -0
  81. package/dist/controls/icon/icon.d.ts +25 -0
  82. package/dist/controls/icon/icon.js +55 -0
  83. package/dist/controls/index.d.ts +57 -0
  84. package/dist/controls/index.js +136 -0
  85. package/dist/controls/ladder/ladder.css +89 -0
  86. package/dist/controls/ladder/ladder.d.ts +27 -0
  87. package/dist/controls/ladder/ladder.js +78 -0
  88. package/dist/controls/list/list.css +118 -0
  89. package/dist/controls/list/list.d.ts +84 -0
  90. package/dist/controls/list/list.js +48 -0
  91. package/dist/controls/menu/menu.css +195 -0
  92. package/dist/controls/menu/menu.d.ts +44 -0
  93. package/dist/controls/menu/menu.js +248 -0
  94. package/dist/controls/modal/modal.css +99 -0
  95. package/dist/controls/modal/modal.d.ts +61 -0
  96. package/dist/controls/modal/modal.js +155 -0
  97. package/dist/controls/popover/popover.css +83 -0
  98. package/dist/controls/popover/popover.d.ts +44 -0
  99. package/dist/controls/popover/popover.js +164 -0
  100. package/dist/controls/progress/progress.css +106 -0
  101. package/dist/controls/progress/progress.d.ts +28 -0
  102. package/dist/controls/progress/progress.js +80 -0
  103. package/dist/controls/radio/radio-group.css +84 -0
  104. package/dist/controls/radio/radio-group.d.ts +152 -0
  105. package/dist/controls/radio/radio-group.js +350 -0
  106. package/dist/controls/radio/radio.css +219 -0
  107. package/dist/controls/radio/radio.d.ts +19 -0
  108. package/dist/controls/radio/radio.js +75 -0
  109. package/dist/controls/ramp/ramp.css +75 -0
  110. package/dist/controls/ramp/ramp.d.ts +29 -0
  111. package/dist/controls/ramp/ramp.js +72 -0
  112. package/dist/controls/row/row.css +152 -0
  113. package/dist/controls/row/row.d.ts +95 -0
  114. package/dist/controls/row/row.js +45 -0
  115. package/dist/controls/segment/segment.css +66 -0
  116. package/dist/controls/segment/segment.d.ts +3 -0
  117. package/dist/controls/segment/segment.js +26 -0
  118. package/dist/controls/segmented-control/segmented-control.css +189 -0
  119. package/dist/controls/segmented-control/segmented-control.d.ts +20 -0
  120. package/dist/controls/segmented-control/segmented-control.js +64 -0
  121. package/dist/controls/select/select.css +467 -0
  122. package/dist/controls/select/select.d.ts +147 -0
  123. package/dist/controls/select/select.js +538 -0
  124. package/dist/controls/slider/slider.css +231 -0
  125. package/dist/controls/slider/slider.d.ts +111 -0
  126. package/dist/controls/slider/slider.js +60 -0
  127. package/dist/controls/slider-multi/slider-multi.css +197 -0
  128. package/dist/controls/slider-multi/slider-multi.d.ts +149 -0
  129. package/dist/controls/slider-multi/slider-multi.js +380 -0
  130. package/dist/controls/sparkline/sparkline-math.d.ts +23 -0
  131. package/dist/controls/sparkline/sparkline-math.js +79 -0
  132. package/dist/controls/sparkline/sparkline.css +61 -0
  133. package/dist/controls/sparkline/sparkline.d.ts +27 -0
  134. package/dist/controls/sparkline/sparkline.js +74 -0
  135. package/dist/controls/split/constrain.d.ts +44 -0
  136. package/dist/controls/split/constrain.js +126 -0
  137. package/dist/controls/split/split-pane.css +63 -0
  138. package/dist/controls/split/split-pane.d.ts +49 -0
  139. package/dist/controls/split/split-pane.js +42 -0
  140. package/dist/controls/split/split.css +127 -0
  141. package/dist/controls/split/split.d.ts +42 -0
  142. package/dist/controls/split/split.js +364 -0
  143. package/dist/controls/stat/stat-model.d.ts +22 -0
  144. package/dist/controls/stat/stat-model.js +69 -0
  145. package/dist/controls/stat/stat.css +101 -0
  146. package/dist/controls/stat/stat.d.ts +30 -0
  147. package/dist/controls/stat/stat.js +73 -0
  148. package/dist/controls/status-stream/status-stream.css +151 -0
  149. package/dist/controls/status-stream/status-stream.d.ts +108 -0
  150. package/dist/controls/status-stream/status-stream.js +417 -0
  151. package/dist/controls/swatch/swatch.css +66 -0
  152. package/dist/controls/swatch/swatch.d.ts +28 -0
  153. package/dist/controls/swatch/swatch.js +74 -0
  154. package/dist/controls/swiper/swiper-item.css +17 -0
  155. package/dist/controls/swiper/swiper-item.d.ts +31 -0
  156. package/dist/controls/swiper/swiper-item.js +47 -0
  157. package/dist/controls/swiper/swiper-label.css +10 -0
  158. package/dist/controls/swiper/swiper-label.d.ts +4 -0
  159. package/dist/controls/swiper/swiper-label.js +20 -0
  160. package/dist/controls/swiper/swiper-paddles.css +26 -0
  161. package/dist/controls/swiper/swiper-paddles.d.ts +10 -0
  162. package/dist/controls/swiper/swiper-paddles.js +62 -0
  163. package/dist/controls/swiper/swiper-pagination.css +60 -0
  164. package/dist/controls/swiper/swiper-pagination.d.ts +31 -0
  165. package/dist/controls/swiper/swiper-pagination.js +78 -0
  166. package/dist/controls/swiper/swiper.css +153 -0
  167. package/dist/controls/swiper/swiper.d.ts +158 -0
  168. package/dist/controls/swiper/swiper.js +659 -0
  169. package/dist/controls/switch/switch.css +194 -0
  170. package/dist/controls/switch/switch.d.ts +5 -0
  171. package/dist/controls/switch/switch.js +18 -0
  172. package/dist/controls/table/table-model.d.ts +43 -0
  173. package/dist/controls/table/table-model.js +124 -0
  174. package/dist/controls/table/table.css +90 -0
  175. package/dist/controls/table/table.d.ts +29 -0
  176. package/dist/controls/table/table.js +178 -0
  177. package/dist/controls/tabs/tab-panel.d.ts +10 -0
  178. package/dist/controls/tabs/tab-panel.js +89 -0
  179. package/dist/controls/tabs/tab.d.ts +26 -0
  180. package/dist/controls/tabs/tab.js +67 -0
  181. package/dist/controls/tabs/tabs.css +181 -0
  182. package/dist/controls/tabs/tabs.d.ts +61 -0
  183. package/dist/controls/tabs/tabs.js +176 -0
  184. package/dist/controls/text/href.d.ts +26 -0
  185. package/dist/controls/text/href.js +48 -0
  186. package/dist/controls/text/text.css +328 -0
  187. package/dist/controls/text/text.d.ts +85 -0
  188. package/dist/controls/text/text.js +273 -0
  189. package/dist/controls/text-field/text-field.css +498 -0
  190. package/dist/controls/text-field/text-field.d.ts +226 -0
  191. package/dist/controls/text-field/text-field.js +905 -0
  192. package/dist/controls/textarea/textarea.css +251 -0
  193. package/dist/controls/textarea/textarea.d.ts +136 -0
  194. package/dist/controls/textarea/textarea.js +312 -0
  195. package/dist/controls/theme-provider/theme-provider.css +48 -0
  196. package/dist/controls/theme-provider/theme-provider.d.ts +59 -0
  197. package/dist/controls/theme-provider/theme-provider.js +40 -0
  198. package/dist/controls/timeline/timeline.css +33 -0
  199. package/dist/controls/timeline/timeline.d.ts +39 -0
  200. package/dist/controls/timeline/timeline.js +52 -0
  201. package/dist/controls/timeline-item/timeline-item.css +495 -0
  202. package/dist/controls/timeline-item/timeline-item.d.ts +79 -0
  203. package/dist/controls/timeline-item/timeline-item.js +360 -0
  204. package/dist/controls/toast/toast-region.css +56 -0
  205. package/dist/controls/toast/toast-region.d.ts +20 -0
  206. package/dist/controls/toast/toast-region.js +113 -0
  207. package/dist/controls/toast/toast.css +96 -0
  208. package/dist/controls/toast/toast.d.ts +28 -0
  209. package/dist/controls/toast/toast.js +191 -0
  210. package/dist/controls/toolbar/toolbar.css +125 -0
  211. package/dist/controls/toolbar/toolbar.d.ts +108 -0
  212. package/dist/controls/toolbar/toolbar.js +68 -0
  213. package/dist/controls/tooltip/tooltip.css +80 -0
  214. package/dist/controls/tooltip/tooltip.d.ts +57 -0
  215. package/dist/controls/tooltip/tooltip.js +205 -0
  216. package/dist/descriptor/component-descriptor.d.ts +112 -0
  217. package/dist/descriptor/component-descriptor.js +492 -0
  218. package/dist/descriptor/index.d.ts +1 -0
  219. package/dist/descriptor/index.js +5 -0
  220. package/dist/dom/container.d.ts +45 -0
  221. package/dist/dom/container.js +60 -0
  222. package/dist/dom/element.d.ts +98 -0
  223. package/dist/dom/element.js +221 -0
  224. package/dist/dom/form.d.ts +240 -0
  225. package/dist/dom/form.js +399 -0
  226. package/dist/dom/index.d.ts +10 -0
  227. package/dist/dom/index.js +31 -0
  228. package/dist/dom/props.d.ts +56 -0
  229. package/dist/dom/props.js +231 -0
  230. package/dist/dom/repeat.d.ts +10 -0
  231. package/dist/dom/repeat.js +202 -0
  232. package/dist/dom/template.d.ts +161 -0
  233. package/dist/dom/template.js +634 -0
  234. package/dist/dom/watch.d.ts +11 -0
  235. package/dist/dom/watch.js +82 -0
  236. package/dist/foundation-styles.css +9 -0
  237. package/dist/index.d.ts +2 -0
  238. package/dist/index.js +3 -0
  239. package/dist/reactive/graph.d.ts +65 -0
  240. package/dist/reactive/graph.js +310 -0
  241. package/dist/reactive/index.d.ts +3 -0
  242. package/dist/reactive/index.js +4 -0
  243. package/dist/reactive/scheduler.d.ts +13 -0
  244. package/dist/reactive/scheduler.js +49 -0
  245. package/dist/traits/area-drag.d.ts +12 -0
  246. package/dist/traits/area-drag.js +70 -0
  247. package/dist/traits/form-registry.d.ts +36 -0
  248. package/dist/traits/form-registry.js +108 -0
  249. package/dist/traits/index.d.ts +24 -0
  250. package/dist/traits/index.js +19 -0
  251. package/dist/traits/overlay.d.ts +50 -0
  252. package/dist/traits/overlay.js +277 -0
  253. package/dist/traits/pane-resize.d.ts +26 -0
  254. package/dist/traits/pane-resize.js +103 -0
  255. package/dist/traits/press-activation.d.ts +11 -0
  256. package/dist/traits/press-activation.js +55 -0
  257. package/dist/traits/roving-focus.d.ts +52 -0
  258. package/dist/traits/roving-focus.js +214 -0
  259. package/dist/traits/scroll-fade.d.ts +28 -0
  260. package/dist/traits/scroll-fade.js +120 -0
  261. package/dist/traits/selection-commit.d.ts +30 -0
  262. package/dist/traits/selection-commit.js +146 -0
  263. package/dist/traits/tabbable.d.ts +12 -0
  264. package/dist/traits/tabbable.js +59 -0
  265. package/dist/traits/track-user-invalid.d.ts +36 -0
  266. package/dist/traits/track-user-invalid.js +52 -0
  267. package/dist/traits/value-codec.d.ts +120 -0
  268. package/dist/traits/value-codec.js +497 -0
  269. package/dist/traits/value-drag.d.ts +18 -0
  270. package/dist/traits/value-drag.js +84 -0
  271. package/package.json +263 -0
@@ -0,0 +1,75 @@
1
+ // radio.ts — UIRadioElement, the radio-button leaf of the Indicator family (Wave 1, S3).
2
+ //
3
+ // Extends UIIndicatorElement for the boolean form value, the checked-state machine + ARIA (ariaChecked,
4
+ // :state(checked)), the toggle via pressActivation + click, and the widget-box geometry seam (LLD-C1..C4).
5
+ // The leaf provides: `static role = 'radio'`; the dot glyph in a circular --md-sys-compact box (radio.css);
6
+ // an optional label slot (the default slot); and group coordination via the `grouped()` hook (LLD-C5).
7
+ //
8
+ // When inside a `ui-radio-group`, the radio:
9
+ // · Prevents clicking an already-checked radio from unchecking it (radio-button semantics: only selecting
10
+ // another radio deselects the current one; the group manages exclusivity).
11
+ // · Emits `change` from the base toggle (unchecked → checked) so the group's delegated listener commits
12
+ // the selection, clears siblings, and updates the group's form value.
13
+ // Without a group parent, the radio behaves like a standalone boolean indicator (useful for single-item
14
+ // "accept" patterns, though lacking a group value owner; the decomp rules the group as canonical).
15
+ //
16
+ // Layer: controls/ — imports controls/_base + dom + traits (inward-only ✓).
17
+ import { UIIndicatorElement } from "../_base/indicator-element.js";
18
+ export class UIRadioElement extends UIIndicatorElement {
19
+ /**
20
+ * LLD-C2: ARIA role for this leaf — `internals.role` is set by the UIIndicatorElement base
21
+ * from this declaration in `connected()`.
22
+ */
23
+ static role = 'radio';
24
+ /**
25
+ * LLD-C5: group wiring hook — called from `connected()` after all base wiring is in place.
26
+ * When this radio is nested inside a `ui-radio-group`, registers a capture-phase click guard:
27
+ * if the radio is already checked, the click is stopped (via stopImmediatePropagation) before it
28
+ * reaches the base's bubble-phase toggle handler. This enforces the radio-button invariant that a
29
+ * selected radio cannot be deselected by clicking — only by selecting another radio in the group.
30
+ *
31
+ * Without a group parent, this is a no-op and the base toggle runs normally (full checkbox semantics:
32
+ * click toggles unchecked ↔ checked).
33
+ */
34
+ grouped() {
35
+ // Detect any UIRadioGroupElement (or subclass) in the ancestor chain via the well-known
36
+ // data attribute marker that UIRadioGroupElement.connected() sets on itself. Using a CSS
37
+ // attribute selector avoids a circular import (radio-group.ts ← radio.ts circular dep) and
38
+ // works with any subclass of UIRadioGroupElement, including probe subclasses in tests.
39
+ const group = this.closest('[data-radio-group]');
40
+ if (!group)
41
+ return;
42
+ // Capture-phase listener: runs BEFORE the base's bubble-phase click → toggle. If the radio is
43
+ // already checked, stopImmediatePropagation cancels all subsequent listeners for this click event
44
+ // (base toggle, group delegation) — the radio stays checked. For unchecked radios, this is a no-op:
45
+ // the base toggle runs normally (sets checked=true, emits input + change), then the change event
46
+ // bubbles to the group for exclusivity + form-value commit.
47
+ this.listen(this, 'click', (event) => {
48
+ if (this.effectiveDisabled())
49
+ return;
50
+ if (this.checked)
51
+ event.stopImmediatePropagation();
52
+ }, { capture: true });
53
+ // Tabindex correction — load-bearing for the LATE-APPENDED radio (TKT-0068 item 3, measured).
54
+ // For a same-subtree connect the ADR-0121 `data-roving` deferral already covers this: rovingFocus's
55
+ // applyTabindexes stamps every item it manages, and tabbable defers its tabIndex=0 write on stamped
56
+ // hosts. But a radio appended to an ALREADY-connected group was never stamped (applyTabindexes ran
57
+ // before it existed), so tabbable sets 0 and the group would grow a second tab stop. We correct
58
+ // non-roving radios back to -1 here, AFTER tabbable has run (grouped() is called at the END of
59
+ // connected()). Pinned by radio-group.test.ts's `group-tabindex-late-append` (mutation-verified).
60
+ //
61
+ // Which radio deserves tabIndex=0? The checked one (matches rovingFocus initialIndex), or the
62
+ // first sibling when nothing is checked. `this === firstOrChecked` → leave at 0; else → -1.
63
+ const siblings = [...group.children].filter(
64
+ // Use the concrete class reference (UIRadioElement is in scope; this is a method body, not a
65
+ // class decorator — no circular-reference issue at call time).
66
+ (el) => el instanceof UIRadioElement);
67
+ const checkedSibling = siblings.find((r) => r.checked);
68
+ const roving = checkedSibling ?? siblings[0];
69
+ if (roving && this !== roving) {
70
+ this.tabIndex = -1;
71
+ }
72
+ }
73
+ }
74
+ if (!customElements.get('ui-radio'))
75
+ customElements.define('ui-radio', UIRadioElement);
@@ -0,0 +1,75 @@
1
+ /* ramp.css — ui-ramp, the Display-class ordered-color-series leaf (LLD-C5, token-surfaces.lld.md §3.2;
2
+ * SPEC-R6/R13/R14/R15/R16). Two clearly-SECTIONED blocks:
3
+ *
4
+ * [1] TOKEN BLOCK — `:where(ui-ramp)` (specificity 0,0,0) declares the `--ui-ramp-*` chain: the
5
+ * whole-shape floor (SPEC-R13 AC1 — an `8em` `min-inline-size` so a bare strip never collapses), the
6
+ * per-cell box size (the swatch-cell precedent), the hairline border, and the density-RESPONSIVE gap.
7
+ * [2] STYLES BLOCK — `@scope (ui-ramp)` consumes ONLY `--ui-ramp-*` (∪ the shared `--md-sys-color-*`/
8
+ * `--md-sys-typescale-*` namespaces). Display-class (SPEC-R16): no `[size]`/`[scale]` row.
9
+ *
10
+ * The strip WRAPS rather than overflowing its container (SPEC-R6 AC2, `flex-wrap: wrap`).
11
+ *
12
+ * RTL (SPEC-R15): the strip keeps PHYSICAL `flex-direction: row` — a tonal ramp reads light→dark
13
+ * left-to-right regardless of locale (the `ui-sparkline` series-direction precedent); labels/values inside
14
+ * each cell stay logical text (documented in ramp.md).
15
+ *
16
+ * WHCM (SPEC-R14): same border-degradation honesty as swatch — a color box cannot paint under
17
+ * `forced-colors: active`; it degrades to a `CanvasText` hairline, never a fake system color.
18
+ *
19
+ * Standalone stylesheet — linked via the component-styles barrel; NEVER injected from ramp.ts
20
+ * (behaviour-only `.ts`, no runtime style injection — plan §2).
21
+ */
22
+
23
+ /* ════════════════ [1] TOKEN BLOCK — :where(ui-ramp) declares --ui-ramp-* ════════════════ */
24
+
25
+ :where(ui-ramp) {
26
+ --ui-ramp-cell-size: 2rem; /* the swatch-cell box (whole-shape) */
27
+ --ui-ramp-border: 1px solid var(--md-sys-color-neutral-outline-variant);
28
+ --ui-ramp-radius: var(--md-sys-shape-corner-base);
29
+ --ui-ramp-gap: var(--md-sys-space-xs); /* rides [density] for free (ADR-0103) */
30
+ --ui-ramp-min-inline-size: 8em; /* the whole-shape floor (SPEC-R13 AC1) — a strip never collapses */
31
+ }
32
+
33
+ /* ════════════════ [2] STYLES BLOCK — @scope (ui-ramp) consumes ONLY --ui-ramp-* ════════════════ */
34
+
35
+ @scope (ui-ramp) {
36
+ :scope {
37
+ display: flex;
38
+ /* `flex-direction: row` alone is NOT physical — CSS Flexbox's "row" is the INLINE axis, which itself
39
+ * mirrors under `dir="rtl"`. `direction: ltr` is what actually pins the main axis to the physical
40
+ * left-to-right series order regardless of locale (SPEC-R15 AC1) — a tonal ramp reads light→dark
41
+ * left-to-right in both locales, the `ui-sparkline` SVG-coordinate precedent, generalized to flex. */
42
+ direction: ltr;
43
+ flex-wrap: wrap; /* a strip WRAPS, never overflows (SPEC-R6 AC2) */
44
+ gap: var(--ui-ramp-gap);
45
+ min-inline-size: var(--ui-ramp-min-inline-size);
46
+ }
47
+
48
+ :scope [data-part='cell'] {
49
+ display: grid;
50
+ justify-items: center;
51
+ gap: 2px;
52
+ }
53
+
54
+ :scope [data-part='box'] {
55
+ inline-size: var(--ui-ramp-cell-size);
56
+ block-size: var(--ui-ramp-cell-size);
57
+ border: var(--ui-ramp-border);
58
+ border-radius: var(--ui-ramp-radius);
59
+ box-sizing: border-box;
60
+ }
61
+
62
+ :scope [data-part='step-label'],
63
+ :scope [data-part='value'] {
64
+ font-size: var(--md-sys-typescale-body-small-size);
65
+ line-height: var(--md-sys-typescale-body-small-line-height);
66
+ text-align: center;
67
+ }
68
+
69
+ @media (forced-colors: active) {
70
+ :scope [data-part='box'] {
71
+ background: transparent !important;
72
+ border-color: CanvasText;
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,29 @@
1
+ import { UIElement, type ReactiveProps } from '../../dom/index.js';
2
+ import { type TokenEntry } from '../_token-surface/token-surface.js';
3
+ declare const props: {
4
+ steps: import("../../index.js").PropConfig<TokenEntry[]>;
5
+ label: {
6
+ reflect: true;
7
+ type: import("../../index.js").PropType<string>;
8
+ default: string;
9
+ attribute?: string | false;
10
+ };
11
+ scheme: import("../../index.js").PropConfig<"auto" | "light" | "dark">;
12
+ };
13
+ export interface UIRampElement extends ReactiveProps<typeof props> {
14
+ }
15
+ export declare class UIRampElement extends UIElement {
16
+ #private;
17
+ static props: {
18
+ steps: import("../../index.js").PropConfig<TokenEntry[]>;
19
+ label: {
20
+ reflect: true;
21
+ type: import("../../index.js").PropType<string>;
22
+ default: string;
23
+ attribute?: string | false;
24
+ };
25
+ scheme: import("../../index.js").PropConfig<"auto" | "light" | "dark">;
26
+ };
27
+ protected connected(): void;
28
+ }
29
+ export {};
@@ -0,0 +1,72 @@
1
+ // ramp.ts — UIRampElement, the Display-class ordered-color-series leaf (LLD-C4, token-surfaces.lld.md §3.2;
2
+ // SPEC-R5…R8; ADR-0118 cl.1/2/4). BEHAVIOUR + props + list-semantics ARIA + the component-built cell strip +
3
+ // self-define ONLY — the value-lane/hardening logic lives in the shared `_token-surface/token-surface.ts`
4
+ // (LLD-C1), the strip/cell geometry in ramp.css.
5
+ //
6
+ // A ramp is data, not decoration (ADR-0118 cl.4): the host carries `role=list` via `ElementInternals` (the
7
+ // `ui-bar-chart`/`ui-list` precedent) — never a host attribute — named by `label` when non-empty (SPEC-R8). Each
8
+ // rendered cell is a real `role=listitem` element whose text content is its label plus the printed value; the
9
+ // color box (`[data-part='box']`) is `aria-hidden` — color cannot be announced, the printed label/value IS the
10
+ // accessible datum (CVD-safe by construction — no step is color-encoded only).
11
+ //
12
+ // Content model — component-built cells, NOT host-as-grid (the chart family's whole-array precedent): `steps` is
13
+ // display-only derived state (no positional reconcile — inert cells with no focus/selection worth reconciling),
14
+ // so `render()` stays the inherited no-op and the steps effect rebuilds the full light-DOM cell list on every
15
+ // `steps`/`scheme` change via `replaceChildren` (a whole-array swap, no incremental API — SPEC-R6).
16
+ //
17
+ // Imports inward only (controls → dom + the sibling shared helper).
18
+ import { UIElement, prop } from "../../dom/index.js";
19
+ import { cleanEntries, cssValue, tokenEntriesProp } from "../_token-surface/token-surface.js";
20
+ const props = {
21
+ steps: tokenEntriesProp(), // { label: string; value: string }[] · safe JSON codec (LLD-C1) · default []
22
+ label: { ...prop.string(''), reflect: true }, // the strip's accessible name — SPEC-R8: unlabeled is legal, never a silent state
23
+ scheme: prop.enum(['auto', 'light', 'dark'], 'auto'), // pins the WHOLE strip's color-scheme resolution
24
+ };
25
+ export class UIRampElement extends UIElement {
26
+ static props = props;
27
+ connected() {
28
+ // List semantics (SPEC-R8, the `ui-bar-chart` precedent) — a constant semantic role, set directly (not
29
+ // inside an effect); re-set on each connect (idempotent).
30
+ this.internals.role = 'list';
31
+ // The label effect — `label` names the list when non-empty; an unlabeled list is legal (SPEC-R8 AC2 —
32
+ // the host stays role=list regardless, never aria-hidden, never a silent state).
33
+ this.effect(() => {
34
+ this.internals.ariaLabel = this.label || null;
35
+ });
36
+ // The steps effect (SPEC-R6/R7): whole-array derived state — every `steps`/`scheme` change rebuilds the
37
+ // full light-DOM cell list via one `replaceChildren`. `cleanEntries` runs again here (not just inside the
38
+ // codec), so a PROPERTY write of garbage never reaches the render path either (the SPEC-R7 property-write
39
+ // guard the codec alone cannot cover — the codec only guards the ATTRIBUTE path).
40
+ this.effect(() => {
41
+ const scheme = this.scheme;
42
+ const entries = cleanEntries(this.steps);
43
+ this.replaceChildren(...entries.map((step) => this.#cellNode(step, scheme)));
44
+ });
45
+ }
46
+ /**
47
+ * Build one component-owned strip cell: `box(aria-hidden) · step-label · value` (LLD-C4's markup). The
48
+ * listitem's combined text content is `{label}{value}` (the two real text spans — the accessible datum,
49
+ * SPEC-R8 AC1, the `ui-bar-chart` listitem-text precedent); the box is `aria-hidden` and carries the
50
+ * strip-shared `scheme` pin (colorScheme) exactly like swatch's own box.
51
+ */
52
+ #cellNode(step, scheme) {
53
+ const item = document.createElement('div');
54
+ item.setAttribute('role', 'listitem');
55
+ item.setAttribute('data-part', 'cell');
56
+ const box = document.createElement('span');
57
+ box.setAttribute('data-part', 'box');
58
+ box.setAttribute('aria-hidden', 'true');
59
+ box.style.background = cssValue(step.value);
60
+ box.style.colorScheme = scheme === 'auto' ? '' : scheme;
61
+ const stepLabel = document.createElement('span');
62
+ stepLabel.setAttribute('data-part', 'step-label');
63
+ stepLabel.textContent = step.label;
64
+ const value = document.createElement('span');
65
+ value.setAttribute('data-part', 'value');
66
+ value.textContent = step.value;
67
+ item.append(box, stepLabel, value);
68
+ return item;
69
+ }
70
+ }
71
+ if (!customElements.get('ui-ramp'))
72
+ customElements.define('ui-ramp', UIRampElement); // idempotent self-define
@@ -0,0 +1,152 @@
1
+ /* row.css — ui-row, the single-file component stylesheet (ADR-0003) and the CANONICAL layout-primitive
2
+ * template (ui-column copies it with the axis flipped; ui-list/ui-grid follow the same shape). Two clearly-
3
+ * SECTIONED blocks, the button.css pattern applied to a layout container:
4
+ *
5
+ * [1] TOKEN BLOCK — `:where(ui-row)` (specificity 0,0,0) DECLARES the `--ui-row-*` chain: the flex grammar
6
+ * (`align`/`justify`/`gap`/`wrap`) mapped to CSS keywords, plus the shared `--md-sys-shape-corner-base`. The
7
+ * `[align]`/`[justify]`/`[gap]`/`[wrap]` attribute selectors (which reflect from the flexProps, s2)
8
+ * REPOINT those tokens — `between` → `space-between`, `gap='md'` → `var(--md-sys-space-md)`, etc. This is
9
+ * the ONE place a literal-union keyword crosses to its CSS value; it is a role-pure repoint, never an
10
+ * inline style.
11
+ * [2] STYLES BLOCK — `@scope (ui-row)` CONSUMES only the `--ui-row-*` chain (so the cross-family surface in
12
+ * controls/_surface/container.css cleanly overrides nothing here, and a planted raw `--md-sys-color-*` would fail
13
+ * css-hygiene). `display: flex; flex-direction: row` is the row IDENTITY (ADR-0016 cl.2 — direction is
14
+ * the tag, not a prop). A `@container (inline-size …)` rule, GATED on `:scope:not([reflow='locked'])`
15
+ * (ADR-0096), reflows the row to a COLUMN under a narrow ANCESTOR container width (ADR-0016 cl.4 —
16
+ * intrinsic responsiveness, no breakpoint props; `reflow` defaults to `auto` so this stays the unchanged
17
+ * default behavior — `reflow="locked"` pins the row horizontal), and a forced-colors block keeps the
18
+ * layout neutral. The row does NOT establish that container itself (ADR-0100 — a row is intrinsically
19
+ * sized, so it can never safely be a query container; see the shared container.css banner) — the
20
+ * `@container` rule resolves against the nearest EXTERNALLY-SIZED ancestor boundary instead.
21
+ *
22
+ * The SURFACE (elevation/brightness plane) is NOT here — it lives once in the shared
23
+ * controls/_surface/container.css (the UIContainerElement base sheet, `@import`-ed FIRST by
24
+ * component-styles.css at s12), which also does NOT establish `container-type` on this element (ADR-0100)
25
+ * — that responsibility belongs to an externally-sized ancestor boundary, never the row itself. row.css adds
26
+ * ONLY the flex layout + the corner radius; it reads no `--md-sys-color-*` colour role at all (colour is
27
+ * delegated entirely to the shared surface seam).
28
+ *
29
+ * NO control height (geometry.md's `Container/layout` size-class): a layout primitive has no frame — spacing
30
+ * is the `--md-sys-space` ladder × `[density]` (the gap), never `--md-sys-height-*`. Standalone sheet — linked via
31
+ * the component-styles barrel (s12); NEVER injected from row.ts (behaviour-only `.ts`, plan §2).
32
+ */
33
+
34
+ /* ════════════════ [1] TOKEN BLOCK — :where(ui-row) declares --ui-row-* ════════════════ */
35
+
36
+ :where(ui-row) {
37
+ /* The flex grammar → CSS values; each default is the flexProps `values[0]` default (s2). The
38
+ literal-union → CSS-keyword crossing happens via the attribute repoints below. */
39
+ --ui-row-align: start; /* align (cross-axis) → align-items */
40
+ --ui-row-justify: start; /* justify (main-axis distribution) → justify-content */
41
+ --ui-row-gap: var(--md-sys-space-none); /* gap → the --md-sys-space density-responsive ladder (ADR-0015 cl.4) */
42
+ --ui-row-wrap: nowrap; /* wrap → flex-wrap (boolean presence) */
43
+
44
+ /* The corner radius — the shared fleet constant (ADR-0015 cl.5). Rounds the surface when a row sits on an
45
+ elevation/brightness plane; a transparent (unset-surface) row shows nothing, so it is harmless there. */
46
+ --ui-row-radius: var(--md-sys-shape-corner-base);
47
+ }
48
+
49
+ /* align → align-items. start/center/end/stretch/baseline are valid align-items keywords (the box-alignment
50
+ logical values), so the mapping is 1:1 — only the non-default members need a repoint. */
51
+ :where(ui-row[align='center']) {
52
+ --ui-row-align: center;
53
+ }
54
+ :where(ui-row[align='end']) {
55
+ --ui-row-align: end;
56
+ }
57
+ :where(ui-row[align='stretch']) {
58
+ --ui-row-align: stretch;
59
+ }
60
+ :where(ui-row[align='baseline']) {
61
+ --ui-row-align: baseline;
62
+ }
63
+
64
+ /* justify → justify-content. start/center/end map 1:1; between/around/evenly cross to the `space-*` forms
65
+ (the one keyword translation in the grammar). */
66
+ :where(ui-row[justify='center']) {
67
+ --ui-row-justify: center;
68
+ }
69
+ :where(ui-row[justify='end']) {
70
+ --ui-row-justify: end;
71
+ }
72
+ :where(ui-row[justify='between']) {
73
+ --ui-row-justify: space-between;
74
+ }
75
+ :where(ui-row[justify='around']) {
76
+ --ui-row-justify: space-around;
77
+ }
78
+ :where(ui-row[justify='evenly']) {
79
+ --ui-row-justify: space-evenly;
80
+ }
81
+
82
+ /* gap → a step on the --md-sys-space ladder (calc(<base px> * var(--md-sys-density)) — so a subtree [density]
83
+ re-multiplies the gutter; [scale] does NOT touch it, ADR-0015 cl.4). `none` is the default 0. */
84
+ :where(ui-row[gap='xs']) {
85
+ --ui-row-gap: var(--md-sys-space-xs);
86
+ }
87
+ :where(ui-row[gap='sm']) {
88
+ --ui-row-gap: var(--md-sys-space-sm);
89
+ }
90
+ :where(ui-row[gap='md']) {
91
+ --ui-row-gap: var(--md-sys-space-md);
92
+ }
93
+ :where(ui-row[gap='lg']) {
94
+ --ui-row-gap: var(--md-sys-space-lg);
95
+ }
96
+ :where(ui-row[gap='xl']) {
97
+ --ui-row-gap: var(--md-sys-space-xl);
98
+ }
99
+ :where(ui-row[gap='2xl']) {
100
+ --ui-row-gap: var(--md-sys-space-2xl);
101
+ }
102
+
103
+ /* wrap → flex-wrap (boolean presence: the `wrap` attribute present ⇒ multi-line). */
104
+ :where(ui-row[wrap]) {
105
+ --ui-row-wrap: wrap;
106
+ }
107
+
108
+ /* ════════════════ [2] STYLES BLOCK — @scope (ui-row) consumes ONLY --ui-row-* ════════════════ */
109
+
110
+ @scope (ui-row) {
111
+ :scope {
112
+ display: flex;
113
+ flex-direction: row; /* the ROW identity — direction is the tag, not a prop (ADR-0016 cl.2) */
114
+ align-items: var(--ui-row-align);
115
+ justify-content: var(--ui-row-justify);
116
+ gap: var(--ui-row-gap);
117
+ flex-wrap: var(--ui-row-wrap);
118
+ box-sizing: border-box;
119
+ border-radius: var(--ui-row-radius);
120
+ /* NO block-size / padding-block — a layout primitive has no control frame (geometry.md Container/layout).
121
+ Height is content-driven; the gap is the only spacing lever. */
122
+ }
123
+
124
+ /* Container-query reflow (ADR-0016 cl.4) — intrinsic responsiveness with NO breakpoint props, PROP-GATED by
125
+ `reflow` (ADR-0096). The row establishes NO query container of its own (ADR-0100 — an intrinsically-sized
126
+ primitive can never safely be one; see container.css's banner), so this `@container` rule resolves against
127
+ the row's nearest EXTERNALLY-SIZED ancestor boundary (the composition it is dropped into — a mount
128
+ surface, app-shell, or any author frame that establishes `container-type: inline-size`): under a narrow
129
+ boundary the row WRAPS to a column, the ADR's named example.
130
+ The selector is `:scope:not([reflow='locked'])`, NOT bare `:scope`: absent/default `reflow` (ui-row
131
+ defaults `auto`, row.ts) KEEPS the existing narrow→stack behavior unchanged, while an explicit
132
+ `reflow="locked"` attribute PINS `flex-direction: row` even in a cramped container (ADR-0096 cl.2/3 — the
133
+ row leg is protective and stays default-on, unlike ui-column's default-off flip). The cross-engine smoke
134
+ proves this changes the computed flex-direction + the child layout px by resizing the WRAPPER (not the
135
+ viewport) — the context-free responsiveness proof. */
136
+ @container (inline-size < 24rem) {
137
+ :scope:not([reflow='locked']) {
138
+ flex-direction: column;
139
+ }
140
+ }
141
+
142
+ /* forced-colors (WHCM) — belt-and-braces with the shared container.css surface block: if the row carries
143
+ an elevation/brightness plane, keep it a system colour (`Canvas`) and drop the tonal wash, so a surfaced
144
+ row survives high-contrast mode and the wash never paints over system text. (The colour roles already
145
+ carry the WHCM mapping in the token layer, so this mostly guarantees the family's uniform discipline.) */
146
+ @media (forced-colors: active) {
147
+ :scope {
148
+ background-color: Canvas;
149
+ background-image: none;
150
+ }
151
+ }
152
+ }
@@ -0,0 +1,95 @@
1
+ import { UIContainerElement } from '../../dom/container.js';
2
+ import type { ReactiveProps } from '../../dom/index.js';
3
+ declare const props: {
4
+ reflow: {
5
+ reflect: true;
6
+ type: import("../../index.js").PropType<"auto" | "locked">;
7
+ default: "auto" | "locked";
8
+ attribute?: string | false;
9
+ };
10
+ align: {
11
+ reflect: true;
12
+ type: import("../../index.js").PropType<"end" | "start" | "center" | "stretch" | "baseline">;
13
+ default: "end" | "start" | "center" | "stretch" | "baseline";
14
+ attribute?: string | false;
15
+ };
16
+ justify: {
17
+ reflect: true;
18
+ type: import("../../index.js").PropType<"end" | "start" | "center" | "between" | "around" | "evenly">;
19
+ default: "end" | "start" | "center" | "between" | "around" | "evenly";
20
+ attribute?: string | false;
21
+ };
22
+ gap: {
23
+ reflect: true;
24
+ type: import("../../index.js").PropType<"none" | "sm" | "md" | "lg" | "xs" | "xl" | "2xl">;
25
+ default: "none" | "sm" | "md" | "lg" | "xs" | "xl" | "2xl";
26
+ attribute?: string | false;
27
+ };
28
+ wrap: {
29
+ reflect: true;
30
+ type: import("../../index.js").PropType<boolean>;
31
+ default: boolean;
32
+ attribute?: string | false;
33
+ };
34
+ elevation: {
35
+ reflect: true;
36
+ type: import("../../index.js").PropType<"0" | "1" | "2" | "3" | "-3" | "-2" | "-1">;
37
+ default: "0" | "1" | "2" | "3" | "-3" | "-2" | "-1";
38
+ attribute?: string | false;
39
+ };
40
+ brightness: {
41
+ reflect: true;
42
+ type: import("../../index.js").PropType<"0" | "1" | "2" | "3" | "-3" | "-2" | "-1">;
43
+ default: "0" | "1" | "2" | "3" | "-3" | "-2" | "-1";
44
+ attribute?: string | false;
45
+ };
46
+ };
47
+ export interface UIRowElement extends ReactiveProps<typeof props> {
48
+ }
49
+ export declare class UIRowElement extends UIContainerElement {
50
+ static props: {
51
+ reflow: {
52
+ reflect: true;
53
+ type: import("../../index.js").PropType<"auto" | "locked">;
54
+ default: "auto" | "locked";
55
+ attribute?: string | false;
56
+ };
57
+ align: {
58
+ reflect: true;
59
+ type: import("../../index.js").PropType<"end" | "start" | "center" | "stretch" | "baseline">;
60
+ default: "end" | "start" | "center" | "stretch" | "baseline";
61
+ attribute?: string | false;
62
+ };
63
+ justify: {
64
+ reflect: true;
65
+ type: import("../../index.js").PropType<"end" | "start" | "center" | "between" | "around" | "evenly">;
66
+ default: "end" | "start" | "center" | "between" | "around" | "evenly";
67
+ attribute?: string | false;
68
+ };
69
+ gap: {
70
+ reflect: true;
71
+ type: import("../../index.js").PropType<"none" | "sm" | "md" | "lg" | "xs" | "xl" | "2xl">;
72
+ default: "none" | "sm" | "md" | "lg" | "xs" | "xl" | "2xl";
73
+ attribute?: string | false;
74
+ };
75
+ wrap: {
76
+ reflect: true;
77
+ type: import("../../index.js").PropType<boolean>;
78
+ default: boolean;
79
+ attribute?: string | false;
80
+ };
81
+ elevation: {
82
+ reflect: true;
83
+ type: import("../../index.js").PropType<"0" | "1" | "2" | "3" | "-3" | "-2" | "-1">;
84
+ default: "0" | "1" | "2" | "3" | "-3" | "-2" | "-1";
85
+ attribute?: string | false;
86
+ };
87
+ brightness: {
88
+ reflect: true;
89
+ type: import("../../index.js").PropType<"0" | "1" | "2" | "3" | "-3" | "-2" | "-1">;
90
+ default: "0" | "1" | "2" | "3" | "-3" | "-2" | "-1";
91
+ attribute?: string | false;
92
+ };
93
+ };
94
+ }
95
+ export {};
@@ -0,0 +1,45 @@
1
+ // row.ts — UIRowElement, the CANONICAL layout primitive (ADR-0016; decomp g9-containers slice s3). The
2
+ // reference template the other flex primitives copy (ui-column flips the axis; ui-list adds list semantics).
3
+ // BEHAVIOUR is deliberately MINIMAL — the whole component is `static props` + a self-define. Every visual
4
+ // decision lives in row.css (the flex mapping) and the shared controls/_surface/container.css (the surface
5
+ // plane + the container-query seam); the `.ts` holds zero layout/colour/runtime-style opinion (plan §2).
6
+ //
7
+ // Content model — host-as-flex (the ADR-0006 host-as-grid pattern, flex flavour): the user's light-DOM
8
+ // children ARE the flex items, laid out directly by row.css's `@scope` block. ui-row does NOT `render()` a
9
+ // wrapper over them, so `render()` stays the inherited no-op (returns nothing → the host render effect
10
+ // commits nothing → the children are never clobbered).
11
+ //
12
+ // Props — the two SPREADABLE prop sets the container family shares (ADR-0013 no-prototype-merge precedent):
13
+ // `UIContainerElement.surfaceProps` (elevation/brightness, ADR-0015) + `UIContainerElement.flexProps`
14
+ // (align/justify/gap/wrap, ADR-0016). All six reflect, so the `[elevation]`/`[align]`/… attribute selectors
15
+ // in the CSS apply to JS-set values too. Plus one element-local prop, `reflow` (ADR-0096, the ADR-0075
16
+ // `stretch` precedent) — gates whether the ADR-0016 cl.4 container-query direction switch may fire; ui-row
17
+ // defaults `auto` (unchanged behavior — its narrow→stack leg is protective).
18
+ //
19
+ // ARIA — NONE. ui-row is a pure layout container (a generic wrapper, like a `<div>`): it carries NO role
20
+ // and no host aria-* attribute. Semantics ride the children; the row contributes none. (Contrast ui-list,
21
+ // which sets `internals.role='list'`; row/column are non-semantic — direction is layout, not meaning.)
22
+ //
23
+ // Imports — `UIContainerElement` from the same dom layer (`controls → dom`, the allowed inward direction).
24
+ // It is imported from `../../dom/container.ts` directly: the `dom/index.ts` barrel re-export of the
25
+ // container base is the integration slice's job (decomp s12), so this slice does not depend on it.
26
+ import { UIContainerElement } from "../../dom/container.js";
27
+ import { prop } from "../../dom/index.js";
28
+ // The layout primitive IS the shared grammar — surface axes + flex grammar, nothing more. Spread (not
29
+ // inherited): props.ts has no static-props prototype merge, so the base hands these down as spreadable
30
+ // schemas the subclass folds into its OWN `static props` (the order is surfaceProps then flexProps).
31
+ const props = {
32
+ ...UIContainerElement.surfaceProps,
33
+ ...UIContainerElement.flexProps,
34
+ // reflow → gates the ADR-0016 cl.4 container-query direction switch (ADR-0096). Element-local, deliberately
35
+ // NOT folded into the shared `flexProps` (the ADR-0075 `stretch` precedent — `ui-list` has no `@container`
36
+ // rule and `ui-grid`'s auto-fit IS its own responsiveness, so both stay untouched). `auto` LEADS the array so
37
+ // it is both ui-row's default AND the invalid-value snap target — UNCHANGED behavior (the narrow→stack leg
38
+ // is protective, ADR-0096 cl.2). `locked` pins `flex-direction: row` regardless of a narrow container.
39
+ reflow: { ...prop.enum(['auto', 'locked'], 'auto'), reflect: true },
40
+ };
41
+ export class UIRowElement extends UIContainerElement {
42
+ static props = props;
43
+ }
44
+ if (!customElements.get('ui-row'))
45
+ customElements.define('ui-row', UIRowElement);
@@ -0,0 +1,66 @@
1
+ /* segment.css — ui-segment, the child leaf of ui-segmented-control (ADR-0095 clause 3).
2
+ *
3
+ * `ui-segment` extends `UIRadioElement`, but `radio.css`'s `@scope (ui-radio)` rules do NOT match this new
4
+ * tag — a feature, not a gap: a segment shows no dot glyph to suppress at all. This stylesheet is
5
+ * deliberately SMALL: only the layout primitives + interaction affordance a segment owns REGARDLESS of
6
+ * which control hosts it (a centered full-cell flex box, cursor/selection hygiene, its own ADR-0009
7
+ * `:focus-visible` fleet ring). The Control-height SIZING (block-size/padding/font/line-height), the ink
8
+ * colours, the dividers, the hover/active washes, the moving selected-fill indicator, and the forced-colors
9
+ * inversion are all GROUP-owned concerns — they live in `segmented-control.css`'s compound selectors
10
+ * (`ui-segmented-control ui-segment`), the same "group restyles its children via a descendant compound
11
+ * selector, the child stays ignorant" split ADR-0086 established for the (now-retired) radio-group variant.
12
+ *
13
+ * Two sectioned blocks (ADR-0003):
14
+ * [1] TOKEN BLOCK — `:where(ui-segment)` — a genuine RESERVE (no design tokens needed yet: the segment's
15
+ * own layout primitives below are geometry-agnostic; every SIZED value is the group's token chain).
16
+ * [2] STYLES BLOCK — `@scope (ui-segment)` consumes only `--ui-segment-*` (empty today) ∪ the shared
17
+ * focus-ring allowlist (`--md-sys-state-focus-ring-*`, `--md-sys-color-focus-ring` — ADR-0009).
18
+ */
19
+
20
+ /* ════════════════ [1] TOKEN BLOCK — :where(ui-segment) ════════════════ */
21
+
22
+ :where(ui-segment) {
23
+ /* No design tokens: every sized value a segment renders (height/padding/font/ink/dividers/fill) is the
24
+ HOST segmented-control's own token chain, consumed via a descendant compound selector
25
+ (segmented-control.css). Reserved here for fleet discipline (the ADR-0003 two-block convention) and any
26
+ future segment-owned-only token. */
27
+ }
28
+
29
+ /* ════════════════ [2] STYLES BLOCK — @scope (ui-segment) ════════════════ */
30
+
31
+ @scope (ui-segment) {
32
+ /* Host: a centered, full-cell flex box — the segment's OWN base layout, independent of which control
33
+ hosts it. `position:relative` is the containing block a future segment-owned pseudo-element could use
34
+ (none today — the moving indicator is the HOST's own `::before`, painted behind via z-index in
35
+ segmented-control.css). */
36
+ :scope {
37
+ display: inline-flex;
38
+ justify-content: center;
39
+ align-items: center;
40
+ position: relative;
41
+
42
+ cursor: pointer;
43
+ -webkit-user-select: none; /* WebKit/Safari: prefix needed */
44
+ user-select: none;
45
+ }
46
+
47
+ /* The dot glyph a plain `ui-radio` would paint (`::before`, radio.css) never renders here — `ui-segment`
48
+ is a different tag; `@scope (ui-radio)` does not match it. Nothing to suppress. */
49
+
50
+ /* Focus ring (ADR-0009): the fleet-wide shared `:focus-visible` outline, reused as-is — the SAME tokens
51
+ `ui-radio`'s own ring reads (radio.css), so a segment and a plain radio look identically focused. Not
52
+ clipped: `ui-segmented-control` sets no `overflow:hidden` (would clip the ring). */
53
+ :scope:focus-visible {
54
+ outline: var(--md-sys-state-focus-ring-width) solid var(--md-sys-color-focus-ring);
55
+ outline-offset: var(--md-sys-state-focus-ring-offset);
56
+ }
57
+
58
+ /* Forced colors (WHCM) — nothing segment-owned to invert here; the GROUP's compound selectors
59
+ (segmented-control.css) own the ink/fill inversion. Present for fleet discipline (every FACE control's
60
+ scope includes a forced-colors rule). */
61
+ @media (forced-colors: active) {
62
+ :scope {
63
+ forced-color-adjust: none;
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,3 @@
1
+ import { UIRadioElement } from '../radio/radio.js';
2
+ export declare class UISegmentElement extends UIRadioElement {
3
+ }
@@ -0,0 +1,26 @@
1
+ // segment.ts — UISegmentElement, the child leaf of ui-segmented-control (ADR-0095 clause 3).
2
+ //
3
+ // Extends UIRadioElement DIRECTLY — adds NO new props, NO new behavior. Everything a segment needs (the
4
+ // boolean form value + checked-state machine, pressActivation toggle, the `grouped()` click-guard +
5
+ // tabindex-correction hook that finds ITS ancestor via the well-known `[data-radio-group]` marker any
6
+ // `UIRadioGroupElement` subclass sets, `static role = 'radio'`) is inherited unchanged; `ui-segmented-
7
+ // control`'s parent class (`UIRadioGroupElement`) finds its children via `instanceof UIRadioElement`
8
+ // (radio-group.ts's `#radios()`), which this subclass satisfies by construction.
9
+ //
10
+ // The ENTIRE point of this class existing (T3's naming win) is the TAG, not new behavior: `<ui-segment>`
11
+ // inside `<ui-segmented-control>` reads as a segmented control's own vocabulary, never leaking "radio" into
12
+ // consumer markup. `radio.css`'s `@scope (ui-radio)` rules do not match `ui-segment` — a feature, not a
13
+ // gap: the dot glyph radio.css suppresses in the old segmented variant simply never renders here (there is
14
+ // no dot rule to suppress), so `ui-segment` authors its OWN small `segment.css` in THIS folder: a centered
15
+ // full-cell flex layout + its own ADR-0009 `:focus-visible` fleet ring. `role='radio'` semantics are
16
+ // inherited from the class. (This lives in its own `controls/segment/` folder, not inside
17
+ // `controls/segmented-control/` — the family-coherence naming trip-wire's `name === folder ||
18
+ // name.startsWith(folder + '-')` rule accepts `radio-group` inside `radio/` but has no prefix relationship
19
+ // for `segment` inside `segmented-control/`; see segmented-control.ts's own note.)
20
+ //
21
+ // Layer: controls/ — imports controls/radio (inward-only ✓).
22
+ import { UIRadioElement } from "../radio/radio.js";
23
+ export class UISegmentElement extends UIRadioElement {
24
+ }
25
+ if (!customElements.get('ui-segment'))
26
+ customElements.define('ui-segment', UISegmentElement);