@ctrl-ui/react 0.8.20 → 0.9.0

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 (161) hide show
  1. package/README.md +1 -0
  2. package/dist/action-bar.d.ts +1 -2
  3. package/dist/action-bar.js +7 -8
  4. package/dist/blocks/design-canvas-inspector.js +4 -1
  5. package/dist/blocks/file-explorer.js +1 -1
  6. package/dist/chat-message.js +4 -13
  7. package/dist/hooks/use-textarea-trigger-menu.d.ts +21 -3
  8. package/dist/hooks/use-textarea-trigger-menu.js +36 -40
  9. package/dist/hooks/use-user-ask.d.ts +1 -0
  10. package/dist/hooks/use-user-ask.js +5 -1
  11. package/dist/knob-contracts/audio-recorder-knobs.d.ts +1 -1
  12. package/dist/knob-contracts/audio-recorder-knobs.js +2 -1
  13. package/dist/knob-contracts/chart-knobs.d.ts +4 -0
  14. package/dist/knob-contracts/chart-knobs.js +24 -0
  15. package/dist/knob-contracts/choice-knobs.d.ts +1 -1
  16. package/dist/knob-contracts/choice-knobs.js +1 -0
  17. package/dist/knob-contracts/dockable-panel-knobs.d.ts +1 -1
  18. package/dist/knob-contracts/dockable-panel-knobs.js +2 -1
  19. package/dist/knob-contracts/emoji-picker-knobs.d.ts +1 -1
  20. package/dist/knob-contracts/emoji-picker-knobs.js +1 -0
  21. package/dist/knob-contracts/empty-knobs.d.ts +1 -1
  22. package/dist/knob-contracts/empty-knobs.js +2 -1
  23. package/dist/knob-contracts/icon-picker-knobs.d.ts +1 -1
  24. package/dist/knob-contracts/icon-picker-knobs.js +3 -1
  25. package/dist/knob-contracts/resizable-knobs.d.ts +1 -1
  26. package/dist/knob-contracts/resizable-knobs.js +2 -2
  27. package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
  28. package/dist/knob-contracts/sidebar-knobs.js +3 -1
  29. package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
  30. package/dist/knob-contracts/tabs-knobs.js +2 -1
  31. package/dist/knob-contracts/tree-knobs.d.ts +1 -1
  32. package/dist/knob-contracts/tree-knobs.js +2 -1
  33. package/dist/lib/color.d.ts +2 -6
  34. package/dist/lib/color.js +45 -28
  35. package/dist/lib/contrast.d.ts +6 -0
  36. package/dist/lib/contrast.js +33 -1
  37. package/dist/lib/gradient.d.ts +17 -0
  38. package/dist/lib/gradient.js +69 -0
  39. package/dist/markdown-editor/drag-handle.js +73 -10
  40. package/dist/markdown-editor/drag-preview.d.ts +3 -0
  41. package/dist/markdown-editor/drag-preview.js +31 -0
  42. package/dist/markdown-editor/extensions.d.ts +2 -2
  43. package/dist/markdown-editor/extensions.js +25 -3
  44. package/dist/markdown-editor/suggestions.js +5 -5
  45. package/dist/markdown-editor/task-item.d.ts +3 -0
  46. package/dist/markdown-editor/task-item.js +50 -0
  47. package/dist/markdown-editor/toolbar.js +122 -63
  48. package/dist/markdown-editor.js +3 -3
  49. package/dist/skin.d.ts +0 -1
  50. package/dist/styles/index.css +3 -2
  51. package/dist/styles/recipes/action-bar.css +1 -1
  52. package/dist/styles/recipes/activity.css +1 -1
  53. package/dist/styles/recipes/alert.css +9 -10
  54. package/dist/styles/recipes/app-shell-header.css +2 -2
  55. package/dist/styles/recipes/audio-recorder.css +15 -4
  56. package/dist/styles/recipes/audio-visualizer.css +4 -4
  57. package/dist/styles/recipes/avatar.css +3 -3
  58. package/dist/styles/recipes/badge.css +2 -2
  59. package/dist/styles/recipes/button.css +2 -5
  60. package/dist/styles/recipes/card.css +0 -2
  61. package/dist/styles/recipes/chart.css +378 -0
  62. package/dist/styles/recipes/chat-composer-attachment.css +3 -3
  63. package/dist/styles/recipes/chat-composer.css +3 -3
  64. package/dist/styles/recipes/chat-layout.css +2 -2
  65. package/dist/styles/recipes/chat-message-social.css +1 -1
  66. package/dist/styles/recipes/chat-message.css +1 -1
  67. package/dist/styles/recipes/choice.css +9 -3
  68. package/dist/styles/recipes/code-diff.css +1 -1
  69. package/dist/styles/recipes/code.css +1 -1
  70. package/dist/styles/recipes/color-picker-details.css +17 -11
  71. package/dist/styles/recipes/color-picker-values.css +17 -7
  72. package/dist/styles/recipes/color-picker.css +88 -70
  73. package/dist/styles/recipes/context.css +2 -2
  74. package/dist/styles/recipes/dockable-panel.css +9 -2
  75. package/dist/styles/recipes/dynamic-notification-motion.css +1 -55
  76. package/dist/styles/recipes/dynamic-notification.css +0 -1
  77. package/dist/styles/recipes/emoji-picker.css +35 -19
  78. package/dist/styles/recipes/empty.css +19 -14
  79. package/dist/styles/recipes/field-parts.css +2 -2
  80. package/dist/styles/recipes/field.css +6 -4
  81. package/dist/styles/recipes/filter-bar.css +10 -10
  82. package/dist/styles/recipes/flow.css +5 -3
  83. package/dist/styles/recipes/gradient-editor.css +8 -0
  84. package/dist/styles/recipes/icon-picker.css +45 -10
  85. package/dist/styles/recipes/inline-attachment.css +4 -4
  86. package/dist/styles/recipes/inline-citation.css +2 -2
  87. package/dist/styles/recipes/item.css +4 -4
  88. package/dist/styles/recipes/markdown-editor.css +35 -24
  89. package/dist/styles/recipes/markdown.css +23 -27
  90. package/dist/styles/recipes/page-layout.css +13 -11
  91. package/dist/styles/recipes/popup-controls.css +2 -2
  92. package/dist/styles/recipes/popup-lists.css +3 -3
  93. package/dist/styles/recipes/popup.css +11 -9
  94. package/dist/styles/recipes/range.css +4 -5
  95. package/dist/styles/recipes/resizable.css +15 -10
  96. package/dist/styles/recipes/resize-handle.css +2 -2
  97. package/dist/styles/recipes/scroll-area.css +1 -1
  98. package/dist/styles/recipes/sidebar-menu.css +5 -15
  99. package/dist/styles/recipes/sidebar-mobile.css +4 -4
  100. package/dist/styles/recipes/sidebar-parts.css +26 -1
  101. package/dist/styles/recipes/sidebar.css +18 -31
  102. package/dist/styles/recipes/skeleton.css +10 -5
  103. package/dist/styles/recipes/stepper.css +1 -1
  104. package/dist/styles/recipes/switch.css +12 -11
  105. package/dist/styles/recipes/table-of-contents.css +6 -8
  106. package/dist/styles/recipes/table.css +4 -4
  107. package/dist/styles/recipes/tabs.css +10 -5
  108. package/dist/styles/recipes/task-list.css +3 -3
  109. package/dist/styles/recipes/tree.css +10 -3
  110. package/dist/styles/recipes/user-ask-details.css +5 -4
  111. package/dist/styles/recipes/user-ask.css +5 -5
  112. package/dist/styles/theme.css +158 -177
  113. package/dist/ui/chart-cartesian.d.ts +801 -0
  114. package/dist/ui/chart-cartesian.js +420 -0
  115. package/dist/ui/chart-colors.d.ts +7 -0
  116. package/dist/ui/chart-colors.js +27 -0
  117. package/dist/ui/chart-curves.d.ts +7 -0
  118. package/dist/ui/chart-curves.js +87 -0
  119. package/dist/ui/chart-marks.d.ts +68 -0
  120. package/dist/ui/chart-marks.js +227 -0
  121. package/dist/ui/chart-polar.d.ts +103 -0
  122. package/dist/ui/chart-polar.js +124 -0
  123. package/dist/ui/chart-series.d.ts +36 -0
  124. package/dist/ui/chart-series.js +71 -0
  125. package/dist/ui/chart.d.ts +32 -0
  126. package/dist/ui/chart.js +257 -0
  127. package/dist/ui/color-picker-area.d.ts +16 -0
  128. package/dist/ui/color-picker-area.js +175 -0
  129. package/dist/ui/color-picker-context.d.ts +27 -0
  130. package/dist/ui/color-picker-context.js +87 -0
  131. package/dist/ui/color-picker-contrast.d.ts +20 -0
  132. package/dist/ui/color-picker-contrast.js +129 -0
  133. package/dist/ui/color-picker-sliders.d.ts +15 -0
  134. package/dist/ui/color-picker-sliders.js +146 -0
  135. package/dist/ui/color-picker-swatches.d.ts +23 -0
  136. package/dist/ui/color-picker-swatches.js +94 -0
  137. package/dist/ui/color-picker-values.d.ts +59 -0
  138. package/dist/ui/color-picker-values.js +244 -0
  139. package/dist/ui/color-picker.d.ts +15 -110
  140. package/dist/ui/color-picker.js +30 -759
  141. package/dist/ui/emoji-picker.d.ts +10 -1
  142. package/dist/ui/emoji-picker.js +71 -14
  143. package/dist/ui/flow.js +1 -1
  144. package/dist/ui/gradient-editor-controls.d.ts +19 -0
  145. package/dist/ui/gradient-editor-controls.js +75 -0
  146. package/dist/ui/gradient-editor.d.ts +23 -22
  147. package/dist/ui/gradient-editor.js +77 -123
  148. package/dist/ui/icon-picker.d.ts +1 -1
  149. package/dist/ui/icon-picker.js +11 -6
  150. package/dist/ui/markdown-elements.d.ts +3 -1
  151. package/dist/ui/markdown-elements.js +62 -22
  152. package/dist/ui/markdown-heading.d.ts +9 -0
  153. package/dist/ui/markdown-heading.js +25 -0
  154. package/dist/ui/resizable.js +24 -9
  155. package/dist/ui/sidebar-provider.js +3 -12
  156. package/dist/ui/typography.d.ts +1 -0
  157. package/dist/ui/typography.js +12 -3
  158. package/dist/user-ask.js +4 -8
  159. package/package.json +7 -3
  160. package/dist/ui/emoji-icon-picker.d.ts +0 -13
  161. package/dist/ui/emoji-icon-picker.js +0 -50
@@ -2,142 +2,19 @@
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { controlEffectsAttribute } from "../skin.js";
4
4
  import { useSkin } from "../skin-provider.js";
5
- import { Button } from "./button.js";
6
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select.js";
7
- import { useColorArea } from "../hooks/use-color-area.js";
8
- import { formatColor, getChannels, hsvaToRgba, parseColor, pointToHueSat, pointToSaturationValue, setChannel } from "../lib/color.js";
9
- import { TARGET_RATIO, contrastOf, fixColorForContrast, formatContrastRatio, nextFixLevel, wcagLevels } from "../lib/contrast.js";
10
- import { Input } from "./input.js";
11
- import { NumberField, NumberFieldGroup, NumberFieldInput } from "./number-field.js";
12
- import { createContext, useContext, useEffect, useId, useRef, useState, useSyncExternalStore } from "react";
13
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
+ import { ColorPickerContext, useColorPicker, useColorState } from "./color-picker-context.js";
6
+ import { ColorPickerArea, ColorPickerAreaThumb, ColorPickerWheel } from "./color-picker-area.js";
7
+ import { ColorPickerAlpha, ColorPickerBrightness, ColorPickerHue, ColorPickerSaturation } from "./color-picker-sliders.js";
8
+ import { ColorPickerChannel, ColorPickerChannels, ColorPickerEyeDropper, ColorPickerFormatSelect, ColorPickerInput, ColorPickerOutput } from "./color-picker-values.js";
9
+ import { GradientEditor, GradientEditorPreview, GradientEditorStop, GradientEditorStopAdd, GradientEditorTrack, useGradientEditor } from "./gradient-editor.js";
10
+ import { ColorPickerAreaContrast, ColorPickerContrast } from "./color-picker-contrast.js";
11
+ import { ColorPickerSwatch, ColorPickerSwatchAdd, ColorPickerSwatches } from "./color-picker-swatches.js";
12
+ import { GradientEditorAngle, GradientEditorInterpolationSelect, GradientEditorTypeSelect } from "./gradient-editor-controls.js";
13
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
14
  import { Popover } from "@base-ui/react/popover";
15
- import { Slider } from "@base-ui/react/slider";
16
15
  //#region src/ui/color-picker.tsx
17
- const HUE_GRADIENT = "linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%))";
18
- const CHECKER = "repeating-conic-gradient(oklch(from var(--border) l c h) 0 25%, transparent 0 50%)";
19
- const CHECKER_SIZE = "10px 10px";
20
- const RING_THUMB = "block -translate-x-1/2";
21
- const isColorFormat = (v) => v === "hex" || v === "rgb" || v === "hsl" || v === "oklch";
22
- const CHANNEL_NAMES = {
23
- r: "Red",
24
- g: "Green",
25
- b: "Blue",
26
- h: "Hue",
27
- s: "Saturation",
28
- l: "Lightness",
29
- okl: "OKLCH lightness",
30
- okc: "OKLCH chroma",
31
- okh: "OKLCH hue",
32
- a: "Opacity"
33
- };
34
- function reconcileAchromatic(prev, next) {
35
- if (next.v === 0) return {
36
- ...next,
37
- h: prev.h,
38
- s: prev.s
39
- };
40
- if (next.s === 0) return {
41
- ...next,
42
- h: prev.h
43
- };
44
- return next;
45
- }
46
- function subscribeEyeDropper() {
47
- return () => {};
48
- }
49
- function getEyeDropperSnapshot() {
50
- return typeof window !== "undefined" && Boolean(window.EyeDropper);
51
- }
52
- function getEyeDropperServerSnapshot() {
53
- return false;
54
- }
55
- const ColorPickerContext = createContext(null);
56
- function useColorPicker() {
57
- const ctx = useContext(ColorPickerContext);
58
- if (!ctx) throw new Error("ColorPicker parts must be rendered inside <ColorPicker>.");
59
- return ctx;
60
- }
61
- function useColorState(props) {
62
- const { value, defaultValue = "#000000", onValueChange, format: formatProp, defaultFormat = "hex", onFormatChange, alpha = true, disabled = false } = props;
63
- const [hsva, setHsvaState] = useState(() => parseColor(value ?? defaultValue) ?? {
64
- h: 0,
65
- s: 0,
66
- v: 0,
67
- a: 1
68
- });
69
- const [formatState, setFormatState] = useState(defaultFormat);
70
- const format = formatProp ?? formatState;
71
- const hsvaRef = useRef(hsva);
72
- const formatRef = useRef(format);
73
- const alphaRef = useRef(alpha);
74
- const onValueChangeRef = useRef(onValueChange);
75
- const lastEmitted = useRef(null);
76
- useEffect(() => {
77
- hsvaRef.current = hsva;
78
- formatRef.current = format;
79
- alphaRef.current = alpha;
80
- onValueChangeRef.current = onValueChange;
81
- }, [
82
- hsva,
83
- format,
84
- alpha,
85
- onValueChange
86
- ]);
87
- const emit = (next, fmt) => {
88
- const str = formatColor(next, fmt, { alpha: alphaRef.current });
89
- lastEmitted.current = str;
90
- onValueChangeRef.current?.(str);
91
- };
92
- const commit = (next) => {
93
- hsvaRef.current = next;
94
- setHsvaState(next);
95
- emit(next, formatRef.current);
96
- };
97
- useEffect(() => {
98
- if (lastEmitted.current === null) lastEmitted.current = formatColor(hsvaRef.current, formatRef.current, { alpha: alphaRef.current });
99
- if (value === void 0 || value === lastEmitted.current) return;
100
- const parsed = parseColor(value);
101
- if (!parsed) return;
102
- const reconciled = reconcileAchromatic(hsvaRef.current, parsed);
103
- hsvaRef.current = reconciled;
104
- lastEmitted.current = value;
105
- setHsvaState(reconciled);
106
- }, [value]);
107
- const setHsva = (partial) => commit({
108
- ...hsvaRef.current,
109
- ...partial
110
- });
111
- const setChannelValue = (id, next) => commit(setChannel(hsvaRef.current, id, next));
112
- const setFromString = (raw) => {
113
- const parsed = parseColor(raw);
114
- if (!parsed) return false;
115
- commit(reconcileAchromatic(hsvaRef.current, parsed));
116
- return true;
117
- };
118
- const setFormat = (next) => {
119
- if (formatProp === void 0) setFormatState(next);
120
- formatRef.current = next;
121
- onFormatChange?.(next);
122
- emit(hsvaRef.current, next);
123
- };
124
- return {
125
- hsva,
126
- format,
127
- alpha,
128
- disabled,
129
- valueString: formatColor(hsva, format, { alpha }),
130
- setHsva,
131
- setChannelValue,
132
- setFromString,
133
- setFormat
134
- };
135
- }
136
16
  function ColorPicker({ open, defaultOpen, onOpenChange, children, ...state }) {
137
- const ctx = useColorState({
138
- ...state,
139
- children
140
- });
17
+ const ctx = useColorState(state);
141
18
  return /* @__PURE__ */ jsx(ColorPickerContext.Provider, {
142
19
  value: ctx,
143
20
  children: /* @__PURE__ */ jsx(Popover.Root, {
@@ -165,11 +42,7 @@ function ColorPickerTrigger({ className, "aria-label": ariaLabel, "aria-labelled
165
42
  "data-control-family": "color-picker",
166
43
  "data-slot": "trigger-checker",
167
44
  "aria-hidden": true,
168
- className: "absolute inset-0",
169
- style: {
170
- backgroundImage: CHECKER,
171
- backgroundSize: CHECKER_SIZE
172
- }
45
+ className: "absolute inset-0"
173
46
  }), /* @__PURE__ */ jsx("span", {
174
47
  "data-control-ui": "color-picker",
175
48
  "data-control-family": "color-picker",
@@ -220,628 +93,26 @@ function ColorPickerPanel({ className, children, ...props }) {
220
93
  children
221
94
  });
222
95
  }
223
- function ColorPickerArea({ className, style, ...props }) {
224
- const { hsva, setHsva, disabled } = useColorPicker();
225
- const { areaRef, onPointerDown, dragging } = useColorArea((offset, rect) => {
226
- if (disabled) return;
227
- const { s, v } = pointToSaturationValue(rect, offset.x, offset.y);
228
- setHsva({
229
- s,
230
- v
231
- });
232
- });
233
- function axisKey(axis, event) {
234
- if (!event.shiftKey) return;
235
- const cur = axis === "s" ? hsva.s : hsva.v;
236
- let delta = 0;
237
- if (event.key === "ArrowUp" || event.key === "ArrowRight") delta = 10;
238
- else if (event.key === "ArrowDown" || event.key === "ArrowLeft") delta = -10;
239
- if (delta === 0) return;
240
- event.preventDefault();
241
- const next = Math.min(100, Math.max(0, cur + delta));
242
- setHsva(axis === "s" ? { s: next } : { v: next });
243
- }
244
- return /* @__PURE__ */ jsxs("div", {
245
- "data-focus-ring": "within",
246
- ref: areaRef,
247
- "data-control-ui": "color-picker",
248
- "data-control-family": "color-picker",
249
- "data-slot": "area",
250
- "data-disabled": disabled ? "true" : void 0,
251
- "data-dragging": dragging ? "true" : void 0,
252
- role: "group",
253
- "aria-label": "Saturation and brightness",
254
- onPointerDown: disabled ? void 0 : onPointerDown,
255
- className: cn("group relative w-full touch-none select-none overflow-hidden", disabled ? "cursor-not-allowed" : "cursor-crosshair", className),
256
- style: {
257
- ...style,
258
- backgroundColor: `hsl(${hsva.h} 100% 50%)`
259
- },
260
- ...props,
261
- children: [
262
- /* @__PURE__ */ jsx("span", {
263
- "aria-hidden": true,
264
- "data-control-ui": "color-picker",
265
- "data-control-family": "color-picker",
266
- "data-slot": "area-saturation",
267
- className: "absolute inset-0"
268
- }),
269
- /* @__PURE__ */ jsx("span", {
270
- "aria-hidden": true,
271
- "data-control-ui": "color-picker",
272
- "data-control-family": "color-picker",
273
- "data-slot": "area-brightness",
274
- className: "absolute inset-0"
275
- }),
276
- /* @__PURE__ */ jsx("input", {
277
- type: "range",
278
- "aria-label": "Saturation",
279
- className: "sr-only",
280
- min: 0,
281
- max: 100,
282
- step: 1,
283
- value: Math.round(hsva.s),
284
- disabled,
285
- onChange: (event) => setHsva({ s: Number(event.target.value) }),
286
- onKeyDown: (event) => axisKey("s", event)
287
- }),
288
- /* @__PURE__ */ jsx("input", {
289
- type: "range",
290
- "aria-label": "Brightness",
291
- className: "sr-only",
292
- min: 0,
293
- max: 100,
294
- step: 1,
295
- value: Math.round(hsva.v),
296
- disabled,
297
- onChange: (event) => setHsva({ v: Number(event.target.value) }),
298
- onKeyDown: (event) => axisKey("v", event)
299
- }),
300
- /* @__PURE__ */ jsx(ColorPickerAreaThumb, { style: {
301
- left: `${hsva.s}%`,
302
- top: `${100 - hsva.v}%`
303
- } })
304
- ]
305
- });
306
- }
307
- function ColorPickerAreaThumb({ className, ...props }) {
308
- return /* @__PURE__ */ jsx("div", {
309
- "data-control-ui": "color-picker",
310
- "data-control-family": "color-picker",
311
- "data-slot": "area-thumb",
312
- "aria-hidden": true,
313
- className: cn("pointer-events-none absolute -translate-x-1/2 -translate-y-1/2", className),
314
- ...props
315
- });
316
- }
317
- function ColorPickerHue({ className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }) {
318
- const { hsva, setHsva, disabled } = useColorPicker();
319
- return /* @__PURE__ */ jsx(Slider.Root, {
320
- "data-control-ui": "color-picker",
321
- "data-control-family": "color-picker",
322
- "data-slot": "hue",
323
- format: {
324
- style: "unit",
325
- unit: "degree"
326
- },
327
- "aria-label": ariaLabelledBy === void 0 ? ariaLabel ?? "Hue" : ariaLabel,
328
- "aria-labelledby": ariaLabelledBy,
329
- value: hsva.h,
330
- min: 0,
331
- max: 360,
332
- step: 1,
333
- disabled,
334
- onValueChange: (next) => setHsva({ h: Array.isArray(next) ? next[0] : next }),
335
- className: cn("relative flex w-full touch-pan-y select-none items-center", className),
336
- ...props,
337
- children: /* @__PURE__ */ jsx(Slider.Control, {
338
- className: "flex h-full w-full items-center",
339
- children: /* @__PURE__ */ jsx(Slider.Track, {
340
- "data-control-ui": "color-picker",
341
- "data-control-family": "color-picker",
342
- "data-slot": "hue-track",
343
- className: "relative w-full",
344
- style: { backgroundImage: HUE_GRADIENT },
345
- children: /* @__PURE__ */ jsx(Slider.Thumb, {
346
- "data-control-ui": "color-picker",
347
- "data-control-family": "color-picker",
348
- "data-slot": "hue-thumb",
349
- "data-focus-ring": "within",
350
- className: RING_THUMB
351
- })
352
- })
353
- })
354
- });
355
- }
356
- function ColorPickerAlpha({ className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }) {
357
- const { hsva, setHsva, alpha, disabled } = useColorPicker();
358
- if (!alpha) return null;
359
- const opaque = formatColor({
360
- ...hsva,
361
- a: 1
362
- }, "hex");
363
- return /* @__PURE__ */ jsx(Slider.Root, {
364
- "data-control-ui": "color-picker",
365
- "data-control-family": "color-picker",
366
- "data-slot": "alpha",
367
- "aria-label": ariaLabelledBy === void 0 ? ariaLabel ?? "Opacity" : ariaLabel,
368
- "aria-labelledby": ariaLabelledBy,
369
- value: hsva.a,
370
- format: { style: "percent" },
371
- min: 0,
372
- max: 1,
373
- step: .01,
374
- disabled,
375
- onValueChange: (next) => setHsva({ a: Array.isArray(next) ? next[0] : next }),
376
- className: cn("relative flex w-full touch-pan-y select-none items-center", className),
377
- ...props,
378
- children: /* @__PURE__ */ jsx(Slider.Control, {
379
- className: "flex h-full w-full items-center",
380
- children: /* @__PURE__ */ jsx(Slider.Track, {
381
- "data-control-ui": "color-picker",
382
- "data-control-family": "color-picker",
383
- "data-slot": "alpha-track",
384
- className: "relative w-full",
385
- style: {
386
- backgroundImage: `linear-gradient(to right, transparent, ${opaque}), ${CHECKER}`,
387
- backgroundSize: `auto, ${CHECKER_SIZE}`
388
- },
389
- children: /* @__PURE__ */ jsx(Slider.Thumb, {
390
- "data-control-ui": "color-picker",
391
- "data-control-family": "color-picker",
392
- "data-slot": "alpha-thumb",
393
- "data-focus-ring": "within",
394
- className: RING_THUMB
395
- })
396
- })
397
- })
398
- });
399
- }
400
- const WHEEL_HUE = "conic-gradient(from 90deg, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%))";
401
- function ColorPickerWheel({ className, style, ...props }) {
402
- const { hsva, setHsva, disabled } = useColorPicker();
403
- const { areaRef, onPointerDown, dragging } = useColorArea((offset, rect) => {
404
- if (disabled) return;
405
- const { h, s } = pointToHueSat(rect, offset.x, offset.y);
406
- setHsva({
407
- h,
408
- s
409
- });
410
- });
411
- const radius = hsva.s / 100 * 50;
412
- const rad = hsva.h * Math.PI / 180;
413
- const left = 50 + radius * Math.cos(rad);
414
- const top = 50 + radius * Math.sin(rad);
415
- return /* @__PURE__ */ jsxs("div", {
416
- "data-focus-ring": "within",
417
- ref: areaRef,
418
- "data-control-ui": "color-picker",
419
- "data-control-family": "color-picker",
420
- "data-slot": "wheel",
421
- "data-disabled": disabled ? "true" : void 0,
422
- "data-dragging": dragging ? "true" : void 0,
423
- role: "group",
424
- "aria-label": "Color wheel",
425
- onPointerDown: disabled ? void 0 : onPointerDown,
426
- className: cn("group relative aspect-square w-full touch-none select-none", disabled ? "cursor-not-allowed" : "cursor-crosshair", className),
427
- style: {
428
- ...style,
429
- backgroundImage: `radial-gradient(circle at center, #fff, transparent 70%), ${WHEEL_HUE}`
430
- },
431
- ...props,
432
- children: [
433
- /* @__PURE__ */ jsx("div", {
434
- "data-control-ui": "color-picker",
435
- "data-control-family": "color-picker",
436
- "data-slot": "wheel-thumb",
437
- "aria-hidden": true,
438
- className: "pointer-events-none absolute -translate-x-1/2 -translate-y-1/2",
439
- style: {
440
- left: `${left}%`,
441
- top: `${top}%`
442
- }
443
- }),
444
- /* @__PURE__ */ jsx("input", {
445
- type: "range",
446
- "aria-label": "Wheel hue",
447
- className: "sr-only",
448
- min: 0,
449
- max: 360,
450
- step: 1,
451
- value: Math.round(hsva.h),
452
- disabled,
453
- onChange: (event) => setHsva({ h: Number(event.target.value) })
454
- }),
455
- /* @__PURE__ */ jsx("input", {
456
- type: "range",
457
- "aria-label": "Wheel saturation",
458
- className: "sr-only",
459
- min: 0,
460
- max: 100,
461
- step: 1,
462
- value: Math.round(hsva.s),
463
- disabled,
464
- onChange: (event) => setHsva({ s: Number(event.target.value) })
465
- })
466
- ]
467
- });
468
- }
469
- function ColorPickerInput({ size = "sm", className, invalidLabel = "Enter a hex, rgb(), hsl() or oklch() color.", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, onFocus, onBlur, onKeyDown, ...props }) {
470
- const { valueString, setFromString, disabled } = useColorPicker();
471
- const invalidMessageId = useId();
472
- const [draft, setDraft] = useState(valueString);
473
- const [editing, setEditing] = useState(false);
474
- const [invalid, setInvalid] = useState(false);
475
- const describedBy = [ariaDescribedBy, invalid ? invalidMessageId : void 0].filter(Boolean).join(" ") || void 0;
476
- const shown = editing ? draft : valueString;
477
- const commit = () => {
478
- const accepted = setFromString(draft);
479
- setInvalid(!accepted);
480
- if (accepted) setEditing(false);
481
- };
482
- const revert = () => {
483
- setInvalid(false);
484
- setEditing(false);
485
- };
486
- return /* @__PURE__ */ jsxs("span", {
487
- "data-control-ui": "color-picker",
488
- "data-control-family": "color-picker",
489
- "data-slot": "input",
490
- className: "contents",
491
- children: [/* @__PURE__ */ jsx(Input, {
492
- ...props,
493
- size,
494
- value: shown,
495
- disabled,
496
- spellCheck: false,
497
- autoComplete: "off",
498
- "aria-label": ariaLabelledBy === void 0 ? ariaLabel ?? "Color value" : ariaLabel,
499
- "aria-labelledby": ariaLabelledBy,
500
- "aria-invalid": invalid || void 0,
501
- "aria-describedby": describedBy,
502
- className,
503
- onFocus: (event) => {
504
- onFocus?.(event);
505
- if (editing) return;
506
- setDraft(valueString);
507
- setEditing(true);
508
- },
509
- onChange: (event) => setDraft(event.target.value),
510
- onBlur: (event) => {
511
- onBlur?.(event);
512
- commit();
513
- },
514
- onKeyDown: (event) => {
515
- onKeyDown?.(event);
516
- if (event.defaultPrevented) return;
517
- if (event.key === "Enter") commit();
518
- else if (event.key === "Escape") revert();
519
- }
520
- }), invalid ? /* @__PURE__ */ jsx("span", {
521
- id: invalidMessageId,
522
- className: "sr-only",
523
- children: invalidLabel
524
- }) : null]
525
- });
526
- }
527
- function ColorPickerFormatSelect({ formats = [
528
- "hex",
529
- "rgb",
530
- "hsl",
531
- "oklch"
532
- ], size = "sm", className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, style }) {
533
- const { format, setFormat, disabled } = useColorPicker();
534
- return /* @__PURE__ */ jsxs(Select, {
535
- value: format,
536
- disabled,
537
- onValueChange: (value) => {
538
- if (isColorFormat(value)) setFormat(value);
539
- },
540
- children: [/* @__PURE__ */ jsx(SelectTrigger, {
541
- size,
542
- "data-control-ui": "color-picker",
543
- "data-slot": "format",
544
- "aria-label": ariaLabelledBy === void 0 ? ariaLabel ?? "Color format" : ariaLabel,
545
- "aria-labelledby": ariaLabelledBy,
546
- className,
547
- style,
548
- children: /* @__PURE__ */ jsx(SelectValue, {})
549
- }), /* @__PURE__ */ jsx(SelectContent, { children: formats.map((f) => /* @__PURE__ */ jsx(SelectItem, {
550
- value: f,
551
- children: f.toUpperCase()
552
- }, f)) })]
553
- });
554
- }
555
- function ColorPickerChannels({ className, children, ...props }) {
556
- const { hsva, format, alpha } = useColorPicker();
557
- const specs = getChannels(hsva, format).filter((spec) => alpha || spec.id !== "a");
558
- return /* @__PURE__ */ jsx("div", {
559
- "data-control-ui": "color-picker",
560
- "data-control-family": "color-picker",
561
- "data-slot": "channels",
562
- className: cn("grid grid-flow-col auto-cols-fr", className),
563
- ...props,
564
- children: children ?? specs.map((spec) => /* @__PURE__ */ jsx(ColorPickerChannel, {
565
- channel: spec.id,
566
- label: spec.label
567
- }, spec.id))
568
- });
569
- }
570
- function ColorPickerChannel({ channel, label, className, "aria-label": ariaLabel, ...props }) {
571
- const { hsva, format, setChannelValue, disabled } = useColorPicker();
572
- const spec = getChannels(hsva, format).find((s) => s.id === channel);
573
- if (!spec) return null;
574
- return /* @__PURE__ */ jsxs("div", {
575
- "data-control-ui": "color-picker",
576
- "data-control-family": "color-picker",
577
- "data-slot": "channel",
578
- className: cn("grid", className),
579
- ...props,
580
- children: [/* @__PURE__ */ jsx(NumberField, {
581
- size: "sm",
582
- value: spec.value,
583
- min: spec.min,
584
- max: spec.max,
585
- step: spec.step,
586
- disabled,
587
- onValueChange: (value) => {
588
- if (value !== null) setChannelValue(channel, value);
589
- },
590
- children: /* @__PURE__ */ jsx(NumberFieldGroup, {
591
- "data-control-ui": "color-picker",
592
- "data-control-family": "color-picker",
593
- "data-slot": "channel",
594
- className: "w-full",
595
- children: /* @__PURE__ */ jsx(NumberFieldInput, {
596
- "aria-label": ariaLabel ?? `${CHANNEL_NAMES[channel]} channel`,
597
- className: "px-1"
598
- })
96
+ function GradientEditorStopColor({ children }) {
97
+ const { selectedStop, setStopColor } = useGradientEditor();
98
+ if (!selectedStop) return null;
99
+ return /* @__PURE__ */ jsx(ColorPicker, {
100
+ value: selectedStop.color,
101
+ onValueChange: (color) => setStopColor(selectedStop.id, color),
102
+ defaultFormat: "hex",
103
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ColorPickerTrigger, {}), /* @__PURE__ */ jsxs(ColorPickerContent, { children: [
104
+ /* @__PURE__ */ jsx(ColorPickerArea, {}),
105
+ /* @__PURE__ */ jsx(ColorPickerHue, {}),
106
+ /* @__PURE__ */ jsx(ColorPickerAlpha, {}),
107
+ /* @__PURE__ */ jsxs("div", {
108
+ "data-control-ui": "gradient-editor",
109
+ "data-control-family": "gradient-editor",
110
+ "data-slot": "value-row",
111
+ className: "flex",
112
+ children: [/* @__PURE__ */ jsx(ColorPickerFormatSelect, {}), /* @__PURE__ */ jsx(ColorPickerInput, { className: "flex-1" })]
599
113
  })
600
- }), /* @__PURE__ */ jsx("span", {
601
- "data-control-ui": "color-picker",
602
- "data-control-family": "color-picker",
603
- "data-slot": "channel-label",
604
- children: label ?? spec.label
605
- })]
606
- });
607
- }
608
- function ColorPickerEyeDropper({ className, children, ...props }) {
609
- const { setFromString, disabled } = useColorPicker();
610
- if (!useSyncExternalStore(subscribeEyeDropper, getEyeDropperSnapshot, getEyeDropperServerSnapshot)) return null;
611
- const pick = async () => {
612
- const Ctor = window.EyeDropper;
613
- if (!Ctor) return;
614
- try {
615
- const result = await new Ctor().open();
616
- setFromString(result.sRGBHex);
617
- } catch {}
618
- };
619
- return /* @__PURE__ */ jsx(Button, {
620
- variant: "surface",
621
- size: "sm",
622
- disabled,
623
- "aria-label": "Pick a color from the screen",
624
- "data-control-ui": "color-picker",
625
- "data-slot": "eye-dropper",
626
- className,
627
- onClick: pick,
628
- ...props,
629
- children: children ?? /* @__PURE__ */ jsx(EyeDropperIcon, {})
630
- });
631
- }
632
- function ColorPickerSwatches({ colors, label, className, children, ...props }) {
633
- return /* @__PURE__ */ jsxs("div", {
634
- "data-control-ui": "color-picker",
635
- "data-control-family": "color-picker",
636
- "data-slot": "swatches-group",
637
- className: "grid",
638
- children: [label ? /* @__PURE__ */ jsx("span", {
639
- "data-control-ui": "color-picker",
640
- "data-control-family": "color-picker",
641
- "data-slot": "swatches-label",
642
- children: label
643
- }) : null, /* @__PURE__ */ jsxs("div", {
644
- "data-control-ui": "color-picker",
645
- "data-control-family": "color-picker",
646
- "data-slot": "swatches",
647
- className: cn("flex flex-wrap", className),
648
- ...props,
649
- children: [colors?.map((color) => /* @__PURE__ */ jsx(ColorPickerSwatch, { color }, color)), children]
650
- })]
651
- });
652
- }
653
- function ColorPickerSwatch({ color, className, "aria-label": ariaLabel, ...props }) {
654
- const { setFromString, valueString, disabled } = useColorPicker();
655
- const selected = sameColor(valueString, color);
656
- return /* @__PURE__ */ jsxs("button", {
657
- type: "button",
658
- "data-control-ui": "color-picker",
659
- "data-control-family": "color-picker",
660
- "data-slot": "swatch",
661
- "data-selected": selected ? "true" : void 0,
662
- "aria-pressed": selected,
663
- "aria-label": ariaLabel ?? `Set color ${color}`,
664
- title: color,
665
- disabled,
666
- onClick: () => setFromString(color),
667
- className: cn("relative shrink-0 cursor-pointer overflow-hidden disabled:cursor-not-allowed", className),
668
- ...props,
669
- children: [/* @__PURE__ */ jsx("span", {
670
- "data-control-ui": "color-picker",
671
- "data-control-family": "color-picker",
672
- "data-slot": "swatch-checker",
673
- "aria-hidden": true,
674
- className: "absolute inset-0",
675
- style: {
676
- backgroundImage: CHECKER,
677
- backgroundSize: CHECKER_SIZE
678
- }
679
- }), /* @__PURE__ */ jsx("span", {
680
- "data-control-ui": "color-picker",
681
- "data-control-family": "color-picker",
682
- "data-slot": "swatch-color",
683
- "aria-hidden": true,
684
- className: "absolute inset-0",
685
- style: { backgroundColor: color }
686
- })]
687
- });
688
- }
689
- function ColorPickerSwatchAdd({ onAdd, className, children, ...props }) {
690
- const { valueString, disabled } = useColorPicker();
691
- return /* @__PURE__ */ jsx("button", {
692
- type: "button",
693
- "data-control-ui": "color-picker",
694
- "data-control-family": "color-picker",
695
- "data-slot": "swatch-add",
696
- "aria-label": "Add current color",
697
- disabled,
698
- onClick: () => onAdd?.(valueString),
699
- className: cn("relative flex shrink-0 cursor-pointer items-center justify-center disabled:cursor-not-allowed", className),
700
- ...props,
701
- children: children ?? /* @__PURE__ */ jsx(PlusIcon, {})
702
- });
703
- }
704
- function ColorPickerContrast({ background = "#ffffff", passLabel = "Passes", failLabel = "Fails", className, ...props }) {
705
- const { hsva, setFromString, disabled } = useColorPicker();
706
- const bg = parseColor(background);
707
- if (!bg) return null;
708
- const bgRgba = hsvaToRgba(bg);
709
- const ratio = contrastOf(hsva, bgRgba);
710
- const levels = wcagLevels(ratio);
711
- const target = nextFixLevel(levels);
712
- const applyFix = () => {
713
- if (!target) return;
714
- const fixed = fixColorForContrast(hsva, bgRgba, TARGET_RATIO[target]);
715
- if (fixed) setFromString(formatColor(fixed, "hex"));
716
- };
717
- return /* @__PURE__ */ jsxs("div", {
718
- "data-control-ui": "color-picker",
719
- "data-control-family": "color-picker",
720
- "data-slot": "contrast",
721
- className: cn("flex items-center", className),
722
- ...props,
723
- children: [
724
- /* @__PURE__ */ jsxs("span", {
725
- "data-control-ui": "color-picker",
726
- "data-control-family": "color-picker",
727
- "data-slot": "contrast-ratio",
728
- children: [formatContrastRatio(ratio), ":1"]
729
- }),
730
- /* @__PURE__ */ jsx(WcagPill, {
731
- ok: levels.AA,
732
- passLabel,
733
- failLabel,
734
- children: "AA"
735
- }),
736
- /* @__PURE__ */ jsx(WcagPill, {
737
- ok: levels.AAA,
738
- passLabel,
739
- failLabel,
740
- children: "AAA"
741
- }),
742
- target ? /* @__PURE__ */ jsxs("button", {
743
- type: "button",
744
- "data-control-ui": "color-picker",
745
- "data-control-family": "color-picker",
746
- "data-slot": "contrast-fix",
747
- disabled,
748
- onClick: applyFix,
749
- className: "ms-auto cursor-pointer disabled:cursor-not-allowed",
750
- children: ["Fix for ", target]
751
- }) : null
752
- ]
753
- });
754
- }
755
- function WcagPill({ ok, passLabel, failLabel, children }) {
756
- return /* @__PURE__ */ jsxs("span", {
757
- "data-control-ui": "color-picker",
758
- "data-control-family": "color-picker",
759
- "data-slot": "contrast-level",
760
- "data-passing": ok ? "true" : void 0,
761
- className: "inline-flex items-center",
762
- children: [
763
- /* @__PURE__ */ jsx("span", {
764
- "aria-hidden": "true",
765
- children: ok ? "✓" : "✕"
766
- }),
767
- /* @__PURE__ */ jsx("span", {
768
- className: "sr-only",
769
- children: ok ? passLabel : failLabel
770
- }),
771
- " ",
772
- children
773
- ]
774
- });
775
- }
776
- function ColorPickerOutput({ className, children, renderValue, ...props }) {
777
- const { valueString } = useColorPicker();
778
- return /* @__PURE__ */ jsx("div", {
779
- "data-control-ui": "color-picker",
780
- "data-control-family": "color-picker",
781
- "data-slot": "output",
782
- className: cn("flex items-center", className),
783
- ...props,
784
- children: renderValue ? renderValue({ value: valueString }) : children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
785
- "data-control-ui": "color-picker",
786
- "data-control-family": "color-picker",
787
- "data-slot": "output-swatch",
788
- className: "relative shrink-0 overflow-hidden",
789
- children: [/* @__PURE__ */ jsx("span", {
790
- "data-control-ui": "color-picker",
791
- "data-control-family": "color-picker",
792
- "data-slot": "output-checker",
793
- "aria-hidden": true,
794
- className: "absolute inset-0",
795
- style: {
796
- backgroundImage: CHECKER,
797
- backgroundSize: CHECKER_SIZE
798
- }
799
- }), /* @__PURE__ */ jsx("span", {
800
- "data-control-ui": "color-picker",
801
- "data-control-family": "color-picker",
802
- "data-slot": "output-color",
803
- "aria-hidden": true,
804
- className: "absolute inset-0",
805
- style: { backgroundColor: valueString }
806
- })]
807
- }), /* @__PURE__ */ jsx("span", {
808
- "data-control-ui": "color-picker",
809
- "data-control-family": "color-picker",
810
- "data-slot": "output-value",
811
- children: valueString
812
- })] })
813
- });
814
- }
815
- function sameColor(a, b) {
816
- const pa = parseColor(a);
817
- const pb = parseColor(b);
818
- if (!pa || !pb) return false;
819
- return formatColor(pa, "hex", { alpha: true }) === formatColor(pb, "hex", { alpha: true });
820
- }
821
- function EyeDropperIcon() {
822
- return /* @__PURE__ */ jsx("svg", {
823
- viewBox: "0 0 16 16",
824
- className: "size-4",
825
- fill: "none",
826
- stroke: "currentColor",
827
- strokeWidth: "1.3",
828
- strokeLinecap: "round",
829
- strokeLinejoin: "round",
830
- "aria-hidden": "true",
831
- children: /* @__PURE__ */ jsx("path", { d: "M10.5 2.5a1.8 1.8 0 0 1 2.5 2.5l-1.3 1.3.9.9-1.1 1.1-.9-.9-4.4 4.4-2.2.6.6-2.2 4.4-4.4-.9-.9 1.1-1.1.9.9 1.3-1.3Z" })
832
- });
833
- }
834
- function PlusIcon() {
835
- return /* @__PURE__ */ jsx("svg", {
836
- viewBox: "0 0 16 16",
837
- className: "size-3.5",
838
- fill: "none",
839
- stroke: "currentColor",
840
- strokeWidth: "1.5",
841
- strokeLinecap: "round",
842
- "aria-hidden": "true",
843
- children: /* @__PURE__ */ jsx("path", { d: "M8 3v10M3 8h10" })
114
+ ] })] })
844
115
  });
845
116
  }
846
117
  //#endregion
847
- export { ColorPicker, ColorPickerAlpha, ColorPickerArea, ColorPickerAreaThumb, ColorPickerChannel, ColorPickerChannels, ColorPickerContent, ColorPickerContrast, ColorPickerEyeDropper, ColorPickerFormatSelect, ColorPickerHue, ColorPickerInput, ColorPickerOutput, ColorPickerPanel, ColorPickerSwatch, ColorPickerSwatchAdd, ColorPickerSwatches, ColorPickerTrigger, ColorPickerWheel };
118
+ export { ColorPicker, ColorPickerAlpha, ColorPickerArea, ColorPickerAreaContrast, ColorPickerAreaThumb, ColorPickerBrightness, ColorPickerChannel, ColorPickerChannels, ColorPickerContent, ColorPickerContrast, ColorPickerEyeDropper, ColorPickerFormatSelect, ColorPickerHue, ColorPickerInput, ColorPickerOutput, ColorPickerPanel, ColorPickerSaturation, ColorPickerSwatch, ColorPickerSwatchAdd, ColorPickerSwatches, ColorPickerTrigger, ColorPickerWheel, GradientEditor, GradientEditorAngle, GradientEditorInterpolationSelect, GradientEditorPreview, GradientEditorStop, GradientEditorStopAdd, GradientEditorStopColor, GradientEditorTrack, GradientEditorTypeSelect };