@grida/svg-editor 1.0.0-alpha.1
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/LICENSE +201 -0
- package/README.md +831 -0
- package/dist/dom-CfP_ZURh.js +963 -0
- package/dist/dom-kA8NDuVh.mjs +929 -0
- package/dist/dom.d.mts +16 -0
- package/dist/dom.d.ts +16 -0
- package/dist/dom.js +3 -0
- package/dist/dom.mjs +2 -0
- package/dist/editor-B5z-gTML.mjs +1821 -0
- package/dist/editor-CTtU2gu4.d.ts +607 -0
- package/dist/editor-DQWUWrVZ.js +1833 -0
- package/dist/editor-JY7AQrR1.d.mts +607 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/index.mjs +2 -0
- package/dist/paint-DHq_3iwU.js +509 -0
- package/dist/paint-DuCg6Y-K.mjs +461 -0
- package/dist/react.d.mts +49 -0
- package/dist/react.d.ts +49 -0
- package/dist/react.js +97 -0
- package/dist/react.mjs +92 -0
- package/package.json +66 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { SVGPathData, SVGPathDataTransformer } from "svg-pathdata";
|
|
2
|
+
//#region src/core/intents.ts
|
|
3
|
+
function num(doc, id, name, fallback = 0) {
|
|
4
|
+
const v = doc.get_attr(id, name);
|
|
5
|
+
if (v === null || v === "") return fallback;
|
|
6
|
+
const n = parseFloat(v);
|
|
7
|
+
return Number.isFinite(n) ? n : fallback;
|
|
8
|
+
}
|
|
9
|
+
function capture_translate_baseline(doc, id) {
|
|
10
|
+
const tag = doc.tag_of(id);
|
|
11
|
+
const own_transform = doc.get_attr(id, "transform");
|
|
12
|
+
if (own_transform !== null || tag === "g") return {
|
|
13
|
+
type: "viaTransform",
|
|
14
|
+
transform: own_transform
|
|
15
|
+
};
|
|
16
|
+
switch (tag) {
|
|
17
|
+
case "rect": return {
|
|
18
|
+
type: "rect",
|
|
19
|
+
x: num(doc, id, "x"),
|
|
20
|
+
y: num(doc, id, "y")
|
|
21
|
+
};
|
|
22
|
+
case "circle": return {
|
|
23
|
+
type: "circle",
|
|
24
|
+
cx: num(doc, id, "cx"),
|
|
25
|
+
cy: num(doc, id, "cy")
|
|
26
|
+
};
|
|
27
|
+
case "ellipse": return {
|
|
28
|
+
type: "ellipse",
|
|
29
|
+
cx: num(doc, id, "cx"),
|
|
30
|
+
cy: num(doc, id, "cy")
|
|
31
|
+
};
|
|
32
|
+
case "line": return {
|
|
33
|
+
type: "line",
|
|
34
|
+
x1: num(doc, id, "x1"),
|
|
35
|
+
y1: num(doc, id, "y1"),
|
|
36
|
+
x2: num(doc, id, "x2"),
|
|
37
|
+
y2: num(doc, id, "y2")
|
|
38
|
+
};
|
|
39
|
+
case "polyline": return {
|
|
40
|
+
type: "polyline",
|
|
41
|
+
points: doc.get_attr(id, "points") ?? ""
|
|
42
|
+
};
|
|
43
|
+
case "polygon": return {
|
|
44
|
+
type: "polygon",
|
|
45
|
+
points: doc.get_attr(id, "points") ?? ""
|
|
46
|
+
};
|
|
47
|
+
case "path": return {
|
|
48
|
+
type: "path",
|
|
49
|
+
d: doc.get_attr(id, "d") ?? ""
|
|
50
|
+
};
|
|
51
|
+
case "text": return {
|
|
52
|
+
type: "text",
|
|
53
|
+
x: num(doc, id, "x"),
|
|
54
|
+
y: num(doc, id, "y")
|
|
55
|
+
};
|
|
56
|
+
case "tspan": return {
|
|
57
|
+
type: "tspan",
|
|
58
|
+
x: num(doc, id, "x"),
|
|
59
|
+
y: num(doc, id, "y")
|
|
60
|
+
};
|
|
61
|
+
case "image": return {
|
|
62
|
+
type: "image",
|
|
63
|
+
x: num(doc, id, "x"),
|
|
64
|
+
y: num(doc, id, "y")
|
|
65
|
+
};
|
|
66
|
+
case "use": return {
|
|
67
|
+
type: "use",
|
|
68
|
+
x: num(doc, id, "x"),
|
|
69
|
+
y: num(doc, id, "y")
|
|
70
|
+
};
|
|
71
|
+
default: return { type: "unsupported" };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function shift_points_string(points, dx, dy) {
|
|
75
|
+
const nums = points.split(/[\s,]+/).filter(Boolean).map(Number);
|
|
76
|
+
const out = [];
|
|
77
|
+
for (let i = 0; i + 1 < nums.length; i += 2) out.push(`${nums[i] + dx},${nums[i + 1] + dy}`);
|
|
78
|
+
return out.join(" ");
|
|
79
|
+
}
|
|
80
|
+
function compose_leading_translate(existing, dx, dy) {
|
|
81
|
+
if (dx === 0 && dy === 0) return existing ? existing : null;
|
|
82
|
+
if (!existing) return `translate(${dx} ${dy})`;
|
|
83
|
+
const N = "[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?";
|
|
84
|
+
const re = new RegExp(`^\\s*translate\\(\\s*(${N})(?:\\s*,\\s*|\\s+)(${N})\\s*\\)\\s*(.*)$`);
|
|
85
|
+
const m = existing.match(re);
|
|
86
|
+
if (m) {
|
|
87
|
+
const tx = parseFloat(m[1]) + dx;
|
|
88
|
+
const ty = parseFloat(m[2]) + dy;
|
|
89
|
+
const rest = m[3].trim();
|
|
90
|
+
return rest ? `translate(${tx} ${ty}) ${rest}` : `translate(${tx} ${ty})`;
|
|
91
|
+
}
|
|
92
|
+
return `translate(${dx} ${dy}) ${existing}`;
|
|
93
|
+
}
|
|
94
|
+
function shift_path_d(d, dx, dy) {
|
|
95
|
+
if (dx === 0 && dy === 0) return d;
|
|
96
|
+
try {
|
|
97
|
+
return new SVGPathData(d).transform(SVGPathDataTransformer.TRANSLATE(dx, dy)).encode();
|
|
98
|
+
} catch {
|
|
99
|
+
return d;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function apply_translate(doc, id, baseline, dx, dy) {
|
|
103
|
+
switch (baseline.type) {
|
|
104
|
+
case "viaTransform":
|
|
105
|
+
doc.set_attr(id, "transform", compose_leading_translate(baseline.transform ?? "", dx, dy));
|
|
106
|
+
return;
|
|
107
|
+
case "rect":
|
|
108
|
+
case "image":
|
|
109
|
+
case "use":
|
|
110
|
+
case "text":
|
|
111
|
+
case "tspan":
|
|
112
|
+
doc.set_attr(id, "x", String(baseline.x + dx));
|
|
113
|
+
doc.set_attr(id, "y", String(baseline.y + dy));
|
|
114
|
+
return;
|
|
115
|
+
case "circle":
|
|
116
|
+
case "ellipse":
|
|
117
|
+
doc.set_attr(id, "cx", String(baseline.cx + dx));
|
|
118
|
+
doc.set_attr(id, "cy", String(baseline.cy + dy));
|
|
119
|
+
return;
|
|
120
|
+
case "line":
|
|
121
|
+
doc.set_attr(id, "x1", String(baseline.x1 + dx));
|
|
122
|
+
doc.set_attr(id, "y1", String(baseline.y1 + dy));
|
|
123
|
+
doc.set_attr(id, "x2", String(baseline.x2 + dx));
|
|
124
|
+
doc.set_attr(id, "y2", String(baseline.y2 + dy));
|
|
125
|
+
return;
|
|
126
|
+
case "polyline":
|
|
127
|
+
case "polygon":
|
|
128
|
+
doc.set_attr(id, "points", shift_points_string(baseline.points, dx, dy));
|
|
129
|
+
return;
|
|
130
|
+
case "path":
|
|
131
|
+
doc.set_attr(id, "d", shift_path_d(baseline.d, dx, dy));
|
|
132
|
+
return;
|
|
133
|
+
case "unsupported": return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function is_resizable(tag) {
|
|
137
|
+
switch (tag) {
|
|
138
|
+
case "rect":
|
|
139
|
+
case "image":
|
|
140
|
+
case "use":
|
|
141
|
+
case "circle":
|
|
142
|
+
case "ellipse":
|
|
143
|
+
case "line":
|
|
144
|
+
case "polyline":
|
|
145
|
+
case "polygon":
|
|
146
|
+
case "path":
|
|
147
|
+
case "text": return true;
|
|
148
|
+
default: return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function capture_resize_baseline(doc, id, bbox) {
|
|
152
|
+
const tag = doc.tag_of(id);
|
|
153
|
+
let attrs;
|
|
154
|
+
switch (tag) {
|
|
155
|
+
case "rect":
|
|
156
|
+
attrs = {
|
|
157
|
+
kind: "rect",
|
|
158
|
+
x: num(doc, id, "x"),
|
|
159
|
+
y: num(doc, id, "y"),
|
|
160
|
+
w: num(doc, id, "width", bbox.width),
|
|
161
|
+
h: num(doc, id, "height", bbox.height)
|
|
162
|
+
};
|
|
163
|
+
break;
|
|
164
|
+
case "image":
|
|
165
|
+
attrs = {
|
|
166
|
+
kind: "image",
|
|
167
|
+
x: num(doc, id, "x"),
|
|
168
|
+
y: num(doc, id, "y"),
|
|
169
|
+
w: num(doc, id, "width", bbox.width),
|
|
170
|
+
h: num(doc, id, "height", bbox.height)
|
|
171
|
+
};
|
|
172
|
+
break;
|
|
173
|
+
case "use":
|
|
174
|
+
attrs = {
|
|
175
|
+
kind: "use",
|
|
176
|
+
x: num(doc, id, "x"),
|
|
177
|
+
y: num(doc, id, "y"),
|
|
178
|
+
w: num(doc, id, "width", bbox.width),
|
|
179
|
+
h: num(doc, id, "height", bbox.height)
|
|
180
|
+
};
|
|
181
|
+
break;
|
|
182
|
+
case "circle":
|
|
183
|
+
attrs = {
|
|
184
|
+
kind: "circle",
|
|
185
|
+
cx: num(doc, id, "cx"),
|
|
186
|
+
cy: num(doc, id, "cy"),
|
|
187
|
+
r: num(doc, id, "r")
|
|
188
|
+
};
|
|
189
|
+
break;
|
|
190
|
+
case "ellipse":
|
|
191
|
+
attrs = {
|
|
192
|
+
kind: "ellipse",
|
|
193
|
+
cx: num(doc, id, "cx"),
|
|
194
|
+
cy: num(doc, id, "cy"),
|
|
195
|
+
rx: num(doc, id, "rx"),
|
|
196
|
+
ry: num(doc, id, "ry")
|
|
197
|
+
};
|
|
198
|
+
break;
|
|
199
|
+
case "line":
|
|
200
|
+
attrs = {
|
|
201
|
+
kind: "line",
|
|
202
|
+
x1: num(doc, id, "x1"),
|
|
203
|
+
y1: num(doc, id, "y1"),
|
|
204
|
+
x2: num(doc, id, "x2"),
|
|
205
|
+
y2: num(doc, id, "y2")
|
|
206
|
+
};
|
|
207
|
+
break;
|
|
208
|
+
case "polyline":
|
|
209
|
+
attrs = {
|
|
210
|
+
kind: "polyline",
|
|
211
|
+
points: doc.get_attr(id, "points") ?? ""
|
|
212
|
+
};
|
|
213
|
+
break;
|
|
214
|
+
case "polygon":
|
|
215
|
+
attrs = {
|
|
216
|
+
kind: "polygon",
|
|
217
|
+
points: doc.get_attr(id, "points") ?? ""
|
|
218
|
+
};
|
|
219
|
+
break;
|
|
220
|
+
case "path":
|
|
221
|
+
attrs = {
|
|
222
|
+
kind: "path",
|
|
223
|
+
d: doc.get_attr(id, "d") ?? ""
|
|
224
|
+
};
|
|
225
|
+
break;
|
|
226
|
+
case "text":
|
|
227
|
+
attrs = {
|
|
228
|
+
kind: "text",
|
|
229
|
+
x: num(doc, id, "x"),
|
|
230
|
+
y: num(doc, id, "y"),
|
|
231
|
+
fontSize: parseFloat(doc.get_attr(id, "font-size") ?? "16") || 16
|
|
232
|
+
};
|
|
233
|
+
break;
|
|
234
|
+
default: attrs = { kind: "unsupported" };
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
bbox,
|
|
238
|
+
attrs
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function compute_resize_factors(baseline, dir, dx, dy, shift) {
|
|
242
|
+
const b = baseline.bbox;
|
|
243
|
+
let anchorX = 0;
|
|
244
|
+
let anchorY = 0;
|
|
245
|
+
let baseHX = 0;
|
|
246
|
+
let baseHY = 0;
|
|
247
|
+
let affectsX = true;
|
|
248
|
+
let affectsY = true;
|
|
249
|
+
switch (dir) {
|
|
250
|
+
case "nw":
|
|
251
|
+
anchorX = b.x + b.width;
|
|
252
|
+
anchorY = b.y + b.height;
|
|
253
|
+
baseHX = b.x;
|
|
254
|
+
baseHY = b.y;
|
|
255
|
+
break;
|
|
256
|
+
case "n":
|
|
257
|
+
anchorX = b.x + b.width / 2;
|
|
258
|
+
anchorY = b.y + b.height;
|
|
259
|
+
baseHX = b.x + b.width / 2;
|
|
260
|
+
baseHY = b.y;
|
|
261
|
+
affectsX = false;
|
|
262
|
+
break;
|
|
263
|
+
case "ne":
|
|
264
|
+
anchorX = b.x;
|
|
265
|
+
anchorY = b.y + b.height;
|
|
266
|
+
baseHX = b.x + b.width;
|
|
267
|
+
baseHY = b.y;
|
|
268
|
+
break;
|
|
269
|
+
case "e":
|
|
270
|
+
anchorX = b.x;
|
|
271
|
+
anchorY = b.y + b.height / 2;
|
|
272
|
+
baseHX = b.x + b.width;
|
|
273
|
+
baseHY = b.y + b.height / 2;
|
|
274
|
+
affectsY = false;
|
|
275
|
+
break;
|
|
276
|
+
case "se":
|
|
277
|
+
anchorX = b.x;
|
|
278
|
+
anchorY = b.y;
|
|
279
|
+
baseHX = b.x + b.width;
|
|
280
|
+
baseHY = b.y + b.height;
|
|
281
|
+
break;
|
|
282
|
+
case "s":
|
|
283
|
+
anchorX = b.x + b.width / 2;
|
|
284
|
+
anchorY = b.y;
|
|
285
|
+
baseHX = b.x + b.width / 2;
|
|
286
|
+
baseHY = b.y + b.height;
|
|
287
|
+
affectsX = false;
|
|
288
|
+
break;
|
|
289
|
+
case "sw":
|
|
290
|
+
anchorX = b.x + b.width;
|
|
291
|
+
anchorY = b.y;
|
|
292
|
+
baseHX = b.x;
|
|
293
|
+
baseHY = b.y + b.height;
|
|
294
|
+
break;
|
|
295
|
+
case "w":
|
|
296
|
+
anchorX = b.x + b.width;
|
|
297
|
+
anchorY = b.y + b.height / 2;
|
|
298
|
+
baseHX = b.x;
|
|
299
|
+
baseHY = b.y + b.height / 2;
|
|
300
|
+
affectsY = false;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
const newHX = baseHX + (affectsX ? dx : 0);
|
|
304
|
+
const newHY = baseHY + (affectsY ? dy : 0);
|
|
305
|
+
const denomX = baseHX - anchorX;
|
|
306
|
+
const denomY = baseHY - anchorY;
|
|
307
|
+
let sx = affectsX && denomX !== 0 ? (newHX - anchorX) / denomX : 1;
|
|
308
|
+
let sy = affectsY && denomY !== 0 ? (newHY - anchorY) / denomY : 1;
|
|
309
|
+
if (shift && affectsX && affectsY) {
|
|
310
|
+
const mag = Math.max(Math.abs(sx), Math.abs(sy));
|
|
311
|
+
sx = sx >= 0 ? mag : -mag;
|
|
312
|
+
sy = sy >= 0 ? mag : -mag;
|
|
313
|
+
}
|
|
314
|
+
sx = Math.max(.001, sx);
|
|
315
|
+
sy = Math.max(.001, sy);
|
|
316
|
+
return {
|
|
317
|
+
sx,
|
|
318
|
+
sy,
|
|
319
|
+
origin: {
|
|
320
|
+
x: anchorX,
|
|
321
|
+
y: anchorY
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function scale_points_string(points, origin, sx, sy) {
|
|
326
|
+
const nums = points.split(/[\s,]+/).filter(Boolean).map(Number);
|
|
327
|
+
const out = [];
|
|
328
|
+
for (let i = 0; i + 1 < nums.length; i += 2) {
|
|
329
|
+
const x = origin.x + (nums[i] - origin.x) * sx;
|
|
330
|
+
const y = origin.y + (nums[i + 1] - origin.y) * sy;
|
|
331
|
+
out.push(`${x},${y}`);
|
|
332
|
+
}
|
|
333
|
+
return out.join(" ");
|
|
334
|
+
}
|
|
335
|
+
function scale_path_d(d, origin, sx, sy) {
|
|
336
|
+
try {
|
|
337
|
+
const e = origin.x * (1 - sx);
|
|
338
|
+
const f = origin.y * (1 - sy);
|
|
339
|
+
return new SVGPathData(d).transform(SVGPathDataTransformer.MATRIX(sx, 0, 0, sy, e, f)).encode();
|
|
340
|
+
} catch {
|
|
341
|
+
return d;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function apply_resize(doc, id, baseline, sx, sy, origin) {
|
|
345
|
+
const a = baseline.attrs;
|
|
346
|
+
switch (a.kind) {
|
|
347
|
+
case "rect":
|
|
348
|
+
case "image":
|
|
349
|
+
case "use":
|
|
350
|
+
doc.set_attr(id, "x", String(origin.x + (a.x - origin.x) * sx));
|
|
351
|
+
doc.set_attr(id, "y", String(origin.y + (a.y - origin.y) * sy));
|
|
352
|
+
doc.set_attr(id, "width", String(Math.max(.001, a.w * sx)));
|
|
353
|
+
doc.set_attr(id, "height", String(Math.max(.001, a.h * sy)));
|
|
354
|
+
return;
|
|
355
|
+
case "circle": {
|
|
356
|
+
const s = Math.min(sx, sy);
|
|
357
|
+
doc.set_attr(id, "cx", String(origin.x + (a.cx - origin.x) * s));
|
|
358
|
+
doc.set_attr(id, "cy", String(origin.y + (a.cy - origin.y) * s));
|
|
359
|
+
doc.set_attr(id, "r", String(Math.max(.001, a.r * s)));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
case "ellipse":
|
|
363
|
+
doc.set_attr(id, "cx", String(origin.x + (a.cx - origin.x) * sx));
|
|
364
|
+
doc.set_attr(id, "cy", String(origin.y + (a.cy - origin.y) * sy));
|
|
365
|
+
doc.set_attr(id, "rx", String(Math.max(.001, a.rx * sx)));
|
|
366
|
+
doc.set_attr(id, "ry", String(Math.max(.001, a.ry * sy)));
|
|
367
|
+
return;
|
|
368
|
+
case "line":
|
|
369
|
+
doc.set_attr(id, "x1", String(origin.x + (a.x1 - origin.x) * sx));
|
|
370
|
+
doc.set_attr(id, "y1", String(origin.y + (a.y1 - origin.y) * sy));
|
|
371
|
+
doc.set_attr(id, "x2", String(origin.x + (a.x2 - origin.x) * sx));
|
|
372
|
+
doc.set_attr(id, "y2", String(origin.y + (a.y2 - origin.y) * sy));
|
|
373
|
+
return;
|
|
374
|
+
case "polyline":
|
|
375
|
+
case "polygon":
|
|
376
|
+
doc.set_attr(id, "points", scale_points_string(a.points, origin, sx, sy));
|
|
377
|
+
return;
|
|
378
|
+
case "path":
|
|
379
|
+
doc.set_attr(id, "d", scale_path_d(a.d, origin, sx, sy));
|
|
380
|
+
return;
|
|
381
|
+
case "text": {
|
|
382
|
+
if (!(sx !== 1 && sy !== 1)) return;
|
|
383
|
+
const s = Math.min(sx, sy);
|
|
384
|
+
doc.set_attr(id, "x", String(origin.x + (a.x - origin.x) * s));
|
|
385
|
+
doc.set_attr(id, "y", String(origin.y + (a.y - origin.y) * s));
|
|
386
|
+
doc.set_attr(id, "font-size", String(Math.max(1, a.fontSize * s)));
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
case "unsupported": return;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/core/paint.ts
|
|
394
|
+
/**
|
|
395
|
+
* Parse a *computed* paint string into the discriminated union. Returns null
|
|
396
|
+
* for `inherit` / `var()` / empty. Returns an invalid-computed-value record
|
|
397
|
+
* for syntactic errors (rare; we're permissive).
|
|
398
|
+
*/
|
|
399
|
+
function parse_paint(declared) {
|
|
400
|
+
if (declared === null || declared === "") return null;
|
|
401
|
+
const trimmed = declared.trim();
|
|
402
|
+
if (trimmed === "") return null;
|
|
403
|
+
if (trimmed === "inherit" || trimmed === "initial" || trimmed === "unset" || trimmed === "revert" || trimmed === "revert-layer") return null;
|
|
404
|
+
if (/^var\s*\(/i.test(trimmed)) return {
|
|
405
|
+
error: "invalid_at_computed_value_time",
|
|
406
|
+
reason: "var() substitution requires a cascade engine (not implemented)"
|
|
407
|
+
};
|
|
408
|
+
if (trimmed === "none") return { kind: "none" };
|
|
409
|
+
if (trimmed === "context-fill" || trimmed === "contextFill") return { kind: "context_fill" };
|
|
410
|
+
if (trimmed === "context-stroke" || trimmed === "contextStroke") return { kind: "context_stroke" };
|
|
411
|
+
const url_match = trimmed.match(/^url\(\s*(["']?)#([^)"']+)\1\s*\)\s*(.*)$/i);
|
|
412
|
+
if (url_match) {
|
|
413
|
+
const id = url_match[2];
|
|
414
|
+
const rest = url_match[3].trim();
|
|
415
|
+
let fallback;
|
|
416
|
+
if (rest !== "") {
|
|
417
|
+
const f = parse_paint(rest);
|
|
418
|
+
if (f && f.kind === "none") fallback = { kind: "none" };
|
|
419
|
+
else if (f && f.kind === "color") fallback = {
|
|
420
|
+
kind: "color",
|
|
421
|
+
value: f.value
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
return fallback ? {
|
|
425
|
+
kind: "ref",
|
|
426
|
+
id,
|
|
427
|
+
fallback
|
|
428
|
+
} : {
|
|
429
|
+
kind: "ref",
|
|
430
|
+
id
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (/^currentcolor$/i.test(trimmed)) return {
|
|
434
|
+
kind: "color",
|
|
435
|
+
value: { kind: "current_color" }
|
|
436
|
+
};
|
|
437
|
+
return {
|
|
438
|
+
kind: "color",
|
|
439
|
+
value: {
|
|
440
|
+
kind: "rgb",
|
|
441
|
+
value: trimmed
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
/** Serialize a Paint back to an SVG attribute / inline-style value. */
|
|
446
|
+
function serialize_paint(paint) {
|
|
447
|
+
switch (paint.kind) {
|
|
448
|
+
case "none": return "none";
|
|
449
|
+
case "context_fill": return "context-fill";
|
|
450
|
+
case "context_stroke": return "context-stroke";
|
|
451
|
+
case "color": return paint.value.kind === "current_color" ? "currentColor" : paint.value.value;
|
|
452
|
+
case "ref":
|
|
453
|
+
if (paint.fallback) {
|
|
454
|
+
const f = paint.fallback.kind === "none" ? "none" : paint.fallback.value.kind === "current_color" ? "currentColor" : paint.fallback.value.value;
|
|
455
|
+
return `url(#${paint.id}) ${f}`;
|
|
456
|
+
}
|
|
457
|
+
return `url(#${paint.id})`;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
//#endregion
|
|
461
|
+
export { capture_resize_baseline as a, is_resizable as c, apply_translate as i, serialize_paint as n, capture_translate_baseline as o, apply_resize as r, compute_resize_factors as s, parse_paint as t };
|
package/dist/react.d.mts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { d as EditorState, f as EditorStyle, k as Providers, o as SvgEditor, t as Commands } from "./editor-JY7AQrR1.mjs";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/react.d.ts
|
|
6
|
+
type SvgEditorProviderProps = {
|
|
7
|
+
svg: string;
|
|
8
|
+
providers?: Providers;
|
|
9
|
+
style?: Partial<EditorStyle>;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Owns the headless editor and exposes it via context. The editor is created
|
|
14
|
+
* once on first render; subsequent prop changes to `svg` call `editor.load()`.
|
|
15
|
+
*/
|
|
16
|
+
declare function SvgEditorProvider({
|
|
17
|
+
svg,
|
|
18
|
+
providers,
|
|
19
|
+
style,
|
|
20
|
+
children
|
|
21
|
+
}: SvgEditorProviderProps): _$react_jsx_runtime0.JSX.Element;
|
|
22
|
+
type SvgEditorCanvasProps = {
|
|
23
|
+
className?: string;
|
|
24
|
+
style?: React.CSSProperties;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Renders the editor's SVG into a `div` and wires it to the DOM surface.
|
|
28
|
+
*
|
|
29
|
+
* Internally calls `attach_dom_surface(editor, { container })` on mount and
|
|
30
|
+
* `handle.detach()` on unmount. This is the only UI component the package
|
|
31
|
+
* ships; everything else (toolbar, property panel, etc.) is consumer-built.
|
|
32
|
+
*/
|
|
33
|
+
declare function SvgEditorCanvas({
|
|
34
|
+
className,
|
|
35
|
+
style
|
|
36
|
+
}: SvgEditorCanvasProps): _$react_jsx_runtime0.JSX.Element;
|
|
37
|
+
declare function useSvgEditor(): SvgEditor;
|
|
38
|
+
/**
|
|
39
|
+
* Subscribe to a slice of `editor.state`. Re-renders when the selected slice
|
|
40
|
+
* changes by reference (or by the supplied `equals` function).
|
|
41
|
+
*/
|
|
42
|
+
declare function useEditorState<T>(selector: (state: EditorState) => T, equals?: (a: T, b: T) => boolean): T;
|
|
43
|
+
/**
|
|
44
|
+
* Sugar for `useSvgEditor().commands`. The returned object is stable across
|
|
45
|
+
* re-renders (commands themselves don't change identity).
|
|
46
|
+
*/
|
|
47
|
+
declare function useCommands(): Commands;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { SvgEditorCanvas, SvgEditorCanvasProps, SvgEditorProvider, SvgEditorProviderProps, useCommands, useEditorState, useSvgEditor };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { d as EditorState, f as EditorStyle, k as Providers, o as SvgEditor, t as Commands } from "./editor-CTtU2gu4.js";
|
|
2
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
//#region src/react.d.ts
|
|
6
|
+
type SvgEditorProviderProps = {
|
|
7
|
+
svg: string;
|
|
8
|
+
providers?: Providers;
|
|
9
|
+
style?: Partial<EditorStyle>;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Owns the headless editor and exposes it via context. The editor is created
|
|
14
|
+
* once on first render; subsequent prop changes to `svg` call `editor.load()`.
|
|
15
|
+
*/
|
|
16
|
+
declare function SvgEditorProvider({
|
|
17
|
+
svg,
|
|
18
|
+
providers,
|
|
19
|
+
style,
|
|
20
|
+
children
|
|
21
|
+
}: SvgEditorProviderProps): _$react_jsx_runtime0.JSX.Element;
|
|
22
|
+
type SvgEditorCanvasProps = {
|
|
23
|
+
className?: string;
|
|
24
|
+
style?: React.CSSProperties;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Renders the editor's SVG into a `div` and wires it to the DOM surface.
|
|
28
|
+
*
|
|
29
|
+
* Internally calls `attach_dom_surface(editor, { container })` on mount and
|
|
30
|
+
* `handle.detach()` on unmount. This is the only UI component the package
|
|
31
|
+
* ships; everything else (toolbar, property panel, etc.) is consumer-built.
|
|
32
|
+
*/
|
|
33
|
+
declare function SvgEditorCanvas({
|
|
34
|
+
className,
|
|
35
|
+
style
|
|
36
|
+
}: SvgEditorCanvasProps): _$react_jsx_runtime0.JSX.Element;
|
|
37
|
+
declare function useSvgEditor(): SvgEditor;
|
|
38
|
+
/**
|
|
39
|
+
* Subscribe to a slice of `editor.state`. Re-renders when the selected slice
|
|
40
|
+
* changes by reference (or by the supplied `equals` function).
|
|
41
|
+
*/
|
|
42
|
+
declare function useEditorState<T>(selector: (state: EditorState) => T, equals?: (a: T, b: T) => boolean): T;
|
|
43
|
+
/**
|
|
44
|
+
* Sugar for `useSvgEditor().commands`. The returned object is stable across
|
|
45
|
+
* re-renders (commands themselves don't change identity).
|
|
46
|
+
*/
|
|
47
|
+
declare function useCommands(): Commands;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { SvgEditorCanvas, SvgEditorCanvasProps, SvgEditorProvider, SvgEditorProviderProps, useCommands, useEditorState, useSvgEditor };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const require_dom = require("./dom-CfP_ZURh.js");
|
|
4
|
+
const require_editor = require("./editor-DQWUWrVZ.js");
|
|
5
|
+
let react = require("react");
|
|
6
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
7
|
+
//#region src/react.tsx
|
|
8
|
+
const SvgEditorContext = (0, react.createContext)(null);
|
|
9
|
+
/**
|
|
10
|
+
* Owns the headless editor and exposes it via context. The editor is created
|
|
11
|
+
* once on first render; subsequent prop changes to `svg` call `editor.load()`.
|
|
12
|
+
*/
|
|
13
|
+
function SvgEditorProvider({ svg, providers, style, children }) {
|
|
14
|
+
const editor_ref = (0, react.useRef)(null);
|
|
15
|
+
if (editor_ref.current === null) editor_ref.current = require_editor.createSvgEditor({
|
|
16
|
+
svg,
|
|
17
|
+
providers,
|
|
18
|
+
style
|
|
19
|
+
});
|
|
20
|
+
const editor = editor_ref.current;
|
|
21
|
+
const last_svg = (0, react.useRef)(svg);
|
|
22
|
+
(0, react.useEffect)(() => {
|
|
23
|
+
if (last_svg.current !== svg) {
|
|
24
|
+
editor.load(svg);
|
|
25
|
+
last_svg.current = svg;
|
|
26
|
+
}
|
|
27
|
+
}, [svg, editor]);
|
|
28
|
+
(0, react.useEffect)(() => {
|
|
29
|
+
return () => {
|
|
30
|
+
editor.dispose();
|
|
31
|
+
};
|
|
32
|
+
}, [editor]);
|
|
33
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEditorContext.Provider, {
|
|
34
|
+
value: editor,
|
|
35
|
+
children
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Renders the editor's SVG into a `div` and wires it to the DOM surface.
|
|
40
|
+
*
|
|
41
|
+
* Internally calls `attach_dom_surface(editor, { container })` on mount and
|
|
42
|
+
* `handle.detach()` on unmount. This is the only UI component the package
|
|
43
|
+
* ships; everything else (toolbar, property panel, etc.) is consumer-built.
|
|
44
|
+
*/
|
|
45
|
+
function SvgEditorCanvas({ className, style }) {
|
|
46
|
+
const editor = useSvgEditor();
|
|
47
|
+
const ref = (0, react.useRef)(null);
|
|
48
|
+
(0, react.useEffect)(() => {
|
|
49
|
+
const container = ref.current;
|
|
50
|
+
if (!container) return;
|
|
51
|
+
const handle = require_dom.attach_dom_surface(editor, { container });
|
|
52
|
+
return () => handle.detach();
|
|
53
|
+
}, [editor]);
|
|
54
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
55
|
+
ref,
|
|
56
|
+
className,
|
|
57
|
+
style
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function useSvgEditor() {
|
|
61
|
+
const editor = (0, react.useContext)(SvgEditorContext);
|
|
62
|
+
if (editor === null) throw new Error("useSvgEditor must be used inside a <SvgEditorProvider>.");
|
|
63
|
+
return editor;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Subscribe to a slice of `editor.state`. Re-renders when the selected slice
|
|
67
|
+
* changes by reference (or by the supplied `equals` function).
|
|
68
|
+
*/
|
|
69
|
+
function useEditorState(selector, equals = Object.is) {
|
|
70
|
+
const editor = useSvgEditor();
|
|
71
|
+
const last_ref = (0, react.useRef)({
|
|
72
|
+
has: false,
|
|
73
|
+
value: void 0
|
|
74
|
+
});
|
|
75
|
+
return (0, react.useSyncExternalStore)((cb) => editor.subscribe(cb), () => {
|
|
76
|
+
const next = selector(editor.state);
|
|
77
|
+
if (!last_ref.current.has || !equals(last_ref.current.value, next)) last_ref.current = {
|
|
78
|
+
has: true,
|
|
79
|
+
value: next
|
|
80
|
+
};
|
|
81
|
+
return last_ref.current.value;
|
|
82
|
+
}, () => selector(editor.state));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Sugar for `useSvgEditor().commands`. The returned object is stable across
|
|
86
|
+
* re-renders (commands themselves don't change identity).
|
|
87
|
+
*/
|
|
88
|
+
function useCommands() {
|
|
89
|
+
const editor = useSvgEditor();
|
|
90
|
+
return (0, react.useMemo)(() => editor.commands, [editor]);
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
exports.SvgEditorCanvas = SvgEditorCanvas;
|
|
94
|
+
exports.SvgEditorProvider = SvgEditorProvider;
|
|
95
|
+
exports.useCommands = useCommands;
|
|
96
|
+
exports.useEditorState = useEditorState;
|
|
97
|
+
exports.useSvgEditor = useSvgEditor;
|