@pixerate/editor 0.145.0 → 0.147.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.
- package/README.md +4 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/agent/index.cjs +36 -34
- package/dist/agent/index.js +7 -5
- package/dist/animation/index.cjs +5 -3
- package/dist/animation/index.js +4 -2
- package/dist/animation-editor/index.cjs +62 -60
- package/dist/animation-editor/index.js +6 -4
- package/dist/canvas/index.cjs +36 -34
- package/dist/canvas/index.js +19 -17
- package/dist/chart/index.cjs +88 -86
- package/dist/chart/index.js +7 -5
- package/dist/{chunk-WBJ6BG4R.js → chunk-2AQL4JHM.js} +6 -6
- package/dist/{chunk-HLMF3E53.cjs → chunk-2WDZI6QD.cjs} +15 -15
- package/dist/{chunk-ZNHB5RG6.js → chunk-37RLSNQ3.js} +2 -2
- package/dist/{chunk-5WOY52O6.cjs → chunk-4YQKRHMI.cjs} +8 -8
- package/dist/chunk-6ONMQ6WF.js +113 -0
- package/dist/{chunk-L434J57V.cjs → chunk-6QTON3VW.cjs} +16 -16
- package/dist/chunk-AA34BA7G.js +1913 -0
- package/dist/{chunk-QX25HWFL.js → chunk-AY65VQSU.js} +1 -1
- package/dist/{chunk-ONZJD2OD.cjs → chunk-BROJYMWU.cjs} +57 -153
- package/dist/{chunk-YM75DN54.js → chunk-DSZCDNTM.js} +1 -1
- package/dist/{chunk-PUR6BZFA.cjs → chunk-DVLMW3GB.cjs} +53 -53
- package/dist/{chunk-ALUWD3FG.js → chunk-EIUTJPEL.js} +1 -1
- package/dist/{chunk-X65C4DWV.js → chunk-EQ4SF4X4.js} +1 -1
- package/dist/{chunk-77SG47WT.js → chunk-FLOGD3QB.js} +1 -1
- package/dist/chunk-HRLT5BYO.js +133 -0
- package/dist/{chunk-AJSW5GMP.js → chunk-HVQVHUTV.js} +19 -115
- package/dist/{chunk-UR37VCIR.cjs → chunk-I4AHLP7F.cjs} +16 -35
- package/dist/chunk-IPJ3TEYR.cjs +113 -0
- package/dist/chunk-KC7HB5GF.cjs +133 -0
- package/dist/chunk-KY3FYKP3.js +52 -0
- package/dist/chunk-LEUZ4FRC.cjs +52 -0
- package/dist/{chunk-H6FX45CP.cjs → chunk-LIXLD433.cjs} +37 -37
- package/dist/{chunk-6GAYGQDC.js → chunk-O5ML2I6O.js} +3 -3
- package/dist/{chunk-KI2VBONS.js → chunk-OET45HIR.js} +2 -2
- package/dist/{chunk-XNZWATSB.cjs → chunk-QCEQIRJQ.cjs} +85 -85
- package/dist/{chunk-HRI3ZMDM.cjs → chunk-RHXW4TA5.cjs} +3 -3
- package/dist/{chunk-UUTTJK77.cjs → chunk-SUF6WRXR.cjs} +2 -2
- package/dist/{chunk-RN767PO6.js → chunk-SUVZJ2CP.js} +15 -34
- package/dist/{chunk-FLUTRPLY.cjs → chunk-TLMRGLQQ.cjs} +16 -16
- package/dist/{chunk-DAS7M4RY.js → chunk-UILRJQFD.js} +2 -2
- package/dist/chunk-UKCJ43YL.cjs +1913 -0
- package/dist/{chunk-XQ33O5BC.cjs → chunk-X35BV5YT.cjs} +44 -44
- package/dist/{chunk-6HMEHNWW.cjs → chunk-XTU6QO2W.cjs} +26 -26
- package/dist/{chunk-VM6JYWXQ.js → chunk-ZQUANEK4.js} +2 -2
- package/dist/cms/index.cjs +5 -3
- package/dist/cms/index.js +18 -16
- package/dist/color/contrast.d.cts +14 -0
- package/dist/color/contrast.d.ts +14 -0
- package/dist/color/convert.d.cts +30 -0
- package/dist/color/convert.d.ts +30 -0
- package/dist/color/dual.d.cts +98 -0
- package/dist/color/dual.d.ts +98 -0
- package/dist/color/format.d.cts +12 -0
- package/dist/color/format.d.ts +12 -0
- package/dist/color/gamut.d.cts +12 -0
- package/dist/color/gamut.d.ts +12 -0
- package/dist/color/harmony.d.cts +22 -0
- package/dist/color/harmony.d.ts +22 -0
- package/dist/color/index.cjs +115 -0
- package/dist/color/index.d.cts +18 -0
- package/dist/color/index.d.ts +18 -0
- package/dist/color/index.js +115 -0
- package/dist/color/mix.d.cts +11 -0
- package/dist/color/mix.d.ts +11 -0
- package/dist/color/named.d.cts +5 -0
- package/dist/color/named.d.ts +5 -0
- package/dist/color/parse.d.cts +11 -0
- package/dist/color/parse.d.ts +11 -0
- package/dist/color/picker.d.cts +52 -0
- package/dist/color/picker.d.ts +52 -0
- package/dist/color/recent.d.cts +8 -0
- package/dist/color/recent.d.ts +8 -0
- package/dist/color/tools.d.cts +15 -0
- package/dist/color/tools.d.ts +15 -0
- package/dist/color/types.d.cts +41 -0
- package/dist/color/types.d.ts +41 -0
- package/dist/color/vision.d.cts +36 -0
- package/dist/color/vision.d.ts +36 -0
- package/dist/diagram/editorDocument.d.cts +12 -0
- package/dist/diagram/editorDocument.d.ts +12 -0
- package/dist/diagram/index.cjs +35 -1
- package/dist/diagram/index.d.cts +1 -0
- package/dist/diagram/index.d.ts +1 -0
- package/dist/diagram/index.js +34 -0
- package/dist/export/index.cjs +6 -4
- package/dist/export/index.js +5 -3
- package/dist/extensions/index.cjs +10 -8
- package/dist/extensions/index.js +9 -7
- package/dist/gradient/controller.d.cts +109 -0
- package/dist/gradient/controller.d.ts +109 -0
- package/dist/gradient/editor.d.cts +38 -0
- package/dist/gradient/editor.d.ts +38 -0
- package/dist/gradient/index.cjs +1549 -0
- package/dist/gradient/index.d.cts +5 -0
- package/dist/gradient/index.d.ts +5 -0
- package/dist/gradient/index.js +1549 -0
- package/dist/gradient/model.d.cts +94 -0
- package/dist/gradient/model.d.ts +94 -0
- package/dist/gradient/tools.d.cts +11 -0
- package/dist/gradient/tools.d.ts +11 -0
- package/dist/gradient/types.d.cts +44 -0
- package/dist/gradient/types.d.ts +44 -0
- package/dist/image-editor/index.cjs +649 -647
- package/dist/image-editor/index.js +6 -4
- package/dist/index.cjs +19 -17
- package/dist/index.js +16 -14
- package/dist/paint-editor/color.d.cts +3 -13
- package/dist/paint-editor/color.d.ts +3 -13
- package/dist/paint-editor/index.cjs +23 -68
- package/dist/paint-editor/index.js +15 -60
- package/dist/persistence/documentStore.d.cts +51 -0
- package/dist/persistence/documentStore.d.ts +51 -0
- package/dist/persistence/documentTools.d.cts +35 -0
- package/dist/persistence/documentTools.d.ts +35 -0
- package/dist/persistence/editorAutosave.d.cts +33 -0
- package/dist/persistence/editorAutosave.d.ts +33 -0
- package/dist/persistence/editorDocument.d.cts +118 -0
- package/dist/persistence/editorDocument.d.ts +118 -0
- package/dist/persistence/index.cjs +294 -1
- package/dist/persistence/index.d.cts +4 -0
- package/dist/persistence/index.d.ts +4 -0
- package/dist/persistence/index.js +293 -0
- package/dist/vector/index.cjs +950 -948
- package/dist/vector/index.js +5 -3
- package/dist/video-editor/index.cjs +5 -3
- package/dist/video-editor/index.js +4 -2
- package/package.json +21 -1
- package/skills/README.md +1 -0
- package/skills/pixerate-agent-tools/SKILL.md +21 -0
- package/skills/pixerate-color/SKILL.md +69 -0
- package/skills/pixerate-diagram/SKILL.md +13 -0
- package/dist/chunk-45VRG4RB.js +0 -65
- package/dist/chunk-5FXOR7F2.cjs +0 -65
|
@@ -0,0 +1,1549 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;
|
|
2
|
+
|
|
3
|
+
var _chunkFJ2MSI43cjs = require('../chunk-FJ2MSI43.cjs');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
var _chunkUKCJ43YLcjs = require('../chunk-UKCJ43YL.cjs');
|
|
15
|
+
require('../chunk-IPJ3TEYR.cjs');
|
|
16
|
+
|
|
17
|
+
// src/gradient/model.ts
|
|
18
|
+
function clamp(v, min, max) {
|
|
19
|
+
return Math.max(min, Math.min(max, v));
|
|
20
|
+
}
|
|
21
|
+
function gradientMidpointRemap(u, m) {
|
|
22
|
+
const mid = clamp(m, 0.05, 0.95);
|
|
23
|
+
if (Math.abs(mid - 0.5) < 1e-6) return u;
|
|
24
|
+
return u <= mid ? 0.5 * u / mid : 0.5 + 0.5 * (u - mid) / (1 - mid);
|
|
25
|
+
}
|
|
26
|
+
function normalizeGradient(input) {
|
|
27
|
+
if (typeof input === "string") {
|
|
28
|
+
const parsed = parseCssGradient(input);
|
|
29
|
+
if (!parsed) {
|
|
30
|
+
return { error: `Could not parse CSS gradient: "${input}".` };
|
|
31
|
+
}
|
|
32
|
+
return { gradient: parsed };
|
|
33
|
+
}
|
|
34
|
+
if (!input || typeof input !== "object") {
|
|
35
|
+
return { error: "Gradient must be an object." };
|
|
36
|
+
}
|
|
37
|
+
const obj = input;
|
|
38
|
+
const type = obj.type === "radial" || obj.type === "conic" ? obj.type : "linear";
|
|
39
|
+
if (!Array.isArray(obj.stops)) {
|
|
40
|
+
return { error: "Gradient stops must be an array." };
|
|
41
|
+
}
|
|
42
|
+
if (obj.stops.length < 2) {
|
|
43
|
+
return { error: `A gradient needs at least 2 stops; it has ${obj.stops.length}.` };
|
|
44
|
+
}
|
|
45
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
46
|
+
for (const s of obj.stops) {
|
|
47
|
+
if (s && typeof s === "object" && typeof s.id === "string" && s.id.trim()) {
|
|
48
|
+
usedIds.add(s.id.trim());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
let nextIdIndex = 1;
|
|
52
|
+
const getNextId = () => {
|
|
53
|
+
while (usedIds.has(`s${nextIdIndex}`)) {
|
|
54
|
+
nextIdIndex++;
|
|
55
|
+
}
|
|
56
|
+
const id = `s${nextIdIndex}`;
|
|
57
|
+
usedIds.add(id);
|
|
58
|
+
nextIdIndex++;
|
|
59
|
+
return id;
|
|
60
|
+
};
|
|
61
|
+
const stops = [];
|
|
62
|
+
for (let i = 0; i < obj.stops.length; i++) {
|
|
63
|
+
const s = obj.stops[i];
|
|
64
|
+
if (!s || typeof s !== "object") {
|
|
65
|
+
return { error: `Stop at index ${i} must be an object.` };
|
|
66
|
+
}
|
|
67
|
+
const rawStop = s;
|
|
68
|
+
let normColor = null;
|
|
69
|
+
if (typeof rawStop.color === "string" || rawStop.color && typeof rawStop.color === "object") {
|
|
70
|
+
const dual = _chunkUKCJ43YLcjs.normalizeDualColor.call(void 0, rawStop.color);
|
|
71
|
+
if (dual) {
|
|
72
|
+
normColor = _chunkUKCJ43YLcjs.formatDualColor.call(void 0, dual);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!normColor) {
|
|
76
|
+
return { error: `Stop at index ${i} has an invalid colour.` };
|
|
77
|
+
}
|
|
78
|
+
let offset = Number(rawStop.offset);
|
|
79
|
+
if (!Number.isFinite(offset)) {
|
|
80
|
+
offset = obj.stops.length === 1 ? 0 : i / (obj.stops.length - 1);
|
|
81
|
+
}
|
|
82
|
+
offset = clamp(offset, 0, 1);
|
|
83
|
+
const id = typeof rawStop.id === "string" && rawStop.id.trim() ? rawStop.id.trim() : getNextId();
|
|
84
|
+
const stop = {
|
|
85
|
+
id,
|
|
86
|
+
offset,
|
|
87
|
+
color: normColor
|
|
88
|
+
};
|
|
89
|
+
if (rawStop.midpoint !== void 0 && rawStop.midpoint !== null) {
|
|
90
|
+
const m = Number(rawStop.midpoint);
|
|
91
|
+
if (Number.isFinite(m)) {
|
|
92
|
+
stop.midpoint = clamp(m, 0.05, 0.95);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
stops.push(stop);
|
|
96
|
+
}
|
|
97
|
+
stops.sort((a, b) => a.offset - b.offset);
|
|
98
|
+
const gradient = {
|
|
99
|
+
type,
|
|
100
|
+
stops
|
|
101
|
+
};
|
|
102
|
+
if (type === "linear" || type === "conic") {
|
|
103
|
+
if (obj.angle !== void 0 && Number.isFinite(Number(obj.angle))) {
|
|
104
|
+
gradient.angle = (Number(obj.angle) % 360 + 360) % 360;
|
|
105
|
+
} else {
|
|
106
|
+
gradient.angle = type === "linear" ? 180 : 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (type === "radial" || type === "conic") {
|
|
110
|
+
if (obj.center && typeof obj.center === "object") {
|
|
111
|
+
const cx = Number(obj.center.x);
|
|
112
|
+
const cy = Number(obj.center.y);
|
|
113
|
+
gradient.center = {
|
|
114
|
+
x: Number.isFinite(cx) ? clamp(cx, 0, 1) : 0.5,
|
|
115
|
+
y: Number.isFinite(cy) ? clamp(cy, 0, 1) : 0.5
|
|
116
|
+
};
|
|
117
|
+
} else {
|
|
118
|
+
gradient.center = { x: 0.5, y: 0.5 };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (type === "radial") {
|
|
122
|
+
gradient.shape = obj.shape === "circle" ? "circle" : "ellipse";
|
|
123
|
+
}
|
|
124
|
+
const allowedInterp = ["srgb", "srgb-linear", "oklab", "oklch"];
|
|
125
|
+
if (obj.interpolation && allowedInterp.includes(obj.interpolation)) {
|
|
126
|
+
gradient.interpolation = obj.interpolation;
|
|
127
|
+
} else {
|
|
128
|
+
gradient.interpolation = "oklab";
|
|
129
|
+
}
|
|
130
|
+
if (obj.repeating) {
|
|
131
|
+
gradient.repeating = true;
|
|
132
|
+
}
|
|
133
|
+
return { gradient };
|
|
134
|
+
}
|
|
135
|
+
function createGradient(colorsOrOptions, options) {
|
|
136
|
+
if (Array.isArray(colorsOrOptions)) {
|
|
137
|
+
const colors = colorsOrOptions;
|
|
138
|
+
const resolvedColors = colors.length >= 2 ? colors : colors.length === 1 ? [colors[0], colors[0]] : ["#000000", "#ffffff"];
|
|
139
|
+
const stops = resolvedColors.map((color, i) => ({
|
|
140
|
+
id: `s${i + 1}`,
|
|
141
|
+
offset: i / (resolvedColors.length - 1),
|
|
142
|
+
color: _chunkUKCJ43YLcjs.formatDualColor.call(void 0, _nullishCoalesce(_chunkUKCJ43YLcjs.normalizeDualColor.call(void 0, color), () => ( { light: "#000000", dark: "#000000" })))
|
|
143
|
+
}));
|
|
144
|
+
const res2 = normalizeGradient({
|
|
145
|
+
...options,
|
|
146
|
+
stops
|
|
147
|
+
});
|
|
148
|
+
if ("error" in res2) {
|
|
149
|
+
throw new Error(res2.error);
|
|
150
|
+
}
|
|
151
|
+
return res2.gradient;
|
|
152
|
+
}
|
|
153
|
+
if (colorsOrOptions && typeof colorsOrOptions === "object" && !Array.isArray(colorsOrOptions)) {
|
|
154
|
+
const raw = colorsOrOptions;
|
|
155
|
+
let stops = raw.stops;
|
|
156
|
+
if (!stops || stops.length === 0) {
|
|
157
|
+
stops = [
|
|
158
|
+
{ id: "s1", offset: 0, color: "#000000" },
|
|
159
|
+
{ id: "s2", offset: 1, color: "#ffffff" }
|
|
160
|
+
];
|
|
161
|
+
} else if (typeof stops[0] === "string") {
|
|
162
|
+
const strStops = stops;
|
|
163
|
+
stops = strStops.map((color, i) => ({
|
|
164
|
+
id: `s${i + 1}`,
|
|
165
|
+
offset: i / (strStops.length - 1),
|
|
166
|
+
color
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
const res2 = normalizeGradient({
|
|
170
|
+
...raw,
|
|
171
|
+
stops
|
|
172
|
+
});
|
|
173
|
+
if ("error" in res2) {
|
|
174
|
+
throw new Error(res2.error);
|
|
175
|
+
}
|
|
176
|
+
return res2.gradient;
|
|
177
|
+
}
|
|
178
|
+
const res = normalizeGradient({
|
|
179
|
+
...options,
|
|
180
|
+
stops: [
|
|
181
|
+
{ id: "s1", offset: 0, color: "#000000" },
|
|
182
|
+
{ id: "s2", offset: 1, color: "#ffffff" }
|
|
183
|
+
]
|
|
184
|
+
});
|
|
185
|
+
if ("error" in res) {
|
|
186
|
+
throw new Error(res.error);
|
|
187
|
+
}
|
|
188
|
+
return res.gradient;
|
|
189
|
+
}
|
|
190
|
+
function isDualColorValue(val) {
|
|
191
|
+
if (typeof val === "string") {
|
|
192
|
+
return val.trim().toLowerCase().startsWith("light-dark(");
|
|
193
|
+
}
|
|
194
|
+
if (_chunkUKCJ43YLcjs.isDualColor.call(void 0, val)) {
|
|
195
|
+
return val.light !== val.dark;
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
function isDualGradient(g) {
|
|
200
|
+
return g.stops.some((s) => isDualColorValue(s.color));
|
|
201
|
+
}
|
|
202
|
+
function resolveGradient(g, scheme) {
|
|
203
|
+
return {
|
|
204
|
+
...g,
|
|
205
|
+
center: g.center ? { ...g.center } : void 0,
|
|
206
|
+
stops: g.stops.map((s) => ({
|
|
207
|
+
...s,
|
|
208
|
+
color: _nullishCoalesce(_chunkUKCJ43YLcjs.resolveColor.call(void 0, s.color, scheme), () => ( s.color))
|
|
209
|
+
}))
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function sampleGradient(g, t, scheme = "light") {
|
|
213
|
+
const resolved = resolveGradient(g, scheme);
|
|
214
|
+
const clampedT = clamp(t, 0, 1);
|
|
215
|
+
const stops = resolved.stops;
|
|
216
|
+
if (clampedT <= stops[0].offset) {
|
|
217
|
+
return stops[0].color;
|
|
218
|
+
}
|
|
219
|
+
if (clampedT >= stops[stops.length - 1].offset) {
|
|
220
|
+
return stops[stops.length - 1].color;
|
|
221
|
+
}
|
|
222
|
+
let i = 0;
|
|
223
|
+
while (i < stops.length - 1 && stops[i + 1].offset < clampedT) {
|
|
224
|
+
i++;
|
|
225
|
+
}
|
|
226
|
+
const s0 = stops[i];
|
|
227
|
+
const s1 = stops[i + 1];
|
|
228
|
+
const span = s1.offset - s0.offset;
|
|
229
|
+
if (span <= 1e-6) {
|
|
230
|
+
return s1.color;
|
|
231
|
+
}
|
|
232
|
+
let u = (clampedT - s0.offset) / span;
|
|
233
|
+
if (s0.midpoint !== void 0) {
|
|
234
|
+
u = gradientMidpointRemap(u, s0.midpoint);
|
|
235
|
+
}
|
|
236
|
+
const mixed = _chunkUKCJ43YLcjs.mixColors.call(void 0, s0.color, s1.color, u, { space: _nullishCoalesce(g.interpolation, () => ( "oklab")) });
|
|
237
|
+
return _nullishCoalesce(mixed, () => ( s0.color));
|
|
238
|
+
}
|
|
239
|
+
function insertGradientStop(g, offset, color) {
|
|
240
|
+
const clampedOffset = clamp(offset, 0, 1);
|
|
241
|
+
const stopColor = color !== void 0 ? color : sampleGradient(g, clampedOffset, "light");
|
|
242
|
+
const used = new Set(g.stops.map((s) => s.id));
|
|
243
|
+
let idx = 1;
|
|
244
|
+
while (used.has(`s${idx}`)) idx++;
|
|
245
|
+
const id = `s${idx}`;
|
|
246
|
+
const newStop = {
|
|
247
|
+
id,
|
|
248
|
+
offset: clampedOffset,
|
|
249
|
+
color: stopColor
|
|
250
|
+
};
|
|
251
|
+
const stops = [...g.stops, newStop];
|
|
252
|
+
const res = normalizeGradient({ ...g, stops });
|
|
253
|
+
if ("error" in res) throw new Error(res.error);
|
|
254
|
+
const added = res.gradient.stops.find((s) => s.id === id);
|
|
255
|
+
return { gradient: res.gradient, stop: added };
|
|
256
|
+
}
|
|
257
|
+
function listValidIds(g) {
|
|
258
|
+
return g.stops.map((s) => `"${s.id}"`).join(", ");
|
|
259
|
+
}
|
|
260
|
+
function updateGradientStop(g, id, patch) {
|
|
261
|
+
const index = g.stops.findIndex((s) => s.id === id);
|
|
262
|
+
if (index === -1) {
|
|
263
|
+
throw new Error(`Stop "${id}" not found. Valid stop IDs are: ${listValidIds(g)}.`);
|
|
264
|
+
}
|
|
265
|
+
const stops = g.stops.map((s, i) => i === index ? { ...s, ...patch } : s);
|
|
266
|
+
const res = normalizeGradient({ ...g, stops });
|
|
267
|
+
if ("error" in res) throw new Error(res.error);
|
|
268
|
+
return res.gradient;
|
|
269
|
+
}
|
|
270
|
+
function removeGradientStop(g, id) {
|
|
271
|
+
if (g.stops.length <= 2) {
|
|
272
|
+
throw new Error("A gradient needs at least 2 stops; add one with add_gradient_stop before removing this one.");
|
|
273
|
+
}
|
|
274
|
+
const index = g.stops.findIndex((s) => s.id === id);
|
|
275
|
+
if (index === -1) {
|
|
276
|
+
throw new Error(`Stop "${id}" not found. Valid stop IDs are: ${listValidIds(g)}.`);
|
|
277
|
+
}
|
|
278
|
+
const stops = g.stops.filter((s) => s.id !== id);
|
|
279
|
+
const res = normalizeGradient({ ...g, stops });
|
|
280
|
+
if ("error" in res) throw new Error(res.error);
|
|
281
|
+
return res.gradient;
|
|
282
|
+
}
|
|
283
|
+
function reverseGradient(g) {
|
|
284
|
+
const reversedStops = g.stops.map((s) => ({
|
|
285
|
+
...s,
|
|
286
|
+
offset: Math.round((1 - s.offset) * 1e4) / 1e4,
|
|
287
|
+
midpoint: s.midpoint !== void 0 ? Math.round((1 - s.midpoint) * 1e4) / 1e4 : void 0
|
|
288
|
+
}));
|
|
289
|
+
reversedStops.sort((a, b) => a.offset - b.offset);
|
|
290
|
+
return {
|
|
291
|
+
...g,
|
|
292
|
+
center: g.center ? { ...g.center } : void 0,
|
|
293
|
+
stops: reversedStops
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function distributeGradientStops(g) {
|
|
297
|
+
const n = g.stops.length;
|
|
298
|
+
const distributed = g.stops.map((s, i) => ({
|
|
299
|
+
...s,
|
|
300
|
+
offset: Math.round(i / (n - 1) * 1e4) / 1e4
|
|
301
|
+
}));
|
|
302
|
+
return {
|
|
303
|
+
...g,
|
|
304
|
+
center: g.center ? { ...g.center } : void 0,
|
|
305
|
+
stops: distributed
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function formatPercent(fraction) {
|
|
309
|
+
const pct = Math.round(fraction * 1e3) / 10;
|
|
310
|
+
return `${pct}%`;
|
|
311
|
+
}
|
|
312
|
+
function gradientToCss(g) {
|
|
313
|
+
const prefix = g.repeating ? `repeating-${g.type}-gradient` : `${g.type}-gradient`;
|
|
314
|
+
const parts = [];
|
|
315
|
+
const interpPrefix = g.interpolation && g.interpolation !== "srgb" ? `in ${g.interpolation} ` : "";
|
|
316
|
+
if (g.type === "linear") {
|
|
317
|
+
const angle = g.angle !== void 0 ? `${g.angle}deg` : "180deg";
|
|
318
|
+
parts.push(`${interpPrefix}${angle}`.trim());
|
|
319
|
+
} else if (g.type === "radial") {
|
|
320
|
+
const shape = _nullishCoalesce(g.shape, () => ( "ellipse"));
|
|
321
|
+
const cx = g.center ? formatPercent(g.center.x) : "50%";
|
|
322
|
+
const cy = g.center ? formatPercent(g.center.y) : "50%";
|
|
323
|
+
parts.push(`${interpPrefix}${shape} at ${cx} ${cy}`.trim());
|
|
324
|
+
} else if (g.type === "conic") {
|
|
325
|
+
const angle = g.angle !== void 0 ? `from ${g.angle}deg` : "from 0deg";
|
|
326
|
+
const cx = g.center ? formatPercent(g.center.x) : "50%";
|
|
327
|
+
const cy = g.center ? formatPercent(g.center.y) : "50%";
|
|
328
|
+
parts.push(`${interpPrefix}${angle} at ${cx} ${cy}`.trim());
|
|
329
|
+
}
|
|
330
|
+
for (let i = 0; i < g.stops.length; i++) {
|
|
331
|
+
const s = g.stops[i];
|
|
332
|
+
parts.push(`${s.color} ${formatPercent(s.offset)}`);
|
|
333
|
+
if (s.midpoint !== void 0 && i < g.stops.length - 1) {
|
|
334
|
+
const next = g.stops[i + 1];
|
|
335
|
+
const midOffset = s.offset + (next.offset - s.offset) * s.midpoint;
|
|
336
|
+
parts.push(formatPercent(midOffset));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return `${prefix}(${parts.join(", ")})`;
|
|
340
|
+
}
|
|
341
|
+
function gradientToCssFallback(g, steps = 16, scheme = "light") {
|
|
342
|
+
const prefix = g.repeating ? `repeating-${g.type}-gradient` : `${g.type}-gradient`;
|
|
343
|
+
const parts = [];
|
|
344
|
+
if (g.type === "linear") {
|
|
345
|
+
const angle = g.angle !== void 0 ? `${g.angle}deg` : "180deg";
|
|
346
|
+
parts.push(angle);
|
|
347
|
+
} else if (g.type === "radial") {
|
|
348
|
+
const shape = _nullishCoalesce(g.shape, () => ( "ellipse"));
|
|
349
|
+
const cx = g.center ? formatPercent(g.center.x) : "50%";
|
|
350
|
+
const cy = g.center ? formatPercent(g.center.y) : "50%";
|
|
351
|
+
parts.push(`${shape} at ${cx} ${cy}`);
|
|
352
|
+
} else if (g.type === "conic") {
|
|
353
|
+
const angle = g.angle !== void 0 ? `from ${g.angle}deg` : "from 0deg";
|
|
354
|
+
const cx = g.center ? formatPercent(g.center.x) : "50%";
|
|
355
|
+
const cy = g.center ? formatPercent(g.center.y) : "50%";
|
|
356
|
+
parts.push(`${angle} at ${cx} ${cy}`);
|
|
357
|
+
}
|
|
358
|
+
for (let i = 0; i <= steps; i++) {
|
|
359
|
+
const t = i / steps;
|
|
360
|
+
const color = sampleGradient(g, t, scheme);
|
|
361
|
+
parts.push(`${color} ${formatPercent(t)}`);
|
|
362
|
+
}
|
|
363
|
+
return `${prefix}(${parts.join(", ")})`;
|
|
364
|
+
}
|
|
365
|
+
function splitCssArgs(str) {
|
|
366
|
+
const parts = [];
|
|
367
|
+
let depth = 0;
|
|
368
|
+
let current = "";
|
|
369
|
+
for (let i = 0; i < str.length; i++) {
|
|
370
|
+
const char = str[i];
|
|
371
|
+
if (char === "(") depth++;
|
|
372
|
+
else if (char === ")") depth = Math.max(0, depth - 1);
|
|
373
|
+
if (char === "," && depth === 0) {
|
|
374
|
+
if (current.trim()) parts.push(current.trim());
|
|
375
|
+
current = "";
|
|
376
|
+
} else {
|
|
377
|
+
current += char;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (current.trim()) parts.push(current.trim());
|
|
381
|
+
return parts;
|
|
382
|
+
}
|
|
383
|
+
function parseCssAngle(val) {
|
|
384
|
+
const trimmed = val.trim().toLowerCase();
|
|
385
|
+
if (trimmed.endsWith("deg")) return parseFloat(trimmed);
|
|
386
|
+
if (trimmed.endsWith("turn")) return parseFloat(trimmed) * 360;
|
|
387
|
+
if (trimmed.endsWith("rad")) return parseFloat(trimmed) * 180 / Math.PI;
|
|
388
|
+
if (trimmed.startsWith("to ")) {
|
|
389
|
+
const dir = trimmed.slice(3).trim();
|
|
390
|
+
if (dir === "bottom") return 180;
|
|
391
|
+
if (dir === "top") return 0;
|
|
392
|
+
if (dir === "right") return 90;
|
|
393
|
+
if (dir === "left") return 270;
|
|
394
|
+
if (dir === "bottom right" || dir === "right bottom") return 135;
|
|
395
|
+
if (dir === "bottom left" || dir === "left bottom") return 225;
|
|
396
|
+
if (dir === "top right" || dir === "right top") return 45;
|
|
397
|
+
if (dir === "top left" || dir === "left top") return 315;
|
|
398
|
+
}
|
|
399
|
+
const n = parseFloat(trimmed);
|
|
400
|
+
return Number.isFinite(n) ? n : 180;
|
|
401
|
+
}
|
|
402
|
+
function parseCssGradient(css) {
|
|
403
|
+
if (typeof css !== "string") return null;
|
|
404
|
+
const trimmed = css.trim();
|
|
405
|
+
const match = trimmed.match(/^(repeating-)?(linear|radial|conic)-gradient\s*\(([\s\S]*)\)$/i);
|
|
406
|
+
if (!match) return null;
|
|
407
|
+
const repeating = Boolean(match[1]);
|
|
408
|
+
const type = match[2].toLowerCase();
|
|
409
|
+
const body = match[3];
|
|
410
|
+
const rawArgs = splitCssArgs(body);
|
|
411
|
+
if (rawArgs.length === 0) return null;
|
|
412
|
+
let angle = type === "linear" ? 180 : 0;
|
|
413
|
+
let center = type !== "linear" ? { x: 0.5, y: 0.5 } : void 0;
|
|
414
|
+
let shape = type === "radial" ? "ellipse" : void 0;
|
|
415
|
+
let interpolation = "srgb";
|
|
416
|
+
let stopStartIndex = 0;
|
|
417
|
+
let first = rawArgs[0].trim();
|
|
418
|
+
const inMatch = first.match(/^in\s+([a-z0-9-]+)\s*(.*)$/i);
|
|
419
|
+
if (inMatch) {
|
|
420
|
+
const space = inMatch[1].toLowerCase();
|
|
421
|
+
if (space === "oklab" || space === "oklch" || space === "srgb" || space === "srgb-linear") {
|
|
422
|
+
interpolation = space;
|
|
423
|
+
}
|
|
424
|
+
first = inMatch[2].trim();
|
|
425
|
+
if (!first) {
|
|
426
|
+
stopStartIndex = 1;
|
|
427
|
+
first = _nullishCoalesce(_optionalChain([rawArgs, 'access', _ => _[1], 'optionalAccess', _2 => _2.trim, 'call', _3 => _3()]), () => ( ""));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (first) {
|
|
431
|
+
if (type === "linear") {
|
|
432
|
+
if (first.endsWith("deg") || first.endsWith("turn") || first.endsWith("rad") || first.startsWith("to ")) {
|
|
433
|
+
angle = parseCssAngle(first);
|
|
434
|
+
stopStartIndex = stopStartIndex === 0 ? 1 : 2;
|
|
435
|
+
}
|
|
436
|
+
} else if (type === "radial") {
|
|
437
|
+
if (first.includes("circle") || first.includes("ellipse") || first.includes("at ")) {
|
|
438
|
+
if (first.includes("circle")) shape = "circle";
|
|
439
|
+
if (first.includes("ellipse")) shape = "ellipse";
|
|
440
|
+
const atIdx = first.indexOf("at ");
|
|
441
|
+
if (atIdx !== -1) {
|
|
442
|
+
const posParts = first.slice(atIdx + 3).trim().split(/\s+/);
|
|
443
|
+
if (posParts[0]) center = { x: parsePos(posParts[0]), y: posParts[1] ? parsePos(posParts[1]) : parsePos(posParts[0]) };
|
|
444
|
+
}
|
|
445
|
+
stopStartIndex = stopStartIndex === 0 ? 1 : 2;
|
|
446
|
+
}
|
|
447
|
+
} else if (type === "conic") {
|
|
448
|
+
if (first.includes("from ") || first.includes("at ")) {
|
|
449
|
+
const fromMatch = first.match(/from\s+([0-9.]+(?:deg|turn|rad)?)/i);
|
|
450
|
+
if (fromMatch) angle = parseCssAngle(fromMatch[1]);
|
|
451
|
+
const atIdx = first.indexOf("at ");
|
|
452
|
+
if (atIdx !== -1) {
|
|
453
|
+
const posParts = first.slice(atIdx + 3).trim().split(/\s+/);
|
|
454
|
+
if (posParts[0]) center = { x: parsePos(posParts[0]), y: posParts[1] ? parsePos(posParts[1]) : parsePos(posParts[0]) };
|
|
455
|
+
}
|
|
456
|
+
stopStartIndex = stopStartIndex === 0 ? 1 : 2;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const stopArgs = rawArgs.slice(stopStartIndex);
|
|
461
|
+
if (stopArgs.length === 0) return null;
|
|
462
|
+
const items = [];
|
|
463
|
+
for (const str of stopArgs) {
|
|
464
|
+
const s = str.trim();
|
|
465
|
+
if (/^([0-9.]+)%$/.test(s)) {
|
|
466
|
+
items.push({ kind: "hint", offset: parseFloat(s) / 100 });
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
const posMatch = s.match(/\s+([0-9.]+)%$/);
|
|
470
|
+
if (posMatch) {
|
|
471
|
+
const colorStr = s.slice(0, posMatch.index).trim();
|
|
472
|
+
const offset = parseFloat(posMatch[1]) / 100;
|
|
473
|
+
items.push({ kind: "stop", color: colorStr, offset });
|
|
474
|
+
} else {
|
|
475
|
+
items.push({ kind: "stop", color: s });
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const rawStops = items.filter((it) => it.kind === "stop");
|
|
479
|
+
if (rawStops.length < 2) return null;
|
|
480
|
+
for (let i = 0; i < rawStops.length; i++) {
|
|
481
|
+
if (rawStops[i].offset === void 0) {
|
|
482
|
+
if (i === 0) rawStops[i].offset = 0;
|
|
483
|
+
else if (i === rawStops.length - 1) rawStops[i].offset = 1;
|
|
484
|
+
else {
|
|
485
|
+
let nextIdx = i + 1;
|
|
486
|
+
while (nextIdx < rawStops.length && rawStops[nextIdx].offset === void 0) {
|
|
487
|
+
nextIdx++;
|
|
488
|
+
}
|
|
489
|
+
const nextOffset = nextIdx < rawStops.length ? rawStops[nextIdx].offset : 1;
|
|
490
|
+
const prevOffset = _nullishCoalesce(rawStops[i - 1].offset, () => ( 0));
|
|
491
|
+
const count = nextIdx - (i - 1);
|
|
492
|
+
for (let k = i; k < nextIdx; k++) {
|
|
493
|
+
rawStops[k].offset = prevOffset + (k - (i - 1)) / count * (nextOffset - prevOffset);
|
|
494
|
+
}
|
|
495
|
+
i = nextIdx - 1;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const finalStops = [];
|
|
500
|
+
let stopIdx = 0;
|
|
501
|
+
for (let i = 0; i < items.length; i++) {
|
|
502
|
+
const it = items[i];
|
|
503
|
+
if (it.kind === "stop") {
|
|
504
|
+
const stop = {
|
|
505
|
+
id: `s${stopIdx + 1}`,
|
|
506
|
+
offset: clamp(_nullishCoalesce(it.offset, () => ( 0)), 0, 1),
|
|
507
|
+
color: it.color
|
|
508
|
+
};
|
|
509
|
+
if (i + 1 < items.length && items[i + 1].kind === "hint") {
|
|
510
|
+
const hintOffset = items[i + 1].offset;
|
|
511
|
+
const nextStopItem = items.slice(i + 2).find((x) => x.kind === "stop");
|
|
512
|
+
if (nextStopItem && nextStopItem.offset !== void 0) {
|
|
513
|
+
const span = nextStopItem.offset - stop.offset;
|
|
514
|
+
if (span > 0) {
|
|
515
|
+
stop.midpoint = clamp((hintOffset - stop.offset) / span, 0.05, 0.95);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
finalStops.push(stop);
|
|
520
|
+
stopIdx++;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const res = normalizeGradient({
|
|
524
|
+
type,
|
|
525
|
+
angle,
|
|
526
|
+
center,
|
|
527
|
+
shape,
|
|
528
|
+
interpolation,
|
|
529
|
+
repeating,
|
|
530
|
+
stops: finalStops
|
|
531
|
+
});
|
|
532
|
+
return "gradient" in res ? res.gradient : null;
|
|
533
|
+
}
|
|
534
|
+
function parsePos(str) {
|
|
535
|
+
const s = str.trim();
|
|
536
|
+
if (s.endsWith("%")) return clamp(parseFloat(s) / 100, 0, 1);
|
|
537
|
+
const val = parseFloat(s);
|
|
538
|
+
return Number.isFinite(val) ? clamp(val, 0, 1) : 0.5;
|
|
539
|
+
}
|
|
540
|
+
function gradientToSvgDef(g, id, scheme = "light") {
|
|
541
|
+
const steps = 32;
|
|
542
|
+
const stopsXml = [];
|
|
543
|
+
for (let i = 0; i <= steps; i++) {
|
|
544
|
+
const t = i / steps;
|
|
545
|
+
const color = sampleGradient(g, t, scheme);
|
|
546
|
+
stopsXml.push(` <stop offset="${formatPercent(t)}" stop-color="${color}" />`);
|
|
547
|
+
}
|
|
548
|
+
if (g.type === "radial") {
|
|
549
|
+
const cx = g.center ? formatPercent(g.center.x) : "50%";
|
|
550
|
+
const cy = g.center ? formatPercent(g.center.y) : "50%";
|
|
551
|
+
const r = g.shape === "circle" ? "50%" : "70.7%";
|
|
552
|
+
return `<radialGradient id="${id}" cx="${cx}" cy="${cy}" r="${r}">
|
|
553
|
+
${stopsXml.join("\n")}
|
|
554
|
+
</radialGradient>`;
|
|
555
|
+
}
|
|
556
|
+
if (g.type === "conic") {
|
|
557
|
+
return `<!-- Note: SVG does not natively support conic gradients; rendering as linear fallback -->
|
|
558
|
+
<linearGradient id="${id}" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
559
|
+
${stopsXml.join("\n")}
|
|
560
|
+
</linearGradient>`;
|
|
561
|
+
}
|
|
562
|
+
const angle = _nullishCoalesce(g.angle, () => ( 180));
|
|
563
|
+
const rad = (90 - angle) * Math.PI / 180;
|
|
564
|
+
const dx = Math.cos(rad);
|
|
565
|
+
const dy = -Math.sin(rad);
|
|
566
|
+
const x1 = formatPercent(0.5 - dx * 0.5);
|
|
567
|
+
const y1 = formatPercent(0.5 - dy * 0.5);
|
|
568
|
+
const x2 = formatPercent(0.5 + dx * 0.5);
|
|
569
|
+
const y2 = formatPercent(0.5 + dy * 0.5);
|
|
570
|
+
return `<linearGradient id="${id}" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}">
|
|
571
|
+
${stopsXml.join("\n")}
|
|
572
|
+
</linearGradient>`;
|
|
573
|
+
}
|
|
574
|
+
function checkGradient(g, options = {}) {
|
|
575
|
+
const dual = isDualGradient(g) || _chunkUKCJ43YLcjs.isDualColor.call(void 0, options.surface) || _chunkUKCJ43YLcjs.isDualColor.call(void 0, options.text);
|
|
576
|
+
if (dual) {
|
|
577
|
+
const lightG = resolveGradient(g, "light");
|
|
578
|
+
const darkG = resolveGradient(g, "dark");
|
|
579
|
+
const lightText = options.text ? _nullishCoalesce(_chunkUKCJ43YLcjs.resolveColor.call(void 0, options.text, "light"), () => ( void 0)) : void 0;
|
|
580
|
+
const darkText = options.text ? _nullishCoalesce(_chunkUKCJ43YLcjs.resolveColor.call(void 0, options.text, "dark"), () => ( void 0)) : void 0;
|
|
581
|
+
const checkScheme = (grad, txt, schemeName) => {
|
|
582
|
+
const issues2 = [];
|
|
583
|
+
for (let i = 0; i < grad.stops.length - 1; i++) {
|
|
584
|
+
const gap = grad.stops[i + 1].offset - grad.stops[i].offset;
|
|
585
|
+
if (gap < 5e-3) {
|
|
586
|
+
issues2.push(`[${schemeName}] Stops "${grad.stops[i].id}" and "${grad.stops[i + 1].id}" are too close (gap < 0.005).`);
|
|
587
|
+
}
|
|
588
|
+
const c1 = _chunkUKCJ43YLcjs.parseColor.call(void 0, grad.stops[i].color);
|
|
589
|
+
const c2 = _chunkUKCJ43YLcjs.parseColor.call(void 0, grad.stops[i + 1].color);
|
|
590
|
+
if (c1 && c2 && gap < 0.05) {
|
|
591
|
+
const dE = _chunkUKCJ43YLcjs.deltaEOK.call(void 0, c1, c2);
|
|
592
|
+
if (dE > 0.5) {
|
|
593
|
+
issues2.push(
|
|
594
|
+
`[${schemeName}] Banding risk between stops "${grad.stops[i].id}" and "${grad.stops[i + 1].id}" (\u0394EOK = ${dE.toFixed(2)} across gap ${gap.toFixed(3)}).`
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
for (const s of grad.stops) {
|
|
600
|
+
const c = _chunkUKCJ43YLcjs.parseColor.call(void 0, s.color);
|
|
601
|
+
if (c && !_chunkUKCJ43YLcjs.inSrgbGamut.call(void 0, c)) {
|
|
602
|
+
issues2.push(`[${schemeName}] Stop "${s.id}" colour ${s.color} is outside standard sRGB gamut.`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
let lowest;
|
|
606
|
+
if (txt) {
|
|
607
|
+
const parsedTxt = _chunkUKCJ43YLcjs.parseColor.call(void 0, txt);
|
|
608
|
+
if (parsedTxt) {
|
|
609
|
+
let minRatio = Infinity;
|
|
610
|
+
for (let step = 0; step <= 32; step++) {
|
|
611
|
+
const sample = _chunkUKCJ43YLcjs.parseColor.call(void 0, sampleGradient(grad, step / 32, schemeName));
|
|
612
|
+
if (sample) {
|
|
613
|
+
const r = _chunkUKCJ43YLcjs.contrastRatio.call(void 0, parsedTxt, sample);
|
|
614
|
+
if (r < minRatio) minRatio = r;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
lowest = Math.round(minRatio * 100) / 100;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return { issues: issues2, lowest };
|
|
621
|
+
};
|
|
622
|
+
const lRes = checkScheme(lightG, lightText, "light");
|
|
623
|
+
const dRes = checkScheme(darkG, darkText, "dark");
|
|
624
|
+
return {
|
|
625
|
+
issues: [...lRes.issues, ...dRes.issues],
|
|
626
|
+
lowestTextContrast: lRes.lowest !== void 0 && dRes.lowest !== void 0 ? { light: lRes.lowest, dark: dRes.lowest } : void 0
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
const issues = [];
|
|
630
|
+
for (let i = 0; i < g.stops.length - 1; i++) {
|
|
631
|
+
const gap = g.stops[i + 1].offset - g.stops[i].offset;
|
|
632
|
+
if (gap < 5e-3) {
|
|
633
|
+
issues.push(`Stops "${g.stops[i].id}" and "${g.stops[i + 1].id}" are too close (gap < 0.005).`);
|
|
634
|
+
}
|
|
635
|
+
const c1 = _chunkUKCJ43YLcjs.parseColor.call(void 0, g.stops[i].color);
|
|
636
|
+
const c2 = _chunkUKCJ43YLcjs.parseColor.call(void 0, g.stops[i + 1].color);
|
|
637
|
+
if (c1 && c2 && gap < 0.05) {
|
|
638
|
+
const dE = _chunkUKCJ43YLcjs.deltaEOK.call(void 0, c1, c2);
|
|
639
|
+
if (dE > 0.5) {
|
|
640
|
+
issues.push(
|
|
641
|
+
`Banding risk between stops "${g.stops[i].id}" and "${g.stops[i + 1].id}" (\u0394EOK = ${dE.toFixed(2)} across gap ${gap.toFixed(3)}).`
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
for (const s of g.stops) {
|
|
647
|
+
const c = _chunkUKCJ43YLcjs.parseColor.call(void 0, s.color);
|
|
648
|
+
if (c && !_chunkUKCJ43YLcjs.inSrgbGamut.call(void 0, c)) {
|
|
649
|
+
issues.push(`Stop "${s.id}" colour ${s.color} is outside standard sRGB gamut.`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
let lowestTextContrast;
|
|
653
|
+
if (options.text) {
|
|
654
|
+
const parsedTxt = _chunkUKCJ43YLcjs.parseColor.call(void 0, options.text);
|
|
655
|
+
if (parsedTxt) {
|
|
656
|
+
let minRatio = Infinity;
|
|
657
|
+
for (let step = 0; step <= 32; step++) {
|
|
658
|
+
const sample = _chunkUKCJ43YLcjs.parseColor.call(void 0, sampleGradient(g, step / 32, "light"));
|
|
659
|
+
if (sample) {
|
|
660
|
+
const r = _chunkUKCJ43YLcjs.contrastRatio.call(void 0, parsedTxt, sample);
|
|
661
|
+
if (r < minRatio) minRatio = r;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
lowestTextContrast = Math.round(minRatio * 100) / 100;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return { issues, lowestTextContrast };
|
|
668
|
+
}
|
|
669
|
+
var GRADIENT_PRESETS = Object.freeze({
|
|
670
|
+
sunset: {
|
|
671
|
+
type: "linear",
|
|
672
|
+
angle: 90,
|
|
673
|
+
interpolation: "oklab",
|
|
674
|
+
stops: [
|
|
675
|
+
{ id: "s1", offset: 0, color: "#f97316" },
|
|
676
|
+
{ id: "s2", offset: 1, color: "#ec4899" }
|
|
677
|
+
]
|
|
678
|
+
},
|
|
679
|
+
ocean: {
|
|
680
|
+
type: "linear",
|
|
681
|
+
angle: 180,
|
|
682
|
+
interpolation: "oklab",
|
|
683
|
+
stops: [
|
|
684
|
+
{ id: "s1", offset: 0, color: "#06b6d4" },
|
|
685
|
+
{ id: "s2", offset: 1, color: "#3b82f6" }
|
|
686
|
+
]
|
|
687
|
+
},
|
|
688
|
+
"mono-fade": {
|
|
689
|
+
type: "linear",
|
|
690
|
+
angle: 180,
|
|
691
|
+
interpolation: "oklab",
|
|
692
|
+
stops: [
|
|
693
|
+
{ id: "s1", offset: 0, color: "#ffffff" },
|
|
694
|
+
{ id: "s2", offset: 1, color: "#000000" }
|
|
695
|
+
]
|
|
696
|
+
},
|
|
697
|
+
"transparent-fade": {
|
|
698
|
+
type: "linear",
|
|
699
|
+
angle: 180,
|
|
700
|
+
interpolation: "oklab",
|
|
701
|
+
stops: [
|
|
702
|
+
{ id: "s1", offset: 0, color: "#000000ff" },
|
|
703
|
+
{ id: "s2", offset: 1, color: "#00000000" }
|
|
704
|
+
]
|
|
705
|
+
},
|
|
706
|
+
aurora: {
|
|
707
|
+
type: "linear",
|
|
708
|
+
angle: 135,
|
|
709
|
+
interpolation: "oklab",
|
|
710
|
+
stops: [
|
|
711
|
+
{ id: "s1", offset: 0, color: "#10b981" },
|
|
712
|
+
{ id: "s2", offset: 0.5, color: "#06b6d4" },
|
|
713
|
+
{ id: "s3", offset: 1, color: "#8b5cf6" }
|
|
714
|
+
]
|
|
715
|
+
},
|
|
716
|
+
neon: {
|
|
717
|
+
type: "linear",
|
|
718
|
+
angle: 90,
|
|
719
|
+
interpolation: "oklab",
|
|
720
|
+
stops: [
|
|
721
|
+
{ id: "s1", offset: 0, color: "#f43f5e" },
|
|
722
|
+
{ id: "s2", offset: 1, color: "#8b5cf6" }
|
|
723
|
+
]
|
|
724
|
+
},
|
|
725
|
+
radial: {
|
|
726
|
+
type: "radial",
|
|
727
|
+
shape: "ellipse",
|
|
728
|
+
center: { x: 0.5, y: 0.5 },
|
|
729
|
+
interpolation: "oklab",
|
|
730
|
+
stops: [
|
|
731
|
+
{ id: "s1", offset: 0, color: "#ffffff" },
|
|
732
|
+
{ id: "s2", offset: 1, color: "#000000" }
|
|
733
|
+
]
|
|
734
|
+
},
|
|
735
|
+
conic: {
|
|
736
|
+
type: "conic",
|
|
737
|
+
angle: 0,
|
|
738
|
+
center: { x: 0.5, y: 0.5 },
|
|
739
|
+
interpolation: "oklab",
|
|
740
|
+
stops: [
|
|
741
|
+
{ id: "s1", offset: 0, color: "#f43f5e" },
|
|
742
|
+
{ id: "s2", offset: 0.33, color: "#eab308" },
|
|
743
|
+
{ id: "s3", offset: 0.66, color: "#06b6d4" },
|
|
744
|
+
{ id: "s4", offset: 1, color: "#f43f5e" }
|
|
745
|
+
]
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
// src/gradient/controller.ts
|
|
750
|
+
var GRADIENT_TYPES = ["linear", "radial", "conic"];
|
|
751
|
+
var GRADIENT_INTERPOLATIONS = ["oklab", "oklch", "srgb", "srgb-linear"];
|
|
752
|
+
var GRADIENT_SHAPES = ["circle", "ellipse"];
|
|
753
|
+
var GEOMETRY_KEYS = ["type", "angle", "shape", "interpolation", "repeating"];
|
|
754
|
+
function geometryError(patch) {
|
|
755
|
+
const list = (values) => values.map((v) => `"${v}"`).join(", ");
|
|
756
|
+
if (patch.type !== void 0 && !GRADIENT_TYPES.includes(patch.type)) {
|
|
757
|
+
return `Unknown gradient type "${String(patch.type)}"; use one of ${list(GRADIENT_TYPES)}.`;
|
|
758
|
+
}
|
|
759
|
+
if (patch.angle !== void 0 && (typeof patch.angle !== "number" || !Number.isFinite(patch.angle))) {
|
|
760
|
+
return `The angle must be a finite number of degrees; got ${String(patch.angle)}.`;
|
|
761
|
+
}
|
|
762
|
+
if (patch.center !== void 0) {
|
|
763
|
+
const c = patch.center;
|
|
764
|
+
const inRange = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 1;
|
|
765
|
+
if (!c || typeof c !== "object" || !inRange(c.x) || !inRange(c.y)) {
|
|
766
|
+
return `The center needs x and y between 0 and 1; got ${JSON.stringify(patch.center)}.`;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
if (patch.shape !== void 0 && !GRADIENT_SHAPES.includes(patch.shape)) {
|
|
770
|
+
return `Unknown radial shape "${String(patch.shape)}"; use ${list(GRADIENT_SHAPES)}.`;
|
|
771
|
+
}
|
|
772
|
+
if (patch.interpolation !== void 0 && !GRADIENT_INTERPOLATIONS.includes(patch.interpolation)) {
|
|
773
|
+
return `Unknown interpolation "${String(patch.interpolation)}"; use one of ${list(GRADIENT_INTERPOLATIONS)}.`;
|
|
774
|
+
}
|
|
775
|
+
if (patch.repeating !== void 0 && typeof patch.repeating !== "boolean") {
|
|
776
|
+
return `"repeating" must be true or false; got ${String(patch.repeating)}.`;
|
|
777
|
+
}
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
function sameGradientJson(a, b) {
|
|
781
|
+
if (a === b) return true;
|
|
782
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return Number.isNaN(a) && Number.isNaN(b);
|
|
783
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
784
|
+
if (Array.isArray(a)) {
|
|
785
|
+
const bb = b;
|
|
786
|
+
return a.length === bb.length && a.every((v, i) => sameGradientJson(v, bb[i]));
|
|
787
|
+
}
|
|
788
|
+
const ao = a;
|
|
789
|
+
const bo = b;
|
|
790
|
+
const keys = Object.keys(ao).filter((k) => ao[k] !== void 0);
|
|
791
|
+
if (keys.length !== Object.keys(bo).filter((k) => bo[k] !== void 0).length) return false;
|
|
792
|
+
return keys.every((k) => Object.prototype.hasOwnProperty.call(bo, k) && sameGradientJson(ao[k], bo[k]));
|
|
793
|
+
}
|
|
794
|
+
var GradientController = (_class = class {
|
|
795
|
+
|
|
796
|
+
__init() {this._selectedStopId = null}
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
__init2() {this._listeners = /* @__PURE__ */ new Set()}
|
|
801
|
+
__init3() {this._gestureKey = null}
|
|
802
|
+
__init4() {this._gestureCount = 0}
|
|
803
|
+
__init5() {this._revision = 0}
|
|
804
|
+
__init6() {this._cachedState = null}
|
|
805
|
+
constructor(options = {}) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);
|
|
806
|
+
if (options.value !== void 0) {
|
|
807
|
+
const norm = normalizeGradient(options.value);
|
|
808
|
+
if ("error" in norm) throw new Error(norm.error);
|
|
809
|
+
this._gradient = norm.gradient;
|
|
810
|
+
} else {
|
|
811
|
+
this._gradient = createGradient(["#000000", "#ffffff"]);
|
|
812
|
+
}
|
|
813
|
+
this._history = _nullishCoalesce(options.history, () => ( new (0, _chunkFJ2MSI43cjs.HistoryManager)()));
|
|
814
|
+
this._readOnlyOption = options.readOnly;
|
|
815
|
+
this._onChange = options.onChange;
|
|
816
|
+
if (this._gradient.stops.length > 0) {
|
|
817
|
+
this._selectedStopId = this._gradient.stops[0].id;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
get state() {
|
|
821
|
+
if (this._cachedState && this._cachedState.revision === this._revision) {
|
|
822
|
+
const hState2 = this._history.getState();
|
|
823
|
+
if (this._cachedState.canUndo === hState2.canUndo && this._cachedState.canRedo === hState2.canRedo && this._cachedState.undoLabel === hState2.undoName && this._cachedState.redoLabel === hState2.redoName && this._cachedState.selectedStopId === this._selectedStopId && this._cachedState.gradient === this._gradient) {
|
|
824
|
+
return this._cachedState;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const hState = this._history.getState();
|
|
828
|
+
this._cachedState = {
|
|
829
|
+
gradient: this._gradient,
|
|
830
|
+
selectedStopId: this._selectedStopId,
|
|
831
|
+
canUndo: hState.canUndo,
|
|
832
|
+
canRedo: hState.canRedo,
|
|
833
|
+
undoLabel: hState.undoName,
|
|
834
|
+
redoLabel: hState.redoName,
|
|
835
|
+
revision: this._revision
|
|
836
|
+
};
|
|
837
|
+
return this._cachedState;
|
|
838
|
+
}
|
|
839
|
+
get readOnlyReason() {
|
|
840
|
+
const r = typeof this._readOnlyOption === "function" ? this._readOnlyOption() : this._readOnlyOption;
|
|
841
|
+
if (!r) return null;
|
|
842
|
+
return typeof r === "string" ? r : "The gradient is read-only.";
|
|
843
|
+
}
|
|
844
|
+
get isReadOnly() {
|
|
845
|
+
return this.readOnlyReason !== null;
|
|
846
|
+
}
|
|
847
|
+
getGradient() {
|
|
848
|
+
return this._gradient;
|
|
849
|
+
}
|
|
850
|
+
getSelectedStopId() {
|
|
851
|
+
return this._selectedStopId;
|
|
852
|
+
}
|
|
853
|
+
getSelectedStop() {
|
|
854
|
+
if (!this._selectedStopId) return null;
|
|
855
|
+
return _nullishCoalesce(this._gradient.stops.find((s) => s.id === this._selectedStopId), () => ( null));
|
|
856
|
+
}
|
|
857
|
+
subscribe(fn) {
|
|
858
|
+
this._listeners.add(fn);
|
|
859
|
+
try {
|
|
860
|
+
fn(this.state);
|
|
861
|
+
} catch (err) {
|
|
862
|
+
console.error("GradientController subscriber error", err);
|
|
863
|
+
}
|
|
864
|
+
return () => {
|
|
865
|
+
this._listeners.delete(fn);
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
/** `changed` is false for view-only updates (selection), which don't call `onChange`. */
|
|
869
|
+
_notify(changed = true) {
|
|
870
|
+
this._revision++;
|
|
871
|
+
const s = this.state;
|
|
872
|
+
for (const fn of this._listeners) {
|
|
873
|
+
try {
|
|
874
|
+
fn(s);
|
|
875
|
+
} catch (err) {
|
|
876
|
+
console.error("GradientController subscriber error", err);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (changed && this._onChange) {
|
|
880
|
+
try {
|
|
881
|
+
this._onChange(this._gradient);
|
|
882
|
+
} catch (err) {
|
|
883
|
+
console.error("GradientController onChange error", err);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
_checkReadOnly() {
|
|
888
|
+
const reason = this.readOnlyReason;
|
|
889
|
+
if (reason) {
|
|
890
|
+
return { ok: false, message: reason };
|
|
891
|
+
}
|
|
892
|
+
return null;
|
|
893
|
+
}
|
|
894
|
+
/** Selects a stop (view state; not an undo step and no `onChange`). Unknown ids are ignored. */
|
|
895
|
+
selectStop(stopId) {
|
|
896
|
+
if (stopId !== null && !this._gradient.stops.some((s) => s.id === stopId)) {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (this._selectedStopId !== stopId) {
|
|
900
|
+
this._selectedStopId = stopId;
|
|
901
|
+
this._notify(false);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
beginGesture(label = "Edit Gradient") {
|
|
905
|
+
if (this._gestureKey === label) return;
|
|
906
|
+
this._history.endGesture();
|
|
907
|
+
this._gestureKey = label;
|
|
908
|
+
this._gestureCount++;
|
|
909
|
+
}
|
|
910
|
+
endGesture() {
|
|
911
|
+
this._gestureKey = null;
|
|
912
|
+
this._history.endGesture();
|
|
913
|
+
}
|
|
914
|
+
_apply(newGradient, label, newStopId = this._selectedStopId) {
|
|
915
|
+
const ro = this._checkReadOnly();
|
|
916
|
+
if (ro) return ro;
|
|
917
|
+
if (newStopId !== null && !newGradient.stops.some((s) => s.id === newStopId)) {
|
|
918
|
+
newStopId = _nullishCoalesce(_optionalChain([newGradient, 'access', _4 => _4.stops, 'access', _5 => _5[0], 'optionalAccess', _6 => _6.id]), () => ( null));
|
|
919
|
+
}
|
|
920
|
+
if (sameGradientJson(this._gradient, newGradient) && this._selectedStopId === newStopId) {
|
|
921
|
+
return { ok: true };
|
|
922
|
+
}
|
|
923
|
+
const beforeGradient = this._gradient;
|
|
924
|
+
const beforeStopId = this._selectedStopId;
|
|
925
|
+
const cmd = {
|
|
926
|
+
name: label,
|
|
927
|
+
tag: "gradient",
|
|
928
|
+
execute: () => {
|
|
929
|
+
this._gradient = newGradient;
|
|
930
|
+
this._selectedStopId = newStopId;
|
|
931
|
+
this._notify();
|
|
932
|
+
},
|
|
933
|
+
undo: () => {
|
|
934
|
+
this._gradient = beforeGradient;
|
|
935
|
+
this._selectedStopId = beforeStopId;
|
|
936
|
+
this._notify();
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
if (this._gestureKey !== null) {
|
|
940
|
+
this._history.executeMerged(cmd, { key: `gradient.gesture:${this._gestureKey}:${this._gestureCount}` });
|
|
941
|
+
} else {
|
|
942
|
+
this._history.execute(cmd);
|
|
943
|
+
}
|
|
944
|
+
return { ok: true };
|
|
945
|
+
}
|
|
946
|
+
/** The selected stop after `next` replaces the gradient: same id if it survives, else same index. */
|
|
947
|
+
_selectionAfterReplace(next) {
|
|
948
|
+
if (this._selectedStopId === null) return null;
|
|
949
|
+
if (next.stops.some((s) => s.id === this._selectedStopId)) return this._selectedStopId;
|
|
950
|
+
const index = this._gradient.stops.findIndex((s) => s.id === this._selectedStopId);
|
|
951
|
+
return _nullishCoalesce(_optionalChain([next, 'access', _7 => _7.stops, 'access', _8 => _8[Math.min(Math.max(index, 0), next.stops.length - 1)], 'optionalAccess', _9 => _9.id]), () => ( null));
|
|
952
|
+
}
|
|
953
|
+
setGradient(value, label = "Set Gradient") {
|
|
954
|
+
const norm = normalizeGradient(value);
|
|
955
|
+
if ("error" in norm) {
|
|
956
|
+
return { ok: false, message: norm.error };
|
|
957
|
+
}
|
|
958
|
+
return this._apply(norm.gradient, label, this._selectionAfterReplace(norm.gradient));
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Replaces the gradient without an undo step (e.g. syncing a controlled `value` from the
|
|
962
|
+
* host). Keeps the selection when the stop survives. Notifies subscribers and `onChange`.
|
|
963
|
+
*/
|
|
964
|
+
load(value, options = {}) {
|
|
965
|
+
const norm = normalizeGradient(value);
|
|
966
|
+
if ("error" in norm) {
|
|
967
|
+
return { ok: false, message: norm.error };
|
|
968
|
+
}
|
|
969
|
+
const nextStopId = this._selectionAfterReplace(norm.gradient);
|
|
970
|
+
if (options.clearHistory) this._history.clear();
|
|
971
|
+
if (sameGradientJson(this._gradient, norm.gradient) && this._selectedStopId === nextStopId) {
|
|
972
|
+
return { ok: true };
|
|
973
|
+
}
|
|
974
|
+
this._gradient = norm.gradient;
|
|
975
|
+
this._selectedStopId = nextStopId;
|
|
976
|
+
this._notify();
|
|
977
|
+
return { ok: true };
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Changes any of type, angle, center, shape, interpolation and repeating as one undo step.
|
|
981
|
+
* Every field is checked before anything changes; an invalid one fails the whole call.
|
|
982
|
+
*/
|
|
983
|
+
setGeometry(patch, label = "Change Gradient Geometry") {
|
|
984
|
+
const ro = this._checkReadOnly();
|
|
985
|
+
if (ro) return ro;
|
|
986
|
+
const error = geometryError(patch);
|
|
987
|
+
if (error) return { ok: false, message: error };
|
|
988
|
+
const merged = { ...this._gradient };
|
|
989
|
+
for (const key of GEOMETRY_KEYS) {
|
|
990
|
+
if (patch[key] !== void 0) merged[key] = patch[key];
|
|
991
|
+
}
|
|
992
|
+
if (patch.center) merged.center = { ...patch.center };
|
|
993
|
+
if (patch.shape !== void 0 && merged.type !== "radial") {
|
|
994
|
+
return { ok: false, message: `The shape applies to radial gradients only; this one is ${merged.type}. Set type "radial" too.` };
|
|
995
|
+
}
|
|
996
|
+
if (patch.center !== void 0 && merged.type === "linear") {
|
|
997
|
+
return { ok: false, message: "The center applies to radial and conic gradients only; this one is linear." };
|
|
998
|
+
}
|
|
999
|
+
if (patch.angle !== void 0 && merged.type === "radial") {
|
|
1000
|
+
return { ok: false, message: "The angle applies to linear and conic gradients only; this one is radial." };
|
|
1001
|
+
}
|
|
1002
|
+
const norm = normalizeGradient(merged);
|
|
1003
|
+
if ("error" in norm) return { ok: false, message: norm.error };
|
|
1004
|
+
return this._apply(norm.gradient, label);
|
|
1005
|
+
}
|
|
1006
|
+
setType(type) {
|
|
1007
|
+
return this.setGeometry({ type }, "Change Gradient Type");
|
|
1008
|
+
}
|
|
1009
|
+
setAngle(angle) {
|
|
1010
|
+
return this.setGeometry({ angle }, "Change Gradient Angle");
|
|
1011
|
+
}
|
|
1012
|
+
setCenter(center) {
|
|
1013
|
+
return this.setGeometry({ center }, "Change Gradient Center");
|
|
1014
|
+
}
|
|
1015
|
+
setShape(shape) {
|
|
1016
|
+
return this.setGeometry({ shape }, "Change Gradient Shape");
|
|
1017
|
+
}
|
|
1018
|
+
setInterpolation(interpolation) {
|
|
1019
|
+
return this.setGeometry({ interpolation }, "Change Gradient Interpolation");
|
|
1020
|
+
}
|
|
1021
|
+
setRepeating(repeating) {
|
|
1022
|
+
return this.setGeometry({ repeating }, "Toggle Gradient Repeating");
|
|
1023
|
+
}
|
|
1024
|
+
addStop(offset, color) {
|
|
1025
|
+
const ro = this._checkReadOnly();
|
|
1026
|
+
if (ro) return { ok: false, message: ro.message };
|
|
1027
|
+
try {
|
|
1028
|
+
const { gradient: next, stop } = insertGradientStop(this._gradient, offset, color);
|
|
1029
|
+
const res = this._apply(next, "Add Gradient Stop", stop.id);
|
|
1030
|
+
if (!res.ok) return res;
|
|
1031
|
+
return { ok: true, stop };
|
|
1032
|
+
} catch (e) {
|
|
1033
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
updateStop(id, patch) {
|
|
1037
|
+
const ro = this._checkReadOnly();
|
|
1038
|
+
if (ro) return ro;
|
|
1039
|
+
try {
|
|
1040
|
+
const next = updateGradientStop(this._gradient, id, patch);
|
|
1041
|
+
return this._apply(next, "Update Gradient Stop");
|
|
1042
|
+
} catch (e) {
|
|
1043
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
removeStop(id) {
|
|
1047
|
+
const ro = this._checkReadOnly();
|
|
1048
|
+
if (ro) return ro;
|
|
1049
|
+
try {
|
|
1050
|
+
const next = removeGradientStop(this._gradient, id);
|
|
1051
|
+
return this._apply(next, "Remove Gradient Stop");
|
|
1052
|
+
} catch (e) {
|
|
1053
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
reverse() {
|
|
1057
|
+
const ro = this._checkReadOnly();
|
|
1058
|
+
if (ro) return ro;
|
|
1059
|
+
const next = reverseGradient(this._gradient);
|
|
1060
|
+
return this._apply(next, "Reverse Gradient");
|
|
1061
|
+
}
|
|
1062
|
+
distribute() {
|
|
1063
|
+
const ro = this._checkReadOnly();
|
|
1064
|
+
if (ro) return ro;
|
|
1065
|
+
const next = distributeGradientStops(this._gradient);
|
|
1066
|
+
return this._apply(next, "Distribute Gradient Stops");
|
|
1067
|
+
}
|
|
1068
|
+
undo() {
|
|
1069
|
+
if (this.isReadOnly || !this._history.getState().canUndo) return false;
|
|
1070
|
+
return this._history.undo();
|
|
1071
|
+
}
|
|
1072
|
+
redo() {
|
|
1073
|
+
if (this.isReadOnly || !this._history.getState().canRedo) return false;
|
|
1074
|
+
return this._history.redo();
|
|
1075
|
+
}
|
|
1076
|
+
dispose() {
|
|
1077
|
+
this._listeners.clear();
|
|
1078
|
+
}
|
|
1079
|
+
}, _class);
|
|
1080
|
+
|
|
1081
|
+
// src/gradient/tools.ts
|
|
1082
|
+
var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
1083
|
+
var WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
1084
|
+
var WRITE_IDEMPOTENT = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
1085
|
+
var readTool = (title) => ({ ...READ, title });
|
|
1086
|
+
var writeTool = (title, destructive = false) => ({ ...WRITE, destructiveHint: destructive, title });
|
|
1087
|
+
var writeIdempotentTool = (title) => ({ ...WRITE_IDEMPOTENT, title });
|
|
1088
|
+
var fail = (message) => {
|
|
1089
|
+
throw new Error(message);
|
|
1090
|
+
};
|
|
1091
|
+
function createGradientToolProvider(controller, options = {}) {
|
|
1092
|
+
const readOnlyReason = () => {
|
|
1093
|
+
const r = typeof options.readOnly === "function" ? options.readOnly() : options.readOnly;
|
|
1094
|
+
if (r) return typeof r === "string" ? r : "The gradient is read-only.";
|
|
1095
|
+
return controller.readOnlyReason;
|
|
1096
|
+
};
|
|
1097
|
+
const tools = [
|
|
1098
|
+
{
|
|
1099
|
+
name: "get_gradient",
|
|
1100
|
+
title: "Get Gradient",
|
|
1101
|
+
description: "Returns the current gradient model, its CSS string representation, SVG definition markup, whether it contains light-dark() dual colours, and the currently selected stop id.",
|
|
1102
|
+
inputSchema: {
|
|
1103
|
+
type: "object",
|
|
1104
|
+
properties: {},
|
|
1105
|
+
additionalProperties: false
|
|
1106
|
+
},
|
|
1107
|
+
annotations: readTool("Get Gradient"),
|
|
1108
|
+
handler: async () => {
|
|
1109
|
+
const g = controller.getGradient();
|
|
1110
|
+
const ro = readOnlyReason();
|
|
1111
|
+
return {
|
|
1112
|
+
gradient: g,
|
|
1113
|
+
css: gradientToCss(g),
|
|
1114
|
+
svgDef: gradientToSvgDef(g, "gradient-preview"),
|
|
1115
|
+
isDual: isDualGradient(g),
|
|
1116
|
+
selectedStopId: controller.getSelectedStopId(),
|
|
1117
|
+
readOnly: ro !== null,
|
|
1118
|
+
...ro ? { readOnlyReason: ro } : {}
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
},
|
|
1122
|
+
{
|
|
1123
|
+
name: "check_gradient",
|
|
1124
|
+
title: "Check Gradient",
|
|
1125
|
+
description: "Evaluates the current gradient for perceptual defects: stops placed too close causing sharp edges, out-of-gamut colours in sRGB, color banding risks, and contrast against background or text across the gradient ramp.",
|
|
1126
|
+
inputSchema: {
|
|
1127
|
+
type: "object",
|
|
1128
|
+
properties: {
|
|
1129
|
+
surface: {
|
|
1130
|
+
description: "Optional background/surface colour to evaluate contrast against."
|
|
1131
|
+
},
|
|
1132
|
+
text: {
|
|
1133
|
+
description: "Optional text colour to evaluate WCAG 2.1 AA/AAA contrast against across all points on the gradient ramp."
|
|
1134
|
+
},
|
|
1135
|
+
fontSize: {
|
|
1136
|
+
type: "number",
|
|
1137
|
+
description: "Font size in points or pixels for WCAG text contrast threshold calculation (default 14)."
|
|
1138
|
+
},
|
|
1139
|
+
bold: {
|
|
1140
|
+
type: "boolean",
|
|
1141
|
+
description: "Whether the text is bold for WCAG threshold calculation."
|
|
1142
|
+
}
|
|
1143
|
+
},
|
|
1144
|
+
additionalProperties: false
|
|
1145
|
+
},
|
|
1146
|
+
annotations: readTool("Check Gradient"),
|
|
1147
|
+
handler: async (args) => {
|
|
1148
|
+
const g = controller.getGradient();
|
|
1149
|
+
const checkOpts = {};
|
|
1150
|
+
if (args.surface !== void 0) checkOpts.surface = args.surface;
|
|
1151
|
+
if (args.text !== void 0) checkOpts.text = args.text;
|
|
1152
|
+
if (typeof args.fontSize === "number") checkOpts.fontSize = args.fontSize;
|
|
1153
|
+
if (typeof args.bold === "boolean") checkOpts.bold = args.bold;
|
|
1154
|
+
const res = checkGradient(g, checkOpts);
|
|
1155
|
+
return {
|
|
1156
|
+
pass: res.issues.length === 0,
|
|
1157
|
+
issueCount: res.issues.length,
|
|
1158
|
+
...res
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
},
|
|
1162
|
+
{
|
|
1163
|
+
name: "list_gradient_presets",
|
|
1164
|
+
title: "List Gradient Presets",
|
|
1165
|
+
description: "Returns the collection of built-in gradient presets with their name, category, and CSS representations.",
|
|
1166
|
+
inputSchema: {
|
|
1167
|
+
type: "object",
|
|
1168
|
+
properties: {},
|
|
1169
|
+
additionalProperties: false
|
|
1170
|
+
},
|
|
1171
|
+
annotations: readTool("List Gradient Presets"),
|
|
1172
|
+
handler: async () => {
|
|
1173
|
+
return {
|
|
1174
|
+
presets: Object.entries(GRADIENT_PRESETS).map(([id, gradient]) => ({
|
|
1175
|
+
id,
|
|
1176
|
+
name: id,
|
|
1177
|
+
css: gradientToCss(gradient),
|
|
1178
|
+
gradient
|
|
1179
|
+
}))
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
];
|
|
1184
|
+
if (!readOnlyReason()) {
|
|
1185
|
+
tools.push(
|
|
1186
|
+
{
|
|
1187
|
+
name: "set_gradient",
|
|
1188
|
+
title: "Set Gradient",
|
|
1189
|
+
description: 'Replaces the entire gradient with a new gradient object or a standard CSS gradient string (e.g. "linear-gradient(90deg, #ff0000, #0000ff)"). Exactly one of "gradient" or "css" must be provided.',
|
|
1190
|
+
inputSchema: {
|
|
1191
|
+
type: "object",
|
|
1192
|
+
properties: {
|
|
1193
|
+
gradient: {
|
|
1194
|
+
type: "object",
|
|
1195
|
+
description: "Gradient object with stops, type, angle, etc."
|
|
1196
|
+
},
|
|
1197
|
+
css: {
|
|
1198
|
+
type: "string",
|
|
1199
|
+
description: "CSS gradient string to parse and apply."
|
|
1200
|
+
}
|
|
1201
|
+
},
|
|
1202
|
+
oneOf: [
|
|
1203
|
+
{ required: ["gradient"] },
|
|
1204
|
+
{ required: ["css"] }
|
|
1205
|
+
],
|
|
1206
|
+
additionalProperties: false
|
|
1207
|
+
},
|
|
1208
|
+
annotations: writeIdempotentTool("Set Gradient"),
|
|
1209
|
+
handler: async (args) => {
|
|
1210
|
+
if (args.gradient !== void 0 && args.css !== void 0) {
|
|
1211
|
+
fail('Provide either "gradient" or "css", not both.');
|
|
1212
|
+
}
|
|
1213
|
+
if (args.gradient === void 0 && args.css === void 0) {
|
|
1214
|
+
fail('Provide either "gradient" or "css".');
|
|
1215
|
+
}
|
|
1216
|
+
const val = args.gradient !== void 0 ? args.gradient : args.css;
|
|
1217
|
+
const res = controller.setGradient(val);
|
|
1218
|
+
if (!res.ok) fail(res.message);
|
|
1219
|
+
const current = controller.getGradient();
|
|
1220
|
+
return {
|
|
1221
|
+
gradient: current,
|
|
1222
|
+
css: gradientToCss(current)
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
},
|
|
1226
|
+
{
|
|
1227
|
+
name: "set_gradient_geometry",
|
|
1228
|
+
title: "Set Gradient Geometry",
|
|
1229
|
+
description: "Updates the geometry of the gradient: type (linear/radial/conic), angle in degrees, center (0 to 1), shape (circle/ellipse), interpolation space or repeating mode. Pass only the fields to change. All fields are checked first and applied as one undo step; an invalid field (e.g. shape on a linear gradient) changes nothing. Returns the resulting gradient and its CSS.",
|
|
1230
|
+
inputSchema: {
|
|
1231
|
+
type: "object",
|
|
1232
|
+
properties: {
|
|
1233
|
+
type: {
|
|
1234
|
+
type: "string",
|
|
1235
|
+
enum: ["linear", "radial", "conic"],
|
|
1236
|
+
description: "The geometry type of the gradient."
|
|
1237
|
+
},
|
|
1238
|
+
angle: {
|
|
1239
|
+
type: "number",
|
|
1240
|
+
description: "The angle in degrees for linear or conic gradients (0 = up/top, 90 = right)."
|
|
1241
|
+
},
|
|
1242
|
+
center: {
|
|
1243
|
+
type: "object",
|
|
1244
|
+
description: "Center { x, y } as fractions of the box (0 to 1) for radial or conic gradients. Default { x: 0.5, y: 0.5 }.",
|
|
1245
|
+
properties: {
|
|
1246
|
+
x: { type: "number", minimum: 0, maximum: 1, description: "Horizontal center, 0 (left) to 1 (right)." },
|
|
1247
|
+
y: { type: "number", minimum: 0, maximum: 1, description: "Vertical center, 0 (top) to 1 (bottom)." }
|
|
1248
|
+
},
|
|
1249
|
+
required: ["x", "y"],
|
|
1250
|
+
additionalProperties: false
|
|
1251
|
+
},
|
|
1252
|
+
shape: {
|
|
1253
|
+
type: "string",
|
|
1254
|
+
enum: ["circle", "ellipse"],
|
|
1255
|
+
description: "Radial gradient shape."
|
|
1256
|
+
},
|
|
1257
|
+
interpolation: {
|
|
1258
|
+
type: "string",
|
|
1259
|
+
enum: [...GRADIENT_INTERPOLATIONS],
|
|
1260
|
+
description: 'Colour interpolation space. "oklab" (the default) gives smooth perceptual blends; "oklch" keeps chroma through hue changes.'
|
|
1261
|
+
},
|
|
1262
|
+
repeating: {
|
|
1263
|
+
type: "boolean",
|
|
1264
|
+
description: "Whether the gradient pattern repeats outside the defined stops."
|
|
1265
|
+
}
|
|
1266
|
+
},
|
|
1267
|
+
minProperties: 1,
|
|
1268
|
+
additionalProperties: false
|
|
1269
|
+
},
|
|
1270
|
+
annotations: writeIdempotentTool("Set Gradient Geometry"),
|
|
1271
|
+
handler: async (args) => {
|
|
1272
|
+
if (!["type", "angle", "center", "shape", "interpolation", "repeating"].some((k) => args[k] !== void 0)) {
|
|
1273
|
+
fail("Pass at least one of type, angle, center, shape, interpolation or repeating.");
|
|
1274
|
+
}
|
|
1275
|
+
const r = controller.setGeometry(
|
|
1276
|
+
{
|
|
1277
|
+
type: args.type,
|
|
1278
|
+
angle: args.angle,
|
|
1279
|
+
center: args.center,
|
|
1280
|
+
shape: args.shape,
|
|
1281
|
+
interpolation: args.interpolation,
|
|
1282
|
+
repeating: args.repeating
|
|
1283
|
+
},
|
|
1284
|
+
"Update Gradient Geometry"
|
|
1285
|
+
);
|
|
1286
|
+
if (!r.ok) fail(r.message);
|
|
1287
|
+
const current = controller.getGradient();
|
|
1288
|
+
return {
|
|
1289
|
+
gradient: current,
|
|
1290
|
+
css: gradientToCss(current)
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
name: "add_gradient_stop",
|
|
1296
|
+
title: "Add Gradient Stop",
|
|
1297
|
+
description: "Adds a new color stop to the gradient. If color is not specified, it is sampled from the gradient at that offset.",
|
|
1298
|
+
inputSchema: {
|
|
1299
|
+
type: "object",
|
|
1300
|
+
properties: {
|
|
1301
|
+
offset: {
|
|
1302
|
+
type: "number",
|
|
1303
|
+
description: "Offset along the gradient ramp from 0 to 1."
|
|
1304
|
+
},
|
|
1305
|
+
color: {
|
|
1306
|
+
type: "string",
|
|
1307
|
+
description: "CSS color string, hex, hsl, oklch, or light-dark() expression for the stop."
|
|
1308
|
+
}
|
|
1309
|
+
},
|
|
1310
|
+
required: ["offset"],
|
|
1311
|
+
additionalProperties: false
|
|
1312
|
+
},
|
|
1313
|
+
annotations: writeTool("Add Gradient Stop"),
|
|
1314
|
+
handler: async (args) => {
|
|
1315
|
+
const res = controller.addStop(args.offset, args.color);
|
|
1316
|
+
if (!res.ok) fail(res.message);
|
|
1317
|
+
return {
|
|
1318
|
+
stop: res.stop,
|
|
1319
|
+
gradient: controller.getGradient(),
|
|
1320
|
+
css: gradientToCss(controller.getGradient())
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
name: "update_gradient_stop",
|
|
1326
|
+
title: "Update Gradient Stop",
|
|
1327
|
+
description: "Updates an existing color stop: changes its color, its offset (position 0..1), or its transition midpoint (0..1).",
|
|
1328
|
+
inputSchema: {
|
|
1329
|
+
type: "object",
|
|
1330
|
+
properties: {
|
|
1331
|
+
id: {
|
|
1332
|
+
type: "string",
|
|
1333
|
+
description: "ID of the stop to update."
|
|
1334
|
+
},
|
|
1335
|
+
color: {
|
|
1336
|
+
type: "string",
|
|
1337
|
+
description: "New color string."
|
|
1338
|
+
},
|
|
1339
|
+
offset: {
|
|
1340
|
+
type: "number",
|
|
1341
|
+
description: "New offset between 0 and 1."
|
|
1342
|
+
},
|
|
1343
|
+
midpoint: {
|
|
1344
|
+
type: "number",
|
|
1345
|
+
description: "Transition midpoint to the next stop (0 to 1, default 0.5)."
|
|
1346
|
+
}
|
|
1347
|
+
},
|
|
1348
|
+
required: ["id"],
|
|
1349
|
+
additionalProperties: false
|
|
1350
|
+
},
|
|
1351
|
+
annotations: writeIdempotentTool("Update Gradient Stop"),
|
|
1352
|
+
handler: async (args) => {
|
|
1353
|
+
const patch = {};
|
|
1354
|
+
if (args.color !== void 0) patch.color = args.color;
|
|
1355
|
+
if (args.offset !== void 0) patch.offset = args.offset;
|
|
1356
|
+
if (args.midpoint !== void 0) patch.midpoint = args.midpoint;
|
|
1357
|
+
const res = controller.updateStop(args.id, patch);
|
|
1358
|
+
if (!res.ok) fail(res.message);
|
|
1359
|
+
return {
|
|
1360
|
+
stop: controller.getGradient().stops.find((s) => s.id === args.id),
|
|
1361
|
+
gradient: controller.getGradient(),
|
|
1362
|
+
css: gradientToCss(controller.getGradient())
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
},
|
|
1366
|
+
{
|
|
1367
|
+
name: "remove_gradient_stop",
|
|
1368
|
+
title: "Remove Gradient Stop",
|
|
1369
|
+
description: "Removes a color stop by its ID. Gradients must retain at least two stops.",
|
|
1370
|
+
inputSchema: {
|
|
1371
|
+
type: "object",
|
|
1372
|
+
properties: {
|
|
1373
|
+
id: {
|
|
1374
|
+
type: "string",
|
|
1375
|
+
description: "ID of the stop to remove."
|
|
1376
|
+
}
|
|
1377
|
+
},
|
|
1378
|
+
required: ["id"],
|
|
1379
|
+
additionalProperties: false
|
|
1380
|
+
},
|
|
1381
|
+
annotations: writeTool("Remove Gradient Stop", true),
|
|
1382
|
+
handler: async (args) => {
|
|
1383
|
+
const res = controller.removeStop(args.id);
|
|
1384
|
+
if (!res.ok) fail(res.message);
|
|
1385
|
+
return {
|
|
1386
|
+
removedId: args.id,
|
|
1387
|
+
gradient: controller.getGradient(),
|
|
1388
|
+
css: gradientToCss(controller.getGradient())
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
},
|
|
1392
|
+
{
|
|
1393
|
+
name: "reverse_gradient",
|
|
1394
|
+
title: "Reverse Gradient",
|
|
1395
|
+
description: "Reverses the order and offsets of all stops in the gradient ramp.",
|
|
1396
|
+
inputSchema: {
|
|
1397
|
+
type: "object",
|
|
1398
|
+
properties: {},
|
|
1399
|
+
additionalProperties: false
|
|
1400
|
+
},
|
|
1401
|
+
annotations: writeTool("Reverse Gradient"),
|
|
1402
|
+
handler: async () => {
|
|
1403
|
+
const res = controller.reverse();
|
|
1404
|
+
if (!res.ok) fail(res.message);
|
|
1405
|
+
return {
|
|
1406
|
+
gradient: controller.getGradient(),
|
|
1407
|
+
css: gradientToCss(controller.getGradient())
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
name: "distribute_gradient_stops",
|
|
1413
|
+
title: "Distribute Gradient Stops",
|
|
1414
|
+
description: "Evenly distributes all color stops between 0 and 1 along the gradient ramp.",
|
|
1415
|
+
inputSchema: {
|
|
1416
|
+
type: "object",
|
|
1417
|
+
properties: {},
|
|
1418
|
+
additionalProperties: false
|
|
1419
|
+
},
|
|
1420
|
+
annotations: writeTool("Distribute Gradient Stops"),
|
|
1421
|
+
handler: async () => {
|
|
1422
|
+
const res = controller.distribute();
|
|
1423
|
+
if (!res.ok) fail(res.message);
|
|
1424
|
+
return {
|
|
1425
|
+
gradient: controller.getGradient(),
|
|
1426
|
+
css: gradientToCss(controller.getGradient())
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
name: "apply_gradient_preset",
|
|
1432
|
+
title: "Apply Gradient Preset",
|
|
1433
|
+
description: "Applies one of the built-in gradient presets by its ID.",
|
|
1434
|
+
inputSchema: {
|
|
1435
|
+
type: "object",
|
|
1436
|
+
properties: {
|
|
1437
|
+
id: {
|
|
1438
|
+
type: "string",
|
|
1439
|
+
description: 'Preset ID (e.g. "sunset", "ocean", "neon-glow", "mesh-aurora").'
|
|
1440
|
+
}
|
|
1441
|
+
},
|
|
1442
|
+
required: ["id"],
|
|
1443
|
+
additionalProperties: false
|
|
1444
|
+
},
|
|
1445
|
+
annotations: writeIdempotentTool("Apply Gradient Preset"),
|
|
1446
|
+
handler: async (args) => {
|
|
1447
|
+
const gradient = GRADIENT_PRESETS[args.id];
|
|
1448
|
+
if (!gradient) {
|
|
1449
|
+
fail(`Unknown gradient preset "${args.id}". Use list_gradient_presets to inspect available presets.`);
|
|
1450
|
+
}
|
|
1451
|
+
const res = controller.setGradient(gradient, `Apply Preset: ${args.id}`);
|
|
1452
|
+
if (!res.ok) fail(res.message);
|
|
1453
|
+
return {
|
|
1454
|
+
preset: args.id,
|
|
1455
|
+
gradient: controller.getGradient(),
|
|
1456
|
+
css: gradientToCss(controller.getGradient())
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1462
|
+
return {
|
|
1463
|
+
id: _nullishCoalesce(options.id, () => ( "gradient")),
|
|
1464
|
+
namespace: options.namespace !== void 0 ? options.namespace : _nullishCoalesce(options.id, () => ( "gradient")),
|
|
1465
|
+
title: "Gradient",
|
|
1466
|
+
description: "Inspect, edit, validate, and check colour gradients.",
|
|
1467
|
+
skill: "pixerate-color",
|
|
1468
|
+
tools: () => tools
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// src/gradient/editor.ts
|
|
1473
|
+
function stopFromTrackPosition(g, x) {
|
|
1474
|
+
return Math.max(0, Math.min(1, Math.round(x * 1e3) / 1e3));
|
|
1475
|
+
}
|
|
1476
|
+
function trackPositionFromStop(stop) {
|
|
1477
|
+
return Math.max(0, Math.min(1, stop.offset));
|
|
1478
|
+
}
|
|
1479
|
+
function gradientKeyStep(g, id, key, shift = false) {
|
|
1480
|
+
const stop = g.stops.find((s) => s.id === id);
|
|
1481
|
+
if (!stop) return null;
|
|
1482
|
+
const step = shift ? 0.1 : 0.01;
|
|
1483
|
+
switch (key) {
|
|
1484
|
+
case "ArrowLeft":
|
|
1485
|
+
case "ArrowDown": {
|
|
1486
|
+
const offset = Math.max(0, Math.round((stop.offset - step) * 1e3) / 1e3);
|
|
1487
|
+
return { stopId: id, offset };
|
|
1488
|
+
}
|
|
1489
|
+
case "ArrowRight":
|
|
1490
|
+
case "ArrowUp": {
|
|
1491
|
+
const offset = Math.min(1, Math.round((stop.offset + step) * 1e3) / 1e3);
|
|
1492
|
+
return { stopId: id, offset };
|
|
1493
|
+
}
|
|
1494
|
+
case "Delete":
|
|
1495
|
+
case "Backspace": {
|
|
1496
|
+
return { stopId: id, remove: true };
|
|
1497
|
+
}
|
|
1498
|
+
default:
|
|
1499
|
+
return null;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
function angleFromPointer(center, point, snapStep) {
|
|
1503
|
+
const dx = point.x - center.x;
|
|
1504
|
+
const dy = point.y - center.y;
|
|
1505
|
+
let deg = Math.atan2(dx, -dy) * 180 / Math.PI;
|
|
1506
|
+
if (deg < 0) deg += 360;
|
|
1507
|
+
if (snapStep && snapStep > 0) {
|
|
1508
|
+
deg = Math.round(deg / snapStep) * snapStep;
|
|
1509
|
+
if (deg >= 360) deg -= 360;
|
|
1510
|
+
}
|
|
1511
|
+
return Math.round(deg);
|
|
1512
|
+
}
|
|
1513
|
+
function gradientTrackBackground(g) {
|
|
1514
|
+
const linearCopy = {
|
|
1515
|
+
...g,
|
|
1516
|
+
type: "linear",
|
|
1517
|
+
angle: 90
|
|
1518
|
+
};
|
|
1519
|
+
return gradientToCss(linearCopy);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
|
|
1528
|
+
|
|
1529
|
+
|
|
1530
|
+
|
|
1531
|
+
|
|
1532
|
+
|
|
1533
|
+
|
|
1534
|
+
|
|
1535
|
+
|
|
1536
|
+
|
|
1537
|
+
|
|
1538
|
+
|
|
1539
|
+
|
|
1540
|
+
|
|
1541
|
+
|
|
1542
|
+
|
|
1543
|
+
|
|
1544
|
+
|
|
1545
|
+
|
|
1546
|
+
|
|
1547
|
+
|
|
1548
|
+
|
|
1549
|
+
exports.GRADIENT_INTERPOLATIONS = GRADIENT_INTERPOLATIONS; exports.GRADIENT_PRESETS = GRADIENT_PRESETS; exports.GRADIENT_TYPES = GRADIENT_TYPES; exports.GradientController = GradientController; exports.angleFromPointer = angleFromPointer; exports.checkGradient = checkGradient; exports.createGradient = createGradient; exports.createGradientToolProvider = createGradientToolProvider; exports.distributeGradientStops = distributeGradientStops; exports.gradientKeyStep = gradientKeyStep; exports.gradientMidpointRemap = gradientMidpointRemap; exports.gradientToCss = gradientToCss; exports.gradientToCssFallback = gradientToCssFallback; exports.gradientToSvgDef = gradientToSvgDef; exports.gradientTrackBackground = gradientTrackBackground; exports.insertGradientStop = insertGradientStop; exports.isDualColorValue = isDualColorValue; exports.isDualGradient = isDualGradient; exports.normalizeGradient = normalizeGradient; exports.parseCssGradient = parseCssGradient; exports.removeGradientStop = removeGradientStop; exports.resolveGradient = resolveGradient; exports.reverseGradient = reverseGradient; exports.sampleGradient = sampleGradient; exports.stopFromTrackPosition = stopFromTrackPosition; exports.trackPositionFromStop = trackPositionFromStop; exports.updateGradientStop = updateGradientStop;
|