@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,231 @@
1
+ /* slider.css — ui-slider, the single-thumb Range-class control (range-element.lld.md · ADR-0042 · ADR-0041).
2
+ * Two clearly-SECTIONED blocks:
3
+ *
4
+ * [1] TOKEN BLOCK — `:where(ui-slider)` (specificity 0,0,0) DECLARES the `--ui-slider-*` chain from
5
+ * the colour roles (`--md-sys-color-{family}-{role}`) and the compact widget ramp (`--md-sys-compact-{size}`,
6
+ * ADR-0041). `[size]` repoints the box token; `[disabled]` mutes to neutral roles. No `color-mix`
7
+ * — colour opinions live in the token layer (ADR-0008). `--ui-slider-rail-height` is a thin fleet
8
+ * constant (3px), NOT widget-box derived — the rail height does NOT scale with the thumb box.
9
+ * [2] STYLES BLOCK — `@scope (ui-slider)` CONSUMES only `--ui-slider-*` for its OWN tokens. The host
10
+ * is a `block` container: its block-size = the widget box (`--md-sys-compact-{size}`). The RAIL (`::before`)
11
+ * is a thin horizontal bar, full-width, vertically centred, with a gradient fill from the primary
12
+ * colour up to `--value-pct %` and neutral beyond — one declaration paints both the fill and the
13
+ * remainder. The THUMB (`::after`) is a circle `box − 2×inset = box − 4px` (ADR-0041 cl.3: the
14
+ * two-pixel inset law) centred vertically and positioned horizontally at `calc(--value-pct * 1%)`
15
+ * with `transform: translate(-50%, -50%)` so the circle centre lands on that percentage of the host.
16
+ * `--value-pct` is a UNITLESS number (0–100) set by UIRangeElement on the host inline style.
17
+ * `:focus-visible` ring (keyboard-only, ADR-0009). Forced-colors safe. Density-invariant.
18
+ *
19
+ * Standalone stylesheet — linked via the host page / component-styles barrel; NEVER injected from
20
+ * slider.ts (behaviour-only `.ts`, no runtime style injection — plan §2).
21
+ *
22
+ * 2026-07-07 fix (Kim-filed visual bug): the dark-mode thumb "blends into its surroundings." Measured
23
+ * root cause — NOT the fill/rail pair ADR-0059 already verified (those still clear 3:1: 4.79:1 light /
24
+ * 3.67:1 dark vs fill, 5.66:1 / 4.41:1 vs rail; unchanged below). The thumb is TALLER than the 3px rail,
25
+ * so most of its perimeter borders the general PAGE SURFACE, not the rail/fill — a dimension ADR-0059
26
+ * never measured (switch's thumb never has this problem: its track fills the FULL switch height, so no
27
+ * "outside the track" area exists). Measured: dark-mode `--md-sys-color-neutral-surface-brightest`
28
+ * (neutral-800, the SAME stop as `--md-sys-color-neutral-surface-highest`) vs the dark surface ladder it
29
+ * actually sits on (neutral-800…950) is only **1.09–1.46:1** — it nearly matches the page's own darkest
30
+ * surface tones by construction. Kim's literal ask ("use onPrimary like the switch") does NOT fix this and
31
+ * REGRESSES the already-solved rail case: `--md-sys-color-primary-on-primary` is a FLAT white in both
32
+ * schemes (unlike this scheme-adaptive thumb), and against the dark-mode rail (neutral-400, itself
33
+ * deliberately brightened by ADR-0059) a flat white thumb measures only **2.76:1** — an SC 1.4.11
34
+ * regression below the rail's own contrast floor. Rejected for the same reason: no single flat thumb fill
35
+ * can simultaneously clear 3:1 against the dark rail (needs thumb luminance ≤ ~0.077) AND against the
36
+ * dark surface ladder (needs thumb luminance ≥ ~0.21 to clear the brightest dark surface plane) — those
37
+ * two floors don't overlap with the current rail/surface tokens (verified via the OKLCH→linear-sRGB→WCAG
38
+ * path `tokens.test.ts` already uses). Fix: leave the interior fill (`--ui-slider-thumb`) UNTOUCHED — it
39
+ * already carries the ADR-0059-verified fill/rail contrast — and add a second, independent ring layer
40
+ * (`--ui-slider-thumb-ring`, a `border` on `::after`) using `--md-sys-color-neutral-on-surface` (the fleet's
41
+ * own page-ink role, AA-verified against every surface plane by `tokens.test.ts`). The ring is BRIGHT in
42
+ * dark mode (near-white) and dark in light mode — giving the thumb the switch's "bright accent" signature
43
+ * in the register (surface-adjacent) where it was actually failing, without touching the fill's proven-safe
44
+ * contrast against the rail/fill it was tuned for. Recorded in ADR-0094 (2026-07-07, extends ADR-0059) —
45
+ * the thumb-vs-PAGE-SURFACE dimension is part of its three-backdrop thumb contrast contract.
46
+ */
47
+
48
+ /* ════════════════ [1] TOKEN BLOCK — :where(ui-slider) declares --ui-slider-* ════════════════ */
49
+
50
+ :where(ui-slider) {
51
+ /* Box geometry — the compact widget-box ramp step (ADR-0041; default size = md). `[size]` repoints. */
52
+ --ui-slider-box: var(--md-sys-compact-md);
53
+
54
+ /* Rail colour — the unselected portion of the track. A state-bearing control part → the SOLID
55
+ --md-sys-color-neutral-track (SC 1.4.11 3:1 vs every surface plane), NOT the old translucent
56
+ --md-sys-color-neutral-outline-variant which composited to 1.51:1 light / 1.73:1 dark (an SC 1.4.11 fail — at
57
+ value 0 the whole track went near-invisible). The fill↔rail luminance boundary is intentionally low
58
+ (primary and neutral are L-close) — the VALUE is carried by the high-contrast THUMB, which clears 3:1
59
+ against both the fill (4.79/3.67) and this rail (4.69/3.74). 2026-07-02 color audit item 4 · ADR-0059. */
60
+ --ui-slider-rail: var(--md-sys-color-neutral-track);
61
+
62
+ /* Fill colour — the selected portion (min → value), primary family. */
63
+ --ui-slider-fill: var(--md-sys-color-primary);
64
+
65
+ /* Thumb colour — the drag handle, brightest neutral surface (≈ white on light; dark on dark).
66
+ Verified vs fill/rail (ADR-0059) — untouched by the 2026-07-07 ring fix below. */
67
+ --ui-slider-thumb: var(--md-sys-color-neutral-surface-brightest);
68
+
69
+ /* Thumb RING (2026-07-07 fix) — a border layer independent of the fill, using the fleet's page-ink
70
+ role (`--md-sys-color-neutral-on-surface`: dark in light mode, bright/white in dark mode — AA-verified
71
+ against every surface plane by tokens.test.ts). Covers the thumb-vs-PAGE-SURFACE contrast dimension
72
+ the fill alone can't (see the file-header note): most of the thumb's perimeter borders the general
73
+ surface, not the thin rail, and in dark mode the fill alone measured only 1.09–1.46:1 there. */
74
+ --ui-slider-thumb-ring: var(--md-sys-color-neutral-on-surface);
75
+
76
+ /* Rail height — thin constant, NOT derived from the widget box (ADR-0041 geometry: the inset law
77
+ governs the THUMB size only; the rail is a separate cosmetic layer). 3px reads cleanly at all
78
+ widget-box sizes (12–28 px). */
79
+ --ui-slider-rail-height: 3px;
80
+ }
81
+
82
+ /* [size] → compact widget ramp repoint (ADR-0041: the per-[scale] table in dimensions.css re-tables
83
+ --md-sys-compact-{sm,md,lg}; `[size]` on the host picks which step the slider box targets). */
84
+ :where(ui-slider[size='sm']) {
85
+ --ui-slider-box: var(--md-sys-compact-sm);
86
+ }
87
+ :where(ui-slider[size='lg']) {
88
+ --ui-slider-box: var(--md-sys-compact-lg);
89
+ }
90
+
91
+ /* [disabled] → muted neutral roles for rail, fill, and thumb. The state holds at idle: the styles
92
+ block sets `pointer-events: none` so `:hover` never matches, preventing any token lift. The rail
93
+ deliberately stays the translucent --md-sys-color-neutral-outline-variant (NOT the solid --md-sys-color-neutral-track): a
94
+ disabled/inactive component is SC 1.4.11-EXEMPT, and the muted rail correctly reads as inactive. */
95
+ :where(ui-slider[disabled]) {
96
+ --ui-slider-rail: var(--md-sys-color-neutral-outline-variant);
97
+ --ui-slider-fill: var(--md-sys-color-neutral-surface-high);
98
+ --ui-slider-thumb: var(--md-sys-color-neutral-on-surface-variant);
99
+ --ui-slider-thumb-ring: var(--md-sys-color-neutral-on-surface-variant); /* SC 1.4.11-exempt; matches fill, no separate ring emphasis */
100
+ }
101
+
102
+ /* ════════════════ [2] STYLES BLOCK — @scope (ui-slider) consumes ONLY --ui-slider-* ════════════════ */
103
+
104
+ @scope (ui-slider) {
105
+ /* Host: a block container whose block-size equals the widget box (the interactive hit area is the
106
+ full height of the slider, spanning the thumb diameter). `position: relative` is the containing
107
+ block for both the rail and thumb absolute pseudo-elements. `touch-action: none` prevents the
108
+ browser from claiming pointer events for scroll/pan while a drag is in progress. */
109
+ :scope {
110
+ display: block;
111
+ position: relative;
112
+ /* A slider is a HORIZONTAL track — without an intrinsic width it collapses to the thumb (a dot) in
113
+ a shrink-wrapping flex/inline context (the same failure class as ui-text-field #74). A
114
+ min-inline-size floor prevents the collapse; above it, width is the layout's job. */
115
+ min-inline-size: var(--ui-slider-min-inline-size, 12rem);
116
+ block-size: var(--ui-slider-box);
117
+ cursor: pointer;
118
+ touch-action: none; /* give pointer events exclusively to the slider during drag (no scroll-conflict) */
119
+ -webkit-user-select: none; /* WebKit/Safari still requires the prefix */
120
+ user-select: none;
121
+ }
122
+
123
+ /* RAIL (::before) — thin horizontal bar, full-width, centred in the block dimension.
124
+ A single `background: linear-gradient` paints BOTH the fill (primary, from 0 to --value-pct%)
125
+ and the remaining rail (neutral, from --value-pct% to 100%). `--value-pct` is a unitless number
126
+ (0–100) so `calc(var(--value-pct, 0) * 1%)` converts it to a CSS percentage. No `color-mix` —
127
+ fill and rail are distinct colour roles (ADR-0008). No position transition: the fill updates in
128
+ real-time during drag; animating a gradient can lag. Background-color transitions (disabled↔enabled
129
+ state) could be added here but are omitted — the rail gradient change is instant. */
130
+ :scope::before {
131
+ content: '';
132
+ position: absolute;
133
+ inset-block-start: 50%;
134
+ transform: translateY(-50%);
135
+ inset-inline-start: 0;
136
+ inline-size: 100%;
137
+ block-size: var(--ui-slider-rail-height);
138
+ border-radius: calc(var(--ui-slider-rail-height) / 2);
139
+ background: linear-gradient(
140
+ to right,
141
+ var(--ui-slider-fill) calc(var(--value-pct, 0) * 1%),
142
+ var(--ui-slider-rail) calc(var(--value-pct, 0) * 1%)
143
+ );
144
+ }
145
+
146
+ /* THUMB (::after) — a circle inset by --md-sys-widget-inset (2px) on all sides (ADR-0041 cl.3).
147
+ *
148
+ * Size: thumb = box − 2×inset = box − 4px (the two-pixel inset law, density-invariant flat fleet constant).
149
+ * Block: inset-block-start: 50% places the top edge at 50% of the host height; `transform: translate(-50%,
150
+ * -50%)` shifts the element back by half its own height and width, so its CENTRE lands at (50% of
151
+ * host height, --value-pct% of host width) — vertically centred in the widget box.
152
+ * Inline: inset-inline-start: calc(var(--value-pct, 0) * 1%) positions the left edge at N% of the host;
153
+ * the -50% X component of the same translate() centres the circle on that point. At value-pct=0
154
+ * and 100 the thumb centre is exactly on the host edge (slight overhang of radius; standard slider
155
+ * behaviour, no overflow clipping needed for the interaction — the hit area is the full host).
156
+ *
157
+ * Motion: NO position transition (drag lag). Background-color transitions for disabled/enabled state.
158
+ * Transition is zeroed under prefers-reduced-motion (accessibility non-negotiable).
159
+ *
160
+ * Ring: a 2px border in `--ui-slider-thumb-ring` (2026-07-07 fix, see file header) — box-sizing:
161
+ * border-box keeps the OUTER diameter at the ADR-0041 cl.3 box−4px law (the border eats into the fill,
162
+ * it never grows the box), so the size proof above is unaffected; the border simply repaints the outer
163
+ * rim in the ink role, giving the thumb a contrast layer independent of its own interior fill. */
164
+ :scope::after {
165
+ content: '';
166
+ position: absolute;
167
+ box-sizing: border-box;
168
+
169
+ /* ADR-0041 cl.3: thumb = box − 2×inset = box − 4px */
170
+ --ui-slider-thumb-size: calc(var(--ui-slider-box) - 2 * var(--md-sys-widget-inset));
171
+ inline-size: var(--ui-slider-thumb-size);
172
+ block-size: var(--ui-slider-thumb-size);
173
+ border-radius: 50%; /* circle */
174
+
175
+ background: var(--ui-slider-thumb);
176
+ border: 2px solid var(--ui-slider-thumb-ring);
177
+
178
+ /* Centre the thumb circle on the point (--value-pct% of host width, 50% of host height). */
179
+ inset-block-start: 50%;
180
+ inset-inline-start: calc(var(--value-pct, 0) * 1%);
181
+ transform: translate(-50%, -50%);
182
+
183
+ transition:
184
+ background-color var(--md-sys-motion-duration-fast) var(--md-sys-motion-easing-standard),
185
+ border-color var(--md-sys-motion-duration-fast) var(--md-sys-motion-easing-standard);
186
+ }
187
+
188
+ /* Focus ring (ADR-0009) — the shared fleet ring: `:focus-visible` (keyboard-only). Surrounds the
189
+ whole control (host box), the fleet standard. Reads fleet-wide tokens directly — the one deliberate
190
+ exception to "consume only --ui-slider-*", matching the checkbox/switch pattern. */
191
+ :scope:focus-visible {
192
+ outline: var(--md-sys-state-focus-ring-width) solid var(--md-sys-color-focus-ring);
193
+ outline-offset: var(--md-sys-state-focus-ring-offset);
194
+ }
195
+
196
+ /* Reduced motion — zero ALL transitions (accessibility non-negotiable). */
197
+ @media (prefers-reduced-motion: reduce) {
198
+ :scope::after {
199
+ transition: none;
200
+ }
201
+ }
202
+
203
+ /* Disabled — pointer-inert + cursor reset. The [disabled] token block already muted the colours;
204
+ `pointer-events: none` ensures `:hover` never matches (muted tokens hold at idle forever). */
205
+ :scope[disabled] {
206
+ pointer-events: none;
207
+ cursor: default;
208
+ }
209
+
210
+ /* Forced-colors — map the rail and thumb to system colours so the slider survives high-contrast mode.
211
+ The rail gradient uses Highlight for the fill and ButtonText for the track; the thumb uses Canvas
212
+ with a Highlight border so the handle is distinct from the fill.
213
+ `forced-color-adjust: none` preserves our explicit system-colour mappings, preventing the browser
214
+ from re-overriding them (same pattern as switch.css / checkbox.css — ADR-0009 ring is free via
215
+ --md-sys-color-focus-ring → Highlight from the token layer). */
216
+ @media (forced-colors: active) {
217
+ :scope::before {
218
+ background: linear-gradient(
219
+ to right,
220
+ Highlight calc(var(--value-pct, 0) * 1%),
221
+ ButtonText calc(var(--value-pct, 0) * 1%)
222
+ );
223
+ forced-color-adjust: none;
224
+ }
225
+ :scope::after {
226
+ background: Canvas;
227
+ border: 2px solid Highlight;
228
+ forced-color-adjust: none;
229
+ }
230
+ }
231
+ }
@@ -0,0 +1,111 @@
1
+ import { type ReactiveProps } from '../../dom/index.js';
2
+ import { UIRangeElement } from '../_base/range-element.js';
3
+ declare const sliderProps: {
4
+ min: {
5
+ reflect: true;
6
+ type: import("../../index.js").PropType<number | null>;
7
+ default: number | null;
8
+ attribute?: string | false;
9
+ };
10
+ max: {
11
+ reflect: true;
12
+ type: import("../../index.js").PropType<number | null>;
13
+ default: number | null;
14
+ attribute?: string | false;
15
+ };
16
+ step: {
17
+ reflect: true;
18
+ type: import("../../index.js").PropType<number | null>;
19
+ default: number | null;
20
+ attribute?: string | false;
21
+ };
22
+ value: {
23
+ reflect: true;
24
+ type: import("../../index.js").PropType<number | null>;
25
+ default: number | null;
26
+ attribute?: string | false;
27
+ };
28
+ size: {
29
+ reflect: true;
30
+ type: import("../../index.js").PropType<"sm" | "md" | "lg">;
31
+ default: "sm" | "md" | "lg";
32
+ attribute?: string | false;
33
+ };
34
+ name: {
35
+ reflect: true;
36
+ type: import("../../index.js").PropType<string>;
37
+ default: string;
38
+ attribute?: string | false;
39
+ };
40
+ disabled: {
41
+ reflect: true;
42
+ type: import("../../index.js").PropType<boolean>;
43
+ default: boolean;
44
+ attribute?: string | false;
45
+ };
46
+ required: {
47
+ reflect: true;
48
+ type: import("../../index.js").PropType<boolean>;
49
+ default: boolean;
50
+ attribute?: string | false;
51
+ };
52
+ };
53
+ export interface UISliderElement extends ReactiveProps<typeof sliderProps> {
54
+ }
55
+ export declare class UISliderElement extends UIRangeElement {
56
+ static props: {
57
+ min: {
58
+ reflect: true;
59
+ type: import("../../index.js").PropType<number | null>;
60
+ default: number | null;
61
+ attribute?: string | false;
62
+ };
63
+ max: {
64
+ reflect: true;
65
+ type: import("../../index.js").PropType<number | null>;
66
+ default: number | null;
67
+ attribute?: string | false;
68
+ };
69
+ step: {
70
+ reflect: true;
71
+ type: import("../../index.js").PropType<number | null>;
72
+ default: number | null;
73
+ attribute?: string | false;
74
+ };
75
+ value: {
76
+ reflect: true;
77
+ type: import("../../index.js").PropType<number | null>;
78
+ default: number | null;
79
+ attribute?: string | false;
80
+ };
81
+ size: {
82
+ reflect: true;
83
+ type: import("../../index.js").PropType<"sm" | "md" | "lg">;
84
+ default: "sm" | "md" | "lg";
85
+ attribute?: string | false;
86
+ };
87
+ name: {
88
+ reflect: true;
89
+ type: import("../../index.js").PropType<string>;
90
+ default: string;
91
+ attribute?: string | false;
92
+ };
93
+ disabled: {
94
+ reflect: true;
95
+ type: import("../../index.js").PropType<boolean>;
96
+ default: boolean;
97
+ attribute?: string | false;
98
+ };
99
+ required: {
100
+ reflect: true;
101
+ type: import("../../index.js").PropType<boolean>;
102
+ default: boolean;
103
+ attribute?: string | false;
104
+ };
105
+ };
106
+ static role: string;
107
+ protected _releaseDrag: (() => void);
108
+ protected connected(): void;
109
+ protected disconnected(): void;
110
+ }
111
+ export {};
@@ -0,0 +1,60 @@
1
+ // slider.ts — UISliderElement, the single-thumb Range-class control (range-element.lld.md · ADR-0042 · ADR-0041).
2
+ // Extends UIRangeElement which owns: the numeric value model + clamp/snap (LLD-C1), the ARIA slider
3
+ // semantics (LLD-C2), keyboard step (LLD-C3), and the --value-pct geometry seam (LLD-C5). The leaf adds:
4
+ // • `static role = 'slider'` — confirmatory; the base sets internals.role='slider' directly in connected()
5
+ // • connected(): tabbable (keyboard focus) + valueDrag wiring (LLD-C4, one thumb at --value-pct)
6
+ // • slider.css — the rail (thin fill line) + thumb circle (box − 4px at --value-pct, ADR-0041 cl.3)
7
+ // • self-define as ui-slider
8
+ //
9
+ // All props (value / min / max / step / size / name / disabled / required) are inherited via the
10
+ // UIRangeElement.props spread. Zero-dep; controls → dom+traits inward only (✓); erasableSyntaxOnly ✓.
11
+ import {} from "../../dom/index.js";
12
+ import { UIRangeElement } from "../_base/range-element.js";
13
+ import { tabbable } from "../../traits/tabbable.js";
14
+ import { valueDrag } from "../../traits/value-drag.js";
15
+ // All props are inherited from UIRangeElement — spread into a local constant so UISliderElement
16
+ // carries its OWN static props (ADR-0013: static props cannot be inherited via the prototype chain;
17
+ // the spread is the documented workaround, matching how UICheckboxElement spreads UIIndicatorElement.props).
18
+ const sliderProps = {
19
+ ...UIRangeElement.props,
20
+ };
21
+ export class UISliderElement extends UIRangeElement {
22
+ static props = sliderProps;
23
+ // LLD-C2 subclass contract: the role this leaf carries. The base hardcodes `this.internals.role =
24
+ // 'slider'` in connected() rather than reading a static field, so this declaration is confirmatory.
25
+ static role = 'slider';
26
+ // Connection-scoped valueDrag cleanup — re-assigned on each reconnect (connected() re-runs).
27
+ // Protected so test probes can call it directly to verify idempotent release (G6 DoD, decomp S1).
28
+ _releaseDrag = () => { };
29
+ connected() {
30
+ super.connected(); // base: normaliser · ARIA (ariaValueNow/Min/Max) · --value-pct seam · keyboard step
31
+ // Keyboard-focusable while enabled; removed from the tab order while disabled (ADR-0010).
32
+ tabbable(this, { disabled: () => this.effectiveDisabled() });
33
+ // LLD-C4: wire the pointer→value gesture controller. The host IS the interactive track surface
34
+ // (light-DOM — no child track element); opts.track() is re-read on each pointerdown so reconnect
35
+ // always resolves the live element. Emits `input` on each stepped value change; `change` is emitted
36
+ // by the base on blur when value has moved since focus (the base's commit-on-blur contract).
37
+ this._releaseDrag = valueDrag(this, {
38
+ track: () => this,
39
+ min: () => this.min ?? 0,
40
+ max: () => this.max ?? 100,
41
+ step: () => this.step ?? 1,
42
+ onValue: (v) => {
43
+ if (!Object.is(v, this.value ?? 0)) {
44
+ this.value = v;
45
+ this.emit('input');
46
+ }
47
+ },
48
+ });
49
+ }
50
+ disconnected() {
51
+ // Explicitly release the valueDrag binding so its closure is marked released — the slider-multi
52
+ // shape, adopted here by the TKT-0068 item 1 ruling (the connection AbortController removes the
53
+ // outer pointerdown listener, but `released = true` is a belt-and-suspenders guard against any
54
+ // edge case where the outer listener fires after abort). Idempotent: releasing twice is a no-op.
55
+ this._releaseDrag();
56
+ this._releaseDrag = () => { };
57
+ }
58
+ }
59
+ if (!customElements.get('ui-slider'))
60
+ customElements.define('ui-slider', UISliderElement);
@@ -0,0 +1,197 @@
1
+ /* slider-multi.css — ui-slider-multi, the dual-thumb Range-class control stylesheet (decomp S2 · ADR-0042 · ADR-0041).
2
+ * Two clearly-SECTIONED blocks:
3
+ *
4
+ * [1] TOKEN BLOCK — `:where(ui-slider-multi)` (specificity 0,0,0) DECLARES the `--ui-slider-multi-*`
5
+ * chain from the colour roles (`--md-sys-color-{family}-{role}`) and the compact widget ramp
6
+ * (`--md-sys-compact-{size}` / ADR-0041). `[size]` repoints the box token (the widget ramp step);
7
+ * `[disabled]` mutes the rail/fill/thumb colours to the neutral muted family.
8
+ * No `color-mix` — colour opinions live in the token layer (ADR-0008).
9
+ * [2] STYLES BLOCK — `@scope (ui-slider-multi)` CONSUMES only `--ui-slider-multi-*` for its OWN
10
+ * tokens. The host is a block-level flex column that sizes to its content; the RAIL (.rail) is a
11
+ * thin horizontal bar inside the interactive-box-height container; the FILL (.fill) spans lo→hi
12
+ * using `--value-pct-lo`/`--value-pct-hi` (set by the JS geometry seam); each THUMB (.thumb) is
13
+ * a circle `box − 4px` = `box − 2×--md-sys-widget-inset` (ADR-0041 cl.3: the 2px-inset law) positioned
14
+ * absolutely on the rail by `left: calc(var(--value-pct-{lo|hi}) * 1%)`.
15
+ * `:focus-visible` ring (keyboard-only, ADR-0009). Unconditional transitions + reduced-motion
16
+ * (the Indicator/Range family pattern — NO :state(ready) gate; ADR-0042 LLD-C6). Forced-colors safe.
17
+ * Density-invariant (box/thumb scale with `--md-sys-compact-{size}`, never a raw px override here).
18
+ *
19
+ * Standalone stylesheet — linked via the host page / component-styles barrel (Phase 2); NEVER injected
20
+ * from slider-multi.ts (behaviour-only `.ts`, no runtime style injection — plan §2).
21
+ */
22
+
23
+ /* ════════════════ [1] TOKEN BLOCK — :where(ui-slider-multi) declares --ui-slider-multi-* ════════════════ */
24
+
25
+ :where(ui-slider-multi) {
26
+ /* Box geometry — the compact widget-box ramp step (ADR-0041; default size = md). `[size]` repoints.
27
+ The box drives the thumb size (box − 4px) and the host's interactive-area height. */
28
+ --ui-slider-multi-box: var(--md-sys-compact-md);
29
+
30
+ /* Rail colour — the unfilled background track (muted neutral outline, same as switch unchecked track). */
31
+ --ui-slider-multi-rail: var(--md-sys-color-neutral-outline-variant);
32
+
33
+ /* Fill colour — the lo→hi active range (primary, matching the single-slider convention). */
34
+ --ui-slider-multi-fill: var(--md-sys-color-primary);
35
+
36
+ /* Thumb colours — surface + border. The thumb is a bright circle outlined in primary. */
37
+ --ui-slider-multi-thumb: var(--md-sys-color-neutral-surface-brightest);
38
+ --ui-slider-multi-thumb-border: var(--md-sys-color-primary);
39
+
40
+ /* Focus ring — tunnelled through a component token so the full token chain is local.
41
+ The fleet ring constant (--md-sys-color-focus-ring) flows through here; override only this token
42
+ to remap the focus colour for this component (WHCM: --md-sys-color-focus-ring → Highlight). */
43
+ --ui-slider-multi-focus-ring: var(--md-sys-color-focus-ring);
44
+ }
45
+
46
+ /* [size] → compact widget ramp repoint (ADR-0041: the LITERAL per-[scale] table in dimensions.css
47
+ re-tables --md-sys-compact-{sm,md,lg}; `[size]` on the host picks which step the slider box targets). */
48
+ :where(ui-slider-multi[size='sm']) { --ui-slider-multi-box: var(--md-sys-compact-sm); }
49
+ :where(ui-slider-multi[size='lg']) { --ui-slider-multi-box: var(--md-sys-compact-lg); }
50
+
51
+ /* [disabled] → muted neutral roles (overrides all states). HOLDS at idle: the disabled host is
52
+ pointer-inert (styles block), so `:hover` never matches it (ADR-0008 disabled pattern). */
53
+ :where(ui-slider-multi[disabled]) {
54
+ --ui-slider-multi-rail: var(--md-sys-color-neutral-surface-high);
55
+ --ui-slider-multi-fill: var(--md-sys-color-neutral-surface-high);
56
+ --ui-slider-multi-thumb: var(--md-sys-color-neutral-on-surface-variant);
57
+ --ui-slider-multi-thumb-border: var(--md-sys-color-neutral-outline-variant);
58
+ }
59
+
60
+ /* ════════════════ [2] STYLES BLOCK — @scope (ui-slider-multi) consumes ONLY --ui-slider-multi-* ════════════════ */
61
+
62
+ @scope (ui-slider-multi) {
63
+ /* Host: block container that centres the rail vertically within the interactive box height.
64
+ display:flex + align-items:center means the rail aligns to the middle of the box,
65
+ and the absolutely-positioned thumbs extend out from the rail's midline. */
66
+ :scope {
67
+ display: flex;
68
+ align-items: center;
69
+ /* A range slider is a HORIZONTAL track — without an intrinsic width it collapses to the thumbs (dots)
70
+ in a shrink-wrapping flex/inline context (ui-text-field #74 failure class). Floor the inline-size. */
71
+ min-inline-size: var(--ui-slider-multi-min-inline-size, 12rem);
72
+ block-size: var(--ui-slider-multi-box); /* interactive box = compact widget ramp step (ADR-0041) */
73
+ position: relative;
74
+ -webkit-user-select: none;
75
+ user-select: none;
76
+ cursor: pointer;
77
+ /* The host is the outer sizing container for left:X% on the thumbs (via .rail containing block). */
78
+ }
79
+
80
+ /* RAIL (.rail) — the thin horizontal track bar. Fills the full inline width of the host. Position:
81
+ relative makes the rail the containing block for the fill and thumb absolute children.
82
+ block-size = box/4 ≈ 4px at md scale — the slot height, not the interactive area. */
83
+ .rail {
84
+ position: relative;
85
+ flex: 1;
86
+ block-size: calc(var(--ui-slider-multi-box) / 4);
87
+ border-radius: 999px; /* pill — fully rounded ends */
88
+ background: var(--ui-slider-multi-rail);
89
+ overflow: visible; /* thumbs extend beyond the rail's block-size */
90
+ }
91
+
92
+ /* FILL (.fill) — the lo→hi active range highlight. Absolutely positioned within the rail.
93
+ * inset-block: 0 stretches it to the full rail height; inset-inline-start + inline-size
94
+ * map the [lo, hi] interval:
95
+ * left edge = --value-pct-lo × 1% (of the rail width)
96
+ * right edge = (100 − --value-pct-hi) × 1% from the end
97
+ *
98
+ * Transitions: the fill endpoints animate on value change (unconditional; the Indicator/Range
99
+ * pattern — no :state(ready) gate, ADR-0042 LLD-C6). Suppressed under prefers-reduced-motion. */
100
+ .fill {
101
+ position: absolute;
102
+ inset-block: 0;
103
+ inset-inline-start: calc(var(--value-pct-lo, 0) * 1%);
104
+ /* inline-size spans from lo% to hi% of the rail */
105
+ inline-size: calc((var(--value-pct-hi, 100) - var(--value-pct-lo, 0)) * 1%);
106
+ background: var(--ui-slider-multi-fill);
107
+ border-radius: 999px;
108
+ pointer-events: none; /* the fill is purely decorative — thumbs capture pointer */
109
+ transition:
110
+ inset-inline-start var(--md-sys-motion-duration-fast) var(--md-sys-motion-easing-standard),
111
+ inline-size var(--md-sys-motion-duration-fast) var(--md-sys-motion-easing-standard),
112
+ background-color var(--md-sys-motion-duration-fast) var(--md-sys-motion-easing-standard);
113
+ }
114
+
115
+ /* THUMBS (.thumb) — circles inset 2px (--md-sys-widget-inset) inside the widget box (ADR-0041 cl.3).
116
+ *
117
+ * Size: thumb = box − 2×inset = box − 4px (the ADR-0041 2px-inset law).
118
+ * Block: top:50% + transform:translateY(-50%) centres the thumb on the rail's midline.
119
+ * Inline: left is set per data-thumb (lo/hi) using the --value-pct-* geometry custom property:
120
+ * `left: calc(var(--value-pct-{thumb}) * 1%)`
121
+ * translateX(-50%) on the transform centres the circle on the computed position.
122
+ *
123
+ * Motion: `translate` / `left` transitions animate the thumb slide — paint-only transforms.
124
+ * Suppressed under prefers-reduced-motion. */
125
+ .thumb {
126
+ position: absolute;
127
+
128
+ /* ADR-0041 cl.3: thumb = box − 2×inset = box − 4px */
129
+ --ui-slider-multi-thumb-size: calc(var(--ui-slider-multi-box) - 2 * var(--md-sys-widget-inset));
130
+ inline-size: var(--ui-slider-multi-thumb-size);
131
+ block-size: var(--ui-slider-multi-thumb-size);
132
+ border-radius: 50%; /* circle */
133
+ box-sizing: border-box;
134
+
135
+ top: 50%;
136
+ transform: translateY(-50%) translateX(-50%);
137
+
138
+ border: 2px solid var(--ui-slider-multi-thumb-border);
139
+ background: var(--ui-slider-multi-thumb);
140
+ cursor: pointer;
141
+
142
+ transition:
143
+ left var(--md-sys-motion-duration-fast) var(--md-sys-motion-easing-standard),
144
+ background-color var(--md-sys-motion-duration-fast) var(--md-sys-motion-easing-standard),
145
+ border-color var(--md-sys-motion-duration-fast) var(--md-sys-motion-easing-standard);
146
+ }
147
+
148
+ /* Per-thumb inline position — driven by the JS geometry seam (--value-pct-lo / --value-pct-hi set via
149
+ element.style on the host; they cascade into this @scope as inherited custom properties). */
150
+ .thumb[data-thumb='lo'] { left: calc(var(--value-pct-lo, 0) * 1%); }
151
+ .thumb[data-thumb='hi'] { left: calc(var(--value-pct-hi, 100) * 1%); }
152
+
153
+ /* Focus ring (ADR-0009) — shared fleet ring on keyboard-focused thumbs. Applied via :focus-visible
154
+ (keyboard-only) directly on the thumb element since the thumbs ARE the focusable nodes.
155
+ Consumes the local --ui-slider-multi-focus-ring (declared in the token block, chained from
156
+ --md-sys-color-focus-ring) — the full token chain is now component-local, no direct fleet reference here. */
157
+ .thumb:focus-visible {
158
+ outline: var(--md-sys-state-focus-ring-width) solid var(--ui-slider-multi-focus-ring);
159
+ outline-offset: var(--md-sys-state-focus-ring-offset);
160
+ }
161
+
162
+ /* Reduced motion — zero all transitions on the fill and thumbs (accessibility non-negotiable;
163
+ geometry.md: NEVER animate the ramp; the Indicator/Range unconditional-transitions contract). */
164
+ @media (prefers-reduced-motion: reduce) {
165
+ .fill { transition: none; }
166
+ .thumb { transition: none; }
167
+ }
168
+
169
+ /* Disabled host — pointer-inert so :hover/:active never lift the muted neutral tokens. */
170
+ :scope[disabled] {
171
+ pointer-events: none;
172
+ cursor: default;
173
+ }
174
+
175
+ /* forced-colors — keep the rail, fill, and thumbs visible as system-colour elements.
176
+ forced-color-adjust:none preserves our explicit system-colour mappings so the browser does not
177
+ re-override them (matches switch.css / checkbox.css pattern for the range family). */
178
+ @media (forced-colors: active) {
179
+ .rail {
180
+ border: 2px solid ButtonText;
181
+ background: Canvas;
182
+ forced-color-adjust: none;
183
+ }
184
+ .fill {
185
+ background: Highlight;
186
+ forced-color-adjust: none;
187
+ }
188
+ .thumb {
189
+ border: 2px solid ButtonText;
190
+ background: ButtonFace;
191
+ forced-color-adjust: none;
192
+ }
193
+ .thumb:focus-visible {
194
+ outline-color: Highlight;
195
+ }
196
+ }
197
+ }