@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
@@ -0,0 +1,257 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { readDurationMs } from "../lib/motion.js";
4
+ import { CHART_COLORS, chartColor } from "./chart-colors.js";
5
+ import { useEffect, useId, useRef } from "react";
6
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
+ import { Chart as Chart$1 } from "@tanstack/charts/react/tooltip";
8
+ //#region src/ui/chart.tsx
9
+ const NON_ID_CHARACTERS = /[^\w-]/g;
10
+ function Chart({ className, style, ...props }) {
11
+ return /* @__PURE__ */ jsx("div", {
12
+ "data-control-ui": "chart",
13
+ "data-control-family": "chart",
14
+ "data-slot": "root",
15
+ className: cn(className),
16
+ style,
17
+ ...props
18
+ });
19
+ }
20
+ function chartPatternStyle(id) {
21
+ return {
22
+ "--_cui-chart-hatch-blue": `url(#${id}-hatch-blue)`,
23
+ "--_cui-chart-hatch-purple": `url(#${id}-hatch-purple)`,
24
+ "--_cui-chart-hatch-pink": `url(#${id}-hatch-pink)`,
25
+ "--_cui-chart-hatch-orange": `url(#${id}-hatch-orange)`,
26
+ "--_cui-chart-hatch-green": `url(#${id}-hatch-green)`,
27
+ "--_cui-chart-hatch-yellow": `url(#${id}-hatch-yellow)`,
28
+ "--_cui-chart-hatch-red": `url(#${id}-hatch-red)`,
29
+ "--_cui-chart-hatch-neutral": `url(#${id}-hatch-neutral)`,
30
+ "--_cui-chart-fade": `url(#${id}-fade)`
31
+ };
32
+ }
33
+ function ChartPatterns({ id }) {
34
+ return /* @__PURE__ */ jsx("svg", {
35
+ "aria-hidden": "true",
36
+ focusable: "false",
37
+ "data-control-ui": "chart",
38
+ "data-control-family": "chart",
39
+ "data-slot": "patterns",
40
+ width: "0",
41
+ height: "0",
42
+ className: "absolute size-0 overflow-hidden",
43
+ children: /* @__PURE__ */ jsxs("defs", { children: [
44
+ CHART_COLORS.map((color) => /* @__PURE__ */ jsxs("pattern", {
45
+ id: `${id}-hatch-${color}`,
46
+ patternUnits: "userSpaceOnUse",
47
+ width: "6",
48
+ height: "6",
49
+ patternTransform: "rotate(45)",
50
+ children: [/* @__PURE__ */ jsx("rect", {
51
+ "data-control-ui": "chart",
52
+ "data-control-family": "chart",
53
+ "data-slot": "hatch-tint",
54
+ width: "6",
55
+ height: "6",
56
+ fill: chartColor(color)
57
+ }), /* @__PURE__ */ jsx("rect", {
58
+ "data-control-ui": "chart",
59
+ "data-control-family": "chart",
60
+ "data-slot": "hatch-stripe",
61
+ width: "2",
62
+ height: "6",
63
+ fill: chartColor(color)
64
+ })]
65
+ }, color)),
66
+ /* @__PURE__ */ jsxs("linearGradient", {
67
+ id: `${id}-fade-gradient`,
68
+ x1: "0",
69
+ y1: "0",
70
+ x2: "0",
71
+ y2: "1",
72
+ children: [/* @__PURE__ */ jsx("stop", {
73
+ offset: "0",
74
+ stopColor: "oklch(1 0 0)"
75
+ }), /* @__PURE__ */ jsx("stop", {
76
+ offset: "1",
77
+ stopColor: "oklch(1 0 0)",
78
+ stopOpacity: "0.15"
79
+ })]
80
+ }),
81
+ /* @__PURE__ */ jsx("mask", {
82
+ id: `${id}-fade`,
83
+ maskContentUnits: "objectBoundingBox",
84
+ children: /* @__PURE__ */ jsx("rect", {
85
+ width: "1",
86
+ height: "1",
87
+ fill: `url(#${id}-fade-gradient)`
88
+ })
89
+ })
90
+ ] })
91
+ });
92
+ }
93
+ function renderChartTooltipBody(context) {
94
+ return /* @__PURE__ */ jsx(ChartTooltipBody, { content: context.content });
95
+ }
96
+ function ChartPlot({ className, style, children, ...props }) {
97
+ const patternId = `cui-chart-${useId().replace(NON_ID_CHARACTERS, "")}`;
98
+ const plotRef = useRef(null);
99
+ useEffect(() => {
100
+ const node = plotRef.current;
101
+ if (!node) return;
102
+ let settleTimer;
103
+ const observer = new ResizeObserver(() => {
104
+ node.dataset.resizing = "";
105
+ clearTimeout(settleTimer);
106
+ settleTimer = window.setTimeout(() => {
107
+ delete node.dataset.resizing;
108
+ }, readDurationMs(node, "--duration-fast"));
109
+ });
110
+ observer.observe(node);
111
+ return () => {
112
+ observer.disconnect();
113
+ clearTimeout(settleTimer);
114
+ };
115
+ }, []);
116
+ useEffect(() => {
117
+ const node = plotRef.current;
118
+ if (!node) return;
119
+ let enterTimer;
120
+ const observer = new MutationObserver((records) => {
121
+ if (!records.some((record) => record.oldValue !== null && record.target instanceof HTMLElement && record.target.classList.contains("ts-chart-tooltip") && !record.target.hidden)) return;
122
+ node.dataset.tooltipEntering = "";
123
+ clearTimeout(enterTimer);
124
+ enterTimer = window.setTimeout(() => {
125
+ delete node.dataset.tooltipEntering;
126
+ }, readDurationMs(node, "--duration-fast"));
127
+ });
128
+ observer.observe(node, {
129
+ subtree: true,
130
+ attributeFilter: ["hidden"],
131
+ attributeOldValue: true
132
+ });
133
+ return () => {
134
+ observer.disconnect();
135
+ clearTimeout(enterTimer);
136
+ };
137
+ }, []);
138
+ return /* @__PURE__ */ jsxs("div", {
139
+ ref: plotRef,
140
+ "data-control-ui": "chart",
141
+ "data-control-family": "chart",
142
+ "data-slot": "plot",
143
+ "data-focus-ring": "within",
144
+ className: cn(className),
145
+ style: {
146
+ ...chartPatternStyle(patternId),
147
+ ...style
148
+ },
149
+ children: [
150
+ /* @__PURE__ */ jsx(ChartPatterns, { id: patternId }),
151
+ /* @__PURE__ */ jsx(Chart$1, {
152
+ renderTooltipBody: renderChartTooltipBody,
153
+ ...props
154
+ }),
155
+ children
156
+ ]
157
+ });
158
+ }
159
+ function ChartCenter({ className, ...props }) {
160
+ return /* @__PURE__ */ jsx("div", {
161
+ "data-control-ui": "chart",
162
+ "data-control-family": "chart",
163
+ "data-slot": "center",
164
+ className: cn("pointer-events-none absolute inset-0 grid place-content-center text-center", className),
165
+ ...props
166
+ });
167
+ }
168
+ function ChartLegend({ className, ...props }) {
169
+ return /* @__PURE__ */ jsx("ul", {
170
+ "data-control-ui": "chart",
171
+ "data-control-family": "chart",
172
+ "data-slot": "legend",
173
+ className: cn("text-label text-muted-foreground", className),
174
+ ...props
175
+ });
176
+ }
177
+ function ChartSwatch({ color }) {
178
+ return /* @__PURE__ */ jsx("span", {
179
+ "aria-hidden": "true",
180
+ "data-control-ui": "chart",
181
+ "data-control-family": "chart",
182
+ "data-slot": "swatch",
183
+ style: { "--_cui-chart-swatch": color }
184
+ });
185
+ }
186
+ function ChartLegendItem({ color, fill = "solid", hidden = false, onHiddenChange, children, className, style, ...props }) {
187
+ const legendStyle = {
188
+ "--_cui-chart-swatch": chartColor(color),
189
+ ...style
190
+ };
191
+ const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
192
+ "aria-hidden": "true",
193
+ "data-control-ui": "chart",
194
+ "data-control-family": "chart",
195
+ "data-slot": "swatch"
196
+ }), children] });
197
+ return /* @__PURE__ */ jsx("li", {
198
+ "data-control-ui": "chart",
199
+ "data-control-family": "chart",
200
+ "data-slot": "legend-item",
201
+ "data-fill": fill,
202
+ "data-hidden": hidden ? "true" : void 0,
203
+ className: cn(className),
204
+ style: legendStyle,
205
+ ...props,
206
+ children: onHiddenChange ? /* @__PURE__ */ jsx("button", {
207
+ type: "button",
208
+ "data-control-ui": "chart",
209
+ "data-control-family": "chart",
210
+ "data-slot": "legend-toggle",
211
+ "aria-pressed": !hidden,
212
+ onClick: () => onHiddenChange(!hidden),
213
+ children: content
214
+ }) : /* @__PURE__ */ jsx("span", {
215
+ className: "inline-flex items-center gap-1.5",
216
+ children: content
217
+ })
218
+ });
219
+ }
220
+ function ChartTooltipBody({ content, className, ...props }) {
221
+ return /* @__PURE__ */ jsx("div", {
222
+ "data-control-ui": "chart",
223
+ "data-control-family": "chart",
224
+ "data-slot": "tooltip",
225
+ className: cn("grid min-w-32 gap-1.5", className),
226
+ ...props,
227
+ children: typeof content === "string" ? /* @__PURE__ */ jsx("p", {
228
+ className: "text-label",
229
+ children: content
230
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [content.title ? /* @__PURE__ */ jsx("p", {
231
+ "data-control-ui": "chart",
232
+ "data-control-family": "chart",
233
+ "data-slot": "tooltip-title",
234
+ className: "font-medium text-label",
235
+ children: content.title
236
+ }) : null, content.rows.map((row) => /* @__PURE__ */ jsxs("div", {
237
+ "data-control-ui": "chart",
238
+ "data-control-family": "chart",
239
+ "data-slot": "tooltip-row",
240
+ "data-active": row.active ? "true" : void 0,
241
+ className: "flex items-center gap-2 text-label",
242
+ children: [
243
+ /* @__PURE__ */ jsx(ChartSwatch, { color: row.color ?? "currentColor" }),
244
+ /* @__PURE__ */ jsx("span", {
245
+ className: row.active ? void 0 : "text-muted-foreground",
246
+ children: row.label
247
+ }),
248
+ /* @__PURE__ */ jsx("span", {
249
+ className: "ms-auto font-medium tabular-nums",
250
+ children: row.value
251
+ })
252
+ ]
253
+ }, row.label))] })
254
+ });
255
+ }
256
+ //#endregion
257
+ export { Chart, ChartCenter, ChartLegend, ChartLegendItem, ChartPlot, ChartTooltipBody };
@@ -0,0 +1,16 @@
1
+ import { ColorPickerKnobStyle } from "../knob-contracts/color-picker-knobs.js";
2
+ import { CSSProperties, ComponentProps } from "react";
3
+ //#region src/ui/color-picker-area.d.ts
4
+ export type ColorPickerAreaProps = Omit<ComponentProps<"div">, "style"> & {
5
+ style?: CSSProperties & ColorPickerKnobStyle;
6
+ };
7
+ export type ColorPickerAreaThumbProps = Omit<ComponentProps<"div">, "style"> & {
8
+ style?: CSSProperties & ColorPickerKnobStyle;
9
+ };
10
+ export type ColorPickerWheelProps = Omit<ComponentProps<"div">, "style"> & {
11
+ style?: CSSProperties & ColorPickerKnobStyle;
12
+ };
13
+ export declare function ColorPickerArea({ className, style, children, ...props }: ColorPickerAreaProps): import("react").JSX.Element;
14
+ export declare function ColorPickerAreaThumb({ className, style, ...props }: ColorPickerAreaThumbProps): import("react").JSX.Element;
15
+ export declare function ColorPickerWheel({ className, style, ...props }: ColorPickerWheelProps): import("react").JSX.Element;
16
+ //#endregion
@@ -0,0 +1,175 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { useColorArea } from "../hooks/use-color-area.js";
4
+ import { pointToHueSat, pointToSaturationValue } from "../lib/color.js";
5
+ import { useColorPicker } from "./color-picker-context.js";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/ui/color-picker-area.tsx
8
+ function ColorPickerArea({ className, style, children, ...props }) {
9
+ const { hsva, setHsva, disabled } = useColorPicker();
10
+ const { areaRef, onPointerDown, dragging } = useColorArea((offset, rect) => {
11
+ if (disabled) return;
12
+ const { s, v } = pointToSaturationValue(rect, offset.x, offset.y);
13
+ setHsva({
14
+ s,
15
+ v
16
+ });
17
+ });
18
+ function axisKey(axis, event) {
19
+ if (!event.shiftKey) return;
20
+ const cur = axis === "s" ? hsva.s : hsva.v;
21
+ let delta = 0;
22
+ if (event.key === "ArrowUp" || event.key === "ArrowRight") delta = 10;
23
+ else if (event.key === "ArrowDown" || event.key === "ArrowLeft") delta = -10;
24
+ if (delta === 0) return;
25
+ event.preventDefault();
26
+ const next = Math.min(100, Math.max(0, cur + delta));
27
+ setHsva(axis === "s" ? { s: next } : { v: next });
28
+ }
29
+ return /* @__PURE__ */ jsxs("div", {
30
+ "data-focus-ring": "within",
31
+ ref: areaRef,
32
+ "data-control-ui": "color-picker",
33
+ "data-control-family": "color-picker",
34
+ "data-slot": "area",
35
+ "data-disabled": disabled ? "true" : void 0,
36
+ "data-dragging": dragging ? "true" : void 0,
37
+ role: "group",
38
+ "aria-label": "Saturation and brightness",
39
+ onPointerDown: disabled ? void 0 : onPointerDown,
40
+ className: cn("group relative w-full touch-none select-none overflow-hidden", disabled ? "cursor-not-allowed" : "cursor-crosshair", className),
41
+ style: {
42
+ ...style,
43
+ backgroundColor: `hsl(${hsva.h} 100% 50%)`
44
+ },
45
+ ...props,
46
+ children: [
47
+ /* @__PURE__ */ jsx("span", {
48
+ "aria-hidden": true,
49
+ "data-control-ui": "color-picker",
50
+ "data-control-family": "color-picker",
51
+ "data-slot": "area-saturation",
52
+ className: "absolute inset-0"
53
+ }),
54
+ /* @__PURE__ */ jsx("span", {
55
+ "aria-hidden": true,
56
+ "data-control-ui": "color-picker",
57
+ "data-control-family": "color-picker",
58
+ "data-slot": "area-brightness",
59
+ className: "absolute inset-0"
60
+ }),
61
+ /* @__PURE__ */ jsx("input", {
62
+ type: "range",
63
+ "aria-label": "Saturation",
64
+ className: "sr-only",
65
+ min: 0,
66
+ max: 100,
67
+ step: 1,
68
+ value: Math.round(hsva.s),
69
+ disabled,
70
+ onChange: (event) => setHsva({ s: Number(event.target.value) }),
71
+ onKeyDown: (event) => axisKey("s", event)
72
+ }),
73
+ /* @__PURE__ */ jsx("input", {
74
+ type: "range",
75
+ "aria-label": "Brightness",
76
+ className: "sr-only",
77
+ min: 0,
78
+ max: 100,
79
+ step: 1,
80
+ value: Math.round(hsva.v),
81
+ disabled,
82
+ onChange: (event) => setHsva({ v: Number(event.target.value) }),
83
+ onKeyDown: (event) => axisKey("v", event)
84
+ }),
85
+ children ?? /* @__PURE__ */ jsx(ColorPickerAreaThumb, {})
86
+ ]
87
+ });
88
+ }
89
+ function ColorPickerAreaThumb({ className, style, ...props }) {
90
+ const { hsva } = useColorPicker();
91
+ return /* @__PURE__ */ jsx("div", {
92
+ "data-control-ui": "color-picker",
93
+ "data-control-family": "color-picker",
94
+ "data-slot": "area-thumb",
95
+ "aria-hidden": true,
96
+ className: cn("pointer-events-none absolute -translate-x-1/2 -translate-y-1/2", className),
97
+ style: {
98
+ ...style,
99
+ left: `${hsva.s}%`,
100
+ top: `${100 - hsva.v}%`
101
+ },
102
+ ...props
103
+ });
104
+ }
105
+ function ColorPickerWheel({ className, style, ...props }) {
106
+ const { hsva, setHsva, disabled } = useColorPicker();
107
+ const { areaRef, onPointerDown, dragging } = useColorArea((offset, rect) => {
108
+ if (disabled) return;
109
+ const { h, s } = pointToHueSat(rect, offset.x, offset.y);
110
+ setHsva({
111
+ h,
112
+ s
113
+ });
114
+ });
115
+ const radius = hsva.s / 100 * 50;
116
+ const rad = hsva.h * Math.PI / 180;
117
+ const left = 50 + radius * Math.cos(rad);
118
+ const top = 50 + radius * Math.sin(rad);
119
+ const wheelStyle = {
120
+ ...style,
121
+ "--_color-picker-wheel-shade": String(1 - hsva.v / 100)
122
+ };
123
+ return /* @__PURE__ */ jsxs("div", {
124
+ "data-focus-ring": "within",
125
+ ref: areaRef,
126
+ "data-control-ui": "color-picker",
127
+ "data-control-family": "color-picker",
128
+ "data-slot": "wheel",
129
+ "data-disabled": disabled ? "true" : void 0,
130
+ "data-dragging": dragging ? "true" : void 0,
131
+ role: "group",
132
+ "aria-label": "Color wheel",
133
+ onPointerDown: disabled ? void 0 : onPointerDown,
134
+ className: cn("group relative aspect-square w-full touch-none select-none", disabled ? "cursor-not-allowed" : "cursor-crosshair", className),
135
+ style: wheelStyle,
136
+ ...props,
137
+ children: [
138
+ /* @__PURE__ */ jsx("div", {
139
+ "data-control-ui": "color-picker",
140
+ "data-control-family": "color-picker",
141
+ "data-slot": "wheel-thumb",
142
+ "aria-hidden": true,
143
+ className: "pointer-events-none absolute -translate-x-1/2 -translate-y-1/2",
144
+ style: {
145
+ left: `${left}%`,
146
+ top: `${top}%`
147
+ }
148
+ }),
149
+ /* @__PURE__ */ jsx("input", {
150
+ type: "range",
151
+ "aria-label": "Wheel hue",
152
+ className: "sr-only",
153
+ min: 0,
154
+ max: 360,
155
+ step: 1,
156
+ value: Math.round(hsva.h),
157
+ disabled,
158
+ onChange: (event) => setHsva({ h: Number(event.target.value) })
159
+ }),
160
+ /* @__PURE__ */ jsx("input", {
161
+ type: "range",
162
+ "aria-label": "Wheel saturation",
163
+ className: "sr-only",
164
+ min: 0,
165
+ max: 100,
166
+ step: 1,
167
+ value: Math.round(hsva.s),
168
+ disabled,
169
+ onChange: (event) => setHsva({ s: Number(event.target.value) })
170
+ })
171
+ ]
172
+ });
173
+ }
174
+ //#endregion
175
+ export { ColorPickerArea, ColorPickerAreaThumb, ColorPickerWheel };
@@ -0,0 +1,27 @@
1
+ import { ChannelId, ColorFormat, Hsva } from "../lib/color.js";
2
+ //#region src/ui/color-picker-context.d.ts
3
+ export type ColorPickerStateProps = {
4
+ value?: string;
5
+ defaultValue?: string;
6
+ onValueChange?: (value: string) => void;
7
+ format?: ColorFormat;
8
+ defaultFormat?: ColorFormat;
9
+ onFormatChange?: (format: ColorFormat) => void;
10
+ alpha?: boolean;
11
+ disabled?: boolean;
12
+ };
13
+ type ColorPickerContextValue = {
14
+ hsva: Hsva;
15
+ format: ColorFormat;
16
+ alpha: boolean;
17
+ disabled: boolean;
18
+ valueString: string;
19
+ setHsva: (partial: Partial<Hsva>) => void;
20
+ setChannelValue: (id: ChannelId, value: number) => void;
21
+ setFromString: (raw: string) => boolean;
22
+ setFormat: (format: ColorFormat) => void;
23
+ };
24
+ export declare const ColorPickerContext: import("react").Context<ColorPickerContextValue | null>;
25
+ export declare function useColorPicker(): ColorPickerContextValue;
26
+ export declare function useColorState(props: ColorPickerStateProps): ColorPickerContextValue;
27
+ //#endregion
@@ -0,0 +1,87 @@
1
+ "use client";
2
+ import { formatColor, parseColor, preserveAchromatic, setChannel } from "../lib/color.js";
3
+ import { createContext, useContext, useEffect, useRef, useState } from "react";
4
+ //#region src/ui/color-picker-context.ts
5
+ const ColorPickerContext = createContext(null);
6
+ function useColorPicker() {
7
+ const ctx = useContext(ColorPickerContext);
8
+ if (!ctx) throw new Error("ColorPicker parts must be rendered inside <ColorPicker>.");
9
+ return ctx;
10
+ }
11
+ function useColorState(props) {
12
+ const { value, defaultValue = "#000000", onValueChange, format: formatProp, defaultFormat = "hex", onFormatChange, alpha = true, disabled = false } = props;
13
+ const [hsva, setHsvaState] = useState(() => parseColor(value ?? defaultValue) ?? {
14
+ h: 0,
15
+ s: 0,
16
+ v: 0,
17
+ a: 1
18
+ });
19
+ const [formatState, setFormatState] = useState(defaultFormat);
20
+ const format = formatProp ?? formatState;
21
+ const hsvaRef = useRef(hsva);
22
+ const formatRef = useRef(format);
23
+ const alphaRef = useRef(alpha);
24
+ const onValueChangeRef = useRef(onValueChange);
25
+ const lastEmitted = useRef(null);
26
+ useEffect(() => {
27
+ hsvaRef.current = hsva;
28
+ formatRef.current = format;
29
+ alphaRef.current = alpha;
30
+ onValueChangeRef.current = onValueChange;
31
+ }, [
32
+ hsva,
33
+ format,
34
+ alpha,
35
+ onValueChange
36
+ ]);
37
+ const emit = (next, fmt) => {
38
+ const str = formatColor(next, fmt, { alpha: alphaRef.current });
39
+ lastEmitted.current = str;
40
+ onValueChangeRef.current?.(str);
41
+ };
42
+ const commit = (next) => {
43
+ hsvaRef.current = next;
44
+ setHsvaState(next);
45
+ emit(next, formatRef.current);
46
+ };
47
+ useEffect(() => {
48
+ if (lastEmitted.current === null) lastEmitted.current = formatColor(hsvaRef.current, formatRef.current, { alpha: alphaRef.current });
49
+ if (value === void 0 || value === lastEmitted.current) return;
50
+ const parsed = parseColor(value);
51
+ if (!parsed) return;
52
+ const reconciled = preserveAchromatic(parsed, hsvaRef.current);
53
+ hsvaRef.current = reconciled;
54
+ lastEmitted.current = value;
55
+ setHsvaState(reconciled);
56
+ }, [value]);
57
+ const setHsva = (partial) => commit({
58
+ ...hsvaRef.current,
59
+ ...partial
60
+ });
61
+ const setChannelValue = (id, next) => commit(setChannel(hsvaRef.current, id, next));
62
+ const setFromString = (raw) => {
63
+ const parsed = parseColor(raw);
64
+ if (!parsed) return false;
65
+ commit(preserveAchromatic(parsed, hsvaRef.current));
66
+ return true;
67
+ };
68
+ const setFormat = (next) => {
69
+ if (formatProp === void 0) setFormatState(next);
70
+ formatRef.current = next;
71
+ onFormatChange?.(next);
72
+ emit(hsvaRef.current, next);
73
+ };
74
+ return {
75
+ hsva,
76
+ format,
77
+ alpha,
78
+ disabled,
79
+ valueString: formatColor(hsva, format, { alpha }),
80
+ setHsva,
81
+ setChannelValue,
82
+ setFromString,
83
+ setFormat
84
+ };
85
+ }
86
+ //#endregion
87
+ export { ColorPickerContext, useColorPicker, useColorState };
@@ -0,0 +1,20 @@
1
+ import { ColorPickerKnobStyle } from "../knob-contracts/color-picker-knobs.js";
2
+ import { WcagLevel } from "../lib/contrast.js";
3
+ import { CSSProperties, ComponentProps } from "react";
4
+ //#region src/ui/color-picker-contrast.d.ts
5
+ export type ColorPickerContrastProps = Omit<Omit<ComponentProps<"div">, "children">, "style"> & {
6
+ style?: CSSProperties & ColorPickerKnobStyle;
7
+ } & {
8
+ background?: string;
9
+ passLabel?: string;
10
+ failLabel?: string;
11
+ };
12
+ export type ColorPickerAreaContrastProps = Omit<ComponentProps<"span">, "children" | "style"> & {
13
+ style?: CSSProperties & ColorPickerKnobStyle;
14
+ } & {
15
+ background?: string;
16
+ level?: WcagLevel;
17
+ };
18
+ export declare function ColorPickerContrast({ background, passLabel, failLabel, className, ...props }: ColorPickerContrastProps): import("react").JSX.Element | null;
19
+ export declare function ColorPickerAreaContrast({ background, level, className, ...props }: ColorPickerAreaContrastProps): import("react").JSX.Element | null;
20
+ //#endregion