chroma-panel 0.2.0 → 1.0.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 (92) hide show
  1. package/README.md +46 -8
  2. package/dist/a11y/contrast.cjs +94 -0
  3. package/dist/a11y/contrast.d.cts +8 -0
  4. package/dist/a11y/contrast.d.ts +8 -0
  5. package/dist/a11y/contrast.js +93 -2
  6. package/dist/color/css4.cjs +397 -0
  7. package/dist/color/css4.d.cts +10 -0
  8. package/dist/color/css4.d.ts +10 -0
  9. package/dist/color/css4.js +392 -0
  10. package/dist/color/parse.cjs +3 -1
  11. package/dist/color/parse.js +3 -1
  12. package/dist/color/types.d.cts +16 -0
  13. package/dist/color/types.d.ts +16 -0
  14. package/dist/color/value.cjs +11 -0
  15. package/dist/color/value.d.cts +4 -0
  16. package/dist/color/value.d.ts +4 -0
  17. package/dist/color/value.js +12 -0
  18. package/dist/color.cjs +43 -0
  19. package/dist/color.d.cts +9 -0
  20. package/dist/color.d.ts +9 -0
  21. package/dist/color.js +8 -0
  22. package/dist/components/ChromaPanel.cjs +16 -6
  23. package/dist/components/ChromaPanel.d.cts +4 -2
  24. package/dist/components/ChromaPanel.d.ts +4 -2
  25. package/dist/components/ChromaPanel.js +16 -6
  26. package/dist/components/ColorInput.cjs +1 -1
  27. package/dist/components/ColorInput.js +1 -1
  28. package/dist/components/PanelFooter.cjs +4 -4
  29. package/dist/components/PanelFooter.js +4 -4
  30. package/dist/contrast.cjs +3 -0
  31. package/dist/contrast.d.cts +2 -2
  32. package/dist/contrast.d.ts +2 -2
  33. package/dist/contrast.js +2 -2
  34. package/dist/core/store.cjs +17 -9
  35. package/dist/core/store.d.cts +6 -6
  36. package/dist/core/store.d.ts +6 -6
  37. package/dist/core/store.js +17 -9
  38. package/dist/core.cjs +10 -0
  39. package/dist/core.d.cts +4 -2
  40. package/dist/core.d.ts +4 -2
  41. package/dist/core.js +3 -1
  42. package/dist/export/tokens.cjs +39 -0
  43. package/dist/export/tokens.d.cts +16 -0
  44. package/dist/export/tokens.d.ts +16 -0
  45. package/dist/export/tokens.js +37 -0
  46. package/dist/export.cjs +6 -0
  47. package/dist/export.d.cts +2 -0
  48. package/dist/export.d.ts +2 -0
  49. package/dist/export.js +2 -0
  50. package/dist/gradient/GradientEditor.cjs +196 -0
  51. package/dist/gradient/GradientEditor.d.cts +17 -0
  52. package/dist/gradient/GradientEditor.d.ts +17 -0
  53. package/dist/gradient/GradientEditor.js +195 -0
  54. package/dist/gradient/model.cjs +63 -0
  55. package/dist/gradient/model.d.cts +23 -0
  56. package/dist/gradient/model.d.ts +23 -0
  57. package/dist/gradient/model.js +60 -0
  58. package/dist/gradient.cjs +9 -0
  59. package/dist/gradient.d.cts +3 -0
  60. package/dist/gradient.d.ts +3 -0
  61. package/dist/gradient.js +3 -0
  62. package/dist/index.cjs +13 -0
  63. package/dist/index.d.cts +5 -3
  64. package/dist/index.d.ts +5 -3
  65. package/dist/index.js +4 -2
  66. package/dist/modes/image/index.cjs +65 -4
  67. package/dist/modes/image/index.d.cts +1 -0
  68. package/dist/modes/image/index.d.ts +1 -0
  69. package/dist/modes/image/index.js +65 -4
  70. package/dist/modes/registry.d.cts +1 -1
  71. package/dist/modes/registry.d.ts +1 -1
  72. package/dist/modes.cjs +30 -0
  73. package/dist/modes.d.cts +10 -0
  74. package/dist/modes.d.ts +10 -0
  75. package/dist/modes.js +17 -0
  76. package/dist/panel.cjs +10 -0
  77. package/dist/panel.d.cts +4 -2
  78. package/dist/panel.d.ts +4 -2
  79. package/dist/panel.js +3 -1
  80. package/dist/primitives/ChannelSlider.cjs +4 -4
  81. package/dist/primitives/ChannelSlider.js +4 -4
  82. package/dist/primitives/ColorArea.cjs +6 -6
  83. package/dist/primitives/ColorArea.js +6 -6
  84. package/dist/primitives/ColorDisc.cjs +6 -6
  85. package/dist/primitives/ColorDisc.js +6 -6
  86. package/dist/primitives/ColorField.cjs +2 -2
  87. package/dist/primitives/ColorField.js +2 -2
  88. package/dist/primitives/NumberField.cjs +2 -2
  89. package/dist/primitives/NumberField.js +2 -2
  90. package/dist/primitives/SwatchGrid.cjs +2 -2
  91. package/dist/primitives/SwatchGrid.js +2 -2
  92. package/package.json +46 -2
@@ -0,0 +1,196 @@
1
+ "use client";
2
+ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
3
+ const require_model = require("./model.cjs");
4
+ let react = require("react");
5
+ react = require_runtime.__toESM(react, 1);
6
+ let react_jsx_runtime = require("react/jsx-runtime");
7
+ //#region src/gradient/GradientEditor.tsx
8
+ const DEFAULT_GRADIENT = {
9
+ type: "linear",
10
+ angle: 90,
11
+ stops: [{
12
+ id: "start",
13
+ color: "#7c3aed",
14
+ position: 0
15
+ }, {
16
+ id: "end",
17
+ color: "#06b6d4",
18
+ position: 100
19
+ }]
20
+ };
21
+ function GradientEditor(props) {
22
+ const { value, defaultValue = DEFAULT_GRADIENT, onChange, onChangeComplete, disabled = false, minStops = 2, maxStops = 12, className, style, "aria-label": ariaLabel = "Gradient editor" } = props;
23
+ const [internal, setInternal] = react.useState(() => require_model.normalizeGradient(defaultValue));
24
+ const current = require_model.normalizeGradient(value ?? internal);
25
+ const currentRef = react.useRef(current);
26
+ currentRef.current = current;
27
+ const update = (next, complete = false) => {
28
+ const normalized = require_model.normalizeGradient(next);
29
+ currentRef.current = normalized;
30
+ if (value === void 0) setInternal(normalized);
31
+ onChange?.(normalized);
32
+ if (complete) onChangeComplete?.(normalized);
33
+ };
34
+ const patchStop = (id, patch, complete = false) => {
35
+ update({
36
+ ...current,
37
+ stops: current.stops.map((stop) => stop.id === id ? {
38
+ ...stop,
39
+ ...patch
40
+ } : stop)
41
+ }, complete);
42
+ };
43
+ const addStop = () => {
44
+ if (current.stops.length >= maxStops) return;
45
+ const position = 50;
46
+ const id = `stop-${Date.now().toString(36)}`;
47
+ update({
48
+ ...current,
49
+ stops: [...current.stops, {
50
+ id,
51
+ color: "#808080",
52
+ position
53
+ }]
54
+ }, true);
55
+ };
56
+ const removeStop = (id) => {
57
+ if (current.stops.length <= minStops) return;
58
+ update({
59
+ ...current,
60
+ stops: current.stops.filter((stop) => stop.id !== id)
61
+ }, true);
62
+ };
63
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
64
+ className,
65
+ style,
66
+ "aria-label": ariaLabel,
67
+ "data-cp-gradient-editor": "",
68
+ children: [
69
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
70
+ role: "img",
71
+ "aria-label": `Gradient preview with ${current.stops.length} color stops`,
72
+ style: {
73
+ height: 48,
74
+ borderRadius: 8,
75
+ background: require_model.gradientToCss(current)
76
+ }
77
+ }),
78
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
79
+ style: {
80
+ display: "flex",
81
+ gap: 8,
82
+ paddingBlock: 8
83
+ },
84
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
85
+ "Type",
86
+ " ",
87
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
88
+ disabled,
89
+ value: current.type,
90
+ onChange: (event) => update({
91
+ ...current,
92
+ type: event.currentTarget.value
93
+ }, true),
94
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
95
+ value: "linear",
96
+ children: "Linear"
97
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
98
+ value: "radial",
99
+ children: "Radial"
100
+ })]
101
+ })
102
+ ] }), current.type === "linear" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
103
+ "Angle",
104
+ " ",
105
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
106
+ type: "number",
107
+ min: 0,
108
+ max: 359,
109
+ step: 1,
110
+ disabled,
111
+ value: current.angle ?? 90,
112
+ onChange: (event) => update({
113
+ ...current,
114
+ angle: event.currentTarget.valueAsNumber
115
+ }),
116
+ onBlur: () => onChangeComplete?.(currentRef.current)
117
+ })
118
+ ] })]
119
+ }),
120
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
121
+ role: "list",
122
+ "aria-label": "Gradient color stops",
123
+ children: current.stops.map((stop, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
124
+ disabled,
125
+ style: {
126
+ display: "flex",
127
+ gap: 8,
128
+ alignItems: "center",
129
+ border: 0,
130
+ padding: "8px 0",
131
+ margin: 0
132
+ },
133
+ children: [
134
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("legend", {
135
+ style: {
136
+ position: "absolute",
137
+ width: 1,
138
+ height: 1,
139
+ overflow: "hidden",
140
+ clipPath: "inset(50%)"
141
+ },
142
+ children: ["Stop ", index + 1]
143
+ }),
144
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
145
+ type: "color",
146
+ value: /^#[0-9a-f]{6}$/i.test(stop.color) ? stop.color : "#000000",
147
+ "aria-label": `Stop ${index + 1} color`,
148
+ onChange: (event) => patchStop(stop.id, { color: event.currentTarget.value }),
149
+ onBlur: () => onChangeComplete?.(currentRef.current)
150
+ }),
151
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
152
+ type: "text",
153
+ value: stop.color,
154
+ "aria-label": `Stop ${index + 1} CSS color`,
155
+ onChange: (event) => patchStop(stop.id, { color: event.currentTarget.value }),
156
+ onBlur: () => onChangeComplete?.(currentRef.current)
157
+ }),
158
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
159
+ type: "range",
160
+ min: 0,
161
+ max: 100,
162
+ step: 1,
163
+ value: stop.position,
164
+ "aria-label": `Stop ${index + 1} position`,
165
+ "aria-valuetext": `${stop.position} percent`,
166
+ onChange: (event) => patchStop(stop.id, { position: event.currentTarget.valueAsNumber }),
167
+ onPointerUp: () => onChangeComplete?.(currentRef.current),
168
+ onKeyUp: (event) => {
169
+ if (event.key.startsWith("Arrow") || event.key === "Home" || event.key === "End") onChangeComplete?.(currentRef.current);
170
+ }
171
+ }),
172
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("output", {
173
+ "aria-live": "off",
174
+ children: [Math.round(stop.position), "%"]
175
+ }),
176
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
177
+ type: "button",
178
+ disabled: disabled || current.stops.length <= minStops,
179
+ onClick: () => removeStop(stop.id),
180
+ "aria-label": `Remove stop ${index + 1}`,
181
+ children: "Remove"
182
+ })
183
+ ]
184
+ }, stop.id))
185
+ }),
186
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
187
+ type: "button",
188
+ disabled: disabled || current.stops.length >= maxStops,
189
+ onClick: addStop,
190
+ children: "Add color stop"
191
+ })
192
+ ]
193
+ });
194
+ }
195
+ //#endregion
196
+ exports.GradientEditor = GradientEditor;
@@ -0,0 +1,17 @@
1
+ import { GradientValue } from "./model.cjs";
2
+ import * as React from "react";
3
+ //#region src/gradient/GradientEditor.d.ts
4
+ export interface GradientEditorProps {
5
+ value?: GradientValue;
6
+ defaultValue?: GradientValue;
7
+ onChange?: (value: GradientValue) => void;
8
+ onChangeComplete?: (value: GradientValue) => void;
9
+ disabled?: boolean;
10
+ minStops?: number;
11
+ maxStops?: number;
12
+ className?: string;
13
+ style?: React.CSSProperties;
14
+ "aria-label"?: string;
15
+ }
16
+ export declare function GradientEditor(props: GradientEditorProps): React.ReactElement;
17
+ //#endregion
@@ -0,0 +1,17 @@
1
+ import { GradientValue } from "./model.js";
2
+ import * as React from "react";
3
+ //#region src/gradient/GradientEditor.d.ts
4
+ export interface GradientEditorProps {
5
+ value?: GradientValue;
6
+ defaultValue?: GradientValue;
7
+ onChange?: (value: GradientValue) => void;
8
+ onChangeComplete?: (value: GradientValue) => void;
9
+ disabled?: boolean;
10
+ minStops?: number;
11
+ maxStops?: number;
12
+ className?: string;
13
+ style?: React.CSSProperties;
14
+ "aria-label"?: string;
15
+ }
16
+ export declare function GradientEditor(props: GradientEditorProps): React.ReactElement;
17
+ //#endregion
@@ -0,0 +1,195 @@
1
+ "use client";
2
+ import { gradientToCss, normalizeGradient } from "./model.js";
3
+ import * as React from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/gradient/GradientEditor.tsx
6
+ const DEFAULT_GRADIENT = {
7
+ type: "linear",
8
+ angle: 90,
9
+ stops: [{
10
+ id: "start",
11
+ color: "#7c3aed",
12
+ position: 0
13
+ }, {
14
+ id: "end",
15
+ color: "#06b6d4",
16
+ position: 100
17
+ }]
18
+ };
19
+ function GradientEditor(props) {
20
+ const { value, defaultValue = DEFAULT_GRADIENT, onChange, onChangeComplete, disabled = false, minStops = 2, maxStops = 12, className, style, "aria-label": ariaLabel = "Gradient editor" } = props;
21
+ const [internal, setInternal] = React.useState(() => normalizeGradient(defaultValue));
22
+ const current = normalizeGradient(value ?? internal);
23
+ const currentRef = React.useRef(current);
24
+ currentRef.current = current;
25
+ const update = (next, complete = false) => {
26
+ const normalized = normalizeGradient(next);
27
+ currentRef.current = normalized;
28
+ if (value === void 0) setInternal(normalized);
29
+ onChange?.(normalized);
30
+ if (complete) onChangeComplete?.(normalized);
31
+ };
32
+ const patchStop = (id, patch, complete = false) => {
33
+ update({
34
+ ...current,
35
+ stops: current.stops.map((stop) => stop.id === id ? {
36
+ ...stop,
37
+ ...patch
38
+ } : stop)
39
+ }, complete);
40
+ };
41
+ const addStop = () => {
42
+ if (current.stops.length >= maxStops) return;
43
+ const position = 50;
44
+ const id = `stop-${Date.now().toString(36)}`;
45
+ update({
46
+ ...current,
47
+ stops: [...current.stops, {
48
+ id,
49
+ color: "#808080",
50
+ position
51
+ }]
52
+ }, true);
53
+ };
54
+ const removeStop = (id) => {
55
+ if (current.stops.length <= minStops) return;
56
+ update({
57
+ ...current,
58
+ stops: current.stops.filter((stop) => stop.id !== id)
59
+ }, true);
60
+ };
61
+ return /* @__PURE__ */ jsxs("div", {
62
+ className,
63
+ style,
64
+ "aria-label": ariaLabel,
65
+ "data-cp-gradient-editor": "",
66
+ children: [
67
+ /* @__PURE__ */ jsx("div", {
68
+ role: "img",
69
+ "aria-label": `Gradient preview with ${current.stops.length} color stops`,
70
+ style: {
71
+ height: 48,
72
+ borderRadius: 8,
73
+ background: gradientToCss(current)
74
+ }
75
+ }),
76
+ /* @__PURE__ */ jsxs("div", {
77
+ style: {
78
+ display: "flex",
79
+ gap: 8,
80
+ paddingBlock: 8
81
+ },
82
+ children: [/* @__PURE__ */ jsxs("label", { children: [
83
+ "Type",
84
+ " ",
85
+ /* @__PURE__ */ jsxs("select", {
86
+ disabled,
87
+ value: current.type,
88
+ onChange: (event) => update({
89
+ ...current,
90
+ type: event.currentTarget.value
91
+ }, true),
92
+ children: [/* @__PURE__ */ jsx("option", {
93
+ value: "linear",
94
+ children: "Linear"
95
+ }), /* @__PURE__ */ jsx("option", {
96
+ value: "radial",
97
+ children: "Radial"
98
+ })]
99
+ })
100
+ ] }), current.type === "linear" && /* @__PURE__ */ jsxs("label", { children: [
101
+ "Angle",
102
+ " ",
103
+ /* @__PURE__ */ jsx("input", {
104
+ type: "number",
105
+ min: 0,
106
+ max: 359,
107
+ step: 1,
108
+ disabled,
109
+ value: current.angle ?? 90,
110
+ onChange: (event) => update({
111
+ ...current,
112
+ angle: event.currentTarget.valueAsNumber
113
+ }),
114
+ onBlur: () => onChangeComplete?.(currentRef.current)
115
+ })
116
+ ] })]
117
+ }),
118
+ /* @__PURE__ */ jsx("div", {
119
+ role: "list",
120
+ "aria-label": "Gradient color stops",
121
+ children: current.stops.map((stop, index) => /* @__PURE__ */ jsxs("fieldset", {
122
+ disabled,
123
+ style: {
124
+ display: "flex",
125
+ gap: 8,
126
+ alignItems: "center",
127
+ border: 0,
128
+ padding: "8px 0",
129
+ margin: 0
130
+ },
131
+ children: [
132
+ /* @__PURE__ */ jsxs("legend", {
133
+ style: {
134
+ position: "absolute",
135
+ width: 1,
136
+ height: 1,
137
+ overflow: "hidden",
138
+ clipPath: "inset(50%)"
139
+ },
140
+ children: ["Stop ", index + 1]
141
+ }),
142
+ /* @__PURE__ */ jsx("input", {
143
+ type: "color",
144
+ value: /^#[0-9a-f]{6}$/i.test(stop.color) ? stop.color : "#000000",
145
+ "aria-label": `Stop ${index + 1} color`,
146
+ onChange: (event) => patchStop(stop.id, { color: event.currentTarget.value }),
147
+ onBlur: () => onChangeComplete?.(currentRef.current)
148
+ }),
149
+ /* @__PURE__ */ jsx("input", {
150
+ type: "text",
151
+ value: stop.color,
152
+ "aria-label": `Stop ${index + 1} CSS color`,
153
+ onChange: (event) => patchStop(stop.id, { color: event.currentTarget.value }),
154
+ onBlur: () => onChangeComplete?.(currentRef.current)
155
+ }),
156
+ /* @__PURE__ */ jsx("input", {
157
+ type: "range",
158
+ min: 0,
159
+ max: 100,
160
+ step: 1,
161
+ value: stop.position,
162
+ "aria-label": `Stop ${index + 1} position`,
163
+ "aria-valuetext": `${stop.position} percent`,
164
+ onChange: (event) => patchStop(stop.id, { position: event.currentTarget.valueAsNumber }),
165
+ onPointerUp: () => onChangeComplete?.(currentRef.current),
166
+ onKeyUp: (event) => {
167
+ if (event.key.startsWith("Arrow") || event.key === "Home" || event.key === "End") onChangeComplete?.(currentRef.current);
168
+ }
169
+ }),
170
+ /* @__PURE__ */ jsxs("output", {
171
+ "aria-live": "off",
172
+ children: [Math.round(stop.position), "%"]
173
+ }),
174
+ /* @__PURE__ */ jsx("button", {
175
+ type: "button",
176
+ disabled: disabled || current.stops.length <= minStops,
177
+ onClick: () => removeStop(stop.id),
178
+ "aria-label": `Remove stop ${index + 1}`,
179
+ children: "Remove"
180
+ })
181
+ ]
182
+ }, stop.id))
183
+ }),
184
+ /* @__PURE__ */ jsx("button", {
185
+ type: "button",
186
+ disabled: disabled || current.stops.length >= maxStops,
187
+ onClick: addStop,
188
+ children: "Add color stop"
189
+ })
190
+ ]
191
+ });
192
+ }
193
+ //#endregion
194
+ export { GradientEditor };
195
+
@@ -0,0 +1,63 @@
1
+ const require_convert = require("../color/convert.cjs");
2
+ const require_css4 = require("../color/css4.cjs");
3
+ const require_value = require("../color/value.cjs");
4
+ //#region src/gradient/model.ts
5
+ function normalizeGradient(value) {
6
+ const stops = value.stops.map((stop) => ({
7
+ ...stop,
8
+ position: Number.isFinite(stop.position) ? require_convert.clamp(stop.position, 0, 100) : 0
9
+ })).sort((a, b) => a.position - b.position);
10
+ return {
11
+ ...value,
12
+ angle: value.angle === void 0 ? void 0 : Number.isFinite(value.angle) ? (value.angle % 360 + 360) % 360 : 90,
13
+ stops
14
+ };
15
+ }
16
+ function gradientToCss(value, options = {}) {
17
+ const normalized = normalizeGradient(value);
18
+ return `${`${options.repeating ? "repeating-" : ""}${normalized.type}-gradient`}(${normalized.type === "linear" ? `${normalized.angle ?? 90}deg, ` : ""}${options.interpolation === void 0 ? "" : `in ${options.interpolation}, `}${normalized.stops.map((stop) => `${stop.color} ${stop.position}%`).join(", ")})`;
19
+ }
20
+ function sampleGradient(value, position, space = "oklab") {
21
+ const stops = normalizeGradient(value).stops;
22
+ if (stops.length === 0) return null;
23
+ const point = require_convert.clamp(position, 0, 100);
24
+ const rightIndex = stops.findIndex((stop) => stop.position >= point);
25
+ const right = stops[rightIndex === -1 ? stops.length - 1 : rightIndex];
26
+ const left = stops[Math.max(0, rightIndex === -1 ? stops.length - 1 : rightIndex - 1)];
27
+ const leftColor = require_value.parseColor(left.color);
28
+ const rightColor = require_value.parseColor(right.color);
29
+ if (leftColor === null || rightColor === null) return null;
30
+ const a = require_css4.convertColor(leftColor, space);
31
+ const b = require_css4.convertColor(rightColor, space);
32
+ const span = right.position - left.position;
33
+ const t = span <= 0 ? 0 : (point - left.position) / span;
34
+ return {
35
+ space,
36
+ channels: a.channels.map((channel, index) => {
37
+ if ((space === "oklch" || space === "lch") && index === 2) return (channel + ((b.channels[2] - channel + 540) % 360 - 180) * t + 360) % 360;
38
+ return channel + (b.channels[index] - channel) * t;
39
+ }),
40
+ alpha: a.alpha + (b.alpha - a.alpha) * t
41
+ };
42
+ }
43
+ function addGradientStop(value, position, id) {
44
+ const sampled = sampleGradient(value, position);
45
+ const color = sampled === null ? "#000000" : require_css4.serializeColor(require_css4.convertColor(sampled, "srgb"), { format: "srgb" });
46
+ return normalizeGradient({
47
+ ...value,
48
+ stops: [...value.stops, {
49
+ id: id ?? `stop-${Date.now().toString(36)}`,
50
+ color,
51
+ position
52
+ }]
53
+ });
54
+ }
55
+ function isGradientColorSupported(color) {
56
+ return require_value.parseColor(color) !== null || require_css4.parseCss4Color(color) !== null;
57
+ }
58
+ //#endregion
59
+ exports.addGradientStop = addGradientStop;
60
+ exports.gradientToCss = gradientToCss;
61
+ exports.isGradientColorSupported = isGradientColorSupported;
62
+ exports.normalizeGradient = normalizeGradient;
63
+ exports.sampleGradient = sampleGradient;
@@ -0,0 +1,23 @@
1
+ import { ColorValue, ModernColorSpace } from "../color/types.cjs";
2
+ //#region src/gradient/model.d.ts
3
+ export type GradientType = "linear" | "radial";
4
+ export interface GradientStop {
5
+ id: string;
6
+ color: string;
7
+ position: number;
8
+ }
9
+ export interface GradientValue {
10
+ type: GradientType;
11
+ angle?: number;
12
+ stops: readonly GradientStop[];
13
+ }
14
+ export interface GradientOptions {
15
+ interpolation?: ModernColorSpace;
16
+ repeating?: boolean;
17
+ }
18
+ export declare function normalizeGradient(value: GradientValue): GradientValue;
19
+ export declare function gradientToCss(value: GradientValue, options?: GradientOptions): string;
20
+ export declare function sampleGradient(value: GradientValue, position: number, space?: ModernColorSpace): ColorValue | null;
21
+ export declare function addGradientStop(value: GradientValue, position: number, id?: string): GradientValue;
22
+ export declare function isGradientColorSupported(color: string): boolean;
23
+ //#endregion
@@ -0,0 +1,23 @@
1
+ import { ColorValue, ModernColorSpace } from "../color/types.js";
2
+ //#region src/gradient/model.d.ts
3
+ export type GradientType = "linear" | "radial";
4
+ export interface GradientStop {
5
+ id: string;
6
+ color: string;
7
+ position: number;
8
+ }
9
+ export interface GradientValue {
10
+ type: GradientType;
11
+ angle?: number;
12
+ stops: readonly GradientStop[];
13
+ }
14
+ export interface GradientOptions {
15
+ interpolation?: ModernColorSpace;
16
+ repeating?: boolean;
17
+ }
18
+ export declare function normalizeGradient(value: GradientValue): GradientValue;
19
+ export declare function gradientToCss(value: GradientValue, options?: GradientOptions): string;
20
+ export declare function sampleGradient(value: GradientValue, position: number, space?: ModernColorSpace): ColorValue | null;
21
+ export declare function addGradientStop(value: GradientValue, position: number, id?: string): GradientValue;
22
+ export declare function isGradientColorSupported(color: string): boolean;
23
+ //#endregion
@@ -0,0 +1,60 @@
1
+ import { clamp } from "../color/convert.js";
2
+ import { convertColor, parseCss4Color, serializeColor } from "../color/css4.js";
3
+ import { parseColor } from "../color/value.js";
4
+ //#region src/gradient/model.ts
5
+ function normalizeGradient(value) {
6
+ const stops = value.stops.map((stop) => ({
7
+ ...stop,
8
+ position: Number.isFinite(stop.position) ? clamp(stop.position, 0, 100) : 0
9
+ })).sort((a, b) => a.position - b.position);
10
+ return {
11
+ ...value,
12
+ angle: value.angle === void 0 ? void 0 : Number.isFinite(value.angle) ? (value.angle % 360 + 360) % 360 : 90,
13
+ stops
14
+ };
15
+ }
16
+ function gradientToCss(value, options = {}) {
17
+ const normalized = normalizeGradient(value);
18
+ return `${`${options.repeating ? "repeating-" : ""}${normalized.type}-gradient`}(${normalized.type === "linear" ? `${normalized.angle ?? 90}deg, ` : ""}${options.interpolation === void 0 ? "" : `in ${options.interpolation}, `}${normalized.stops.map((stop) => `${stop.color} ${stop.position}%`).join(", ")})`;
19
+ }
20
+ function sampleGradient(value, position, space = "oklab") {
21
+ const stops = normalizeGradient(value).stops;
22
+ if (stops.length === 0) return null;
23
+ const point = clamp(position, 0, 100);
24
+ const rightIndex = stops.findIndex((stop) => stop.position >= point);
25
+ const right = stops[rightIndex === -1 ? stops.length - 1 : rightIndex];
26
+ const left = stops[Math.max(0, rightIndex === -1 ? stops.length - 1 : rightIndex - 1)];
27
+ const leftColor = parseColor(left.color);
28
+ const rightColor = parseColor(right.color);
29
+ if (leftColor === null || rightColor === null) return null;
30
+ const a = convertColor(leftColor, space);
31
+ const b = convertColor(rightColor, space);
32
+ const span = right.position - left.position;
33
+ const t = span <= 0 ? 0 : (point - left.position) / span;
34
+ return {
35
+ space,
36
+ channels: a.channels.map((channel, index) => {
37
+ if ((space === "oklch" || space === "lch") && index === 2) return (channel + ((b.channels[2] - channel + 540) % 360 - 180) * t + 360) % 360;
38
+ return channel + (b.channels[index] - channel) * t;
39
+ }),
40
+ alpha: a.alpha + (b.alpha - a.alpha) * t
41
+ };
42
+ }
43
+ function addGradientStop(value, position, id) {
44
+ const sampled = sampleGradient(value, position);
45
+ const color = sampled === null ? "#000000" : serializeColor(convertColor(sampled, "srgb"), { format: "srgb" });
46
+ return normalizeGradient({
47
+ ...value,
48
+ stops: [...value.stops, {
49
+ id: id ?? `stop-${Date.now().toString(36)}`,
50
+ color,
51
+ position
52
+ }]
53
+ });
54
+ }
55
+ function isGradientColorSupported(color) {
56
+ return parseColor(color) !== null || parseCss4Color(color) !== null;
57
+ }
58
+ //#endregion
59
+ export { addGradientStop, gradientToCss, isGradientColorSupported, normalizeGradient, sampleGradient };
60
+
@@ -0,0 +1,9 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_model = require("./gradient/model.cjs");
3
+ const require_GradientEditor = require("./gradient/GradientEditor.cjs");
4
+ exports.GradientEditor = require_GradientEditor.GradientEditor;
5
+ exports.addGradientStop = require_model.addGradientStop;
6
+ exports.gradientToCss = require_model.gradientToCss;
7
+ exports.isGradientColorSupported = require_model.isGradientColorSupported;
8
+ exports.normalizeGradient = require_model.normalizeGradient;
9
+ exports.sampleGradient = require_model.sampleGradient;
@@ -0,0 +1,3 @@
1
+ import { GradientOptions, GradientStop, GradientType, GradientValue, addGradientStop, gradientToCss, isGradientColorSupported, normalizeGradient, sampleGradient } from "./gradient/model.cjs";
2
+ import { GradientEditor, GradientEditorProps } from "./gradient/GradientEditor.cjs";
3
+ export { GradientEditor, type GradientEditorProps, type GradientOptions, type GradientStop, type GradientType, type GradientValue, addGradientStop, gradientToCss, isGradientColorSupported, normalizeGradient, sampleGradient };
@@ -0,0 +1,3 @@
1
+ import { GradientOptions, GradientStop, GradientType, GradientValue, addGradientStop, gradientToCss, isGradientColorSupported, normalizeGradient, sampleGradient } from "./gradient/model.js";
2
+ import { GradientEditor, GradientEditorProps } from "./gradient/GradientEditor.js";
3
+ export { GradientEditor, type GradientEditorProps, type GradientOptions, type GradientStop, type GradientType, type GradientValue, addGradientStop, gradientToCss, isGradientColorSupported, normalizeGradient, sampleGradient };
@@ -0,0 +1,3 @@
1
+ import { addGradientStop, gradientToCss, isGradientColorSupported, normalizeGradient, sampleGradient } from "./gradient/model.js";
2
+ import { GradientEditor } from "./gradient/GradientEditor.js";
3
+ export { GradientEditor, addGradientStop, gradientToCss, isGradientColorSupported, normalizeGradient, sampleGradient };