@pixerate/editor 0.29.0 → 0.31.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/dist/animation/index.cjs +6 -2
- package/dist/animation/index.cjs.map +1 -1
- package/dist/animation/index.d.cts +2 -2
- package/dist/animation/index.d.ts +2 -2
- package/dist/animation/index.js +17 -13
- package/dist/canvas/index.cjs +618 -33
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +193 -9
- package/dist/canvas/index.d.ts +193 -9
- package/dist/canvas/index.js +603 -18
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-UB2OY7FD.js +262 -0
- package/dist/chunk-UB2OY7FD.js.map +1 -0
- package/dist/{chunk-PWJARCI4.js → chunk-W7BOBWZD.js} +5 -196
- package/dist/chunk-W7BOBWZD.js.map +1 -0
- package/dist/{chunk-TTYCN6ZH.cjs → chunk-XHRZSB77.cjs} +8 -199
- package/dist/chunk-XHRZSB77.cjs.map +1 -0
- package/dist/chunk-XZ4L3R4K.cjs +262 -0
- package/dist/chunk-XZ4L3R4K.cjs.map +1 -0
- package/dist/{easing-B6iAnJHk.d.cts → easing-DTmX99QG.d.cts} +20 -6
- package/dist/{easing-B6iAnJHk.d.ts → easing-DTmX99QG.d.ts} +20 -6
- package/dist/parameters/index.cjs +38 -1
- package/dist/parameters/index.cjs.map +1 -1
- package/dist/parameters/index.d.cts +32 -2
- package/dist/parameters/index.d.ts +32 -2
- package/dist/parameters/index.js +37 -0
- package/dist/parameters/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-PWJARCI4.js.map +0 -1
- package/dist/chunk-TTYCN6ZH.cjs.map +0 -1
package/dist/canvas/index.js
CHANGED
|
@@ -25,35 +25,38 @@ import {
|
|
|
25
25
|
resolveToken
|
|
26
26
|
} from "../chunk-D6AVOTSD.js";
|
|
27
27
|
import {
|
|
28
|
-
ANIMATION_EASING_LABELS,
|
|
29
|
-
ANIMATION_EASING_PRESETS,
|
|
30
28
|
ANIMATION_TIME_EPSILON,
|
|
31
29
|
AnimationPlayer,
|
|
32
|
-
CUBIC_BEZIER_PRESETS,
|
|
33
30
|
ManualClock,
|
|
34
31
|
createAnimationPlayer,
|
|
35
|
-
cubicBezierAt,
|
|
36
|
-
easingFromInfluence,
|
|
37
|
-
easingLabel,
|
|
38
|
-
easingToCss,
|
|
39
|
-
evaluateEasing,
|
|
40
32
|
evaluateTrack,
|
|
41
33
|
formatOklab,
|
|
42
34
|
interpolateAnimationValue,
|
|
43
|
-
isAnimationEasing,
|
|
44
35
|
isAnimationKeyframe,
|
|
45
36
|
mixColorsOklab,
|
|
46
37
|
normalizeKeyframes,
|
|
47
38
|
oklabToRgb,
|
|
48
39
|
pageHidden,
|
|
49
|
-
parseCssEasing,
|
|
50
40
|
parseOklab,
|
|
51
41
|
realClock,
|
|
52
|
-
resolveEasing,
|
|
53
42
|
rgbToOklab,
|
|
54
|
-
springAt,
|
|
55
43
|
wrapAnimationTime
|
|
56
|
-
} from "../chunk-
|
|
44
|
+
} from "../chunk-W7BOBWZD.js";
|
|
45
|
+
import {
|
|
46
|
+
ANIMATION_EASING_LABELS,
|
|
47
|
+
ANIMATION_EASING_PRESETS,
|
|
48
|
+
CUBIC_BEZIER_PRESETS,
|
|
49
|
+
cubicBezierAt,
|
|
50
|
+
easingFromInfluence,
|
|
51
|
+
easingLabel,
|
|
52
|
+
easingToCss,
|
|
53
|
+
elasticAt,
|
|
54
|
+
evaluateEasing,
|
|
55
|
+
isAnimationEasing,
|
|
56
|
+
parseCssEasing,
|
|
57
|
+
resolveEasing,
|
|
58
|
+
springAt
|
|
59
|
+
} from "../chunk-UB2OY7FD.js";
|
|
57
60
|
import {
|
|
58
61
|
normalizeTokenColor
|
|
59
62
|
} from "../chunk-TGD52FEZ.js";
|
|
@@ -2449,6 +2452,233 @@ function fitFreehandPath(points, options = {}) {
|
|
|
2449
2452
|
return fitPathData(points, { tolerance: 1, ...options, closed: false });
|
|
2450
2453
|
}
|
|
2451
2454
|
|
|
2455
|
+
// src/canvas/gradient.ts
|
|
2456
|
+
var CANVAS_GRADIENT_PRESETS = {
|
|
2457
|
+
sunset: {
|
|
2458
|
+
type: "linear",
|
|
2459
|
+
angle: 90,
|
|
2460
|
+
stops: [
|
|
2461
|
+
{ offset: 0, color: "#f97316" },
|
|
2462
|
+
{ offset: 1, color: "#ec4899" }
|
|
2463
|
+
]
|
|
2464
|
+
},
|
|
2465
|
+
ocean: {
|
|
2466
|
+
type: "linear",
|
|
2467
|
+
angle: 90,
|
|
2468
|
+
stops: [
|
|
2469
|
+
{ offset: 0, color: "#06b6d4" },
|
|
2470
|
+
{ offset: 1, color: "#3b82f6" }
|
|
2471
|
+
]
|
|
2472
|
+
},
|
|
2473
|
+
aurora: {
|
|
2474
|
+
type: "linear",
|
|
2475
|
+
angle: 90,
|
|
2476
|
+
stops: [
|
|
2477
|
+
{ offset: 0, color: "#10b981" },
|
|
2478
|
+
{ offset: 1, color: "#6366f1" }
|
|
2479
|
+
]
|
|
2480
|
+
},
|
|
2481
|
+
neon: {
|
|
2482
|
+
type: "linear",
|
|
2483
|
+
angle: 90,
|
|
2484
|
+
stops: [
|
|
2485
|
+
{ offset: 0, color: "#a855f7" },
|
|
2486
|
+
{ offset: 1, color: "#ec4899" }
|
|
2487
|
+
]
|
|
2488
|
+
},
|
|
2489
|
+
amber: {
|
|
2490
|
+
type: "linear",
|
|
2491
|
+
angle: 90,
|
|
2492
|
+
stops: [
|
|
2493
|
+
{ offset: 0, color: "#f59e0b" },
|
|
2494
|
+
{ offset: 1, color: "#ef4444" }
|
|
2495
|
+
]
|
|
2496
|
+
},
|
|
2497
|
+
cosmic: {
|
|
2498
|
+
type: "linear",
|
|
2499
|
+
angle: 90,
|
|
2500
|
+
stops: [
|
|
2501
|
+
{ offset: 0, color: "#3b82f6" },
|
|
2502
|
+
{ offset: 0.5, color: "#8b5cf6" },
|
|
2503
|
+
{ offset: 1, color: "#ec4899" }
|
|
2504
|
+
]
|
|
2505
|
+
}
|
|
2506
|
+
};
|
|
2507
|
+
function isCanvasGradient(value) {
|
|
2508
|
+
if (!value || typeof value !== "object") return false;
|
|
2509
|
+
const val = value;
|
|
2510
|
+
if (val.type !== "linear" && val.type !== "radial") return false;
|
|
2511
|
+
if (!Array.isArray(val.stops)) return false;
|
|
2512
|
+
return true;
|
|
2513
|
+
}
|
|
2514
|
+
function splitCssArgs(str) {
|
|
2515
|
+
const parts = [];
|
|
2516
|
+
let depth = 0;
|
|
2517
|
+
let current = "";
|
|
2518
|
+
for (let i = 0; i < str.length; i++) {
|
|
2519
|
+
const char = str[i];
|
|
2520
|
+
if (char === "(") depth++;
|
|
2521
|
+
else if (char === ")") depth = Math.max(0, depth - 1);
|
|
2522
|
+
if (char === "," && depth === 0) {
|
|
2523
|
+
if (current.trim()) parts.push(current.trim());
|
|
2524
|
+
current = "";
|
|
2525
|
+
} else {
|
|
2526
|
+
current += char;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
if (current.trim()) parts.push(current.trim());
|
|
2530
|
+
return parts;
|
|
2531
|
+
}
|
|
2532
|
+
function parseCssAngle(val) {
|
|
2533
|
+
const trimmed = val.trim().toLowerCase();
|
|
2534
|
+
if (trimmed.endsWith("deg")) {
|
|
2535
|
+
return parseFloat(trimmed);
|
|
2536
|
+
}
|
|
2537
|
+
if (trimmed.endsWith("turn")) {
|
|
2538
|
+
return parseFloat(trimmed) * 360;
|
|
2539
|
+
}
|
|
2540
|
+
if (trimmed.endsWith("rad")) {
|
|
2541
|
+
return parseFloat(trimmed) * 180 / Math.PI;
|
|
2542
|
+
}
|
|
2543
|
+
if (trimmed.startsWith("to ")) {
|
|
2544
|
+
const dir = trimmed.slice(3).trim();
|
|
2545
|
+
if (dir === "bottom") return 180;
|
|
2546
|
+
if (dir === "top") return 0;
|
|
2547
|
+
if (dir === "right") return 90;
|
|
2548
|
+
if (dir === "left") return 270;
|
|
2549
|
+
if (dir === "bottom right" || dir === "right bottom") return 135;
|
|
2550
|
+
if (dir === "bottom left" || dir === "left bottom") return 225;
|
|
2551
|
+
if (dir === "top right" || dir === "right top") return 45;
|
|
2552
|
+
if (dir === "top left" || dir === "left top") return 315;
|
|
2553
|
+
}
|
|
2554
|
+
const n = parseFloat(trimmed);
|
|
2555
|
+
return Number.isFinite(n) ? n : 180;
|
|
2556
|
+
}
|
|
2557
|
+
function parseCssGradient(css) {
|
|
2558
|
+
const trimmed = css.trim();
|
|
2559
|
+
const linearMatch = trimmed.match(/^linear-gradient\s*\((.*)\)$/is);
|
|
2560
|
+
if (linearMatch) {
|
|
2561
|
+
const rawArgs = splitCssArgs(linearMatch[1]);
|
|
2562
|
+
if (rawArgs.length === 0) return null;
|
|
2563
|
+
let angle = 180;
|
|
2564
|
+
let stopStartIndex = 0;
|
|
2565
|
+
const first = rawArgs[0].trim().toLowerCase();
|
|
2566
|
+
if (first.endsWith("deg") || first.endsWith("turn") || first.endsWith("rad") || first.startsWith("to ")) {
|
|
2567
|
+
angle = parseCssAngle(first);
|
|
2568
|
+
stopStartIndex = 1;
|
|
2569
|
+
}
|
|
2570
|
+
const stopStrings = rawArgs.slice(stopStartIndex);
|
|
2571
|
+
if (stopStrings.length === 0) return null;
|
|
2572
|
+
const stops = parseStopStrings(stopStrings);
|
|
2573
|
+
if (stops.length === 0) return null;
|
|
2574
|
+
return {
|
|
2575
|
+
type: "linear",
|
|
2576
|
+
angle,
|
|
2577
|
+
stops
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
const radialMatch = trimmed.match(/^radial-gradient\s*\((.*)\)$/is);
|
|
2581
|
+
if (radialMatch) {
|
|
2582
|
+
const rawArgs = splitCssArgs(radialMatch[1]);
|
|
2583
|
+
if (rawArgs.length === 0) return null;
|
|
2584
|
+
let stopStartIndex = 0;
|
|
2585
|
+
const first = rawArgs[0].trim().toLowerCase();
|
|
2586
|
+
if (first.startsWith("circle") || first.startsWith("ellipse") || first.includes("at ")) {
|
|
2587
|
+
stopStartIndex = 1;
|
|
2588
|
+
}
|
|
2589
|
+
const stopStrings = rawArgs.slice(stopStartIndex);
|
|
2590
|
+
if (stopStrings.length === 0) return null;
|
|
2591
|
+
const stops = parseStopStrings(stopStrings);
|
|
2592
|
+
if (stops.length === 0) return null;
|
|
2593
|
+
return {
|
|
2594
|
+
type: "radial",
|
|
2595
|
+
cx: 0.5,
|
|
2596
|
+
cy: 0.5,
|
|
2597
|
+
r: 0.5,
|
|
2598
|
+
stops
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
return null;
|
|
2602
|
+
}
|
|
2603
|
+
function parseStopStrings(stopStrings) {
|
|
2604
|
+
const count = stopStrings.length;
|
|
2605
|
+
return stopStrings.map((str, idx) => {
|
|
2606
|
+
const parts = str.trim().split(/\s+(?=[0-9%])/);
|
|
2607
|
+
const color = parts[0];
|
|
2608
|
+
let offset = count === 1 ? 0 : idx / (count - 1);
|
|
2609
|
+
if (parts.length > 1) {
|
|
2610
|
+
const posStr = parts[1].trim();
|
|
2611
|
+
if (posStr.endsWith("%")) {
|
|
2612
|
+
offset = parseFloat(posStr) / 100;
|
|
2613
|
+
} else {
|
|
2614
|
+
const val = parseFloat(posStr);
|
|
2615
|
+
if (Number.isFinite(val)) offset = val;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
return {
|
|
2619
|
+
offset: Math.min(1, Math.max(0, offset)),
|
|
2620
|
+
color
|
|
2621
|
+
};
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
function resolveCanvasGradient(value) {
|
|
2625
|
+
if (!value) return null;
|
|
2626
|
+
if (isCanvasGradient(value)) return value;
|
|
2627
|
+
if (typeof value === "string") {
|
|
2628
|
+
const lower = value.trim().toLowerCase();
|
|
2629
|
+
if (CANVAS_GRADIENT_PRESETS[lower]) {
|
|
2630
|
+
return CANVAS_GRADIENT_PRESETS[lower];
|
|
2631
|
+
}
|
|
2632
|
+
if (lower.startsWith("linear-gradient") || lower.startsWith("radial-gradient")) {
|
|
2633
|
+
return parseCssGradient(value);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
return null;
|
|
2637
|
+
}
|
|
2638
|
+
function angleToSvgLinearCoords(angleDeg = 180) {
|
|
2639
|
+
const rad = (90 - angleDeg) * Math.PI / 180;
|
|
2640
|
+
const dx = Math.cos(rad);
|
|
2641
|
+
const dy = -Math.sin(rad);
|
|
2642
|
+
const half = 0.5;
|
|
2643
|
+
return {
|
|
2644
|
+
x1: Math.round((half - dx * half) * 1e3) / 1e3,
|
|
2645
|
+
y1: Math.round((half - dy * half) * 1e3) / 1e3,
|
|
2646
|
+
x2: Math.round((half + dx * half) * 1e3) / 1e3,
|
|
2647
|
+
y2: Math.round((half + dy * half) * 1e3) / 1e3
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
function gradientToSvgDef(gradient, id) {
|
|
2651
|
+
const stops = (gradient.stops ?? []).map((s) => ({
|
|
2652
|
+
offset: `${Math.round(s.offset * 1e3) / 10}%`,
|
|
2653
|
+
stopColor: s.color,
|
|
2654
|
+
stopOpacity: s.opacity
|
|
2655
|
+
}));
|
|
2656
|
+
if (gradient.type === "radial") {
|
|
2657
|
+
return {
|
|
2658
|
+
id,
|
|
2659
|
+
type: "radial",
|
|
2660
|
+
attrs: {
|
|
2661
|
+
cx: gradient.cx !== void 0 ? `${gradient.cx * 100}%` : "50%",
|
|
2662
|
+
cy: gradient.cy !== void 0 ? `${gradient.cy * 100}%` : "50%",
|
|
2663
|
+
r: gradient.r !== void 0 ? `${gradient.r * 100}%` : "50%"
|
|
2664
|
+
},
|
|
2665
|
+
stops
|
|
2666
|
+
};
|
|
2667
|
+
}
|
|
2668
|
+
const coords = angleToSvgLinearCoords(gradient.angle ?? 180);
|
|
2669
|
+
return {
|
|
2670
|
+
id,
|
|
2671
|
+
type: "linear",
|
|
2672
|
+
attrs: {
|
|
2673
|
+
x1: `${coords.x1 * 100}%`,
|
|
2674
|
+
y1: `${coords.y1 * 100}%`,
|
|
2675
|
+
x2: `${coords.x2 * 100}%`,
|
|
2676
|
+
y2: `${coords.y2 * 100}%`
|
|
2677
|
+
},
|
|
2678
|
+
stops
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2452
2682
|
// src/canvas/shapeGeometry.ts
|
|
2453
2683
|
var SHAPE_KINDS = ["rect", "ellipse", "polygon", "star", "path"];
|
|
2454
2684
|
function isShapeDescription(value) {
|
|
@@ -2858,23 +3088,54 @@ function getShapeSvg(shape, size) {
|
|
|
2858
3088
|
}
|
|
2859
3089
|
return transforms.length > 0 ? { d, transform: transforms.join(" "), fillRule } : { d, fillRule };
|
|
2860
3090
|
}
|
|
2861
|
-
function getShapeRenderData(shape, size = {}) {
|
|
3091
|
+
function getShapeRenderData(shape, size = {}, options = {}) {
|
|
2862
3092
|
const vb = shape.kind === "path" ? validViewBox(shape.viewBox) : null;
|
|
2863
3093
|
const width = size.width && size.width > 0 ? size.width : vb ? vb[2] : 100;
|
|
2864
3094
|
const height = size.height && size.height > 0 ? size.height : vb ? vb[3] : 100;
|
|
2865
3095
|
const svg = getShapeSvg(shape, { width, height });
|
|
3096
|
+
const baseId = options.id || Math.random().toString(36).slice(2, 9);
|
|
3097
|
+
let fillStr = "currentColor";
|
|
3098
|
+
let fillGradientDef;
|
|
3099
|
+
const rawFill = shape.fillGradient ?? shape.fill;
|
|
3100
|
+
const resolvedFillGradient = resolveCanvasGradient(rawFill);
|
|
3101
|
+
if (resolvedFillGradient) {
|
|
3102
|
+
const gradId = `shape-fill-${baseId}`;
|
|
3103
|
+
fillGradientDef = gradientToSvgDef(resolvedFillGradient, gradId);
|
|
3104
|
+
fillStr = `url(#${gradId})`;
|
|
3105
|
+
} else if (typeof shape.fill === "string") {
|
|
3106
|
+
fillStr = shape.fill;
|
|
3107
|
+
} else if (shape.fill === void 0) {
|
|
3108
|
+
fillStr = "currentColor";
|
|
3109
|
+
}
|
|
3110
|
+
let strokeStr = "none";
|
|
3111
|
+
let strokeGradientDef;
|
|
3112
|
+
const rawStroke = shape.strokeGradient ?? shape.stroke;
|
|
3113
|
+
const resolvedStrokeGradient = resolveCanvasGradient(rawStroke);
|
|
3114
|
+
if (resolvedStrokeGradient) {
|
|
3115
|
+
const gradId = `shape-stroke-${baseId}`;
|
|
3116
|
+
strokeGradientDef = gradientToSvgDef(resolvedStrokeGradient, gradId);
|
|
3117
|
+
strokeStr = `url(#${gradId})`;
|
|
3118
|
+
} else if (typeof shape.stroke === "string") {
|
|
3119
|
+
strokeStr = shape.stroke;
|
|
3120
|
+
} else if (shape.stroke === void 0) {
|
|
3121
|
+
strokeStr = "none";
|
|
3122
|
+
}
|
|
2866
3123
|
const data = {
|
|
2867
3124
|
viewBox: `0 0 ${formatNumber2(width, 3)} ${formatNumber2(height, 3)}`,
|
|
2868
3125
|
width,
|
|
2869
3126
|
height,
|
|
2870
3127
|
d: svg.d,
|
|
2871
3128
|
fillRule: svg.fillRule,
|
|
2872
|
-
fill:
|
|
2873
|
-
stroke:
|
|
3129
|
+
fill: fillStr,
|
|
3130
|
+
stroke: strokeStr,
|
|
2874
3131
|
strokeWidth: shape.strokeWidth ?? 1
|
|
2875
3132
|
};
|
|
2876
3133
|
if (svg.transform) data.transform = svg.transform;
|
|
2877
3134
|
if (shape.opacity !== void 0) data.opacity = shape.opacity;
|
|
3135
|
+
if (shape.strokeLinecap) data.strokeLinecap = shape.strokeLinecap;
|
|
3136
|
+
if (shape.strokeLinejoin) data.strokeLinejoin = shape.strokeLinejoin;
|
|
3137
|
+
if (fillGradientDef) data.fillGradient = fillGradientDef;
|
|
3138
|
+
if (strokeGradientDef) data.strokeGradient = strokeGradientDef;
|
|
2878
3139
|
return data;
|
|
2879
3140
|
}
|
|
2880
3141
|
function clampRatio(value) {
|
|
@@ -5310,12 +5571,18 @@ function revertShapeCombination(nodes, change, previous) {
|
|
|
5310
5571
|
}
|
|
5311
5572
|
var INVISIBLE_PAINT = /* @__PURE__ */ new Set(["none", "transparent"]);
|
|
5312
5573
|
function hasVisibleStroke(shape) {
|
|
5313
|
-
if (!shape
|
|
5574
|
+
if (!shape) return false;
|
|
5575
|
+
const stroke = shape.strokeGradient ?? shape.stroke;
|
|
5576
|
+
if (stroke === void 0 || stroke === null) return false;
|
|
5577
|
+
if (typeof stroke === "string" && INVISIBLE_PAINT.has(stroke.trim().toLowerCase())) return false;
|
|
5314
5578
|
const width = shape.strokeWidth ?? 1;
|
|
5315
5579
|
return Number.isFinite(width) && width > 0;
|
|
5316
5580
|
}
|
|
5317
5581
|
function hasVisibleFill(shape) {
|
|
5318
|
-
|
|
5582
|
+
if (shape.fillGradient) return true;
|
|
5583
|
+
if (shape.fill === void 0) return true;
|
|
5584
|
+
if (typeof shape.fill === "object" && shape.fill !== null) return true;
|
|
5585
|
+
return !INVISIBLE_PAINT.has(String(shape.fill).trim().toLowerCase());
|
|
5319
5586
|
}
|
|
5320
5587
|
function shapeTargets(nodes, ids) {
|
|
5321
5588
|
const idSet = new Set(ids);
|
|
@@ -11397,6 +11664,310 @@ function getCanvasComponentDragId(dataTransfer) {
|
|
|
11397
11664
|
const id = dataTransfer?.getData(CANVAS_COMPONENT_DRAG_TYPE);
|
|
11398
11665
|
return id ? id : void 0;
|
|
11399
11666
|
}
|
|
11667
|
+
|
|
11668
|
+
// src/canvas/freehand.ts
|
|
11669
|
+
function createDefaultStrokeStyle(tool) {
|
|
11670
|
+
if (tool === "highlighter") {
|
|
11671
|
+
return {
|
|
11672
|
+
color: "#facc15",
|
|
11673
|
+
width: 24,
|
|
11674
|
+
opacity: 0.35,
|
|
11675
|
+
cap: "round",
|
|
11676
|
+
join: "round"
|
|
11677
|
+
};
|
|
11678
|
+
}
|
|
11679
|
+
return {
|
|
11680
|
+
color: "#3b82f6",
|
|
11681
|
+
width: 3,
|
|
11682
|
+
opacity: 1,
|
|
11683
|
+
cap: "round",
|
|
11684
|
+
join: "round"
|
|
11685
|
+
};
|
|
11686
|
+
}
|
|
11687
|
+
function streamlinePoints(points, factor = 0.35) {
|
|
11688
|
+
if (points.length < 3 || factor <= 0) return [...points];
|
|
11689
|
+
const clampedFactor = Math.min(Math.max(factor, 0), 0.95);
|
|
11690
|
+
const smoothed = [points[0]];
|
|
11691
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
11692
|
+
const prev = smoothed[i - 1];
|
|
11693
|
+
const curr = points[i];
|
|
11694
|
+
const x = prev[0] * clampedFactor + curr[0] * (1 - clampedFactor);
|
|
11695
|
+
const y = prev[1] * clampedFactor + curr[1] * (1 - clampedFactor);
|
|
11696
|
+
smoothed.push([x, y]);
|
|
11697
|
+
}
|
|
11698
|
+
smoothed.push(points[points.length - 1]);
|
|
11699
|
+
return smoothed;
|
|
11700
|
+
}
|
|
11701
|
+
function strokeBoundingBox(points, margin = 0) {
|
|
11702
|
+
if (points.length === 0) {
|
|
11703
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
11704
|
+
}
|
|
11705
|
+
let minX = Infinity;
|
|
11706
|
+
let minY = Infinity;
|
|
11707
|
+
let maxX = -Infinity;
|
|
11708
|
+
let maxY = -Infinity;
|
|
11709
|
+
for (const [x, y] of points) {
|
|
11710
|
+
if (x < minX) minX = x;
|
|
11711
|
+
if (y < minY) minY = y;
|
|
11712
|
+
if (x > maxX) maxX = x;
|
|
11713
|
+
if (y > maxY) maxY = y;
|
|
11714
|
+
}
|
|
11715
|
+
const w = Math.max(maxX - minX, 0);
|
|
11716
|
+
const h = Math.max(maxY - minY, 0);
|
|
11717
|
+
return {
|
|
11718
|
+
x: minX - margin,
|
|
11719
|
+
y: minY - margin,
|
|
11720
|
+
width: Math.max(w + margin * 2, 1),
|
|
11721
|
+
height: Math.max(h + margin * 2, 1)
|
|
11722
|
+
};
|
|
11723
|
+
}
|
|
11724
|
+
function strokePointsToPathData(points, options = {}) {
|
|
11725
|
+
if (points.length === 0) return "";
|
|
11726
|
+
if (points.length === 1) {
|
|
11727
|
+
const [x, y] = points[0];
|
|
11728
|
+
return `M ${x} ${y} L ${x + 0.01} ${y}`;
|
|
11729
|
+
}
|
|
11730
|
+
if (points.length === 2) {
|
|
11731
|
+
const [p0, p1] = points;
|
|
11732
|
+
return `M ${p0[0]} ${p0[1]} L ${p1[0]} ${p1[1]}`;
|
|
11733
|
+
}
|
|
11734
|
+
const fitted = fitFreehandPath(points, options);
|
|
11735
|
+
return fitted.length > 0 ? fitted : `M ${points[0][0]} ${points[0][1]}`;
|
|
11736
|
+
}
|
|
11737
|
+
function strokeToShapeNode(stroke, options = {}) {
|
|
11738
|
+
const points = stroke.points;
|
|
11739
|
+
const strokeWidth = stroke.style.width || 3;
|
|
11740
|
+
const bounds = strokeBoundingBox(points, 0);
|
|
11741
|
+
const localPoints = points.map(
|
|
11742
|
+
([x, y]) => [x - bounds.x, y - bounds.y]
|
|
11743
|
+
);
|
|
11744
|
+
const d = strokePointsToPathData(localPoints, {
|
|
11745
|
+
tolerance: options.tolerance ?? 1,
|
|
11746
|
+
cornerAngleDeg: options.cornerAngleDeg ?? 40
|
|
11747
|
+
});
|
|
11748
|
+
const width = Math.max(bounds.width, 1);
|
|
11749
|
+
const height = Math.max(bounds.height, 1);
|
|
11750
|
+
const strokePaint = stroke.style.gradient ?? stroke.style.color;
|
|
11751
|
+
const strokeGradient = stroke.style.gradient ?? (typeof stroke.style.color === "object" && stroke.style.color ? stroke.style.color : void 0);
|
|
11752
|
+
return {
|
|
11753
|
+
id: options.id ?? stroke.id,
|
|
11754
|
+
type: "shape",
|
|
11755
|
+
position: { x: bounds.x, y: bounds.y },
|
|
11756
|
+
width,
|
|
11757
|
+
height,
|
|
11758
|
+
style: {
|
|
11759
|
+
width,
|
|
11760
|
+
height
|
|
11761
|
+
},
|
|
11762
|
+
data: {
|
|
11763
|
+
shape: {
|
|
11764
|
+
kind: "path",
|
|
11765
|
+
d,
|
|
11766
|
+
viewBox: [0, 0, width, height],
|
|
11767
|
+
stroke: strokePaint,
|
|
11768
|
+
strokeWidth,
|
|
11769
|
+
opacity: stroke.style.opacity,
|
|
11770
|
+
strokeLinecap: stroke.style.cap ?? "round",
|
|
11771
|
+
strokeLinejoin: stroke.style.join ?? "round",
|
|
11772
|
+
...strokeGradient ? { strokeGradient } : {},
|
|
11773
|
+
fill: "none"
|
|
11774
|
+
},
|
|
11775
|
+
tool: stroke.tool
|
|
11776
|
+
}
|
|
11777
|
+
};
|
|
11778
|
+
}
|
|
11779
|
+
function findStrokesAtPoint(point, nodes, radius = 12) {
|
|
11780
|
+
const matches2 = [];
|
|
11781
|
+
for (const node of nodes) {
|
|
11782
|
+
const shape = getNodeShape(node);
|
|
11783
|
+
if (!shape || shape.kind !== "path") continue;
|
|
11784
|
+
const rect = getNodeRect(node);
|
|
11785
|
+
const strokeWidth = shape.strokeWidth ?? 2;
|
|
11786
|
+
const effectiveRadius = radius + strokeWidth / 2;
|
|
11787
|
+
if (point[0] < rect.x - effectiveRadius || point[0] > rect.x + rect.width + effectiveRadius || point[1] < rect.y - effectiveRadius || point[1] > rect.y + rect.height + effectiveRadius) {
|
|
11788
|
+
continue;
|
|
11789
|
+
}
|
|
11790
|
+
const localPoint = [point[0] - rect.x, point[1] - rect.y];
|
|
11791
|
+
try {
|
|
11792
|
+
const dist3 = distanceToBezierPath(shape.d, localPoint);
|
|
11793
|
+
if (dist3 <= effectiveRadius) {
|
|
11794
|
+
matches2.push(node);
|
|
11795
|
+
}
|
|
11796
|
+
} catch {
|
|
11797
|
+
if (point[0] >= rect.x && point[0] <= rect.x + rect.width && point[1] >= rect.y && point[1] <= rect.y + rect.height) {
|
|
11798
|
+
matches2.push(node);
|
|
11799
|
+
}
|
|
11800
|
+
}
|
|
11801
|
+
}
|
|
11802
|
+
return matches2;
|
|
11803
|
+
}
|
|
11804
|
+
|
|
11805
|
+
// src/canvas/drawController.ts
|
|
11806
|
+
function createCanvasDrawController(options) {
|
|
11807
|
+
let currentTool = options.tool ?? "select";
|
|
11808
|
+
let currentStyle = {
|
|
11809
|
+
...createDefaultStrokeStyle(currentTool),
|
|
11810
|
+
...options.style
|
|
11811
|
+
};
|
|
11812
|
+
let activeStroke = null;
|
|
11813
|
+
const erasedInDrag = /* @__PURE__ */ new Map();
|
|
11814
|
+
let dragStartNodes = null;
|
|
11815
|
+
function setTool(tool) {
|
|
11816
|
+
if (currentTool === tool) return;
|
|
11817
|
+
cancelStroke();
|
|
11818
|
+
currentTool = tool;
|
|
11819
|
+
if (tool === "pen" || tool === "highlighter") {
|
|
11820
|
+
currentStyle = {
|
|
11821
|
+
...createDefaultStrokeStyle(tool),
|
|
11822
|
+
...options.style
|
|
11823
|
+
};
|
|
11824
|
+
options.onStyleChange?.(currentStyle);
|
|
11825
|
+
}
|
|
11826
|
+
options.onToolChange?.(tool);
|
|
11827
|
+
}
|
|
11828
|
+
function setStyle(style) {
|
|
11829
|
+
currentStyle = { ...currentStyle, ...style };
|
|
11830
|
+
options.onStyleChange?.(currentStyle);
|
|
11831
|
+
}
|
|
11832
|
+
function startStroke(point) {
|
|
11833
|
+
if (currentTool === "select") return;
|
|
11834
|
+
if (currentTool === "eraser") {
|
|
11835
|
+
erasedInDrag.clear();
|
|
11836
|
+
dragStartNodes = [...options.getNodes()];
|
|
11837
|
+
eraseAt(point);
|
|
11838
|
+
return;
|
|
11839
|
+
}
|
|
11840
|
+
const id = options.generateNodeId?.() ?? `stroke-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
11841
|
+
activeStroke = {
|
|
11842
|
+
id,
|
|
11843
|
+
tool: currentTool,
|
|
11844
|
+
points: [point],
|
|
11845
|
+
style: { ...currentStyle },
|
|
11846
|
+
startTime: Date.now()
|
|
11847
|
+
};
|
|
11848
|
+
options.onActiveStrokeChange?.(activeStroke);
|
|
11849
|
+
}
|
|
11850
|
+
function continueStroke(point) {
|
|
11851
|
+
if (currentTool === "eraser") {
|
|
11852
|
+
eraseAt(point);
|
|
11853
|
+
return;
|
|
11854
|
+
}
|
|
11855
|
+
if (!activeStroke) return;
|
|
11856
|
+
const points = activeStroke.points;
|
|
11857
|
+
const last = points[points.length - 1];
|
|
11858
|
+
if (Math.hypot(point[0] - last[0], point[1] - last[1]) < 0.25) return;
|
|
11859
|
+
points.push(point);
|
|
11860
|
+
options.onActiveStrokeChange?.({ ...activeStroke, points: [...points] });
|
|
11861
|
+
}
|
|
11862
|
+
function endStroke() {
|
|
11863
|
+
if (currentTool === "eraser") {
|
|
11864
|
+
const startSnapshot = dragStartNodes;
|
|
11865
|
+
dragStartNodes = null;
|
|
11866
|
+
if (erasedInDrag.size > 0 && options.history && startSnapshot) {
|
|
11867
|
+
const erasedIds = new Set(Array.from(erasedInDrag.keys()));
|
|
11868
|
+
options.history.execute({
|
|
11869
|
+
name: "Erase strokes",
|
|
11870
|
+
execute: () => {
|
|
11871
|
+
const remaining = options.getNodes().filter((n) => !erasedIds.has(n.id));
|
|
11872
|
+
options.setNodes(remaining);
|
|
11873
|
+
},
|
|
11874
|
+
undo: () => {
|
|
11875
|
+
options.setNodes(startSnapshot);
|
|
11876
|
+
}
|
|
11877
|
+
});
|
|
11878
|
+
}
|
|
11879
|
+
erasedInDrag.clear();
|
|
11880
|
+
return null;
|
|
11881
|
+
}
|
|
11882
|
+
if (!activeStroke) return null;
|
|
11883
|
+
const stroke = activeStroke;
|
|
11884
|
+
activeStroke = null;
|
|
11885
|
+
options.onActiveStrokeChange?.(null);
|
|
11886
|
+
if (stroke.points.length === 0) return null;
|
|
11887
|
+
const drawOpts = options.drawOptions ?? {};
|
|
11888
|
+
const smoothed = streamlinePoints(stroke.points, drawOpts.streamline ?? 0.35);
|
|
11889
|
+
const node = strokeToShapeNode(
|
|
11890
|
+
{ ...stroke, points: smoothed },
|
|
11891
|
+
{
|
|
11892
|
+
tolerance: drawOpts.tolerance ?? 1,
|
|
11893
|
+
cornerAngleDeg: drawOpts.cornerAngleDeg ?? 40
|
|
11894
|
+
}
|
|
11895
|
+
);
|
|
11896
|
+
if (options.history) {
|
|
11897
|
+
options.history.execute({
|
|
11898
|
+
name: `Draw ${stroke.tool}`,
|
|
11899
|
+
execute: () => {
|
|
11900
|
+
options.setNodes([...options.getNodes(), node]);
|
|
11901
|
+
},
|
|
11902
|
+
undo: () => {
|
|
11903
|
+
options.setNodes(options.getNodes().filter((n) => n.id !== node.id));
|
|
11904
|
+
}
|
|
11905
|
+
});
|
|
11906
|
+
} else {
|
|
11907
|
+
options.setNodes([...options.getNodes(), node]);
|
|
11908
|
+
}
|
|
11909
|
+
options.onStrokeComplete?.(node);
|
|
11910
|
+
return node;
|
|
11911
|
+
}
|
|
11912
|
+
function cancelStroke() {
|
|
11913
|
+
if (activeStroke) {
|
|
11914
|
+
activeStroke = null;
|
|
11915
|
+
options.onActiveStrokeChange?.(null);
|
|
11916
|
+
}
|
|
11917
|
+
if (erasedInDrag.size > 0) {
|
|
11918
|
+
const entries = Array.from(erasedInDrag.values());
|
|
11919
|
+
const current = [...options.getNodes()];
|
|
11920
|
+
const sorted = [...entries].sort((a, b) => a.index - b.index);
|
|
11921
|
+
for (const { node, index } of sorted) {
|
|
11922
|
+
const insertAt = Math.min(index, current.length);
|
|
11923
|
+
current.splice(insertAt, 0, node);
|
|
11924
|
+
}
|
|
11925
|
+
options.setNodes(current);
|
|
11926
|
+
erasedInDrag.clear();
|
|
11927
|
+
}
|
|
11928
|
+
}
|
|
11929
|
+
function eraseAt(point, radius = 12) {
|
|
11930
|
+
const nodes = options.getNodes();
|
|
11931
|
+
const candidates = findStrokesAtPoint(point, nodes, radius);
|
|
11932
|
+
const newlyErased = [];
|
|
11933
|
+
for (const candidate of candidates) {
|
|
11934
|
+
if (erasedInDrag.has(candidate.id) || isLocked(candidate)) continue;
|
|
11935
|
+
const index = nodes.findIndex((n) => n.id === candidate.id);
|
|
11936
|
+
if (index !== -1) {
|
|
11937
|
+
erasedInDrag.set(candidate.id, { node: candidate, index });
|
|
11938
|
+
newlyErased.push(candidate);
|
|
11939
|
+
}
|
|
11940
|
+
}
|
|
11941
|
+
if (newlyErased.length > 0) {
|
|
11942
|
+
const erasedSet = new Set(newlyErased.map((n) => n.id));
|
|
11943
|
+
const nextNodes = nodes.filter((n) => !erasedSet.has(n.id));
|
|
11944
|
+
options.setNodes(nextNodes);
|
|
11945
|
+
options.onNodesErase?.(newlyErased);
|
|
11946
|
+
}
|
|
11947
|
+
return newlyErased;
|
|
11948
|
+
}
|
|
11949
|
+
return {
|
|
11950
|
+
get tool() {
|
|
11951
|
+
return currentTool;
|
|
11952
|
+
},
|
|
11953
|
+
setTool,
|
|
11954
|
+
get style() {
|
|
11955
|
+
return currentStyle;
|
|
11956
|
+
},
|
|
11957
|
+
setStyle,
|
|
11958
|
+
get activeStroke() {
|
|
11959
|
+
return activeStroke;
|
|
11960
|
+
},
|
|
11961
|
+
get isDrawing() {
|
|
11962
|
+
return activeStroke !== null;
|
|
11963
|
+
},
|
|
11964
|
+
startStroke,
|
|
11965
|
+
continueStroke,
|
|
11966
|
+
endStroke,
|
|
11967
|
+
cancelStroke,
|
|
11968
|
+
eraseAt
|
|
11969
|
+
};
|
|
11970
|
+
}
|
|
11400
11971
|
export {
|
|
11401
11972
|
ANIMATABLE_PROPERTIES,
|
|
11402
11973
|
ANIMATION_DATA_KEY,
|
|
@@ -11407,6 +11978,7 @@ export {
|
|
|
11407
11978
|
AnimationEditError,
|
|
11408
11979
|
AnimationPlayer,
|
|
11409
11980
|
CANVAS_COMPONENT_DRAG_TYPE,
|
|
11981
|
+
CANVAS_GRADIENT_PRESETS,
|
|
11410
11982
|
CANVAS_TOKEN_PROPERTIES,
|
|
11411
11983
|
CHILD_LAYOUT_DATA_KEY,
|
|
11412
11984
|
COMPONENT_DATA_KEY,
|
|
@@ -11439,6 +12011,7 @@ export {
|
|
|
11439
12011
|
addCanvasVariant,
|
|
11440
12012
|
addKeyframesAtTime,
|
|
11441
12013
|
alignNodes,
|
|
12014
|
+
angleToSvgLinearCoords,
|
|
11442
12015
|
animateNode,
|
|
11443
12016
|
applyAnimation,
|
|
11444
12017
|
applyAnimationOverrides,
|
|
@@ -11485,8 +12058,10 @@ export {
|
|
|
11485
12058
|
createCanvasAutoLayoutController,
|
|
11486
12059
|
createCanvasComponent,
|
|
11487
12060
|
createCanvasComponentsController,
|
|
12061
|
+
createCanvasDrawController,
|
|
11488
12062
|
createCanvasInstance,
|
|
11489
12063
|
createCanvasScenesStore,
|
|
12064
|
+
createDefaultStrokeStyle,
|
|
11490
12065
|
createFanOutTrajectory,
|
|
11491
12066
|
createGuideIndex,
|
|
11492
12067
|
createNodeGuideIndex,
|
|
@@ -11507,6 +12082,7 @@ export {
|
|
|
11507
12082
|
easingFromInfluence,
|
|
11508
12083
|
easingLabel,
|
|
11509
12084
|
easingToCss,
|
|
12085
|
+
elasticAt,
|
|
11510
12086
|
ensureLayout,
|
|
11511
12087
|
estimateCanvasHeight,
|
|
11512
12088
|
estimateNodeSize,
|
|
@@ -11516,6 +12092,7 @@ export {
|
|
|
11516
12092
|
excludePaths,
|
|
11517
12093
|
exportCssAnimation,
|
|
11518
12094
|
findConnectorSnap,
|
|
12095
|
+
findStrokesAtPoint,
|
|
11519
12096
|
findVariant,
|
|
11520
12097
|
fitCubicRun,
|
|
11521
12098
|
fitCubics,
|
|
@@ -11582,6 +12159,7 @@ export {
|
|
|
11582
12159
|
getVariantOptions,
|
|
11583
12160
|
getVariantSet,
|
|
11584
12161
|
getVariantValues,
|
|
12162
|
+
gradientToSvgDef,
|
|
11585
12163
|
handlePoint,
|
|
11586
12164
|
hasVisibleStroke,
|
|
11587
12165
|
inferResizeHandle,
|
|
@@ -11596,6 +12174,7 @@ export {
|
|
|
11596
12174
|
isAutoLayoutFrame,
|
|
11597
12175
|
isCanvasComponent,
|
|
11598
12176
|
isCanvasComponentSet,
|
|
12177
|
+
isCanvasGradient,
|
|
11599
12178
|
isCanvasInstance,
|
|
11600
12179
|
isCssExportExact,
|
|
11601
12180
|
isEventFromTextInput,
|
|
@@ -11647,6 +12226,7 @@ export {
|
|
|
11647
12226
|
parseCanvasClipboardData,
|
|
11648
12227
|
parseCanvasScenes,
|
|
11649
12228
|
parseCssEasing,
|
|
12229
|
+
parseCssGradient,
|
|
11650
12230
|
parseKeyframeClipboard,
|
|
11651
12231
|
parseNodeStyleDeclarations,
|
|
11652
12232
|
parseOklab,
|
|
@@ -11682,6 +12262,7 @@ export {
|
|
|
11682
12262
|
repositionNodesTo,
|
|
11683
12263
|
resetCanvasInstanceOverrides,
|
|
11684
12264
|
resizeHandleAxes,
|
|
12265
|
+
resolveCanvasGradient,
|
|
11685
12266
|
resolveCanvasTokens,
|
|
11686
12267
|
resolveEasing,
|
|
11687
12268
|
resolvePadding,
|
|
@@ -11724,7 +12305,11 @@ export {
|
|
|
11724
12305
|
snapToGrid,
|
|
11725
12306
|
splitSegment,
|
|
11726
12307
|
springAt,
|
|
12308
|
+
streamlinePoints,
|
|
12309
|
+
strokeBoundingBox,
|
|
11727
12310
|
strokeOutlineRings,
|
|
12311
|
+
strokePointsToPathData,
|
|
12312
|
+
strokeToShapeNode,
|
|
11728
12313
|
subSegment,
|
|
11729
12314
|
subtractPaths,
|
|
11730
12315
|
subtractPolygons,
|