@adia-ai/web-components 0.4.5 → 0.4.7

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 (316) hide show
  1. package/README.md +63 -24
  2. package/USAGE.md +604 -0
  3. package/components/accordion/accordion.d.ts +17 -0
  4. package/components/accordion/accordion.js +10 -117
  5. package/components/accordion/class.js +132 -0
  6. package/components/action-list/action-list.d.ts +15 -0
  7. package/components/action-list/action-list.js +9 -140
  8. package/components/action-list/class.js +156 -0
  9. package/components/agent-artifact/agent-artifact.d.ts +25 -0
  10. package/components/agent-artifact/agent-artifact.js +8 -181
  11. package/components/agent-artifact/class.js +200 -0
  12. package/components/agent-feedback-bar/agent-feedback-bar.d.ts +21 -0
  13. package/components/agent-feedback-bar/agent-feedback-bar.js +8 -143
  14. package/components/agent-feedback-bar/class.js +162 -0
  15. package/components/agent-questions/agent-questions.d.ts +23 -0
  16. package/components/agent-questions/agent-questions.js +8 -180
  17. package/components/agent-questions/class.js +199 -0
  18. package/components/agent-reasoning/agent-reasoning.d.ts +23 -0
  19. package/components/agent-reasoning/agent-reasoning.js +8 -494
  20. package/components/agent-reasoning/class.js +513 -0
  21. package/components/agent-suggestions/agent-suggestions.d.ts +21 -0
  22. package/components/agent-suggestions/agent-suggestions.js +8 -78
  23. package/components/agent-suggestions/class.js +97 -0
  24. package/components/agent-trace/agent-trace.d.ts +19 -0
  25. package/components/alert/alert.d.ts +29 -0
  26. package/components/alert/alert.js +8 -175
  27. package/components/alert/class.js +194 -0
  28. package/components/avatar/avatar.d.ts +27 -0
  29. package/components/avatar/avatar.js +9 -159
  30. package/components/avatar/class.js +173 -0
  31. package/components/badge/badge.d.ts +27 -0
  32. package/components/badge/badge.js +9 -75
  33. package/components/badge/class.js +93 -0
  34. package/components/block/block.d.ts +19 -0
  35. package/components/block/block.js +9 -15
  36. package/components/block/class.js +33 -0
  37. package/components/breadcrumb/breadcrumb.d.ts +23 -0
  38. package/components/breadcrumb/breadcrumb.js +8 -113
  39. package/components/breadcrumb/class.js +132 -0
  40. package/components/button/button.d.ts +34 -0
  41. package/components/button/button.js +15 -66
  42. package/components/button/class.js +80 -0
  43. package/components/calendar-picker/calendar-picker.a2ui.json +6 -1
  44. package/components/calendar-picker/calendar-picker.d.ts +27 -0
  45. package/components/calendar-picker/calendar-picker.js +8 -332
  46. package/components/calendar-picker/calendar-picker.yaml +51 -177
  47. package/components/calendar-picker/class.js +351 -0
  48. package/components/canvas/canvas.a2ui.json +6 -1
  49. package/components/canvas/canvas.d.ts +17 -0
  50. package/components/canvas/canvas.yaml +19 -36
  51. package/components/card/card.a2ui.json +3 -0
  52. package/components/card/card.d.ts +27 -0
  53. package/components/card/card.js +9 -50
  54. package/components/card/card.yaml +171 -433
  55. package/components/card/class.js +68 -0
  56. package/components/chart/chart.d.ts +41 -0
  57. package/components/chart/chart.js +8 -2131
  58. package/components/chart/class.js +2150 -0
  59. package/components/chart-legend/chart-legend.d.ts +27 -0
  60. package/components/chart-legend/chart-legend.js +8 -197
  61. package/components/chart-legend/class.js +215 -0
  62. package/components/chat-thread/chat-thread.d.ts +17 -0
  63. package/components/chat-thread/chat-thread.js +8 -157
  64. package/components/chat-thread/class.js +176 -0
  65. package/components/check/check.d.ts +30 -0
  66. package/components/check/check.js +11 -52
  67. package/components/check/class.js +68 -0
  68. package/components/code/class.js +501 -0
  69. package/components/code/code.d.ts +39 -0
  70. package/components/code/code.js +8 -482
  71. package/components/col/class.js +30 -0
  72. package/components/col/col.d.ts +23 -0
  73. package/components/col/col.js +10 -13
  74. package/components/color-picker/class.js +550 -0
  75. package/components/color-picker/color-picker.d.ts +37 -0
  76. package/components/color-picker/color-picker.js +8 -531
  77. package/components/command/class.js +364 -0
  78. package/components/command/command.a2ui.json +3 -0
  79. package/components/command/command.d.ts +19 -0
  80. package/components/command/command.js +8 -345
  81. package/components/command/command.yaml +105 -124
  82. package/components/demo-toggle/class.js +153 -0
  83. package/components/demo-toggle/demo-toggle.d.ts +23 -0
  84. package/components/demo-toggle/demo-toggle.js +8 -135
  85. package/components/description-list/class.js +86 -0
  86. package/components/description-list/description-list.d.ts +21 -0
  87. package/components/description-list/description-list.js +8 -67
  88. package/components/divider/class.js +57 -0
  89. package/components/divider/divider.d.ts +19 -0
  90. package/components/divider/divider.js +10 -40
  91. package/components/drawer/class.js +306 -0
  92. package/components/drawer/drawer.d.ts +25 -0
  93. package/components/drawer/drawer.js +8 -287
  94. package/components/embed/class.js +73 -0
  95. package/components/embed/embed.d.ts +23 -0
  96. package/components/embed/embed.js +9 -55
  97. package/components/empty-state/class.js +108 -0
  98. package/components/empty-state/empty-state.d.ts +21 -0
  99. package/components/empty-state/empty-state.js +9 -90
  100. package/components/feed/class.js +381 -0
  101. package/components/feed/feed.d.ts +19 -0
  102. package/components/feed/feed.js +9 -367
  103. package/components/field/class.js +266 -0
  104. package/components/field/field.d.ts +23 -0
  105. package/components/field/field.js +8 -247
  106. package/components/fields/class.js +106 -0
  107. package/components/fields/fields.d.ts +19 -0
  108. package/components/fields/fields.js +8 -87
  109. package/components/grid/class.js +31 -0
  110. package/components/grid/grid.d.ts +23 -0
  111. package/components/grid/grid.js +10 -14
  112. package/components/heatmap/class.js +305 -0
  113. package/components/heatmap/heatmap.d.ts +31 -0
  114. package/components/heatmap/heatmap.js +8 -286
  115. package/components/icon/class.js +54 -0
  116. package/components/icon/icon.d.ts +23 -0
  117. package/components/icon/icon.js +13 -40
  118. package/components/image/class.js +112 -0
  119. package/components/image/image.d.ts +33 -0
  120. package/components/image/image.js +9 -94
  121. package/components/index.js +1 -0
  122. package/components/input/class.js +773 -0
  123. package/components/input/input.a2ui.json +3 -0
  124. package/components/input/input.d.ts +61 -0
  125. package/components/input/input.js +8 -755
  126. package/components/input/input.yaml +171 -442
  127. package/components/inspector/class.js +142 -0
  128. package/components/inspector/inspector.a2ui.json +8 -1
  129. package/components/inspector/inspector.d.ts +17 -0
  130. package/components/inspector/inspector.js +8 -124
  131. package/components/inspector/inspector.yaml +15 -30
  132. package/components/kbd/class.js +34 -0
  133. package/components/kbd/kbd.a2ui.json +3 -0
  134. package/components/kbd/kbd.d.ts +17 -0
  135. package/components/kbd/kbd.js +10 -17
  136. package/components/kbd/kbd.yaml +54 -185
  137. package/components/link/class.js +187 -0
  138. package/components/link/link.d.ts +55 -0
  139. package/components/link/link.js +8 -168
  140. package/components/list/class.js +249 -0
  141. package/components/list/list.d.ts +23 -0
  142. package/components/list/list.js +9 -231
  143. package/components/menu/class.js +332 -0
  144. package/components/menu/menu.d.ts +21 -0
  145. package/components/menu/menu.js +11 -316
  146. package/components/modal/class.js +231 -0
  147. package/components/modal/modal.a2ui.json +5 -1
  148. package/components/modal/modal.d.ts +23 -0
  149. package/components/modal/modal.js +8 -212
  150. package/components/modal/modal.yaml +19 -39
  151. package/components/nav/class.js +150 -0
  152. package/components/nav/nav.d.ts +31 -0
  153. package/components/nav/nav.js +8 -131
  154. package/components/nav-group/class.js +152 -0
  155. package/components/nav-group/nav-group.d.ts +35 -0
  156. package/components/nav-group/nav-group.js +9 -134
  157. package/components/nav-item/class.js +86 -0
  158. package/components/nav-item/nav-item.d.ts +37 -0
  159. package/components/nav-item/nav-item.js +10 -69
  160. package/components/noodles/class.js +510 -0
  161. package/components/noodles/noodles.d.ts +33 -0
  162. package/components/noodles/noodles.js +9 -493
  163. package/components/option-card/class.js +167 -0
  164. package/components/option-card/option-card.d.ts +30 -0
  165. package/components/option-card/option-card.js +8 -149
  166. package/components/otp-input/class.js +180 -0
  167. package/components/otp-input/otp-input.a2ui.json +5 -1
  168. package/components/otp-input/otp-input.d.ts +25 -0
  169. package/components/otp-input/otp-input.js +9 -162
  170. package/components/otp-input/otp-input.yaml +45 -174
  171. package/components/page/class.js +97 -0
  172. package/components/page/page.d.ts +46 -0
  173. package/components/page/page.js +8 -79
  174. package/components/pagination/class.js +195 -0
  175. package/components/pagination/pagination.d.ts +23 -0
  176. package/components/pagination/pagination.js +9 -177
  177. package/components/pane/class.js +186 -0
  178. package/components/pane/pane.a2ui.json +12 -1
  179. package/components/pane/pane.css +10 -0
  180. package/components/pane/pane.d.ts +31 -0
  181. package/components/pane/pane.js +8 -143
  182. package/components/pane/pane.yaml +57 -157
  183. package/components/pipeline-status/class.js +189 -0
  184. package/components/pipeline-status/pipeline-status.a2ui.json +7 -1
  185. package/components/pipeline-status/pipeline-status.d.ts +21 -0
  186. package/components/pipeline-status/pipeline-status.js +9 -172
  187. package/components/pipeline-status/pipeline-status.yaml +34 -72
  188. package/components/popover/class.js +194 -0
  189. package/components/popover/popover.d.ts +23 -0
  190. package/components/popover/popover.js +9 -176
  191. package/components/progress/class.js +74 -0
  192. package/components/progress/progress.a2ui.json +3 -0
  193. package/components/progress/progress.d.ts +19 -0
  194. package/components/progress/progress.js +10 -57
  195. package/components/progress/progress.yaml +124 -287
  196. package/components/progress-row/class.js +110 -0
  197. package/components/progress-row/progress-row.d.ts +23 -0
  198. package/components/progress-row/progress-row.js +8 -92
  199. package/components/radio/class.js +83 -0
  200. package/components/radio/radio.d.ts +28 -0
  201. package/components/radio/radio.js +11 -67
  202. package/components/range/class.js +194 -0
  203. package/components/range/range.d.ts +31 -0
  204. package/components/range/range.js +9 -176
  205. package/components/rating/class.js +148 -0
  206. package/components/rating/rating.d.ts +33 -0
  207. package/components/rating/rating.js +9 -130
  208. package/components/richtext/class.js +87 -0
  209. package/components/richtext/richtext.a2ui.json +7 -1
  210. package/components/richtext/richtext.d.ts +19 -0
  211. package/components/richtext/richtext.js +8 -68
  212. package/components/richtext/richtext.yaml +30 -65
  213. package/components/row/class.js +50 -0
  214. package/components/row/row.d.ts +27 -0
  215. package/components/row/row.js +10 -33
  216. package/components/search/class.js +134 -0
  217. package/components/search/search.d.ts +35 -0
  218. package/components/search/search.js +10 -117
  219. package/components/segment/class.js +62 -0
  220. package/components/segment/segment.d.ts +25 -0
  221. package/components/segment/segment.js +10 -45
  222. package/components/segmented/class.js +165 -0
  223. package/components/segmented/segmented.a2ui.json +4 -0
  224. package/components/segmented/segmented.d.ts +24 -0
  225. package/components/segmented/segmented.js +10 -148
  226. package/components/segmented/segmented.yaml +41 -59
  227. package/components/select/class.js +408 -0
  228. package/components/select/select.d.ts +57 -0
  229. package/components/select/select.js +15 -396
  230. package/components/skeleton/class.js +52 -0
  231. package/components/skeleton/skeleton.d.ts +23 -0
  232. package/components/skeleton/skeleton.js +8 -34
  233. package/components/slider/class.js +184 -0
  234. package/components/slider/slider.d.ts +31 -0
  235. package/components/slider/slider.js +9 -166
  236. package/components/stack/class.js +28 -0
  237. package/components/stack/stack.d.ts +17 -0
  238. package/components/stack/stack.js +10 -11
  239. package/components/step-progress/class.js +98 -0
  240. package/components/step-progress/step-progress.d.ts +27 -0
  241. package/components/step-progress/step-progress.js +8 -79
  242. package/components/stepper/class.js +126 -0
  243. package/components/stepper/stepper.d.ts +19 -0
  244. package/components/stepper/stepper.js +9 -112
  245. package/components/stream/class.js +109 -0
  246. package/components/stream/stream.d.ts +19 -0
  247. package/components/stream/stream.js +8 -90
  248. package/components/swatch/class.js +131 -0
  249. package/components/swatch/swatch.d.ts +28 -0
  250. package/components/swatch/swatch.js +8 -112
  251. package/components/swiper/class.js +373 -0
  252. package/components/swiper/swiper.a2ui.json +4 -0
  253. package/components/swiper/swiper.d.ts +31 -0
  254. package/components/swiper/swiper.js +8 -354
  255. package/components/swiper/swiper.yaml +68 -212
  256. package/components/switch/class.js +63 -0
  257. package/components/switch/switch.a2ui.json +6 -1
  258. package/components/switch/switch.d.ts +30 -0
  259. package/components/switch/switch.js +11 -47
  260. package/components/switch/switch.yaml +70 -265
  261. package/components/table/class.js +1453 -0
  262. package/components/table/table.d.ts +37 -0
  263. package/components/table/table.js +8 -1435
  264. package/components/table-toolbar/class.js +680 -0
  265. package/components/table-toolbar/table-toolbar.d.ts +33 -0
  266. package/components/table-toolbar/table-toolbar.js +8 -689
  267. package/components/tabs/class.js +242 -0
  268. package/components/tabs/tabs.d.ts +21 -0
  269. package/components/tabs/tabs.js +8 -223
  270. package/components/tag/class.js +99 -0
  271. package/components/tag/tag.d.ts +27 -0
  272. package/components/tag/tag.js +8 -80
  273. package/components/text/class.js +46 -0
  274. package/components/text/text.d.ts +25 -0
  275. package/components/text/text.js +9 -28
  276. package/components/textarea/class.js +134 -0
  277. package/components/textarea/textarea.d.ts +31 -0
  278. package/components/textarea/textarea.js +11 -118
  279. package/components/timeline/class.js +176 -0
  280. package/components/timeline/timeline.d.ts +19 -0
  281. package/components/timeline/timeline.js +9 -162
  282. package/components/toast/class.js +92 -0
  283. package/components/toast/toast.d.ts +23 -0
  284. package/components/toast/toast.js +9 -76
  285. package/components/toggle-group/class.js +154 -0
  286. package/components/toggle-group/toggle-group.d.ts +19 -0
  287. package/components/toggle-group/toggle-group.js +11 -140
  288. package/components/toggle-scheme/class.js +286 -0
  289. package/components/toggle-scheme/toggle-scheme.a2ui.json +197 -0
  290. package/components/toggle-scheme/toggle-scheme.css +20 -0
  291. package/components/toggle-scheme/toggle-scheme.d.ts +41 -0
  292. package/components/toggle-scheme/toggle-scheme.js +17 -0
  293. package/components/toggle-scheme/toggle-scheme.yaml +173 -0
  294. package/components/toolbar/class.js +388 -0
  295. package/components/toolbar/toolbar.d.ts +23 -0
  296. package/components/toolbar/toolbar.js +10 -376
  297. package/components/tooltip/class.js +299 -0
  298. package/components/tooltip/tooltip.d.ts +27 -0
  299. package/components/tooltip/tooltip.js +8 -280
  300. package/components/tree/class.js +245 -0
  301. package/components/tree/tree.d.ts +15 -0
  302. package/components/tree/tree.js +9 -244
  303. package/components/upload/class.js +199 -0
  304. package/components/upload/upload.d.ts +27 -0
  305. package/components/upload/upload.js +11 -183
  306. package/core/element.d.ts +174 -0
  307. package/core/form.d.ts +108 -0
  308. package/core/index.d.ts +11 -0
  309. package/core/index.js +1 -0
  310. package/core/register.d.ts +25 -0
  311. package/core/register.js +58 -0
  312. package/core/signals.d.ts +94 -0
  313. package/core/template.d.ts +70 -0
  314. package/index.d.ts +315 -0
  315. package/package.json +25 -6
  316. package/traits/CATEGORIES.md +1 -1
@@ -0,0 +1,550 @@
1
+ /**
2
+ * Non-side-effect class export for `<color-picker-ui>`.
3
+ *
4
+ * Importing this file gives you the class(es) without auto-registering the tag.
5
+ * Useful for test isolation, subclassing with tag-name override, or selective
6
+ * composition.
7
+ *
8
+ * The auto-register path stays at `@adia-ai/web-components/components/color-picker`
9
+ * (which imports this file + calls `defineIfFree()`).
10
+ *
11
+ * @see ../../USAGE.md#registration--auto-vs-explicit
12
+ */
13
+
14
+ /**
15
+ * <color-picker-ui value="#3b82f6" format="hex"></color-picker-ui>
16
+ *
17
+ * OKLCH-native color picker with 2D area + H/C/L sliders.
18
+ *
19
+ * Layout:
20
+ * [═══════════ color area (chroma x lightness) ═══════════]
21
+ * H [────────────── slider ──────────────] 260
22
+ * C [────────────── slider ──────────────] 0.188
23
+ * L [────────────── slider ──────────────] 0.623
24
+ * [───────────────── hue track ──────────────────]
25
+ * oklch(0.62 0.188 260) [copy]
26
+ * #3b82f6 [copy]
27
+ *
28
+ * Dogfoods: slider-ui (H/C/L channels)
29
+ * Form-associated via UIFormElement + ElementInternals.
30
+ */
31
+
32
+ import { UIFormElement } from '../../core/form.js';
33
+
34
+ // ── OKLCH ↔ sRGB conversion ──────────────────────────────
35
+
36
+ const MAX_CHROMA = 0.4;
37
+
38
+ function oklchToOklab(L, C, H) {
39
+ const hRad = H * Math.PI / 180;
40
+ return { L, a: C * Math.cos(hRad), b: C * Math.sin(hRad) };
41
+ }
42
+
43
+ function oklabToLinearSrgb(L, a, b) {
44
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
45
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
46
+ const s_ = L - 0.0894841775 * a - 1.2914855480 * b;
47
+ const l = l_ * l_ * l_;
48
+ const m = m_ * m_ * m_;
49
+ const s = s_ * s_ * s_;
50
+ return [
51
+ +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
52
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
53
+ -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
54
+ ];
55
+ }
56
+
57
+ function linearToSrgb(c) {
58
+ return c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
59
+ }
60
+
61
+ function srgbToLinear(c) {
62
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
63
+ }
64
+
65
+ function oklchToRgb(L, C, H) {
66
+ const { a, b } = oklchToOklab(L, C, H);
67
+ const [lr, lg, lb] = oklabToLinearSrgb(L, a, b);
68
+ return [
69
+ Math.max(0, Math.min(1, linearToSrgb(lr))),
70
+ Math.max(0, Math.min(1, linearToSrgb(lg))),
71
+ Math.max(0, Math.min(1, linearToSrgb(lb))),
72
+ ];
73
+ }
74
+
75
+ function rgbToHex(r, g, b) {
76
+ const h = c => Math.round(c * 255).toString(16).padStart(2, '0');
77
+ return `#${h(r)}${h(g)}${h(b)}`;
78
+ }
79
+
80
+ function oklchToHex(L, C, H) {
81
+ const [r, g, b] = oklchToRgb(L, C, H);
82
+ return rgbToHex(r, g, b);
83
+ }
84
+
85
+ function hexToOklch(hex) {
86
+ hex = hex.replace('#', '');
87
+ if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
88
+ const r = srgbToLinear(parseInt(hex.slice(0,2),16)/255);
89
+ const g = srgbToLinear(parseInt(hex.slice(2,4),16)/255);
90
+ const b = srgbToLinear(parseInt(hex.slice(4,6),16)/255);
91
+ const l_ = Math.cbrt(0.4122214708*r + 0.5363325363*g + 0.0514459929*b);
92
+ const m_ = Math.cbrt(0.2119034982*r + 0.6806995451*g + 0.1073969566*b);
93
+ const s_ = Math.cbrt(0.0883024619*r + 0.2817188376*g + 0.6299787005*b);
94
+ const L = 0.2104542553*l_ + 0.7936177850*m_ - 0.0040720468*s_;
95
+ const a = 1.9779984951*l_ - 2.4285922050*m_ + 0.4505937099*s_;
96
+ const bv = 0.0259040371*l_ + 0.7827717662*m_ - 0.8086757660*s_;
97
+ const C = Math.sqrt(a*a + bv*bv);
98
+ let H = Math.atan2(bv, a) * 180 / Math.PI;
99
+ if (H < 0) H += 360;
100
+ return { L, C, H };
101
+ }
102
+
103
+ function isInGamut(r, g, b) {
104
+ const e = 0.001;
105
+ return r >= -e && r <= 1+e && g >= -e && g <= 1+e && b >= -e && b <= 1+e;
106
+ }
107
+
108
+ function gamutMapChroma(L, C, H) {
109
+ const { a, b } = oklchToOklab(L, C, H);
110
+ const [lr, lg, lb] = oklabToLinearSrgb(L, a, b);
111
+ if (isInGamut(linearToSrgb(lr), linearToSrgb(lg), linearToSrgb(lb))) return C;
112
+ let lo = 0, hi = C;
113
+ for (let i = 0; i < 8; i++) {
114
+ const mid = (lo + hi) / 2;
115
+ const { a: ma, b: mb } = oklchToOklab(L, mid, H);
116
+ const [mr, mg, mbb] = oklabToLinearSrgb(L, ma, mb);
117
+ if (isInGamut(linearToSrgb(mr), linearToSrgb(mg), linearToSrgb(mbb))) lo = mid;
118
+ else hi = mid;
119
+ }
120
+ return lo;
121
+ }
122
+
123
+ // ── Component ────────────────────────────────────────────
124
+
125
+ export class UIColorPicker extends UIFormElement {
126
+ static properties = {
127
+ ...UIFormElement.properties,
128
+ value: { type: String, default: '#3b82f6', reflect: true },
129
+ format: { type: String, default: 'hex', reflect: true },
130
+ disabled: { type: Boolean, default: false, reflect: true },
131
+ };
132
+
133
+ #L = 0.6; #C = 0.15; #H = 230;
134
+ #bound = false;
135
+ #dragging = null; // 'area' | 'hue' | null
136
+ #internalUpdate = false;
137
+
138
+ // DOM refs
139
+ #canvas = null;
140
+ #ctx = null;
141
+ #areaEl = null;
142
+ #areaThumb = null;
143
+ #hueTrack = null;
144
+ #hueThumb = null;
145
+ #oklchVal = null;
146
+ #hexVal = null;
147
+ #areaResizeObs = null;
148
+ #hueSlider = null;
149
+ #chromaSlider = null;
150
+ #lightnessSlider = null;
151
+ #copyButtons = []; // [{ el, handler }]
152
+ #copyTimer = null;
153
+
154
+ static template = () => null;
155
+
156
+ // ── Stable listeners ──
157
+
158
+ #onSliderInput = (e) => {
159
+ const slider = e.currentTarget;
160
+ const channel = slider.getAttribute('data-channel');
161
+ if (channel === 'hue') this.#H = +slider.value;
162
+ else if (channel === 'chroma') this.#C = +slider.value;
163
+ else if (channel === 'lightness') this.#L = +slider.value;
164
+ this.#commit('input');
165
+ };
166
+
167
+ #onAreaPointerDown = (e) => {
168
+ if (this.disabled) return;
169
+ this.#dragging = 'area';
170
+ this.#areaEl.setPointerCapture(e.pointerId);
171
+ this.#updateAreaFromPointer(e);
172
+ };
173
+
174
+ #onAreaPointerMove = (e) => {
175
+ if (this.#dragging === 'area') this.#updateAreaFromPointer(e);
176
+ };
177
+
178
+ #onAreaPointerUp = () => {
179
+ if (this.#dragging === 'area') { this.#dragging = null; this.#commit('change'); }
180
+ };
181
+
182
+ #onHuePointerDown = (e) => {
183
+ if (this.disabled) return;
184
+ this.#dragging = 'hue';
185
+ this.#hueTrack.setPointerCapture(e.pointerId);
186
+ this.#updateHueFromPointer(e);
187
+ };
188
+
189
+ #onHuePointerMove = (e) => {
190
+ if (this.#dragging === 'hue') this.#updateHueFromPointer(e);
191
+ };
192
+
193
+ #onHuePointerUp = () => {
194
+ if (this.#dragging === 'hue') { this.#dragging = null; this.#commit('change'); }
195
+ };
196
+
197
+ #onAreaKeydown = (e) => {
198
+ if (this.disabled) return;
199
+ const step = e.shiftKey ? 0.05 : 0.01;
200
+ const cStep = e.shiftKey ? 0.04 : 0.004;
201
+ switch (e.key) {
202
+ case 'ArrowRight': e.preventDefault(); this.#C = Math.min(MAX_CHROMA, this.#C + cStep); break;
203
+ case 'ArrowLeft': e.preventDefault(); this.#C = Math.max(0, this.#C - cStep); break;
204
+ case 'ArrowUp': e.preventDefault(); this.#L = Math.min(1, this.#L + step); break;
205
+ case 'ArrowDown': e.preventDefault(); this.#L = Math.max(0, this.#L - step); break;
206
+ default: return;
207
+ }
208
+ this.#commit('change');
209
+ };
210
+
211
+ #onHueKeydown = (e) => {
212
+ if (this.disabled) return;
213
+ const step = e.shiftKey ? 10 : 1;
214
+ switch (e.key) {
215
+ case 'ArrowRight': case 'ArrowUp': e.preventDefault(); this.#H = (this.#H + step) % 360; break;
216
+ case 'ArrowLeft': case 'ArrowDown': e.preventDefault(); this.#H = (this.#H - step + 360) % 360; break;
217
+ default: return;
218
+ }
219
+ this.#commit('change');
220
+ };
221
+
222
+ connected() {
223
+ super.connected();
224
+ this.#parseValue(this.value);
225
+
226
+ if (this.#bound) return;
227
+ this.#bound = true;
228
+
229
+ // ── Build DOM ──
230
+
231
+ // Color area
232
+ this.#areaEl = document.createElement('div');
233
+ this.#areaEl.setAttribute('data-area', '');
234
+ this.#areaEl.setAttribute('tabindex', '0');
235
+ this.#areaEl.setAttribute('role', 'slider');
236
+ this.#areaEl.setAttribute('aria-label', 'Color area: chroma and lightness');
237
+
238
+ this.#canvas = document.createElement('canvas');
239
+ this.#canvas.style.cssText = 'width:100%;height:100%;display:block;border-radius:inherit;';
240
+ this.#areaEl.appendChild(this.#canvas);
241
+ this.#ctx = this.#canvas.getContext('2d', { willReadFrequently: true });
242
+
243
+ this.#areaThumb = document.createElement('div');
244
+ this.#areaThumb.setAttribute('data-area-thumb', '');
245
+ this.#areaEl.appendChild(this.#areaThumb);
246
+
247
+ this.appendChild(this.#areaEl);
248
+
249
+ // Slider rows
250
+ const sliders = document.createElement('div');
251
+ sliders.setAttribute('data-sliders', '');
252
+
253
+ const makeSlider = (label, channel, min, max, step) => {
254
+ const slider = document.createElement('slider-ui');
255
+ slider.setAttribute('data-channel', channel);
256
+ slider.setAttribute('label', label);
257
+ slider.setAttribute('min', min);
258
+ slider.setAttribute('max', max);
259
+ slider.setAttribute('step', step);
260
+
261
+ slider.addEventListener('input', this.#onSliderInput);
262
+
263
+ sliders.appendChild(slider);
264
+ return slider;
265
+ };
266
+
267
+ this.#hueSlider = makeSlider('H', 'hue', 0, 360, 1);
268
+ this.#chromaSlider = makeSlider('C', 'chroma', 0, MAX_CHROMA, 0.001);
269
+ this.#lightnessSlider = makeSlider('L', 'lightness', 0, 1, 0.001);
270
+ this.appendChild(sliders);
271
+
272
+ // Hue gradient track
273
+ this.#hueTrack = document.createElement('div');
274
+ this.#hueTrack.setAttribute('data-hue-track', '');
275
+ this.#hueTrack.setAttribute('tabindex', '0');
276
+ this.#hueTrack.setAttribute('role', 'slider');
277
+ this.#hueTrack.setAttribute('aria-label', 'Hue');
278
+ this.#hueThumb = document.createElement('div');
279
+ this.#hueThumb.setAttribute('data-hue-thumb', '');
280
+ this.#hueTrack.appendChild(this.#hueThumb);
281
+ this.appendChild(this.#hueTrack);
282
+
283
+ // Output rows
284
+ const output = document.createElement('div');
285
+ output.setAttribute('data-output', '');
286
+
287
+ const makeOutput = (format) => {
288
+ const group = document.createElement('div');
289
+ group.setAttribute('data-output-group', '');
290
+
291
+ const val = document.createElement('span');
292
+ val.setAttribute('data-output-value', '');
293
+ val.setAttribute('data-format', format);
294
+
295
+ const btn = document.createElement('div');
296
+ btn.setAttribute('role', 'button');
297
+ btn.setAttribute('tabindex', '0');
298
+ btn.setAttribute('data-copy', format);
299
+ btn.setAttribute('aria-label', `Copy ${format}`);
300
+ btn.innerHTML = '<icon-ui name="copy"></icon-ui>';
301
+ const handler = () => this.#copy(format, btn);
302
+ btn.addEventListener('click', handler);
303
+ this.#copyButtons.push({ el: btn, handler });
304
+
305
+ group.appendChild(val);
306
+ group.appendChild(btn);
307
+ output.appendChild(group);
308
+ return val;
309
+ };
310
+
311
+ this.#oklchVal = makeOutput('oklch');
312
+ this.#hexVal = makeOutput('hex');
313
+ this.appendChild(output);
314
+
315
+ // ── Area drag ──
316
+
317
+ this.#areaEl.addEventListener('pointerdown', this.#onAreaPointerDown);
318
+ this.#areaEl.addEventListener('pointermove', this.#onAreaPointerMove);
319
+ this.#areaEl.addEventListener('pointerup', this.#onAreaPointerUp);
320
+
321
+ // ── Hue track drag ──
322
+
323
+ this.#hueTrack.addEventListener('pointerdown', this.#onHuePointerDown);
324
+ this.#hueTrack.addEventListener('pointermove', this.#onHuePointerMove);
325
+ this.#hueTrack.addEventListener('pointerup', this.#onHuePointerUp);
326
+
327
+ // ── Keyboard on area ──
328
+
329
+ this.#areaEl.addEventListener('keydown', this.#onAreaKeydown);
330
+
331
+ // ── Keyboard on hue ──
332
+
333
+ this.#hueTrack.addEventListener('keydown', this.#onHueKeydown);
334
+
335
+ // Observe area resize to redraw canvas
336
+ this.#areaResizeObs = new ResizeObserver(() => this.#drawArea());
337
+ this.#areaResizeObs.observe(this.#areaEl);
338
+ }
339
+
340
+ disconnected() {
341
+ super.disconnected();
342
+ if (this.#copyTimer != null) {
343
+ clearTimeout(this.#copyTimer);
344
+ this.#copyTimer = null;
345
+ }
346
+ if (this.#areaResizeObs) {
347
+ this.#areaResizeObs.disconnect();
348
+ this.#areaResizeObs = null;
349
+ }
350
+ if (this.#areaEl) {
351
+ this.#areaEl.removeEventListener('pointerdown', this.#onAreaPointerDown);
352
+ this.#areaEl.removeEventListener('pointermove', this.#onAreaPointerMove);
353
+ this.#areaEl.removeEventListener('pointerup', this.#onAreaPointerUp);
354
+ this.#areaEl.removeEventListener('keydown', this.#onAreaKeydown);
355
+ }
356
+ if (this.#hueTrack) {
357
+ this.#hueTrack.removeEventListener('pointerdown', this.#onHuePointerDown);
358
+ this.#hueTrack.removeEventListener('pointermove', this.#onHuePointerMove);
359
+ this.#hueTrack.removeEventListener('pointerup', this.#onHuePointerUp);
360
+ this.#hueTrack.removeEventListener('keydown', this.#onHueKeydown);
361
+ }
362
+ this.#hueSlider?.removeEventListener('input', this.#onSliderInput);
363
+ this.#chromaSlider?.removeEventListener('input', this.#onSliderInput);
364
+ this.#lightnessSlider?.removeEventListener('input', this.#onSliderInput);
365
+ for (const { el, handler } of this.#copyButtons) {
366
+ el.removeEventListener('click', handler);
367
+ }
368
+ this.#copyButtons = [];
369
+ this.#bound = false;
370
+ }
371
+
372
+ // ── Pointer helpers ──
373
+
374
+ #updateAreaFromPointer(e) {
375
+ const rect = this.#areaEl.getBoundingClientRect();
376
+ const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
377
+ const y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height));
378
+ this.#C = x * MAX_CHROMA;
379
+ this.#L = 1 - y;
380
+ this.#commit('input');
381
+ }
382
+
383
+ #updateHueFromPointer(e) {
384
+ const rect = this.#hueTrack.getBoundingClientRect();
385
+ const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
386
+ this.#H = x * 360;
387
+ this.#commit('input');
388
+ }
389
+
390
+ // ── Value parsing ──
391
+
392
+ #parseValue(val) {
393
+ if (!val) return;
394
+ if (val.startsWith('#')) {
395
+ const o = hexToOklch(val);
396
+ this.#L = o.L; this.#C = o.C; this.#H = o.H;
397
+ } else if (val.startsWith('oklch(')) {
398
+ const m = val.match(/oklch\(\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)/);
399
+ if (m) { this.#L = +m[1]; this.#C = +m[2]; this.#H = +m[3]; }
400
+ }
401
+ }
402
+
403
+ // ── Commit: update value + fire events ──
404
+
405
+ #commit(eventType) {
406
+ const clampedC = gamutMapChroma(this.#L, this.#C, this.#H);
407
+ const hexVal = oklchToHex(this.#L, clampedC, this.#H);
408
+ const oklchStr = this.#oklchStr();
409
+
410
+ this.#internalUpdate = true;
411
+ this.value = this.format === 'oklch' ? oklchStr : hexVal;
412
+ this.syncValue();
413
+
414
+ const detail = { value: this.value, l: this.#L, c: this.#C, h: this.#H, hex: hexVal, oklch: oklchStr };
415
+ this.dispatchEvent(new CustomEvent(eventType, { bubbles: true, detail }));
416
+ }
417
+
418
+ #oklchStr() {
419
+ return `oklch(${this.#L.toFixed(2)} ${this.#C.toFixed(3)} ${Math.round(this.#H)})`;
420
+ }
421
+
422
+ // ── Copy ──
423
+
424
+ #copy(fmt, btn) {
425
+ const text = fmt === 'oklch'
426
+ ? this.#oklchStr()
427
+ : oklchToHex(this.#L, gamutMapChroma(this.#L, this.#C, this.#H), this.#H);
428
+
429
+ const iconEl = btn.querySelector('icon-ui');
430
+ const onDone = (ok) => {
431
+ if (iconEl) {
432
+ iconEl.setAttribute('name', ok ? 'check' : 'warning');
433
+ if (this.#copyTimer != null) clearTimeout(this.#copyTimer);
434
+ this.#copyTimer = setTimeout(() => {
435
+ this.#copyTimer = null;
436
+ iconEl.setAttribute('name', 'copy');
437
+ }, 1500);
438
+ }
439
+ };
440
+
441
+ if (navigator.clipboard?.writeText) {
442
+ navigator.clipboard.writeText(text).then(
443
+ () => { if (!this.isConnected) return; onDone(true); },
444
+ () => { if (!this.isConnected) return; onDone(false); },
445
+ );
446
+ } else {
447
+ const ta = Object.assign(document.createElement('textarea'), { value: text });
448
+ ta.style.cssText = 'position:fixed;opacity:0';
449
+ document.body.appendChild(ta);
450
+ ta.select();
451
+ onDone(document.execCommand('copy'));
452
+ ta.remove();
453
+ }
454
+ }
455
+
456
+ // ── Canvas rendering ──
457
+
458
+ #drawArea() {
459
+ if (!this.#canvas || !this.#ctx || !this.#areaEl) return;
460
+
461
+ const dpr = Math.min(window.devicePixelRatio || 1, 2) * 0.5;
462
+ const w = Math.round(this.#areaEl.clientWidth * dpr);
463
+ const h = Math.round(this.#areaEl.clientHeight * dpr);
464
+ if (w <= 0 || h <= 0) return;
465
+
466
+ this.#canvas.width = w;
467
+ this.#canvas.height = h;
468
+
469
+ const img = this.#ctx.createImageData(w, h);
470
+ const data = img.data;
471
+ const hue = this.#H;
472
+
473
+ for (let y = 0; y < h; y++) {
474
+ const L = 1 - y / (h - 1);
475
+ for (let x = 0; x < w; x++) {
476
+ const C = (x / (w - 1)) * MAX_CHROMA;
477
+ const clampedC = gamutMapChroma(L, C, hue);
478
+ const [r, g, b] = oklchToRgb(L, clampedC, hue);
479
+ const i = (y * w + x) * 4;
480
+ data[i] = Math.round(r * 255);
481
+ data[i + 1] = Math.round(g * 255);
482
+ data[i + 2] = Math.round(b * 255);
483
+ data[i + 3] = 255;
484
+ }
485
+ }
486
+
487
+ this.#ctx.putImageData(img, 0, 0);
488
+ }
489
+
490
+ // ── Render ──
491
+
492
+ render() {
493
+ if (this.#internalUpdate) {
494
+ this.#internalUpdate = false;
495
+ } else {
496
+ this.#parseValue(this.value);
497
+ }
498
+
499
+ this.#drawArea();
500
+
501
+ // Area thumb
502
+ if (this.#areaThumb) {
503
+ const xPct = (this.#C / MAX_CHROMA) * 100;
504
+ const yPct = (1 - this.#L) * 100;
505
+ this.#areaThumb.style.left = `${xPct}%`;
506
+ this.#areaThumb.style.top = `${yPct}%`;
507
+ this.#areaThumb.style.background = oklchToHex(this.#L, gamutMapChroma(this.#L, this.#C, this.#H), this.#H);
508
+ }
509
+
510
+ // Hue track gradient
511
+ if (this.#hueTrack) {
512
+ const stops = [];
513
+ for (let h = 0; h <= 360; h += 30) stops.push(oklchToHex(0.7, 0.15, h));
514
+ this.#hueTrack.style.background = `linear-gradient(to right, ${stops.join(', ')})`;
515
+ this.#hueTrack.setAttribute('aria-valuenow', String(Math.round(this.#H)));
516
+ }
517
+
518
+ // Hue thumb
519
+ if (this.#hueThumb) {
520
+ this.#hueThumb.style.left = `${(this.#H / 360) * 100}%`;
521
+ this.#hueThumb.style.background = oklchToHex(0.7, 0.15, this.#H);
522
+ }
523
+
524
+ // ARIA
525
+ if (this.#areaEl) {
526
+ this.#areaEl.setAttribute('aria-valuetext', this.#oklchStr());
527
+ }
528
+
529
+ // Slider values
530
+ const hueSlider = this.querySelector('slider-ui[data-channel="hue"]');
531
+ const chromaSlider = this.querySelector('slider-ui[data-channel="chroma"]');
532
+ const lightnessSlider = this.querySelector('slider-ui[data-channel="lightness"]');
533
+ if (hueSlider) hueSlider.value = Math.round(this.#H);
534
+ if (chromaSlider) chromaSlider.value = +this.#C.toFixed(3);
535
+ if (lightnessSlider) lightnessSlider.value = +this.#L.toFixed(3);
536
+
537
+ // Disabled
538
+ if (this.disabled) this.setAttribute('aria-disabled', 'true');
539
+ else this.removeAttribute('aria-disabled');
540
+
541
+ // Output values
542
+ const hex = oklchToHex(this.#L, gamutMapChroma(this.#L, this.#C, this.#H), this.#H);
543
+ if (this.#oklchVal) this.#oklchVal.textContent = this.#oklchStr();
544
+ if (this.#hexVal) this.#hexVal.textContent = hex;
545
+ }
546
+
547
+ onFormReset() {
548
+ this.value = '#3b82f6';
549
+ }
550
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * `<color-picker-ui>` — Color picker with hex / rgb / hsl / oklch formats.
3
+ *
4
+ * @see https://ui-kit.exe.xyz/site/components/color-picker
5
+ */
6
+
7
+ import { UIFormElement } from '../../core/form.js';
8
+
9
+ export type ColorFormat = 'hex' | 'rgb' | 'hsl' | 'oklch';
10
+
11
+ export interface ColorPickerChangeEventDetail {
12
+ value: string;
13
+ format: ColorFormat;
14
+ }
15
+ export type ColorPickerChangeEvent = CustomEvent<ColorPickerChangeEventDetail>;
16
+ export type ColorPickerInputEvent = CustomEvent<ColorPickerChangeEventDetail>;
17
+
18
+ export interface ColorPickerFormatChangeEventDetail {
19
+ format: ColorFormat;
20
+ }
21
+ export type ColorPickerFormatChangeEvent = CustomEvent<ColorPickerFormatChangeEventDetail>;
22
+
23
+ export class UIColorPicker extends UIFormElement {
24
+ /** Current color as a string in the active `format`. */
25
+ value: string;
26
+ /** Output format. */
27
+ format: ColorFormat;
28
+
29
+ addEventListener<K extends keyof HTMLElementEventMap>(
30
+ type: K,
31
+ listener: (this: UIColorPicker, ev: HTMLElementEventMap[K]) => unknown,
32
+ options?: boolean | AddEventListenerOptions,
33
+ ): void;
34
+ addEventListener(type: 'change', listener: (ev: ColorPickerChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
35
+ addEventListener(type: 'input', listener: (ev: ColorPickerInputEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
36
+ addEventListener(type: 'format-change', listener: (ev: ColorPickerFormatChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
37
+ }