@kenlo/sds-design-system 0.9.9

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 (246) hide show
  1. package/README.md +129 -0
  2. package/components/Accordion/Accordion.css +186 -0
  3. package/components/Accordion/accordion-variants.js +17 -0
  4. package/components/Accordion/accordion.marko +264 -0
  5. package/components/AccordionGroup/AccordionGroup.css +35 -0
  6. package/components/AccordionGroup/accordion-group.marko +17 -0
  7. package/components/Alert/Alert.css +104 -0
  8. package/components/Alert/alert-variants.js +35 -0
  9. package/components/Alert/alert.marko +94 -0
  10. package/components/AttachmentList/AttachmentList.css +92 -0
  11. package/components/AttachmentList/attachment-list.marko +51 -0
  12. package/components/AttachmentList/attachment-row.marko +167 -0
  13. package/components/Avatar/Avatar.css +110 -0
  14. package/components/Avatar/avatar-variants.js +12 -0
  15. package/components/Avatar/avatar.marko +71 -0
  16. package/components/Badge/Badge.css +381 -0
  17. package/components/Badge/badge-dismissible.marko +143 -0
  18. package/components/Badge/badge-variants.js +35 -0
  19. package/components/Badge/badge.marko +134 -0
  20. package/components/BottomSheet/BottomSheet.css +300 -0
  21. package/components/BottomSheet/bottom-sheet.marko +340 -0
  22. package/components/BulkActionBar/BulkActionBar.css +88 -0
  23. package/components/BulkActionBar/bulk-action-bar.marko +186 -0
  24. package/components/Button/Button.css +340 -0
  25. package/components/Button/button-variants.js +25 -0
  26. package/components/Button/button.marko +139 -0
  27. package/components/Card/Card.css +391 -0
  28. package/components/Card/card-body.marko +5 -0
  29. package/components/Card/card-collapsible.marko +94 -0
  30. package/components/Card/card-footer.marko +12 -0
  31. package/components/Card/card-header.marko +30 -0
  32. package/components/Card/card-info-pair.marko +17 -0
  33. package/components/Card/card-info-row.marko +12 -0
  34. package/components/Card/card-section.marko +14 -0
  35. package/components/Card/card-variants.js +24 -0
  36. package/components/Card/card.marko +124 -0
  37. package/components/Checkbox/Checkbox.css +249 -0
  38. package/components/Checkbox/checkbox-variants.js +21 -0
  39. package/components/Checkbox/checkbox.marko +128 -0
  40. package/components/ColorPicker/ColorPicker.css +253 -0
  41. package/components/ColorPicker/color-maths.js +104 -0
  42. package/components/ColorPicker/color-picker.marko +474 -0
  43. package/components/DatePicker/DatePicker.css +429 -0
  44. package/components/DatePicker/date-logic.js +225 -0
  45. package/components/DatePicker/date-picker.marko +492 -0
  46. package/components/Drawer/Drawer.css +174 -0
  47. package/components/Drawer/drawer.marko +262 -0
  48. package/components/Floating/Floating.css +28 -0
  49. package/components/Floating/floating-position.js +251 -0
  50. package/components/Floating/floating.marko +291 -0
  51. package/components/Form/Form.css +15 -0
  52. package/components/Form/form-context.js +69 -0
  53. package/components/Form/form.marko +73 -0
  54. package/components/GradientPicker/GradientPicker.css +179 -0
  55. package/components/GradientPicker/gradient-picker.marko +398 -0
  56. package/components/Grid/Grid.css +161 -0
  57. package/components/Grid/col.marko +36 -0
  58. package/components/Grid/grid-variants.js +43 -0
  59. package/components/Grid/grid.marko +25 -0
  60. package/components/Header/Header.css +457 -0
  61. package/components/Header/header.marko +523 -0
  62. package/components/Icon/Icon.css +44 -0
  63. package/components/Icon/icon-colors.js +31 -0
  64. package/components/Icon/icon.marko +56 -0
  65. package/components/IconButton/IconButton.css +85 -0
  66. package/components/IconButton/icon-button-variants.js +18 -0
  67. package/components/IconButton/icon-button.marko +103 -0
  68. package/components/Incrementer/Incrementer.css +94 -0
  69. package/components/Incrementer/incrementer.marko +70 -0
  70. package/components/Input/Input.css +547 -0
  71. package/components/Input/input-countries.js +109 -0
  72. package/components/Input/input-masks.js +218 -0
  73. package/components/Input/input-types.js +194 -0
  74. package/components/Input/input.marko +530 -0
  75. package/components/Menu/Menu.css +651 -0
  76. package/components/Menu/menu-variants.js +89 -0
  77. package/components/Menu/menu.marko +560 -0
  78. package/components/Modal/Modal.css +160 -0
  79. package/components/Modal/modal.marko +296 -0
  80. package/components/Nav/Nav.css +298 -0
  81. package/components/Nav/nav-variants.js +23 -0
  82. package/components/Nav/nav.marko +164 -0
  83. package/components/Notification/Notification.css +78 -0
  84. package/components/Notification/notification-variants.js +30 -0
  85. package/components/Notification/notification.marko +55 -0
  86. package/components/Pagination/Pagination.css +78 -0
  87. package/components/Pagination/pagination-pages.js +66 -0
  88. package/components/Pagination/pagination.marko +141 -0
  89. package/components/Pill/Pill.css +193 -0
  90. package/components/Pill/pill-variants.js +16 -0
  91. package/components/Pill/pill.marko +165 -0
  92. package/components/Popover/Popover.css +121 -0
  93. package/components/Popover/popover.marko +96 -0
  94. package/components/Radio/Radio.css +230 -0
  95. package/components/Radio/radio-variants.js +16 -0
  96. package/components/Radio/radio.marko +93 -0
  97. package/components/RadioGroup/RadioGroup.css +32 -0
  98. package/components/RadioGroup/radio-group.marko +93 -0
  99. package/components/SearchBar/SearchBar.css +181 -0
  100. package/components/SearchBar/search-bar-variants.js +24 -0
  101. package/components/SearchBar/search-bar.marko +157 -0
  102. package/components/Select/Select.css +427 -0
  103. package/components/Select/select-options.js +163 -0
  104. package/components/Select/select.marko +449 -0
  105. package/components/Separator/Separator.css +22 -0
  106. package/components/Separator/separator.marko +14 -0
  107. package/components/Skeleton/Skeleton.css +100 -0
  108. package/components/Skeleton/skeleton-avatar.marko +28 -0
  109. package/components/Skeleton/skeleton-button.marko +22 -0
  110. package/components/Skeleton/skeleton-image.marko +20 -0
  111. package/components/Skeleton/skeleton-input.marko +22 -0
  112. package/components/Skeleton/skeleton-variants.js +16 -0
  113. package/components/Skeleton/skeleton.marko +53 -0
  114. package/components/Spinner/Spinner.css +173 -0
  115. package/components/Spinner/spinner-geometry.js +39 -0
  116. package/components/Spinner/spinner-overlay.marko +97 -0
  117. package/components/Spinner/spinner.marko +88 -0
  118. package/components/StatCard/StatCard.css +90 -0
  119. package/components/StatCard/stat-card-group.marko +5 -0
  120. package/components/StatCard/stat-card.marko +57 -0
  121. package/components/Switch/Switch.css +209 -0
  122. package/components/Switch/switch.marko +99 -0
  123. package/components/Table/Table.css +270 -0
  124. package/components/Table/table-cell.marko +28 -0
  125. package/components/Table/table-header.marko +27 -0
  126. package/components/Table/table-logic.js +129 -0
  127. package/components/Table/table.marko +372 -0
  128. package/components/Text/Text.css +79 -0
  129. package/components/Text/text-variants.js +25 -0
  130. package/components/Text/text.marko +48 -0
  131. package/components/TextEditor/TextEditor.css +78 -0
  132. package/components/TextEditor/safe-url.js +50 -0
  133. package/components/TextEditor/text-editor-commands.js +22 -0
  134. package/components/TextEditor/text-editor.marko +253 -0
  135. package/components/Toast/Toast.css +88 -0
  136. package/components/Toast/toast-api.js +19 -0
  137. package/components/Toast/toast-store.js +118 -0
  138. package/components/Toast/toast-variants.js +16 -0
  139. package/components/Toast/toast.marko +93 -0
  140. package/components/Toast/toaster.marko +70 -0
  141. package/components/Toggle/Toggle.css +169 -0
  142. package/components/Toggle/toggle.marko +93 -0
  143. package/components/Tooltip/Tooltip.css +115 -0
  144. package/components/Tooltip/tooltip.marko +77 -0
  145. package/components/Uploader/Uploader.css +180 -0
  146. package/components/Uploader/crop-geometry.js +161 -0
  147. package/components/Uploader/uploader.marko +269 -0
  148. package/foundations/Scrollbar/Scrollbar.css +26 -0
  149. package/foundations/attrs.js +27 -0
  150. package/foundations/breakpoints.js +27 -0
  151. package/foundations/contrast.js +74 -0
  152. package/foundations/custom-scheme.js +222 -0
  153. package/foundations/overlay.js +108 -0
  154. package/foundations/styles.css +284 -0
  155. package/foundations/text.js +22 -0
  156. package/foundations/theme-server.js +81 -0
  157. package/foundations/theme.js +42 -0
  158. package/foundations/tokens.css +1656 -0
  159. package/icons/custom/custom-video-horizontal-filled.marko +6 -0
  160. package/icons/custom/custom-video-horizontal-regular.marko +6 -0
  161. package/icons/custom/custom-video-tour-3d-filled.marko +7 -0
  162. package/icons/custom/custom-video-tour-3d-regular.marko +9 -0
  163. package/icons/custom/custom-video-vertical-filled.marko +6 -0
  164. package/icons/custom/custom-video-vertical-regular.marko +6 -0
  165. package/icons/generated/fluent-add-regular.marko +3 -0
  166. package/icons/generated/fluent-alert-regular.marko +3 -0
  167. package/icons/generated/fluent-apps-regular.marko +3 -0
  168. package/icons/generated/fluent-arrow-download-regular.marko +3 -0
  169. package/icons/generated/fluent-arrow-left-regular.marko +3 -0
  170. package/icons/generated/fluent-arrow-right-regular.marko +3 -0
  171. package/icons/generated/fluent-arrow-rotate-clockwise-regular.marko +3 -0
  172. package/icons/generated/fluent-arrow-rotate-counterclockwise-regular.marko +3 -0
  173. package/icons/generated/fluent-border-all-regular.marko +3 -0
  174. package/icons/generated/fluent-calendar-regular.marko +3 -0
  175. package/icons/generated/fluent-checkmark-circle-filled.marko +3 -0
  176. package/icons/generated/fluent-checkmark-regular.marko +3 -0
  177. package/icons/generated/fluent-chevron-down-regular.marko +3 -0
  178. package/icons/generated/fluent-chevron-left-regular.marko +3 -0
  179. package/icons/generated/fluent-chevron-right-regular.marko +3 -0
  180. package/icons/generated/fluent-chevron-up-regular.marko +3 -0
  181. package/icons/generated/fluent-circle-half-fill-regular.marko +3 -0
  182. package/icons/generated/fluent-clock-regular.marko +3 -0
  183. package/icons/generated/fluent-color-regular.marko +3 -0
  184. package/icons/generated/fluent-crop-regular.marko +3 -0
  185. package/icons/generated/fluent-dark-theme-regular.marko +3 -0
  186. package/icons/generated/fluent-delete-regular.marko +3 -0
  187. package/icons/generated/fluent-desktop-regular.marko +3 -0
  188. package/icons/generated/fluent-dismiss-circle-filled.marko +3 -0
  189. package/icons/generated/fluent-dismiss-regular.marko +3 -0
  190. package/icons/generated/fluent-document-pdf-regular.marko +3 -0
  191. package/icons/generated/fluent-document-regular.marko +3 -0
  192. package/icons/generated/fluent-eye-off-regular.marko +3 -0
  193. package/icons/generated/fluent-eye-regular.marko +3 -0
  194. package/icons/generated/fluent-eyedropper-regular.marko +3 -0
  195. package/icons/generated/fluent-grid-regular.marko +3 -0
  196. package/icons/generated/fluent-history-regular.marko +3 -0
  197. package/icons/generated/fluent-home-regular.marko +3 -0
  198. package/icons/generated/fluent-image-regular.marko +3 -0
  199. package/icons/generated/fluent-info-regular.marko +3 -0
  200. package/icons/generated/fluent-link-regular.marko +3 -0
  201. package/icons/generated/fluent-more-horizontal-regular.marko +3 -0
  202. package/icons/generated/fluent-navigation-regular.marko +3 -0
  203. package/icons/generated/fluent-paint-brush-regular.marko +3 -0
  204. package/icons/generated/fluent-question-circle-regular.marko +3 -0
  205. package/icons/generated/fluent-search-regular.marko +3 -0
  206. package/icons/generated/fluent-settings-regular.marko +3 -0
  207. package/icons/generated/fluent-square-hint-regular.marko +3 -0
  208. package/icons/generated/fluent-subtract-regular.marko +3 -0
  209. package/icons/generated/fluent-text-align-center-regular.marko +3 -0
  210. package/icons/generated/fluent-text-align-left-regular.marko +3 -0
  211. package/icons/generated/fluent-text-align-right-regular.marko +3 -0
  212. package/icons/generated/fluent-text-bold-regular.marko +3 -0
  213. package/icons/generated/fluent-text-bullet-list-regular.marko +3 -0
  214. package/icons/generated/fluent-text-font-regular.marko +3 -0
  215. package/icons/generated/fluent-text-font-size-regular.marko +3 -0
  216. package/icons/generated/fluent-text-italic-regular.marko +3 -0
  217. package/icons/generated/fluent-text-number-list-ltr-regular.marko +3 -0
  218. package/icons/generated/fluent-video-regular.marko +3 -0
  219. package/icons/generated/fluent-warning-filled.marko +3 -0
  220. package/icons/generated/fluent-weather-moon-regular.marko +3 -0
  221. package/icons/generated/fluent-weather-sunny-regular.marko +3 -0
  222. package/icons/generated/fluent-zoom-in-regular.marko +3 -0
  223. package/icons/generated/fluent-zoom-out-regular.marko +3 -0
  224. package/icons/manifest.json +61 -0
  225. package/icons/social/social-facebook-color.marko +7 -0
  226. package/icons/social/social-facebook.marko +6 -0
  227. package/icons/social/social-google-color.marko +20 -0
  228. package/icons/social/social-google.marko +6 -0
  229. package/icons/social/social-instagram-color.marko +16 -0
  230. package/icons/social/social-instagram.marko +6 -0
  231. package/icons/social/social-linkedin-color.marko +8 -0
  232. package/icons/social/social-linkedin.marko +8 -0
  233. package/icons/social/social-messenger-color.marko +15 -0
  234. package/icons/social/social-messenger.marko +6 -0
  235. package/icons/social/social-pinterest-color.marko +6 -0
  236. package/icons/social/social-pinterest.marko +6 -0
  237. package/icons/social/social-telegram-color.marko +7 -0
  238. package/icons/social/social-telegram.marko +6 -0
  239. package/icons/social/social-whatsapp-color.marko +8 -0
  240. package/icons/social/social-whatsapp.marko +7 -0
  241. package/icons/social/social-x-color.marko +7 -0
  242. package/icons/social/social-x.marko +6 -0
  243. package/icons/social/social-youtube-color.marko +7 -0
  244. package/icons/social/social-youtube.marko +6 -0
  245. package/marko.json +203 -0
  246. package/package.json +55 -0
@@ -0,0 +1,474 @@
1
+ <!-- sds-color-picker — ported from kdls-design-system v2.5.1 (0f3edf8)
2
+ components/ColorPicker/ColorPicker.tsx.
3
+
4
+ A swatch that opens a picker: a saturation canvas, a hue slider, an opacity slider, the
5
+ eyedropper where the browser has one, and R/G/B/A and HEX fields. The conversions are
6
+ in color-maths.js and tested there — a round trip through HSV and back is exactly the
7
+ arithmetic that drifts by one, and a picker whose colour creeps as you drag is a picker
8
+ nobody trusts.
9
+
10
+ Divergences:
11
+
12
+ KDLS falls back to [41, 142, 230] when it cannot read the value — #298EE6, Kenlo blue,
13
+ in a component that is supposed to carry no brand. It was in the stylesheet too. Both
14
+ now start from the middle of the neutral ramp: a colour with no opinion, which is what
15
+ a picker with no value should show.
16
+
17
+ The trigger is a <button>. KDLS uses a div with role="button" and a hand-written
18
+ tabindex, which gets focus but not Enter or Space — so the picker cannot be opened from
19
+ the keyboard at all.
20
+
21
+ hexToRgb checks the shape before the value. KDLS relies on parseInt returning NaN, and
22
+ parseInt reads a valid prefix and stops: "12zzzz" parses as 0x12 and is accepted as a
23
+ colour.
24
+
25
+ The canvas and the sliders take arrow keys. KDLS binds mousedown only, so every control
26
+ inside the panel is mouse-only — on a picker whose whole job is choosing a precise
27
+ value.
28
+
29
+ `overlay` takes the panel out of the flow entirely: a backdrop over the page with the
30
+ panel centred in it, instead of a panel anchored to the trigger. It is for one situation —
31
+ this control living inside another panel, where anchoring would put a panel on top of the
32
+ panel it came from — and for a phone, where there is no room beside a trigger to anchor
33
+ anything. Whatever opened this is told through `onOpenChange`, so it can get out of the
34
+ way.
35
+ -->
36
+
37
+ import {
38
+ hsvToRgb, rgbToHex, hexToRgb, hexToAlpha, alphaToHex, rgbToHsv, startingRgb, startingAlpha,
39
+ } from "./color-maths.js";
40
+ import eyedropperIcon from "../../icons/generated/fluent-eyedropper-regular.marko";
41
+
42
+ class {
43
+ onCreate(input) {
44
+ const [h, s, v] = rgbToHsv(...startingRgb(input.value));
45
+ this.state = {
46
+ open: false,
47
+ hue: h,
48
+ sat: s,
49
+ bri: v,
50
+ opacity: startingAlpha(input.value),
51
+ hexInput: "",
52
+ };
53
+ this.lastValue = input.value;
54
+ // `this.input` is not set during onCreate — Marko passes it in. See
55
+ // .claude/component-standard.md §3.
56
+ this.syncHexInput(input);
57
+ }
58
+
59
+ onInput(input) {
60
+ // Follows the caller when the caller changes it, and not otherwise.
61
+ if (input.value === this.lastValue) return;
62
+ this.lastValue = input.value;
63
+ const [h, s, v] = rgbToHsv(...startingRgb(input.value));
64
+ this.state.hue = h;
65
+ this.state.sat = s;
66
+ this.state.bri = v;
67
+ this.state.opacity = startingAlpha(input.value);
68
+ this.syncHexInput();
69
+ }
70
+
71
+ get currentHex() {
72
+ return rgbToHex(...hsvToRgb(this.state.hue, this.state.sat, this.state.bri));
73
+ }
74
+
75
+ get rgb() {
76
+ return hsvToRgb(this.state.hue, this.state.sat, this.state.bri);
77
+ }
78
+
79
+ syncHexInput(input = this.input) {
80
+ const base = this.currentHex.replace("#", "");
81
+ this.state.hexInput = input.disableAlpha ? base : base + alphaToHex(this.state.opacity);
82
+ }
83
+
84
+ setHsv(h, s, v) {
85
+ this.state.hue = h;
86
+ this.state.sat = s;
87
+ this.state.bri = v;
88
+ this.syncHexInput();
89
+ }
90
+
91
+ setOpacity(percent) {
92
+ this.state.opacity = percent;
93
+ this.syncHexInput();
94
+ }
95
+
96
+ toggle() {
97
+ if (this.input.disabled) return;
98
+ this.state.open = !this.state.open;
99
+ }
100
+
101
+ // The backdrop dismisses, the panel does not: a drag that starts on a slider and ends
102
+ // outside it is still a drag. Escape is listened for on the document because the
103
+ // backdrop only receives key events while something inside it holds focus.
104
+ onMount() {
105
+ this.escape = (event) => this.closeOnEscape(event);
106
+ document.addEventListener("keydown", this.escape);
107
+ }
108
+
109
+ onDestroy() {
110
+ document.removeEventListener("keydown", this.escape);
111
+ }
112
+
113
+ closeFromBackdrop(event, element) {
114
+ if (event.target !== element) return;
115
+ this.setOpen(false);
116
+ }
117
+
118
+ closeOnEscape(event) {
119
+ if (event.key !== "Escape") return;
120
+ if (!this.input.overlay || !(this.input.open ?? this.state.open)) return;
121
+ event.preventDefault();
122
+ this.setOpen(false);
123
+ }
124
+
125
+ setOpen(next) {
126
+ if (this.input.disabled) return;
127
+ if (this.input.open === undefined) this.state.open = next;
128
+ this.input.onOpenChange?.(next);
129
+ }
130
+
131
+ /** A drag that keeps following the pointer after it leaves the control. */
132
+ track(element, read, event) {
133
+ if (!element) return;
134
+ const apply = (moveEvent) => {
135
+ const rect = element.getBoundingClientRect();
136
+ const x = Math.max(0, Math.min(1, (moveEvent.clientX - rect.left) / rect.width));
137
+ const y = Math.max(0, Math.min(1, (moveEvent.clientY - rect.top) / rect.height));
138
+ read(x, y);
139
+ };
140
+ apply(event);
141
+ const move = (moveEvent) => apply(moveEvent);
142
+ const up = () => {
143
+ window.removeEventListener("mousemove", move);
144
+ window.removeEventListener("mouseup", up);
145
+ };
146
+ window.addEventListener("mousemove", move);
147
+ window.addEventListener("mouseup", up);
148
+ }
149
+
150
+ dragCanvas(event) {
151
+ this.track(this.getEl("canvas"), (x, y) => {
152
+ this.setHsv(this.state.hue, Math.round(x * 100), Math.round((1 - y) * 100));
153
+ }, event);
154
+ }
155
+
156
+ dragHue(event) {
157
+ this.track(this.getEl("hue"), (x) => {
158
+ this.setHsv(Math.round(x * 360), this.state.sat, this.state.bri);
159
+ }, event);
160
+ }
161
+
162
+ dragOpacity(event) {
163
+ this.track(this.getEl("opacity"), (x) => this.setOpacity(Math.round(x * 100)), event);
164
+ }
165
+
166
+ /**
167
+ * Arrow keys on the canvas and the sliders.
168
+ *
169
+ * KDLS binds mousedown and nothing else, so a picker whose entire purpose is choosing a
170
+ * precise value cannot be operated without a mouse.
171
+ */
172
+ nudge(which, event) {
173
+ const step = event.shiftKey ? 10 : 1;
174
+ const keys = { ArrowLeft: -1, ArrowRight: 1, ArrowDown: -1, ArrowUp: 1 };
175
+ const direction = keys[event.key];
176
+ if (direction === undefined) return;
177
+ event.preventDefault();
178
+
179
+ const horizontal = event.key === "ArrowLeft" || event.key === "ArrowRight";
180
+ const clamp = (n, max) => Math.max(0, Math.min(max, n));
181
+
182
+ if (which === "hue") {
183
+ this.setHsv(clamp(this.state.hue + direction * step, 360), this.state.sat, this.state.bri);
184
+ } else if (which === "opacity") {
185
+ this.setOpacity(clamp(this.state.opacity + direction * step, 100));
186
+ } else if (horizontal) {
187
+ this.setHsv(this.state.hue, clamp(this.state.sat + direction * step, 100), this.state.bri);
188
+ } else {
189
+ this.setHsv(this.state.hue, this.state.sat, clamp(this.state.bri + direction * step, 100));
190
+ }
191
+ }
192
+
193
+ setChannel(channel, raw) {
194
+ const value = Math.max(0, Math.min(255, Number(raw) || 0));
195
+ const [r, g, b] = this.rgb;
196
+ const next = { r: [value, g, b], g: [r, value, b], b: [r, g, value] }[channel];
197
+ this.setHsv(...rgbToHsv(...next));
198
+ }
199
+
200
+ setAlphaChannel(raw) {
201
+ const value = Math.max(0, Math.min(255, Number(raw) || 0));
202
+ this.setOpacity(Math.round((value / 255) * 100));
203
+ }
204
+
205
+ setHex(raw) {
206
+ this.state.hexInput = String(raw).toUpperCase();
207
+ const rgb = hexToRgb(raw);
208
+ if (rgb) this.setHsv(...rgbToHsv(...rgb));
209
+ if (this.input.disableAlpha) return;
210
+ const alpha = hexToAlpha(raw);
211
+ if (alpha !== null) this.state.opacity = alpha;
212
+ }
213
+
214
+ async pickFromScreen() {
215
+ if (typeof window === "undefined" || !("EyeDropper" in window)) return;
216
+ try {
217
+ const result = await new window.EyeDropper().open();
218
+ const rgb = hexToRgb(result.sRGBHex);
219
+ if (rgb) this.setHsv(...rgbToHsv(...rgb));
220
+ } catch {
221
+ // The visitor pressed Escape. Nothing to do, and nothing to report.
222
+ }
223
+ }
224
+
225
+ apply() {
226
+ const hex = this.input.disableAlpha
227
+ ? this.currentHex
228
+ : this.currentHex + alphaToHex(this.state.opacity);
229
+ this.input.onChange?.(hex);
230
+ this.state.open = false;
231
+ }
232
+ }
233
+
234
+ $ {
235
+ const {
236
+ label,
237
+ width = "hug",
238
+ zIndex,
239
+ disableAlpha = false,
240
+ disabled = false,
241
+ applyLabel = "Aplicar",
242
+ overlay = false,
243
+ open: openInput,
244
+ showTrigger = true,
245
+ class: extraClass = "",
246
+ } = input;
247
+
248
+ if (!label) throw new Error("sds-color-picker needs a `label`.");
249
+ if (width !== "hug" && width !== "fill") {
250
+ throw new Error(`Unknown color picker width: "${width}". Allowed: hug, fill`);
251
+ }
252
+
253
+ const [r, g, b] = component.rgb;
254
+ const hex = component.currentHex;
255
+ // Controlled when the caller passes `open`. An overlay picker usually has to be: it is
256
+ // opened from somewhere that then gets out of the way, and a picker that owns its own
257
+ // open state dies with whatever it was rendered inside.
258
+ const open = openInput !== undefined ? openInput : state.open;
259
+
260
+ const triggerClass = [
261
+ "sds-colorpicker__trigger",
262
+ open ? "sds-colorpicker__trigger--open" : "",
263
+ disabled ? "sds-colorpicker__trigger--disabled" : "",
264
+ ].filter(Boolean).join(" ");
265
+ }
266
+
267
+ <div class=[
268
+ "sds-colorpicker",
269
+ showTrigger ? "" : "sds-colorpicker--headless",
270
+ width === "fill" ? "sds-colorpicker--fill" : "",
271
+ extraClass,
272
+ ].filter(Boolean).join(" ")>
273
+ <if(showTrigger)>
274
+ <sds-floating
275
+ open=open
276
+ onOpenChange=(next) => component.setOpen(next)
277
+ placement="bottomLeft"
278
+ zIndex=zIndex
279
+ >
280
+ <@content>
281
+ <if(!overlay)>
282
+ <div class="sds-colorpicker__panel">
283
+ <div
284
+ key="canvas"
285
+ class="sds-colorpicker__canvas"
286
+ style=`--cp-hue:${state.hue}`
287
+ role="application"
288
+ aria-label=`Saturação e brilho de ${label}`
289
+ tabindex=(disabled ? -1 : 0)
290
+ on-mousedown("dragCanvas")
291
+ on-keydown("nudge", "canvas")
292
+ >
293
+ <div class="sds-colorpicker__handle" style=`left:${state.sat}%;top:${100 - state.bri}%`/>
294
+ </div>
295
+
296
+ <div class="sds-colorpicker__slider-row">
297
+ <sds-icon-button size="md" aria-label="Selecionar cor da tela"
298
+ onClick=() => component.pickFromScreen()>
299
+ <@icon><${eyedropperIcon}/></@icon>
300
+ </sds-icon-button>
301
+
302
+ <div class="sds-colorpicker__sliders">
303
+ <div
304
+ key="hue"
305
+ class="sds-colorpicker__slider sds-colorpicker__slider--hue"
306
+ role="slider"
307
+ aria-label="Matiz"
308
+ aria-valuemin="0"
309
+ aria-valuemax="360"
310
+ aria-valuenow=String(state.hue)
311
+ tabindex="0"
312
+ on-mousedown("dragHue")
313
+ on-keydown("nudge", "hue")
314
+ >
315
+ <div class="sds-colorpicker__slider-thumb" style=`left:${(state.hue / 360) * 100}%`/>
316
+ </div>
317
+
318
+ <if(!disableAlpha)>
319
+ <div
320
+ key="opacity"
321
+ class="sds-colorpicker__slider sds-colorpicker__slider--opacity"
322
+ style=`--cp-hex:${hex}`
323
+ role="slider"
324
+ aria-label="Opacidade"
325
+ aria-valuemin="0"
326
+ aria-valuemax="100"
327
+ aria-valuenow=String(state.opacity)
328
+ aria-valuetext=`${state.opacity}%`
329
+ tabindex="0"
330
+ on-mousedown("dragOpacity")
331
+ on-keydown("nudge", "opacity")
332
+ >
333
+ <div class="sds-colorpicker__slider-thumb" style=`left:${state.opacity}%`/>
334
+ </div>
335
+ </if>
336
+ </div>
337
+ </div>
338
+
339
+ <div class=[
340
+ "sds-colorpicker__input-group",
341
+ !disableAlpha ? "sds-colorpicker__input-group--rgba" : "",
342
+ ].filter(Boolean).join(" ")>
343
+ <sds-input label="R" value=String(r) onChange=(event, value) => component.setChannel("r", value)/>
344
+ <sds-input label="G" value=String(g) onChange=(event, value) => component.setChannel("g", value)/>
345
+ <sds-input label="B" value=String(b) onChange=(event, value) => component.setChannel("b", value)/>
346
+ <if(!disableAlpha)>
347
+ <sds-input
348
+ label="A"
349
+ value=String(Math.round((state.opacity / 100) * 255))
350
+ onChange=(event, value) => component.setAlphaChannel(value)
351
+ />
352
+ </if>
353
+ </div>
354
+
355
+ <sds-input label="HEX" value=state.hexInput
356
+ onChange=(event, value) => component.setHex(value)/>
357
+
358
+ <sds-button variant="primary" size="md" label=applyLabel
359
+ class="sds-colorpicker__apply" onClick=() => component.apply()/>
360
+ </div>
361
+ </if>
362
+ </@content>
363
+
364
+ <!-- A real button. KDLS uses a div with role="button" and a tabindex, which takes focus
365
+ but answers neither Enter nor Space — so the picker cannot be opened by keyboard. -->
366
+ <button
367
+ type="button"
368
+ class=triggerClass
369
+ disabled=(disabled || null)
370
+ aria-haspopup="dialog"
371
+ aria-expanded=(open ? "true" : "false")
372
+ on-click("toggle")
373
+ >
374
+ <span class="sds-colorpicker__swatch" style=`background:${hex}` aria-hidden="true"/>
375
+ <span class="sds-colorpicker__field">
376
+ <sds-text as="span" variant="labelRegular">${label}</sds-text>
377
+ <span class="sds-colorpicker__hex">#${state.hexInput}</span>
378
+ </span>
379
+ </button>
380
+ </sds-floating>
381
+ </if>
382
+
383
+ <!-- Centred, over everything, when `overlay` is on. Escape and the backdrop both close
384
+ it; the panel itself does not, so a drag that ends outside the panel is still a
385
+ drag rather than a dismissal. -->
386
+ <if(overlay && open)>
387
+ <div
388
+ class="sds-colorpicker__backdrop"
389
+ style=(zIndex !== undefined ? `z-index:${zIndex}` : null)
390
+ on-click("closeFromBackdrop")
391
+ on-keydown("closeOnEscape")
392
+ >
393
+ <div class="sds-colorpicker__panel">
394
+ <div
395
+ key="canvas"
396
+ class="sds-colorpicker__canvas"
397
+ style=`--cp-hue:${state.hue}`
398
+ role="application"
399
+ aria-label=`Saturação e brilho de ${label}`
400
+ tabindex=(disabled ? -1 : 0)
401
+ on-mousedown("dragCanvas")
402
+ on-keydown("nudge", "canvas")
403
+ >
404
+ <div class="sds-colorpicker__handle" style=`left:${state.sat}%;top:${100 - state.bri}%`/>
405
+ </div>
406
+
407
+ <div class="sds-colorpicker__slider-row">
408
+ <sds-icon-button size="md" aria-label="Selecionar cor da tela"
409
+ onClick=() => component.pickFromScreen()>
410
+ <@icon><${eyedropperIcon}/></@icon>
411
+ </sds-icon-button>
412
+
413
+ <div class="sds-colorpicker__sliders">
414
+ <div
415
+ key="hue"
416
+ class="sds-colorpicker__slider sds-colorpicker__slider--hue"
417
+ role="slider"
418
+ aria-label="Matiz"
419
+ aria-valuemin="0"
420
+ aria-valuemax="360"
421
+ aria-valuenow=String(state.hue)
422
+ tabindex="0"
423
+ on-mousedown("dragHue")
424
+ on-keydown("nudge", "hue")
425
+ >
426
+ <div class="sds-colorpicker__slider-thumb" style=`left:${(state.hue / 360) * 100}%`/>
427
+ </div>
428
+
429
+ <if(!disableAlpha)>
430
+ <div
431
+ key="opacity"
432
+ class="sds-colorpicker__slider sds-colorpicker__slider--opacity"
433
+ style=`--cp-hex:${hex}`
434
+ role="slider"
435
+ aria-label="Opacidade"
436
+ aria-valuemin="0"
437
+ aria-valuemax="100"
438
+ aria-valuenow=String(state.opacity)
439
+ aria-valuetext=`${state.opacity}%`
440
+ tabindex="0"
441
+ on-mousedown("dragOpacity")
442
+ on-keydown("nudge", "opacity")
443
+ >
444
+ <div class="sds-colorpicker__slider-thumb" style=`left:${state.opacity}%`/>
445
+ </div>
446
+ </if>
447
+ </div>
448
+ </div>
449
+
450
+ <div class=[
451
+ "sds-colorpicker__input-group",
452
+ !disableAlpha ? "sds-colorpicker__input-group--rgba" : "",
453
+ ].filter(Boolean).join(" ")>
454
+ <sds-input label="R" value=String(r) onChange=(event, value) => component.setChannel("r", value)/>
455
+ <sds-input label="G" value=String(g) onChange=(event, value) => component.setChannel("g", value)/>
456
+ <sds-input label="B" value=String(b) onChange=(event, value) => component.setChannel("b", value)/>
457
+ <if(!disableAlpha)>
458
+ <sds-input
459
+ label="A"
460
+ value=String(Math.round((state.opacity / 100) * 255))
461
+ onChange=(event, value) => component.setAlphaChannel(value)
462
+ />
463
+ </if>
464
+ </div>
465
+
466
+ <sds-input label="HEX" value=state.hexInput
467
+ onChange=(event, value) => component.setHex(value)/>
468
+
469
+ <sds-button variant="primary" size="md" label=applyLabel
470
+ class="sds-colorpicker__apply" onClick=() => component.apply()/>
471
+ </div>
472
+ </div>
473
+ </if>
474
+ </div>