@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,149 @@
1
+ import { type ReactiveProps } from '../../dom/index.js';
2
+ import type { FormValue } from '../../dom/index.js';
3
+ import { UIRangeElement } from '../_base/index.js';
4
+ declare const sliderMultiProps: {
5
+ valueLo: {
6
+ reflect: true;
7
+ attribute: string;
8
+ type: import("../../index.js").PropType<number | null>;
9
+ default: number | null;
10
+ };
11
+ valueHi: {
12
+ reflect: true;
13
+ attribute: string;
14
+ type: import("../../index.js").PropType<number | null>;
15
+ default: number | null;
16
+ };
17
+ min: {
18
+ reflect: true;
19
+ type: import("../../index.js").PropType<number | null>;
20
+ default: number | null;
21
+ attribute?: string | false;
22
+ };
23
+ max: {
24
+ reflect: true;
25
+ type: import("../../index.js").PropType<number | null>;
26
+ default: number | null;
27
+ attribute?: string | false;
28
+ };
29
+ step: {
30
+ reflect: true;
31
+ type: import("../../index.js").PropType<number | null>;
32
+ default: number | null;
33
+ attribute?: string | false;
34
+ };
35
+ value: {
36
+ reflect: true;
37
+ type: import("../../index.js").PropType<number | null>;
38
+ default: number | null;
39
+ attribute?: string | false;
40
+ };
41
+ size: {
42
+ reflect: true;
43
+ type: import("../../index.js").PropType<"sm" | "md" | "lg">;
44
+ default: "sm" | "md" | "lg";
45
+ attribute?: string | false;
46
+ };
47
+ name: {
48
+ reflect: true;
49
+ type: import("../../index.js").PropType<string>;
50
+ default: string;
51
+ attribute?: string | false;
52
+ };
53
+ disabled: {
54
+ reflect: true;
55
+ type: import("../../index.js").PropType<boolean>;
56
+ default: boolean;
57
+ attribute?: string | false;
58
+ };
59
+ required: {
60
+ reflect: true;
61
+ type: import("../../index.js").PropType<boolean>;
62
+ default: boolean;
63
+ attribute?: string | false;
64
+ };
65
+ };
66
+ export interface UISliderMultiElement extends ReactiveProps<typeof sliderMultiProps> {
67
+ }
68
+ export declare class UISliderMultiElement extends UIRangeElement {
69
+ #private;
70
+ static props: {
71
+ valueLo: {
72
+ reflect: true;
73
+ attribute: string;
74
+ type: import("../../index.js").PropType<number | null>;
75
+ default: number | null;
76
+ };
77
+ valueHi: {
78
+ reflect: true;
79
+ attribute: string;
80
+ type: import("../../index.js").PropType<number | null>;
81
+ default: number | null;
82
+ };
83
+ min: {
84
+ reflect: true;
85
+ type: import("../../index.js").PropType<number | null>;
86
+ default: number | null;
87
+ attribute?: string | false;
88
+ };
89
+ max: {
90
+ reflect: true;
91
+ type: import("../../index.js").PropType<number | null>;
92
+ default: number | null;
93
+ attribute?: string | false;
94
+ };
95
+ step: {
96
+ reflect: true;
97
+ type: import("../../index.js").PropType<number | null>;
98
+ default: number | null;
99
+ attribute?: string | false;
100
+ };
101
+ value: {
102
+ reflect: true;
103
+ type: import("../../index.js").PropType<number | null>;
104
+ default: number | null;
105
+ attribute?: string | false;
106
+ };
107
+ size: {
108
+ reflect: true;
109
+ type: import("../../index.js").PropType<"sm" | "md" | "lg">;
110
+ default: "sm" | "md" | "lg";
111
+ attribute?: string | false;
112
+ };
113
+ name: {
114
+ reflect: true;
115
+ type: import("../../index.js").PropType<string>;
116
+ default: string;
117
+ attribute?: string | false;
118
+ };
119
+ disabled: {
120
+ reflect: true;
121
+ type: import("../../index.js").PropType<boolean>;
122
+ default: boolean;
123
+ attribute?: string | false;
124
+ };
125
+ required: {
126
+ reflect: true;
127
+ type: import("../../index.js").PropType<boolean>;
128
+ default: boolean;
129
+ attribute?: string | false;
130
+ };
131
+ };
132
+ /**
133
+ * LLD-C1: the [lo, hi] form value submitted to the owning form. Submitted as FormData with two
134
+ * entries keyed by the control's `name` (the standard multi-value form-data shape).
135
+ */
136
+ protected formValue(): FormValue;
137
+ /** Restore to the declared prop defaults on form reset (low = 0, high = 100). */
138
+ protected formReset(): void;
139
+ protected connected(): void;
140
+ protected disconnected(): void;
141
+ /** Expose the protected `internals` for test probes that need to read ariaValueText / role / states. */
142
+ protected get internalsSeam(): ElementInternals;
143
+ /** Expose the valueDrag cleanup pair for idempotency proof (C10 zero-residue; call twice, no throw). */
144
+ protected get loBinding(): (() => void) | null;
145
+ protected get hiBinding(): (() => void) | null;
146
+ /** Expose formValue() for direct testing (the cast removes the `protected` guard). */
147
+ protected formValueSeam(): FormValue;
148
+ }
149
+ export {};
@@ -0,0 +1,380 @@
1
+ // slider-multi.ts — UISliderMultiElement, the dual-thumb Range-class control (decomp S2 · ADR-0042 · ADR-0041).
2
+ // Light-DOM FACE form control — extends UIRangeElement which owns: min/max/step/size/formProps; this leaf
3
+ // WIDENS the value model to a [lo, hi] pair (its own valueLo/valueHi props + formValue() override), provides
4
+ // two light-DOM thumb elements (each a focusable div[role=slider]), wires TWO valueDrag bindings (one per
5
+ // thumb), and enforces the lo ≤ hi invariant in all write paths.
6
+ //
7
+ // Architecture decisions:
8
+ // • Value model: `valueLo` / `valueHi` props replace the base's single `value` prop (base `value` is NOT
9
+ // re-declared here; the base's normaliser + ARIA + geometry effects are NOT invoked — no super.connected()).
10
+ // • Nearer-thumb-grabs: a `pointerdown` listener registered BEFORE both valueDrag bindings sets the
11
+ // `#activeThumb` gate ('lo' or 'hi'); each valueDrag binding's `track()` returns null unless it owns the
12
+ // current active thumb → only the correct binding captures the pointer.
13
+ // • lo ≤ hi invariant: enforced in THREE places: (a) the normalization effect clamps after snap; (b)
14
+ // valueDrag's onValue clamps against the sibling; (c) keyboard steps at the sibling boundary clamp.
15
+ // • ARIA: host role='group'; each thumb div carries role="slider" + aria-value* attributes set reactively.
16
+ // • Light DOM: `.rail > (.fill + .thumb[data-thumb=lo] + .thumb[data-thumb=hi])` created once on first
17
+ // connect; reconnect finds the DOM already present (reconnect guard).
18
+ // • Cleanup: valueDrag cleanup functions stored + called in `disconnected()` (idempotent double-release safe).
19
+ //
20
+ // Zero-dep; controls → dom+traits inward only (✓); erasableSyntaxOnly ✓ (no enum/decorator).
21
+ import { prop } from "../../dom/index.js";
22
+ import { UIRangeElement } from "../_base/index.js";
23
+ import { valueDrag } from "../../traits/value-drag.js";
24
+ // The pair value model (LLD-C1 widened): spread UIRangeElement.props (which includes the shared min/max/step/
25
+ // value/size/formProps) and ADD valueLo/valueHi. The base's single `value` prop is kept in the spread for
26
+ // TypeScript static-side compatibility (the subclass's static props must structurally extend the base's);
27
+ // we do NOT call super.connected(), so the base's normaliser/ARIA/geometry/keyboard effects on `value` are
28
+ // never activated — `value` is an inherited prop that slider-multi does not actively use.
29
+ // Each class must redeclare all its props (no static-props prototype inheritance — UIFormElement.formProps note).
30
+ const sliderMultiProps = {
31
+ ...UIRangeElement.props, // min · max · step · value · size · formProps
32
+ valueLo: { ...prop.number(0), reflect: true, attribute: 'value-lo' }, // low bound; HTML attribute = value-lo (kebab)
33
+ valueHi: { ...prop.number(100), reflect: true, attribute: 'value-hi' }, // high bound; HTML attribute = value-hi (kebab)
34
+ };
35
+ export class UISliderMultiElement extends UIRangeElement {
36
+ static props = sliderMultiProps;
37
+ // Light-DOM element refs — created once on first connect; null until then.
38
+ // NOTE: #fill is not stored — the fill div is position-driven by CSS custom properties alone; the JS
39
+ // geometry seam writes --value-pct-lo/hi on the host and CSS computes fill geometry from those.
40
+ #rail = null;
41
+ #loThumb = null;
42
+ #hiThumb = null;
43
+ // Nearer-thumb gate: set by the pointerdown picker BEFORE both valueDrag listeners fire.
44
+ // 'lo' / 'hi' tells the correct binding's track() to return the rail; the other returns null.
45
+ #activeThumb = null;
46
+ // valueDrag cleanup functions (stored for explicit release in disconnected() and test idempotency proof).
47
+ #releaseLoBinding = null;
48
+ #releaseHiBinding = null;
49
+ // Focus-time baseline for `change` emission on blur/focusout.
50
+ #committedLo = null;
51
+ #committedHi = null;
52
+ /** LLD-C1: normalise a raw number against min/max/step (same algorithm as the base; redeclared here since
53
+ * the base's normaliser is #private and we do not call super.connected()). */
54
+ #normalize(raw) {
55
+ const min = this.min ?? 0;
56
+ const max = this.max ?? 100;
57
+ const step = this.step ?? 1;
58
+ if (min > max)
59
+ return min; // degenerate — zero-length range
60
+ const clamped = Math.max(min, Math.min(max, raw));
61
+ if (step <= 0)
62
+ return clamped; // continuous — no snap
63
+ if (clamped === max)
64
+ return max; // max always reachable (End rule)
65
+ const steps = Math.round((clamped - min) / step);
66
+ const snapped = min + steps * step;
67
+ return snapped > max ? min + (steps - 1) * step : snapped; // snap-down if over max
68
+ }
69
+ /** LLD-C5: percentage position of a value along [min, max] — maps to --value-pct-lo / --value-pct-hi. */
70
+ #valuePct(value) {
71
+ const min = this.min ?? 0;
72
+ const max = this.max ?? 100;
73
+ if (min >= max)
74
+ return 0;
75
+ return ((value - min) / (max - min)) * 100;
76
+ }
77
+ /**
78
+ * LLD-C1: the [lo, hi] form value submitted to the owning form. Submitted as FormData with two
79
+ * entries keyed by the control's `name` (the standard multi-value form-data shape).
80
+ */
81
+ formValue() {
82
+ const lo = this.#normalize(this.valueLo ?? 0);
83
+ const hi = this.#normalize(this.valueHi ?? 100);
84
+ const fd = new FormData();
85
+ const name = this.name || '';
86
+ fd.append(name, String(lo));
87
+ fd.append(name, String(hi));
88
+ return fd;
89
+ }
90
+ /** Restore to the declared prop defaults on form reset (low = 0, high = 100). */
91
+ formReset() {
92
+ this.valueLo = 0;
93
+ this.valueHi = 100;
94
+ }
95
+ /** Build the light-DOM structure on first connect (reconnect guard: no-op if already built). */
96
+ #buildDOM() {
97
+ if (this.#rail)
98
+ return; // already built — reconnect case
99
+ const rail = document.createElement('div');
100
+ rail.className = 'rail';
101
+ const fill = document.createElement('div');
102
+ fill.className = 'fill';
103
+ fill.setAttribute('aria-hidden', 'true'); // decorative; the aria-value* on the thumbs carry semantics
104
+ const loThumb = document.createElement('div');
105
+ loThumb.className = 'thumb';
106
+ loThumb.setAttribute('data-thumb', 'lo');
107
+ loThumb.setAttribute('role', 'slider');
108
+ loThumb.setAttribute('tabindex', '0');
109
+ loThumb.setAttribute('aria-label', 'Low value');
110
+ const hiThumb = document.createElement('div');
111
+ hiThumb.className = 'thumb';
112
+ hiThumb.setAttribute('data-thumb', 'hi');
113
+ hiThumb.setAttribute('role', 'slider');
114
+ hiThumb.setAttribute('tabindex', '0');
115
+ hiThumb.setAttribute('aria-label', 'High value');
116
+ rail.append(fill, loThumb, hiThumb);
117
+ this.append(rail);
118
+ this.#rail = rail;
119
+ this.#loThumb = loThumb;
120
+ this.#hiThumb = hiThumb;
121
+ }
122
+ connected() {
123
+ // LLD-C2: host role='group' (two-slider composite — a group element contains the two slider foci).
124
+ // The individual thumbs each carry role='slider' via HTML attributes (light-DOM children, not the host).
125
+ this.internals.role = 'group';
126
+ // Reset the committed baseline (prevent a stale baseline from a previous connect firing a spurious change).
127
+ this.#committedLo = null;
128
+ this.#committedHi = null;
129
+ // Build light DOM structure once (subsequent reconnects find it already present).
130
+ this.#buildDOM();
131
+ // ── LLD-C1: normalization effect ──────────────────────────────────────────────────────────────────
132
+ // Clamp + snap BOTH values and enforce lo ≤ hi in a single combined effect. Reads four signals
133
+ // (valueLo, valueHi, min, max, step), so it re-runs whenever any of them changes. The Object.is
134
+ // cutoff prevents redundant writes when values are already normalised (no infinite loop).
135
+ this.effect(() => {
136
+ const lo = this.#normalize(this.valueLo ?? 0);
137
+ const hi = this.#normalize(this.valueHi ?? 100);
138
+ const clampedLo = Math.min(lo, hi); // lo ≤ hi invariant: lo cannot exceed hi
139
+ const clampedHi = Math.max(lo, hi); // hi ≥ lo invariant: hi cannot go below lo
140
+ if (!Object.is(clampedLo, this.valueLo ?? 0))
141
+ this.valueLo = clampedLo;
142
+ if (!Object.is(clampedHi, this.valueHi ?? 100))
143
+ this.valueHi = clampedHi;
144
+ });
145
+ // ── LLD-C2: ARIA effect — lo thumb ────────────────────────────────────────────────────────────────
146
+ // The lo thumb's aria-valuenow/min/max: its max is constrained to the current hi value (not the
147
+ // range max) so a screen reader user understands the lo thumb cannot exceed hi.
148
+ this.effect(() => {
149
+ const lo = this.#normalize(this.valueLo ?? 0);
150
+ const hi = this.#normalize(this.valueHi ?? 100);
151
+ const clampedLo = Math.min(lo, hi);
152
+ this.#loThumb?.setAttribute('aria-valuenow', String(clampedLo));
153
+ this.#loThumb?.setAttribute('aria-valuemin', String(this.min ?? 0));
154
+ this.#loThumb?.setAttribute('aria-valuemax', String(hi));
155
+ this.#loThumb?.setAttribute('aria-valuetext', String(clampedLo));
156
+ });
157
+ // ── LLD-C2: ARIA effect — hi thumb ────────────────────────────────────────────────────────────────
158
+ // The hi thumb's aria-valuenow/min/max: its min is constrained to the current lo value.
159
+ this.effect(() => {
160
+ const lo = this.#normalize(this.valueLo ?? 0);
161
+ const hi = this.#normalize(this.valueHi ?? 100);
162
+ const clampedHi = Math.max(lo, hi);
163
+ this.#hiThumb?.setAttribute('aria-valuenow', String(clampedHi));
164
+ this.#hiThumb?.setAttribute('aria-valuemin', String(lo));
165
+ this.#hiThumb?.setAttribute('aria-valuemax', String(this.max ?? 100));
166
+ this.#hiThumb?.setAttribute('aria-valuetext', String(clampedHi));
167
+ });
168
+ // ── LLD-C5: geometry seam — --value-pct-lo / --value-pct-hi ──────────────────────────────────────
169
+ // CSS consumes these to position the fill and both thumbs. Values are dimensionless numbers
170
+ // (0–100) that the CSS multiplies by 1% (e.g. `left: calc(var(--value-pct-lo) * 1%)`).
171
+ this.effect(() => {
172
+ const lo = this.#normalize(this.valueLo ?? 0);
173
+ const hi = this.#normalize(this.valueHi ?? 100);
174
+ const clampedLo = Math.min(lo, hi);
175
+ const clampedHi = Math.max(lo, hi);
176
+ this.style.setProperty('--value-pct-lo', String(this.#valuePct(clampedLo)));
177
+ this.style.setProperty('--value-pct-hi', String(this.#valuePct(clampedHi)));
178
+ });
179
+ // ── Disabled state: thumb tabindex + aria-disabled ────────────────────────────────────────────────
180
+ // Disabled removes the tabindex ATTRIBUTE entirely (not tabindex='-1') — the fleet dialect ruled in
181
+ // TKT-0068 item 2: native-parity, a disabled part is not even programmatically focusable (the
182
+ // textarea/text-field/combo-box editor shape).
183
+ this.effect(() => {
184
+ const disabled = this.effectiveDisabled();
185
+ if (disabled) {
186
+ this.#loThumb?.removeAttribute('tabindex');
187
+ this.#hiThumb?.removeAttribute('tabindex');
188
+ }
189
+ else {
190
+ this.#loThumb?.setAttribute('tabindex', '0');
191
+ this.#hiThumb?.setAttribute('tabindex', '0');
192
+ }
193
+ this.#loThumb?.setAttribute('aria-disabled', String(disabled));
194
+ this.#hiThumb?.setAttribute('aria-disabled', String(disabled));
195
+ });
196
+ // ── LLD-C4: nearer-thumb picker — must register BEFORE both valueDrag bindings ──────────────────
197
+ // On pointerdown, determine which thumb the pointer is closer to (by percentage position along the
198
+ // rail). Sets #activeThumb so the correct valueDrag binding's track() returns the rail (the other
199
+ // returns null). Also focuses the selected thumb for keyboard accessibility post-click.
200
+ this.listen(this, 'pointerdown', (event) => {
201
+ if (this.effectiveDisabled())
202
+ return;
203
+ const rail = this.#rail;
204
+ if (!rail)
205
+ return;
206
+ const pe = event;
207
+ const rect = rail.getBoundingClientRect();
208
+ const ratio = rect.width > 0
209
+ ? Math.max(0, Math.min(1, (pe.clientX - rect.left) / rect.width))
210
+ : 0;
211
+ const loPct = this.#valuePct(this.#normalize(this.valueLo ?? 0)) / 100;
212
+ const hiPct = this.#valuePct(this.#normalize(this.valueHi ?? 100)) / 100;
213
+ const distLo = Math.abs(ratio - loPct);
214
+ const distHi = Math.abs(ratio - hiPct);
215
+ // When equidistant (thumbs coincide at same value), prefer lo so they can be separated.
216
+ this.#activeThumb = distLo <= distHi ? 'lo' : 'hi';
217
+ if (this.#activeThumb === 'lo')
218
+ this.#loThumb?.focus();
219
+ else
220
+ this.#hiThumb?.focus();
221
+ });
222
+ // ── LLD-C4: two valueDrag bindings (one per thumb) ────────────────────────────────────────────────
223
+ // Each binding's track() accessor returns the rail ONLY when its thumb was selected by the
224
+ // nearer-thumb picker above (#activeThumb gate). The onValue callback enforces lo ≤ hi before writing
225
+ // (the thumb cannot drag past its sibling — clamped at the sibling's current value).
226
+ this.#releaseLoBinding = valueDrag(this, {
227
+ track: () => this.#activeThumb === 'lo' ? this.#rail : null,
228
+ min: () => this.min ?? 0,
229
+ max: () => this.max ?? 100,
230
+ step: () => this.step ?? 1,
231
+ onValue: (v) => {
232
+ // lo ≤ hi: clamp lo at hi so the lo thumb cannot cross past the hi thumb.
233
+ const hi = this.#normalize(this.valueHi ?? 100);
234
+ this.valueLo = Math.min(v, hi);
235
+ this.emit('input');
236
+ },
237
+ });
238
+ this.#releaseHiBinding = valueDrag(this, {
239
+ track: () => this.#activeThumb === 'hi' ? this.#rail : null,
240
+ min: () => this.min ?? 0,
241
+ max: () => this.max ?? 100,
242
+ step: () => this.step ?? 1,
243
+ onValue: (v) => {
244
+ // lo ≤ hi: clamp hi at lo so the hi thumb cannot cross past the lo thumb.
245
+ const lo = this.#normalize(this.valueLo ?? 0);
246
+ this.valueHi = Math.max(v, lo);
247
+ this.emit('input');
248
+ },
249
+ });
250
+ // ── LLD-C3: keyboard step ─────────────────────────────────────────────────────────────────────────
251
+ // Arrow ±step, PageUp/Down ±10×step, Home/End → boundary (matching the base's step schema).
252
+ // The event.target determines WHICH thumb receives the step; disabled host is inert.
253
+ // For lo: End clamps to hi (not max); for hi: Home clamps to lo (not min) — the lo ≤ hi invariant.
254
+ this.listen(this, 'keydown', (event) => {
255
+ if (this.effectiveDisabled())
256
+ return;
257
+ const e = event;
258
+ const isLo = e.target === this.#loThumb;
259
+ const isHi = e.target === this.#hiThumb;
260
+ if (!isLo && !isHi)
261
+ return;
262
+ const min = this.min ?? 0;
263
+ const max = this.max ?? 100;
264
+ const step = this.step ?? 1;
265
+ const largeStep = step * 10;
266
+ if (isLo) {
267
+ const current = this.#normalize(this.valueLo ?? 0);
268
+ const hi = this.#normalize(this.valueHi ?? 100);
269
+ let next = null;
270
+ switch (e.key) {
271
+ case 'ArrowRight':
272
+ case 'ArrowUp':
273
+ next = this.#normalize(current + step);
274
+ break;
275
+ case 'ArrowLeft':
276
+ case 'ArrowDown':
277
+ next = this.#normalize(current - step);
278
+ break;
279
+ case 'PageUp':
280
+ next = this.#normalize(current + largeStep);
281
+ break;
282
+ case 'PageDown':
283
+ next = this.#normalize(current - largeStep);
284
+ break;
285
+ case 'Home':
286
+ next = min;
287
+ break;
288
+ case 'End':
289
+ next = hi;
290
+ break; // lo End → the hi value (not max)
291
+ default: return;
292
+ }
293
+ // lo ≤ hi: a step that would push lo past hi clamps at hi.
294
+ next = Math.min(next, hi);
295
+ e.preventDefault();
296
+ if (!Object.is(next, current)) {
297
+ this.valueLo = next;
298
+ this.emit('input');
299
+ }
300
+ }
301
+ else { // isHi
302
+ const lo = this.#normalize(this.valueLo ?? 0);
303
+ const current = this.#normalize(this.valueHi ?? 100);
304
+ let next = null;
305
+ switch (e.key) {
306
+ case 'ArrowRight':
307
+ case 'ArrowUp':
308
+ next = this.#normalize(current + step);
309
+ break;
310
+ case 'ArrowLeft':
311
+ case 'ArrowDown':
312
+ next = this.#normalize(current - step);
313
+ break;
314
+ case 'PageUp':
315
+ next = this.#normalize(current + largeStep);
316
+ break;
317
+ case 'PageDown':
318
+ next = this.#normalize(current - largeStep);
319
+ break;
320
+ case 'Home':
321
+ next = lo;
322
+ break; // hi Home → the lo value (not min)
323
+ case 'End':
324
+ next = max;
325
+ break;
326
+ default: return;
327
+ }
328
+ // lo ≤ hi: a step that would push hi below lo clamps at lo.
329
+ next = Math.max(next, lo);
330
+ e.preventDefault();
331
+ if (!Object.is(next, current)) {
332
+ this.valueHi = next;
333
+ this.emit('input');
334
+ }
335
+ }
336
+ });
337
+ // ── LLD-C3: change event on blur ──────────────────────────────────────────────────────────────────
338
+ // Track both values at focus (focusin bubbles from either thumb); emit `change` on focusout if either
339
+ // value moved since the baseline. focusin/focusout bubble (unlike focus/blur), so no capture needed.
340
+ this.listen(this, 'focusin', () => {
341
+ this.#committedLo = this.#normalize(this.valueLo ?? 0);
342
+ this.#committedHi = this.#normalize(this.valueHi ?? 100);
343
+ });
344
+ this.listen(this, 'focusout', () => {
345
+ const lo = this.#normalize(this.valueLo ?? 0);
346
+ const hi = this.#normalize(this.valueHi ?? 100);
347
+ const loMoved = this.#committedLo !== null && !Object.is(lo, this.#committedLo);
348
+ const hiMoved = this.#committedHi !== null && !Object.is(hi, this.#committedHi);
349
+ if (loMoved || hiMoved)
350
+ this.emit('change');
351
+ this.#committedLo = null;
352
+ this.#committedHi = null;
353
+ });
354
+ }
355
+ disconnected() {
356
+ // Explicitly release both valueDrag bindings so their closures are marked released (the connection
357
+ // AbortController removes the outer pointerdown listeners, but `released = true` is a belt-and-
358
+ // suspenders guard against any edge case where the outer listener fires after abort). Idempotent:
359
+ // calling the cleanup twice sets `released = true` twice — no throw, no state corruption.
360
+ this.#releaseLoBinding?.();
361
+ this.#releaseHiBinding?.();
362
+ this.#releaseLoBinding = null;
363
+ this.#releaseHiBinding = null;
364
+ this.#activeThumb = null;
365
+ }
366
+ // ── protected test seams ─────────────────────────────────────────────────────────────────────────────
367
+ // These are the minimal seams the test suite drives (idempotency proof, internals access, formValue).
368
+ /** Expose the protected `internals` for test probes that need to read ariaValueText / role / states. */
369
+ get internalsSeam() { return this.internals; }
370
+ /** Expose the valueDrag cleanup pair for idempotency proof (C10 zero-residue; call twice, no throw). */
371
+ get loBinding() { return this.#releaseLoBinding; }
372
+ get hiBinding() { return this.#releaseHiBinding; }
373
+ /** Expose formValue() for direct testing (the cast removes the `protected` guard). */
374
+ formValueSeam() {
375
+ return this.formValue.call(this);
376
+ }
377
+ }
378
+ if (!customElements.get('ui-slider-multi')) {
379
+ customElements.define('ui-slider-multi', UISliderMultiElement);
380
+ }
@@ -0,0 +1,23 @@
1
+ import type { PropConfig } from '../../dom/props.js';
2
+ export interface SparklineGeometry {
3
+ points: string;
4
+ area: string | null;
5
+ count: number;
6
+ first: number;
7
+ last: number;
8
+ min: number;
9
+ max: number;
10
+ }
11
+ /** Hardening (SPEC-R3): non-array -> []; entries kept only if `typeof v === 'number' && Number.isFinite(v)`. */
12
+ export declare function cleanSeries(input: unknown): number[];
13
+ /** null when the clean series is empty. Coordinates rounded to 2 decimals (stable strings for tests). */
14
+ export declare function sparklineGeometry(values: readonly number[]): SparklineGeometry | null;
15
+ /** The SPEC-R4 sentence — exact wordings, Intl.NumberFormat (default locale, module-memoized) numbers:
16
+ * n>=2: `{n} points, starts {first}, ends {last}, low {min}, high {max}` · n=1: `1 point, value {v}`
17
+ * · n=0/null: `no data`; a non-empty label prefixes as `{label}: {summary}`. */
18
+ export declare function sparklineSummary(label: string, g: SparklineGeometry | null): string;
19
+ /** The safe values codec (SPEC-R3 row 1/2): `from(attr)` = null -> [], JSON.parse in try/catch -> [] on
20
+ * throw, then cleanSeries; `to` = JSON.stringify. dom/props.ts `jsonType` is NOT used — its bare
21
+ * JSON.parse throws on malformed attributes and maps a removed attribute to `null`, both of which
22
+ * SPEC-R3 forbids reaching the render path (verified against props.ts:73-82). */
23
+ export declare const sparklineValuesProp: PropConfig<number[]>;
@@ -0,0 +1,79 @@
1
+ // sparkline-math.ts — the pure, DOM-free math for `ui-sparkline` (LLD-C1, chart-family.lld.md §2).
2
+ // Everything here is a plain function over numbers/strings — no DOM, no signals, unit-testable without a
3
+ // browser (SPEC-N2: jsdom/browser truth is the ELEMENT's job; this module's truth is arithmetic).
4
+ //
5
+ // Ordinal spacing by index (SPEC §2 "Series"): x(i) = n===1 ? 50 : (i/(n-1))*100. Auto vertical range:
6
+ // y(v) = span===0 ? 50 : 100 - ((v-min)/span)*100 — the zero-range case (all-equal, n>=2) and negative
7
+ // values both fall out of this one formula (SPEC-R3 rows 4/5), no special-casing beyond span===0.
8
+ // n===1 duplicates its single coordinate ("50,{y} 50,{y}") so a round `stroke-linecap` paints a dot
9
+ // (SPEC-R3 row 3) with no second element kind. `area` (the closed fill polygon) is built only for n>=2.
10
+ /** Round to 2 decimals — stable coordinate strings (test-friendly, avoids float-tail churn). */
11
+ function round2(v) {
12
+ return Math.round(v * 100) / 100;
13
+ }
14
+ /** Hardening (SPEC-R3): non-array -> []; entries kept only if `typeof v === 'number' && Number.isFinite(v)`. */
15
+ export function cleanSeries(input) {
16
+ if (!Array.isArray(input))
17
+ return [];
18
+ return input.filter((v) => typeof v === 'number' && Number.isFinite(v));
19
+ }
20
+ /** null when the clean series is empty. Coordinates rounded to 2 decimals (stable strings for tests). */
21
+ export function sparklineGeometry(values) {
22
+ const n = values.length;
23
+ if (n === 0)
24
+ return null;
25
+ let min = values[0];
26
+ let max = values[0];
27
+ for (const v of values) {
28
+ if (v < min)
29
+ min = v;
30
+ if (v > max)
31
+ max = v;
32
+ }
33
+ const span = max - min;
34
+ const coords = values.map((v, i) => {
35
+ const x = n === 1 ? 50 : (i / (n - 1)) * 100;
36
+ const y = span === 0 ? 50 : 100 - ((v - min) / span) * 100;
37
+ return `${round2(x)},${round2(y)}`;
38
+ });
39
+ const points = n === 1 ? `${coords[0]} ${coords[0]}` : coords.join(' ');
40
+ const area = n >= 2 ? `${points} 100,100 0,100` : null;
41
+ return { points, area, count: n, first: values[0], last: values[n - 1], min, max };
42
+ }
43
+ // Module-memoized: one Intl.NumberFormat (default locale) for every summary — SPEC-R4's grounding note
44
+ // ("numbers formatted with the platform default-locale Intl.NumberFormat") and its own perf hygiene.
45
+ const numberFormat = new Intl.NumberFormat();
46
+ /** The SPEC-R4 sentence — exact wordings, Intl.NumberFormat (default locale, module-memoized) numbers:
47
+ * n>=2: `{n} points, starts {first}, ends {last}, low {min}, high {max}` · n=1: `1 point, value {v}`
48
+ * · n=0/null: `no data`; a non-empty label prefixes as `{label}: {summary}`. */
49
+ export function sparklineSummary(label, g) {
50
+ const fmt = (v) => numberFormat.format(v);
51
+ const summary = g === null
52
+ ? 'no data'
53
+ : g.count === 1
54
+ ? `1 point, value ${fmt(g.first)}`
55
+ : `${g.count} points, starts ${fmt(g.first)}, ends ${fmt(g.last)}, low ${fmt(g.min)}, high ${fmt(g.max)}`;
56
+ return label ? `${label}: ${summary}` : summary;
57
+ }
58
+ /** The safe values codec (SPEC-R3 row 1/2): `from(attr)` = null -> [], JSON.parse in try/catch -> [] on
59
+ * throw, then cleanSeries; `to` = JSON.stringify. dom/props.ts `jsonType` is NOT used — its bare
60
+ * JSON.parse throws on malformed attributes and maps a removed attribute to `null`, both of which
61
+ * SPEC-R3 forbids reaching the render path (verified against props.ts:73-82). */
62
+ export const sparklineValuesProp = {
63
+ type: {
64
+ from(attr) {
65
+ if (attr === null)
66
+ return [];
67
+ try {
68
+ return cleanSeries(JSON.parse(attr));
69
+ }
70
+ catch {
71
+ return [];
72
+ }
73
+ },
74
+ to(value) {
75
+ return JSON.stringify(value);
76
+ },
77
+ },
78
+ default: [],
79
+ };