@pixerate/editor 0.29.0 → 0.30.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.
@@ -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-PWJARCI4.js";
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";
@@ -11397,6 +11400,305 @@ function getCanvasComponentDragId(dataTransfer) {
11397
11400
  const id = dataTransfer?.getData(CANVAS_COMPONENT_DRAG_TYPE);
11398
11401
  return id ? id : void 0;
11399
11402
  }
11403
+
11404
+ // src/canvas/freehand.ts
11405
+ function createDefaultStrokeStyle(tool) {
11406
+ if (tool === "highlighter") {
11407
+ return {
11408
+ color: "#facc15",
11409
+ width: 24,
11410
+ opacity: 0.35,
11411
+ cap: "round",
11412
+ join: "round"
11413
+ };
11414
+ }
11415
+ return {
11416
+ color: "#3b82f6",
11417
+ width: 3,
11418
+ opacity: 1,
11419
+ cap: "round",
11420
+ join: "round"
11421
+ };
11422
+ }
11423
+ function streamlinePoints(points, factor = 0.35) {
11424
+ if (points.length < 3 || factor <= 0) return [...points];
11425
+ const clampedFactor = Math.min(Math.max(factor, 0), 0.95);
11426
+ const smoothed = [points[0]];
11427
+ for (let i = 1; i < points.length - 1; i++) {
11428
+ const prev = smoothed[i - 1];
11429
+ const curr = points[i];
11430
+ const x = prev[0] * clampedFactor + curr[0] * (1 - clampedFactor);
11431
+ const y = prev[1] * clampedFactor + curr[1] * (1 - clampedFactor);
11432
+ smoothed.push([x, y]);
11433
+ }
11434
+ smoothed.push(points[points.length - 1]);
11435
+ return smoothed;
11436
+ }
11437
+ function strokeBoundingBox(points, margin = 0) {
11438
+ if (points.length === 0) {
11439
+ return { x: 0, y: 0, width: 0, height: 0 };
11440
+ }
11441
+ let minX = Infinity;
11442
+ let minY = Infinity;
11443
+ let maxX = -Infinity;
11444
+ let maxY = -Infinity;
11445
+ for (const [x, y] of points) {
11446
+ if (x < minX) minX = x;
11447
+ if (y < minY) minY = y;
11448
+ if (x > maxX) maxX = x;
11449
+ if (y > maxY) maxY = y;
11450
+ }
11451
+ const w = Math.max(maxX - minX, 0);
11452
+ const h = Math.max(maxY - minY, 0);
11453
+ return {
11454
+ x: minX - margin,
11455
+ y: minY - margin,
11456
+ width: Math.max(w + margin * 2, 1),
11457
+ height: Math.max(h + margin * 2, 1)
11458
+ };
11459
+ }
11460
+ function strokePointsToPathData(points, options = {}) {
11461
+ if (points.length === 0) return "";
11462
+ if (points.length === 1) {
11463
+ const [x, y] = points[0];
11464
+ return `M ${x} ${y} L ${x + 0.01} ${y}`;
11465
+ }
11466
+ if (points.length === 2) {
11467
+ const [p0, p1] = points;
11468
+ return `M ${p0[0]} ${p0[1]} L ${p1[0]} ${p1[1]}`;
11469
+ }
11470
+ const fitted = fitFreehandPath(points, options);
11471
+ return fitted.length > 0 ? fitted : `M ${points[0][0]} ${points[0][1]}`;
11472
+ }
11473
+ function strokeToShapeNode(stroke, options = {}) {
11474
+ const points = stroke.points;
11475
+ const strokeWidth = stroke.style.width || 3;
11476
+ const bounds = strokeBoundingBox(points, 0);
11477
+ const localPoints = points.map(
11478
+ ([x, y]) => [x - bounds.x, y - bounds.y]
11479
+ );
11480
+ const d = strokePointsToPathData(localPoints, {
11481
+ tolerance: options.tolerance ?? 1,
11482
+ cornerAngleDeg: options.cornerAngleDeg ?? 40
11483
+ });
11484
+ const width = Math.max(bounds.width, 1);
11485
+ const height = Math.max(bounds.height, 1);
11486
+ return {
11487
+ id: options.id ?? stroke.id,
11488
+ type: "shape",
11489
+ position: { x: bounds.x, y: bounds.y },
11490
+ width,
11491
+ height,
11492
+ style: {
11493
+ width,
11494
+ height
11495
+ },
11496
+ data: {
11497
+ shape: {
11498
+ kind: "path",
11499
+ d,
11500
+ viewBox: [0, 0, width, height],
11501
+ stroke: stroke.style.color,
11502
+ strokeWidth,
11503
+ opacity: stroke.style.opacity,
11504
+ fill: "none"
11505
+ },
11506
+ tool: stroke.tool
11507
+ }
11508
+ };
11509
+ }
11510
+ function findStrokesAtPoint(point, nodes, radius = 12) {
11511
+ const matches2 = [];
11512
+ for (const node of nodes) {
11513
+ const shape = getNodeShape(node);
11514
+ if (!shape || shape.kind !== "path") continue;
11515
+ const rect = getNodeRect(node);
11516
+ const strokeWidth = shape.strokeWidth ?? 2;
11517
+ const effectiveRadius = radius + strokeWidth / 2;
11518
+ 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) {
11519
+ continue;
11520
+ }
11521
+ const localPoint = [point[0] - rect.x, point[1] - rect.y];
11522
+ try {
11523
+ const dist3 = distanceToBezierPath(shape.d, localPoint);
11524
+ if (dist3 <= effectiveRadius) {
11525
+ matches2.push(node);
11526
+ }
11527
+ } catch {
11528
+ if (point[0] >= rect.x && point[0] <= rect.x + rect.width && point[1] >= rect.y && point[1] <= rect.y + rect.height) {
11529
+ matches2.push(node);
11530
+ }
11531
+ }
11532
+ }
11533
+ return matches2;
11534
+ }
11535
+
11536
+ // src/canvas/drawController.ts
11537
+ function createCanvasDrawController(options) {
11538
+ let currentTool = options.tool ?? "select";
11539
+ let currentStyle = {
11540
+ ...createDefaultStrokeStyle(currentTool),
11541
+ ...options.style
11542
+ };
11543
+ let activeStroke = null;
11544
+ const erasedInDrag = /* @__PURE__ */ new Map();
11545
+ let dragStartNodes = null;
11546
+ function setTool(tool) {
11547
+ if (currentTool === tool) return;
11548
+ cancelStroke();
11549
+ currentTool = tool;
11550
+ if (tool === "pen" || tool === "highlighter") {
11551
+ currentStyle = {
11552
+ ...createDefaultStrokeStyle(tool),
11553
+ ...options.style
11554
+ };
11555
+ options.onStyleChange?.(currentStyle);
11556
+ }
11557
+ options.onToolChange?.(tool);
11558
+ }
11559
+ function setStyle(style) {
11560
+ currentStyle = { ...currentStyle, ...style };
11561
+ options.onStyleChange?.(currentStyle);
11562
+ }
11563
+ function startStroke(point) {
11564
+ if (currentTool === "select") return;
11565
+ if (currentTool === "eraser") {
11566
+ erasedInDrag.clear();
11567
+ dragStartNodes = [...options.getNodes()];
11568
+ eraseAt(point);
11569
+ return;
11570
+ }
11571
+ const id = options.generateNodeId?.() ?? `stroke-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
11572
+ activeStroke = {
11573
+ id,
11574
+ tool: currentTool,
11575
+ points: [point],
11576
+ style: { ...currentStyle },
11577
+ startTime: Date.now()
11578
+ };
11579
+ options.onActiveStrokeChange?.(activeStroke);
11580
+ }
11581
+ function continueStroke(point) {
11582
+ if (currentTool === "eraser") {
11583
+ eraseAt(point);
11584
+ return;
11585
+ }
11586
+ if (!activeStroke) return;
11587
+ const points = activeStroke.points;
11588
+ const last = points[points.length - 1];
11589
+ if (Math.hypot(point[0] - last[0], point[1] - last[1]) < 0.25) return;
11590
+ points.push(point);
11591
+ options.onActiveStrokeChange?.({ ...activeStroke, points: [...points] });
11592
+ }
11593
+ function endStroke() {
11594
+ if (currentTool === "eraser") {
11595
+ const startSnapshot = dragStartNodes;
11596
+ dragStartNodes = null;
11597
+ if (erasedInDrag.size > 0 && options.history && startSnapshot) {
11598
+ const erasedIds = new Set(Array.from(erasedInDrag.keys()));
11599
+ options.history.execute({
11600
+ name: "Erase strokes",
11601
+ execute: () => {
11602
+ const remaining = options.getNodes().filter((n) => !erasedIds.has(n.id));
11603
+ options.setNodes(remaining);
11604
+ },
11605
+ undo: () => {
11606
+ options.setNodes(startSnapshot);
11607
+ }
11608
+ });
11609
+ }
11610
+ erasedInDrag.clear();
11611
+ return null;
11612
+ }
11613
+ if (!activeStroke) return null;
11614
+ const stroke = activeStroke;
11615
+ activeStroke = null;
11616
+ options.onActiveStrokeChange?.(null);
11617
+ if (stroke.points.length === 0) return null;
11618
+ const drawOpts = options.drawOptions ?? {};
11619
+ const smoothed = streamlinePoints(stroke.points, drawOpts.streamline ?? 0.35);
11620
+ const node = strokeToShapeNode(
11621
+ { ...stroke, points: smoothed },
11622
+ {
11623
+ tolerance: drawOpts.tolerance ?? 1,
11624
+ cornerAngleDeg: drawOpts.cornerAngleDeg ?? 40
11625
+ }
11626
+ );
11627
+ if (options.history) {
11628
+ options.history.execute({
11629
+ name: `Draw ${stroke.tool}`,
11630
+ execute: () => {
11631
+ options.setNodes([...options.getNodes(), node]);
11632
+ },
11633
+ undo: () => {
11634
+ options.setNodes(options.getNodes().filter((n) => n.id !== node.id));
11635
+ }
11636
+ });
11637
+ } else {
11638
+ options.setNodes([...options.getNodes(), node]);
11639
+ }
11640
+ options.onStrokeComplete?.(node);
11641
+ return node;
11642
+ }
11643
+ function cancelStroke() {
11644
+ if (activeStroke) {
11645
+ activeStroke = null;
11646
+ options.onActiveStrokeChange?.(null);
11647
+ }
11648
+ if (erasedInDrag.size > 0) {
11649
+ const entries = Array.from(erasedInDrag.values());
11650
+ const current = [...options.getNodes()];
11651
+ const sorted = [...entries].sort((a, b) => a.index - b.index);
11652
+ for (const { node, index } of sorted) {
11653
+ const insertAt = Math.min(index, current.length);
11654
+ current.splice(insertAt, 0, node);
11655
+ }
11656
+ options.setNodes(current);
11657
+ erasedInDrag.clear();
11658
+ }
11659
+ }
11660
+ function eraseAt(point, radius = 12) {
11661
+ const nodes = options.getNodes();
11662
+ const candidates = findStrokesAtPoint(point, nodes, radius);
11663
+ const newlyErased = [];
11664
+ for (const candidate of candidates) {
11665
+ if (erasedInDrag.has(candidate.id) || isLocked(candidate)) continue;
11666
+ const index = nodes.findIndex((n) => n.id === candidate.id);
11667
+ if (index !== -1) {
11668
+ erasedInDrag.set(candidate.id, { node: candidate, index });
11669
+ newlyErased.push(candidate);
11670
+ }
11671
+ }
11672
+ if (newlyErased.length > 0) {
11673
+ const erasedSet = new Set(newlyErased.map((n) => n.id));
11674
+ const nextNodes = nodes.filter((n) => !erasedSet.has(n.id));
11675
+ options.setNodes(nextNodes);
11676
+ options.onNodesErase?.(newlyErased);
11677
+ }
11678
+ return newlyErased;
11679
+ }
11680
+ return {
11681
+ get tool() {
11682
+ return currentTool;
11683
+ },
11684
+ setTool,
11685
+ get style() {
11686
+ return currentStyle;
11687
+ },
11688
+ setStyle,
11689
+ get activeStroke() {
11690
+ return activeStroke;
11691
+ },
11692
+ get isDrawing() {
11693
+ return activeStroke !== null;
11694
+ },
11695
+ startStroke,
11696
+ continueStroke,
11697
+ endStroke,
11698
+ cancelStroke,
11699
+ eraseAt
11700
+ };
11701
+ }
11400
11702
  export {
11401
11703
  ANIMATABLE_PROPERTIES,
11402
11704
  ANIMATION_DATA_KEY,
@@ -11485,8 +11787,10 @@ export {
11485
11787
  createCanvasAutoLayoutController,
11486
11788
  createCanvasComponent,
11487
11789
  createCanvasComponentsController,
11790
+ createCanvasDrawController,
11488
11791
  createCanvasInstance,
11489
11792
  createCanvasScenesStore,
11793
+ createDefaultStrokeStyle,
11490
11794
  createFanOutTrajectory,
11491
11795
  createGuideIndex,
11492
11796
  createNodeGuideIndex,
@@ -11507,6 +11811,7 @@ export {
11507
11811
  easingFromInfluence,
11508
11812
  easingLabel,
11509
11813
  easingToCss,
11814
+ elasticAt,
11510
11815
  ensureLayout,
11511
11816
  estimateCanvasHeight,
11512
11817
  estimateNodeSize,
@@ -11516,6 +11821,7 @@ export {
11516
11821
  excludePaths,
11517
11822
  exportCssAnimation,
11518
11823
  findConnectorSnap,
11824
+ findStrokesAtPoint,
11519
11825
  findVariant,
11520
11826
  fitCubicRun,
11521
11827
  fitCubics,
@@ -11724,7 +12030,11 @@ export {
11724
12030
  snapToGrid,
11725
12031
  splitSegment,
11726
12032
  springAt,
12033
+ streamlinePoints,
12034
+ strokeBoundingBox,
11727
12035
  strokeOutlineRings,
12036
+ strokePointsToPathData,
12037
+ strokeToShapeNode,
11728
12038
  subSegment,
11729
12039
  subtractPaths,
11730
12040
  subtractPolygons,