@pixerate/editor 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +100 -13
  2. package/THIRD_PARTY_NOTICES.md +88 -0
  3. package/dist/agent/index.cjs +86 -111
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +69 -211
  6. package/dist/agent/index.d.ts +69 -211
  7. package/dist/agent/index.js +77 -102
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/arrangeShortcuts-B8O3f8-P.d.cts +184 -0
  10. package/dist/arrangeShortcuts-sLlD2rSX.d.ts +184 -0
  11. package/dist/canvas/index.cjs +2112 -60
  12. package/dist/canvas/index.cjs.map +1 -1
  13. package/dist/canvas/index.d.cts +712 -181
  14. package/dist/canvas/index.d.ts +712 -181
  15. package/dist/canvas/index.js +2105 -53
  16. package/dist/canvas/index.js.map +1 -1
  17. package/dist/chunk-26BVFKHU.cjs +453 -0
  18. package/dist/chunk-26BVFKHU.cjs.map +1 -0
  19. package/dist/chunk-2H7ZHPEJ.js +762 -0
  20. package/dist/chunk-2H7ZHPEJ.js.map +1 -0
  21. package/dist/chunk-7DWMJCAT.js +453 -0
  22. package/dist/chunk-7DWMJCAT.js.map +1 -0
  23. package/dist/{chunk-TVRC4KVT.cjs → chunk-AA4ZEVCC.cjs} +845 -90
  24. package/dist/chunk-AA4ZEVCC.cjs.map +1 -0
  25. package/dist/chunk-ARRTP33I.js +101 -0
  26. package/dist/chunk-ARRTP33I.js.map +1 -0
  27. package/dist/{chunk-LA7PEGU3.cjs → chunk-CL37JGFW.cjs} +4 -4
  28. package/dist/{chunk-LA7PEGU3.cjs.map → chunk-CL37JGFW.cjs.map} +1 -1
  29. package/dist/chunk-CPOKFNBG.cjs +101 -0
  30. package/dist/chunk-CPOKFNBG.cjs.map +1 -0
  31. package/dist/chunk-G2I4QJOT.cjs +762 -0
  32. package/dist/chunk-G2I4QJOT.cjs.map +1 -0
  33. package/dist/{chunk-4QPLEUL2.cjs → chunk-GVSMRPHB.cjs} +1046 -54
  34. package/dist/chunk-GVSMRPHB.cjs.map +1 -0
  35. package/dist/{chunk-KYRZDWM4.js → chunk-JJDLPEWL.js} +2 -2
  36. package/dist/{chunk-GVW4P7BG.js → chunk-K3R5NG2D.js} +448 -54
  37. package/dist/chunk-K3R5NG2D.js.map +1 -0
  38. package/dist/{chunk-3TYMNULT.js → chunk-MJZBNWG7.js} +807 -52
  39. package/dist/chunk-MJZBNWG7.js.map +1 -0
  40. package/dist/{chunk-A7DKIA72.js → chunk-QBJ22YZR.js} +1059 -93
  41. package/dist/chunk-QBJ22YZR.js.map +1 -0
  42. package/dist/{chunk-CYFJCKC4.js → chunk-QGHGJ56O.js} +997 -5
  43. package/dist/chunk-QGHGJ56O.js.map +1 -0
  44. package/dist/{chunk-JYRIOFEN.cjs → chunk-RIKBJLS2.cjs} +1064 -98
  45. package/dist/chunk-RIKBJLS2.cjs.map +1 -0
  46. package/dist/{chunk-U6NDQFOK.cjs → chunk-XDMXXC4Z.cjs} +569 -175
  47. package/dist/chunk-XDMXXC4Z.cjs.map +1 -0
  48. package/dist/commands/index.cjs +860 -0
  49. package/dist/commands/index.cjs.map +1 -0
  50. package/dist/commands/index.d.cts +592 -0
  51. package/dist/commands/index.d.ts +592 -0
  52. package/dist/commands/index.js +860 -0
  53. package/dist/commands/index.js.map +1 -0
  54. package/dist/{controller-OUdxPY-X.d.ts → controller-pePCJLT1.d.cts} +216 -11
  55. package/dist/{controller-OUdxPY-X.d.cts → controller-pePCJLT1.d.ts} +216 -11
  56. package/dist/extensions/index.cjs +14 -4
  57. package/dist/extensions/index.cjs.map +1 -1
  58. package/dist/extensions/index.d.cts +3 -3
  59. package/dist/extensions/index.d.ts +3 -3
  60. package/dist/extensions/index.js +13 -3
  61. package/dist/history/index.cjs +12 -2
  62. package/dist/history/index.cjs.map +1 -1
  63. package/dist/history/index.d.cts +211 -7
  64. package/dist/history/index.d.ts +211 -7
  65. package/dist/history/index.js +13 -3
  66. package/dist/image-editor/index.cjs +418 -13
  67. package/dist/image-editor/index.cjs.map +1 -1
  68. package/dist/image-editor/index.d.cts +154 -3
  69. package/dist/image-editor/index.d.ts +154 -3
  70. package/dist/image-editor/index.js +417 -12
  71. package/dist/image-editor/index.js.map +1 -1
  72. package/dist/{index-k3mFbPx_.d.cts → index-CCr_z9kF.d.cts} +3 -3
  73. package/dist/{index-Ng1pG7JE.d.ts → index-Ck6ajwXh.d.ts} +3 -3
  74. package/dist/index.cjs +275 -83
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/index.d.cts +296 -47
  77. package/dist/index.d.ts +296 -47
  78. package/dist/index.js +266 -74
  79. package/dist/index.js.map +1 -1
  80. package/dist/registry-D5-R90p0.d.cts +209 -0
  81. package/dist/registry-D5-R90p0.d.ts +209 -0
  82. package/dist/{registry-iAHbqkYt.d.cts → registry-GJI4gToU.d.cts} +15 -4
  83. package/dist/{registry-iAHbqkYt.d.ts → registry-GJI4gToU.d.ts} +15 -4
  84. package/dist/serializers/index.cjs +3 -3
  85. package/dist/serializers/index.d.cts +1 -1
  86. package/dist/serializers/index.d.ts +1 -1
  87. package/dist/serializers/index.js +2 -2
  88. package/dist/spreadsheet/index.cjs +23 -3
  89. package/dist/spreadsheet/index.cjs.map +1 -1
  90. package/dist/spreadsheet/index.d.cts +97 -10
  91. package/dist/spreadsheet/index.d.ts +97 -10
  92. package/dist/spreadsheet/index.js +22 -2
  93. package/dist/{track-changes-G8JcWB8b.d.cts → track-changes-BSF58RK0.d.cts} +92 -9
  94. package/dist/{track-changes-G8JcWB8b.d.ts → track-changes-BSF58RK0.d.ts} +92 -9
  95. package/dist/{types-DMe0vFB1.d.cts → types-DX4Mh4SL.d.cts} +1 -1
  96. package/dist/{types-DMe0vFB1.d.ts → types-DX4Mh4SL.d.ts} +1 -1
  97. package/dist/types-GT5a5YAX.d.cts +86 -0
  98. package/dist/types-GT5a5YAX.d.ts +86 -0
  99. package/package.json +8 -2
  100. package/dist/chunk-2TWGLC5K.cjs +0 -262
  101. package/dist/chunk-2TWGLC5K.cjs.map +0 -1
  102. package/dist/chunk-3TYMNULT.js.map +0 -1
  103. package/dist/chunk-4QPLEUL2.cjs.map +0 -1
  104. package/dist/chunk-A7DKIA72.js.map +0 -1
  105. package/dist/chunk-CYFJCKC4.js.map +0 -1
  106. package/dist/chunk-GVW4P7BG.js.map +0 -1
  107. package/dist/chunk-JYRIOFEN.cjs.map +0 -1
  108. package/dist/chunk-KAADNAPF.js +0 -262
  109. package/dist/chunk-KAADNAPF.js.map +0 -1
  110. package/dist/chunk-TVRC4KVT.cjs.map +0 -1
  111. package/dist/chunk-U6NDQFOK.cjs.map +0 -1
  112. package/dist/types-BZgcDsC5.d.cts +0 -42
  113. package/dist/types-BZgcDsC5.d.ts +0 -42
  114. /package/dist/{chunk-KYRZDWM4.js.map → chunk-JJDLPEWL.js.map} +0 -0
@@ -1,6 +1,8 @@
1
- import { R as Rect, C as CanvasNode, X as XYPosition, a as CanvasEdge, I as IncrementalLayoutOptions, S as Side, b as CanvasLayoutOptions, P as PlaceBesideOptions, T as TrajectoryFunction, F as FanOutTrajectoryOptions, N as NodeTransition, c as CanvasNodeStyle, d as ReflowDisplacementOptions, D as DirectionalDisplacementOptions, e as CanvasClipboardPayload, f as Position } from '../types-DMe0vFB1.js';
2
- export { g as CanvasDockingCallbacks, h as CollapseNodeOptions, i as DisplacementDirection, j as DockStrategy, E as ExplodeNodeOptions, k as ExplosionSnapshot, G as GraphDifferences, l as Point, m as TrajectoryPoint } from '../types-DMe0vFB1.js';
3
- import { C as Command } from '../types-BZgcDsC5.js';
1
+ import { R as Rect, C as CanvasNode, X as XYPosition, b as CanvasEdge, I as IncrementalLayoutOptions, S as Side, c as CanvasLayoutOptions, P as PlaceBesideOptions, T as TrajectoryFunction, F as FanOutTrajectoryOptions, N as NodeTransition, a as CanvasNodeStyle, d as ReflowDisplacementOptions, D as DirectionalDisplacementOptions, e as CanvasClipboardPayload, f as Position, g as Point } from '../types-DX4Mh4SL.js';
2
+ export { h as CanvasDockingCallbacks, i as CollapseNodeOptions, j as DisplacementDirection, k as DockStrategy, E as ExplodeNodeOptions, l as ExplosionSnapshot, G as GraphDifferences, m as TrajectoryPoint } from '../types-DX4Mh4SL.js';
3
+ import { b as CanvasNodeUpdate, N as NodeRectOptions, A as AlignEdge, c as AlignNodesOptions, D as DistributeAxis, a as DistributeMode, d as DistributeNodesOptions, S as SizeDimension, M as MatchSizeOptions, Z as ZOrderOperation, C as CanvasArrangeAction } from '../arrangeShortcuts-sLlD2rSX.js';
4
+ export { e as ArrangeShortcutBinding, f as DEFAULT_ARRANGE_SHORTCUTS, L as LOCKED_DATA_KEY, g as LOCKED_PREVIOUS_FLAGS_KEY, h as alignNodes, i as applyNodeUpdates, j as createNodeLookup, k as distributeNodes, l as getAbsolutePosition, m as getNodeRect, n as getNodeSize, o as invertNodeUpdates, p as isLocked, q as lockNodes, r as matchArrangeShortcut, s as matchSize, t as readStyleSize, u as reorderNodes, v as toggleLockNodes, w as unlockNodes, x as withStyleSize } from '../arrangeShortcuts-sLlD2rSX.js';
5
+ import { C as Command } from '../types-GT5a5YAX.js';
4
6
 
5
7
  declare const DEFAULT_NODE_WIDTH = 200;
6
8
  declare const DEFAULT_NODE_HEIGHT = 60;
@@ -328,172 +330,30 @@ declare function blendLayout<TNode extends CanvasNode = CanvasNode>(from: TNode[
328
330
  */
329
331
  declare function connectedOnly<TNode extends CanvasNode = CanvasNode, TEdge extends CanvasEdge = CanvasEdge>(nodes: TNode[], edges: TEdge[]): TNode[];
330
332
 
331
- /**
332
- * Edge (or centre line) used by `alignNodes`.
333
- * `center` aligns horizontal centres (a vertical line); `middle` aligns vertical centres.
334
- */
335
- type AlignEdge = 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom';
336
- /** Axis used by `distributeNodes`. `both` distributes horizontally and vertically. */
337
- type DistributeAxis = 'horizontal' | 'vertical' | 'both';
338
- /**
339
- * `gaps` leaves equal space between neighbouring nodes, `centres` spaces their centres equally.
340
- */
341
- type DistributeMode = 'gaps' | 'centres';
342
- /** Dimension(s) copied by `matchSize`. */
343
- type SizeDimension = 'width' | 'height' | 'both';
344
- /**
345
- * Partial node change produced by the arrange helpers. `position` is in the node's own
346
- * coordinate space (relative to its `parentId` parent when it has one).
347
- */
348
- interface CanvasNodeUpdate {
349
- id: string;
350
- position?: XYPosition;
351
- width?: number;
352
- height?: number;
353
- zIndex?: number;
354
- }
355
- interface NodeRectOptions {
356
- /** Width used when a node has no measured, explicit or style size and estimation is off. */
357
- defaultWidth?: number;
358
- /** Height used when a node has no measured, explicit or style size and estimation is off. */
359
- defaultHeight?: number;
360
- /** Estimate unmeasured node sizes from their label via `estimateNodeSize` (default `true`). */
361
- estimate?: boolean;
362
- }
363
- /** Data key marking a node as locked. See `lockNodes`. */
364
- declare const LOCKED_DATA_KEY = "locked";
365
- /** Data key holding the interaction flags a node had before it was locked. */
366
- declare const LOCKED_PREVIOUS_FLAGS_KEY = "lockedPrevious";
367
- /**
368
- * Reads a pixel `width` / `height` from a node style, which may be a CSS string (SvelteFlow)
369
- * or a style object (React Flow). Non-pixel values (`%`, `auto`, ...) are ignored.
370
- */
371
- declare function readStyleSize(style: CanvasNodeStyle | undefined | null): {
372
- width?: number;
373
- height?: number;
374
- };
375
- /**
376
- * Returns `style` with its pixel `width` / `height` replaced, keeping its representation
377
- * (string or object). Only dimensions already present in the style are rewritten, because
378
- * xyflow applies the node's own `width` / `height` props otherwise.
379
- */
380
- declare function withStyleSize(style: CanvasNodeStyle | undefined | null, size: {
381
- width?: number;
382
- height?: number;
383
- }): CanvasNodeStyle | undefined;
384
- /**
385
- * Resolves a node's size: measured DOM size, then explicit `width` / `height`, then a pixel
386
- * size in `style`, then a label-based estimate (or the defaults when `estimate` is `false`).
387
- */
388
- declare function getNodeSize(node: CanvasNode, options?: NodeRectOptions): {
389
- width: number;
390
- height: number;
391
- };
392
- /**
393
- * Absolute (flow-space) position of a node, following its `parentId` chain.
394
- * Cycles and missing parents are tolerated.
395
- */
396
- declare function getAbsolutePosition(node: CanvasNode, lookup: ReadonlyMap<string, CanvasNode>): XYPosition;
397
- /** Builds an id lookup for a node array. */
398
- declare function createNodeLookup<TNode extends CanvasNode>(nodes: TNode[]): Map<string, TNode>;
399
- /** Absolute flow-space rectangle of a node. */
400
- declare function getNodeRect(node: CanvasNode, lookup?: ReadonlyMap<string, CanvasNode>, options?: NodeRectOptions): Rect;
401
- /**
402
- * Whether a node has been locked with `lockNodes`.
403
- */
404
- declare function isLocked(node: CanvasNode | undefined | null): boolean;
405
- /**
406
- * Locks nodes: marks `data.locked = true` and sets `draggable: false` and `deletable: false`
407
- * so both SvelteFlow and React Flow stop dragging and deleting them. Nodes stay `selectable`
408
- * so they can be unlocked again. The previous flags are kept in `data.lockedPrevious` and
409
- * restored by `unlockNodes`. Returns a new array; untouched nodes keep their identity.
410
- */
411
- declare function lockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[];
412
- /**
413
- * Reverses `lockNodes`, restoring the `draggable` / `deletable` flags the node had before.
414
- */
415
- declare function unlockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[];
416
- /** Locks the given nodes if any of them is unlocked, otherwise unlocks them all. */
417
- declare function toggleLockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[];
418
- interface AlignNodesOptions extends NodeRectOptions {
419
- /**
420
- * What to align to: the selection bounding box (default) or a fixed rectangle.
421
- * Locked nodes never move but still count towards the selection bounds, so they act as anchors.
422
- */
423
- to?: 'selection' | Rect;
424
- }
425
- /**
426
- * Aligns nodes to a shared edge or centre line of their bounding box (Freeform's
427
- * Align Left / Center / Right / Top / Middle / Bottom). Needs at least two nodes unless
428
- * `options.to` is a rectangle. Locked nodes are not moved.
429
- */
430
- declare function alignNodes(nodes: CanvasNode[], ids: Iterable<string>, edge: AlignEdge, options?: AlignNodesOptions): CanvasNodeUpdate[];
431
- interface DistributeNodesOptions extends NodeRectOptions {
432
- /** Explicit gap between neighbours for `gaps` mode. Default: spread evenly between the outer nodes. */
433
- gap?: number;
434
- }
435
- /**
436
- * Distributes nodes along an axis (Freeform's Distribute Horizontally / Vertically / Evenly).
437
- * The outermost nodes stay put and the others are spaced with equal gaps (`gaps`, default)
438
- * or equally spaced centres (`centres`). Needs at least three nodes (two with an explicit
439
- * `gap`). Locked nodes keep their place in the order but are not moved.
440
- */
441
- declare function distributeNodes(nodes: CanvasNode[], ids: Iterable<string>, axis: DistributeAxis, mode?: DistributeMode, options?: DistributeNodesOptions): CanvasNodeUpdate[];
442
- interface MatchSizeOptions extends NodeRectOptions {
443
- /**
444
- * Size source: the `largest` (default) or `smallest` node, the `first` id given,
445
- * or a specific node id.
446
- */
447
- reference?: 'largest' | 'smallest' | 'first' | {
448
- id: string;
449
- };
450
- /** Keep each node's centre in place instead of its top-left corner (default `false`). */
451
- keepCentre?: boolean;
452
- }
453
- /**
454
- * Makes nodes the same width, height or both (Freeform's Make Same Size). Returns size
455
- * updates (plus position updates with `keepCentre`). Locked nodes are not resized but can
456
- * be the reference.
457
- */
458
- declare function matchSize(nodes: CanvasNode[], ids: Iterable<string>, dimension: SizeDimension, options?: MatchSizeOptions): CanvasNodeUpdate[];
459
- /** Z-order operation. */
460
- type ZOrderOperation = 'bringForward' | 'sendBackward' | 'bringToFront' | 'sendToBack';
461
- /**
462
- * Restacks nodes (Bring Forward / Send Backward / Bring to Front / Send to Back).
463
- *
464
- * Both SvelteFlow and React Flow stack nodes by `zIndex` (missing = 0) and, for equal
465
- * `zIndex`, by array order. This computes that effective order for the siblings that share
466
- * the targets' parent, applies the operation, and returns `zIndex` updates assigning each
467
- * sibling its new rank. The array order is left alone, because xyflow requires parents to
468
- * precede their children. Locked nodes are not restacked. "Forward" moves past the next
469
- * sibling the node overlaps, falling back to the next sibling when nothing overlaps.
470
- */
471
- declare function reorderNodes(nodes: CanvasNode[], ids: Iterable<string>, operation: ZOrderOperation, options?: NodeRectOptions): CanvasNodeUpdate[];
472
- /**
473
- * Applies `CanvasNodeUpdate`s to a node array, returning a new array. Unchanged nodes keep
474
- * their identity. Size updates set `width` / `height` and rewrite pixel sizes already present
475
- * in `style` (string or object). An existing `measured` size is updated too, so geometry
476
- * helpers see the new size before xyflow re-measures the node.
477
- */
478
- declare function applyNodeUpdates<TNode extends CanvasNode>(nodes: TNode[], updates: CanvasNodeUpdate[]): TNode[];
479
- /**
480
- * Inverse of a set of updates against the current nodes, for undo. Restores the previous
481
- * position / size / zIndex of every node an update touches.
482
- */
483
- declare function invertNodeUpdates(nodes: CanvasNode[], updates: CanvasNodeUpdate[]): CanvasNodeUpdate[];
484
-
485
333
  /**
486
334
  * A guide line to render while dragging, in flow coordinates.
487
335
  * `edge` and `centre` guides are alignment lines; `spacing` guides mark equal gaps.
336
+ * `measure` lines (with a distance `label`) and dashed `extension` lines come from the
337
+ * distance measuring helpers (`measureBetween`, `measureSelection`).
488
338
  */
489
339
  interface GuideLine {
490
340
  orientation: 'vertical' | 'horizontal';
491
- kind: 'edge' | 'centre' | 'spacing';
341
+ kind: 'edge' | 'centre' | 'spacing' | 'measure' | 'extension';
492
342
  x1: number;
493
343
  y1: number;
494
344
  x2: number;
495
345
  y2: number;
346
+ /** Text drawn at the middle of the line (measuring distances). */
347
+ label?: string;
496
348
  }
349
+ /**
350
+ * Snaps `value` to a grid with the given `spacing` and `origin`. A spacing of 0 or less
351
+ * (or a non-finite one) leaves the value unchanged.
352
+ *
353
+ * Ported from vectorcraft-main/crates/geom/src/snap.rs (MIT OR Apache-2.0, © ArtCraft Team
354
+ * and contributors). See THIRD_PARTY_NOTICES.md.
355
+ */
356
+ declare function snapToGrid(value: number, spacing: number, origin?: number): number;
497
357
  interface SnapGuidesOptions {
498
358
  /** Current viewport zoom; the threshold is in screen pixels and divided by it (default 1). */
499
359
  zoom?: number;
@@ -505,6 +365,13 @@ interface SnapGuidesOptions {
505
365
  centres?: boolean;
506
366
  /** Snap to equal spacing between neighbouring shapes in the same row / column (default `true`). */
507
367
  spacing?: boolean;
368
+ /**
369
+ * Grid spacing in flow units (default 0, off). An axis that does not snap to a shape snaps
370
+ * its top-left (drag) or moving edge (resize) to the grid; grid snaps draw no guides.
371
+ */
372
+ gridSize?: number;
373
+ /** Grid origin in flow units (default `{ x: 0, y: 0 }`). */
374
+ gridOrigin?: XYPosition;
508
375
  }
509
376
  interface SnapResult {
510
377
  /** Snapped top-left of the moving rectangle. */
@@ -513,6 +380,7 @@ interface SnapResult {
513
380
  offset: XYPosition;
514
381
  /** Guide segments to draw for the snap that was applied. */
515
382
  guides: GuideLine[];
383
+ /** Whether x snapped to a shape (grid snaps do not count). */
516
384
  snappedX: boolean;
517
385
  snappedY: boolean;
518
386
  }
@@ -739,31 +607,498 @@ declare function fitViewportToNodes(nodes: CanvasNode[], size: {
739
607
  }, ids?: Iterable<string>, options?: FitViewportOptions & NodeRectOptions): CanvasViewport | null;
740
608
 
741
609
  /**
742
- * Canvas arrangement actions that have keyboard shortcuts.
610
+ * Polygon boolean operations (union, intersection, difference, xor and divide) on
611
+ * multi-polygons with holes.
612
+ *
613
+ * Algorithm (overlay + winding classification):
614
+ * 1. Every input edge is collected into one segment set. Vertices are merged through a
615
+ * tolerance-aware point pool, so points closer than `epsilon` become one vertex.
616
+ * 2. Segments are split at every crossing, T-junction and collinear overlap until no two
617
+ * segments cross (a planar arrangement). Splitting is repeated because snapping a new
618
+ * vertex can move a segment by up to `epsilon`.
619
+ * 3. Coincident segments (shared edges) are merged, keeping for every operand the signed
620
+ * number of its edges that run along the segment.
621
+ * 4. For every segment the winding number of each operand on its right side is computed
622
+ * with a ray cast from the segment midpoint (the segment itself excluded), and the left
623
+ * side follows from the signed multiplicities. The fill rule turns windings into
624
+ * inside / outside flags and the operation decides whether each side is in the result.
625
+ * 5. Segments with the result on exactly one side are kept, oriented with the result on
626
+ * their left, and traced into rings (taking the sharpest left turn at shared vertices,
627
+ * so rings that touch at a vertex stay separate simple rings). Rings with a positive
628
+ * signed area are outer rings; negative ones are holes, assigned to the smallest outer
629
+ * ring that contains them.
630
+ *
631
+ * Handles shared edges, touching vertices, collinear overlaps, holes, nested and disjoint
632
+ * inputs and self-intersecting input rings (resolved by the operand's fill rule).
633
+ *
634
+ * Limits: the overlay is O(n²) in the number of edges (pruned by a sweep on x), which is
635
+ * comfortable for canvas shapes (thousands of edges) but not for GIS-scale data. Features
636
+ * narrower than `epsilon` collapse. Coordinates must be finite.
743
637
  */
744
- type CanvasArrangeAction = 'alignLeft' | 'alignCenter' | 'alignRight' | 'alignTop' | 'alignMiddle' | 'alignBottom' | 'distributeHorizontal' | 'distributeVertical' | 'lock' | 'unlock' | 'bringForward' | 'sendBackward' | 'bringToFront' | 'sendToBack' | 'nextScene' | 'previousScene';
745
- interface ArrangeShortcutBinding {
746
- action: CanvasArrangeAction;
747
- /** `KeyboardEvent.code` (layout- and Option-independent, e.g. `KeyA`, `BracketRight`). */
748
- code: string;
749
- mod?: boolean;
750
- alt?: boolean;
751
- shift?: boolean;
752
- /** Human readable label, e.g. for menus. `Mod` is ⌘ on macOS and Ctrl elsewhere. */
753
- label: string;
638
+
639
+ /** A closed ring of points. The closing point may be repeated or omitted. */
640
+ type PolygonRing = Point[];
641
+ /** A polygon: the outer ring followed by its holes. */
642
+ type PolygonWithHoles = PolygonRing[];
643
+ /** A set of polygons. */
644
+ type MultiPolygon = PolygonWithHoles[];
645
+ /** SVG fill rule used to decide which areas of a ring set are inside. */
646
+ type PolygonFillRule = 'nonzero' | 'evenodd';
647
+ /** Boolean operations. `xor` is Freeform's Exclude. */
648
+ type PolygonBooleanOperation = 'union' | 'intersection' | 'difference' | 'xor';
649
+ /**
650
+ * A raw ring set with a fill rule (e.g. flattened SVG path data). Ring orientation is
651
+ * significant for `nonzero`.
652
+ */
653
+ interface PolygonOperand {
654
+ rings: PolygonRing[];
655
+ fillRule?: PolygonFillRule;
754
656
  }
657
+ type PolygonInput = MultiPolygon | PolygonOperand;
658
+ interface PolygonBooleanOptions {
659
+ /**
660
+ * Distance below which points are treated as equal. Defaults to `1e-9` times the
661
+ * largest coordinate magnitude of the inputs (at least `1e-9`).
662
+ */
663
+ epsilon?: number;
664
+ }
665
+ /** One region of `dividePolygons`, with the indices of the operands that cover it. */
666
+ interface PolygonPiece {
667
+ polygon: PolygonWithHoles;
668
+ operands: number[];
669
+ }
670
+ /** Signed shoelace area of a ring (positive when counter-clockwise in a y-up frame). */
671
+ declare function ringSignedArea(ring: readonly Point[]): number;
672
+ /** Area of a polygon (outer ring minus holes). */
673
+ declare function polygonArea(polygon: PolygonWithHoles): number;
674
+ /** Total area of a multi-polygon (holes subtracted). */
675
+ declare function multiPolygonArea(multi: MultiPolygon): number;
676
+ /** Axis-aligned bounds of a multi-polygon (or ring list), or `null` when empty. */
677
+ declare function multiPolygonBounds(input: MultiPolygon | PolygonRing[]): {
678
+ minX: number;
679
+ minY: number;
680
+ maxX: number;
681
+ maxY: number;
682
+ } | null;
683
+ /**
684
+ * Whether a point is inside a ring set under a fill rule. Points exactly on an edge may
685
+ * report either side.
686
+ */
687
+ declare function pointInRings(point: Point, rings: readonly (readonly Point[])[], fillRule?: PolygonFillRule): boolean;
688
+ /** Whether a point is inside a multi-polygon (holes excluded). */
689
+ declare function pointInMultiPolygon(point: Point, multi: MultiPolygon): boolean;
690
+ /**
691
+ * Applies a boolean operation to any number of operands.
692
+ * - `union`: covered by any operand.
693
+ * - `intersection`: covered by every operand.
694
+ * - `difference`: the first operand minus all others.
695
+ * - `xor`: covered by an odd number of operands (Exclude).
696
+ *
697
+ * Returns simple rings: outer rings have a positive signed area, holes a negative one.
698
+ * Rings may touch at vertices but never cross.
699
+ */
700
+ declare function polygonBoolean(operation: PolygonBooleanOperation, operands: PolygonInput[], options?: PolygonBooleanOptions): MultiPolygon;
701
+ declare const unionPolygons: (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) => MultiPolygon;
702
+ declare const intersectPolygons: (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) => MultiPolygon;
703
+ declare const subtractPolygons: (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) => MultiPolygon;
704
+ declare const xorPolygons: (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) => MultiPolygon;
755
705
  /**
756
- * Default bindings. Align/distribute follow Figma (Option/Alt + letter), layer order follows
757
- * the common `Mod+]` / `Mod+[` convention, lock follows Keynote/Freeform (`Mod+L`,
758
- * `Mod+Alt+L` to unlock) and scenes step with PageDown / PageUp.
706
+ * Splits the overlay of the operands into its distinct regions (Freeform's Divide): every
707
+ * connected area covered by the same set of operands becomes one piece. Pieces are ordered
708
+ * by their operand set, then by size (largest first).
759
709
  */
760
- declare const DEFAULT_ARRANGE_SHORTCUTS: readonly ArrangeShortcutBinding[];
710
+ declare function dividePolygons(operands: PolygonInput[], options?: PolygonBooleanOptions): PolygonPiece[];
711
+
761
712
  /**
762
- * Resolves a keydown event to an arrange action, or `null`. Events from text inputs,
763
- * textareas, selects, contenteditable and ProseMirror are ignored (see `isEventFromTextInput`).
764
- * `Mod` is ⌘ or Ctrl.
713
+ * Shape geometry: shape descriptions (rect, rounded rect, ellipse, regular polygon, star and
714
+ * SVG path data) to polygons, SVG path parsing / flattening, and polygons back to path data.
765
715
  */
766
- declare function matchArrangeShortcut(event: KeyboardEvent, bindings?: readonly ArrangeShortcutBinding[]): CanvasArrangeAction | null;
716
+
717
+ /** Paint shared by every shape kind. Colours are any CSS colour. */
718
+ interface ShapePaint {
719
+ fill?: string;
720
+ stroke?: string;
721
+ strokeWidth?: number;
722
+ opacity?: number;
723
+ }
724
+ interface ShapeBase extends ShapePaint {
725
+ /** Clockwise rotation in degrees (on a y-down canvas) about the centre of the node box. */
726
+ rotation?: number;
727
+ }
728
+ /** Rectangle filling the node box, optionally with rounded corners. */
729
+ interface RectShape extends ShapeBase {
730
+ kind: 'rect';
731
+ /** One radius, or `[topLeft, topRight, bottomRight, bottomLeft]`. Clamped to fit. */
732
+ cornerRadius?: number | readonly [number, number, number, number];
733
+ }
734
+ /** Ellipse (circle when the node is square) inscribed in the node box. */
735
+ interface EllipseShape extends ShapeBase {
736
+ kind: 'ellipse';
737
+ }
738
+ /** Regular polygon, stretched so its bounding box fills the node box. */
739
+ interface RegularPolygonShape extends ShapeBase {
740
+ kind: 'polygon';
741
+ /** Number of sides (at least 3, default 6). The first vertex points up. */
742
+ sides?: number;
743
+ }
744
+ /** Star, stretched so its bounding box fills the node box. */
745
+ interface StarShape extends ShapeBase {
746
+ kind: 'star';
747
+ /** Number of points (at least 3, default 5). The first point points up. */
748
+ points?: number;
749
+ /** Inner radius as a fraction of the outer radius (default 0.5). */
750
+ innerRadius?: number;
751
+ }
752
+ /** Arbitrary SVG path data. */
753
+ interface PathShape extends ShapeBase {
754
+ kind: 'path';
755
+ d: string;
756
+ /**
757
+ * `[minX, minY, width, height]` of the path's coordinate space, scaled to the node box.
758
+ * Without it the path coordinates are node-local pixels.
759
+ */
760
+ viewBox?: readonly [number, number, number, number];
761
+ /** Fill rule (default `nonzero`, as in SVG). */
762
+ fillRule?: PolygonFillRule;
763
+ }
764
+ /**
765
+ * Shape description stored on a node as `data.shape`. Geometry is relative to the node
766
+ * box (`0,0` to `width,height`), so resizing the node resizes the shape.
767
+ */
768
+ type ShapeDescription = RectShape | EllipseShape | RegularPolygonShape | StarShape | PathShape;
769
+ type ShapeKind = ShapeDescription['kind'];
770
+ declare const SHAPE_KINDS: readonly ShapeKind[];
771
+ /** Whether a value is a supported shape description. */
772
+ declare function isShapeDescription(value: unknown): value is ShapeDescription;
773
+ interface ShapeSize {
774
+ width: number;
775
+ height: number;
776
+ }
777
+ interface FlattenOptions {
778
+ /** Maximum distance between a curve and its polyline, in output units (default 0.25). */
779
+ tolerance?: number;
780
+ /** Upper bound on the segments one curve is split into (default 512). */
781
+ maxSegments?: number;
782
+ }
783
+ /** Normalised absolute path command produced by `parseSvgPath`. */
784
+ type PathCommand = {
785
+ type: 'M';
786
+ x: number;
787
+ y: number;
788
+ } | {
789
+ type: 'L';
790
+ x: number;
791
+ y: number;
792
+ } | {
793
+ type: 'C';
794
+ x1: number;
795
+ y1: number;
796
+ x2: number;
797
+ y2: number;
798
+ x: number;
799
+ y: number;
800
+ } | {
801
+ type: 'Q';
802
+ x1: number;
803
+ y1: number;
804
+ x: number;
805
+ y: number;
806
+ } | {
807
+ type: 'A';
808
+ rx: number;
809
+ ry: number;
810
+ rotation: number;
811
+ largeArc: boolean;
812
+ sweep: boolean;
813
+ x: number;
814
+ y: number;
815
+ } | {
816
+ type: 'Z';
817
+ };
818
+ /**
819
+ * Parses SVG path data into absolute `M`, `L`, `C`, `Q`, `A` and `Z` commands. Relative
820
+ * commands, `H` / `V`, smooth curves (`S` / `T`) and implicit repeated commands are
821
+ * normalised. Parsing stops at the first malformed command (as browsers do).
822
+ */
823
+ declare function parseSvgPath(d: string): PathCommand[];
824
+ /**
825
+ * Flattens SVG path data into closed rings (one per subpath; open subpaths are closed the
826
+ * way SVG fills them). Curves are approximated within `tolerance`.
827
+ */
828
+ declare function flattenSvgPath(d: string | PathCommand[], options?: FlattenOptions): PolygonRing[];
829
+ /**
830
+ * SVG path data for a shape in node-local pixels (exact curves: arcs for rounded corners
831
+ * and ellipses), plus the fill rule and a `transform` for rotation / path view boxes.
832
+ * Use it to render a shape; use `shapeToPolygon` for geometry.
833
+ */
834
+ declare function getShapeSvg(shape: ShapeDescription, size: ShapeSize): {
835
+ d: string;
836
+ transform?: string;
837
+ fillRule: PolygonFillRule;
838
+ };
839
+ /** Everything a renderer needs to draw a shape as one SVG `<path>`. */
840
+ interface ShapeRenderData {
841
+ /** SVG `viewBox` (`0 0 width height`): node-local pixels. */
842
+ viewBox: string;
843
+ width: number;
844
+ height: number;
845
+ d: string;
846
+ transform?: string;
847
+ fillRule: PolygonFillRule;
848
+ fill: string;
849
+ stroke: string;
850
+ strokeWidth: number;
851
+ opacity?: number;
852
+ }
853
+ /**
854
+ * Resolves the SVG drawing of a shape for a node size. Without a size, a path's view box
855
+ * size (or 100x100) is used. Paint defaults: `fill: currentColor`, `stroke: none`,
856
+ * `strokeWidth: 1`.
857
+ */
858
+ declare function getShapeRenderData(shape: ShapeDescription, size?: Partial<ShapeSize>): ShapeRenderData;
859
+ /**
860
+ * Converts a shape to rings in node-local pixels (rotation applied), flattening curves at
861
+ * `tolerance`. Returns the rings with the fill rule they must be read with.
862
+ */
863
+ declare function shapeToPolygon(shape: ShapeDescription, size: ShapeSize, options?: FlattenOptions): {
864
+ rings: PolygonRing[];
865
+ fillRule: PolygonFillRule;
866
+ };
867
+ /** Formats a number with at most `precision` decimals, without trailing zeros. */
868
+ declare function formatNumber(value: number, precision?: number): string;
869
+ /**
870
+ * Simplifies a closed ring with Ramer–Douglas–Peucker at `tolerance`. Returns an empty
871
+ * ring when fewer than three points survive.
872
+ */
873
+ declare function simplifyRing(ring: readonly Point[], tolerance: number): Point[];
874
+ interface PolygonToPathOptions {
875
+ /** Decimal places in the output (default 3). */
876
+ precision?: number;
877
+ /** Ramer–Douglas–Peucker tolerance; `0` (default) keeps every vertex. */
878
+ simplify?: number;
879
+ /** Added to every point before formatting (e.g. to make coordinates node-local). */
880
+ offset?: {
881
+ x: number;
882
+ y: number;
883
+ };
884
+ /**
885
+ * `'lines'` (default) writes every ring as `M`/`L`/`Z`. `'fit'` fits cubic Béziers to each
886
+ * ring (`fitPathData`), so flattened curves come back as smooth `C` curves while sharp
887
+ * corners (turning more than `cornerAngleDeg`) and straight edges stay as they are.
888
+ */
889
+ curves?: 'lines' | 'fit';
890
+ /** Curve-fitting tolerance in path units for `curves: 'fit'` (default 0.25). */
891
+ curveTolerance?: number;
892
+ /** Corner threshold in degrees for `curves: 'fit'` (default 30). */
893
+ cornerAngleDeg?: number;
894
+ }
895
+ /**
896
+ * Converts a multi-polygon (or a list of rings) to SVG path data using `M`/`L`/`Z`. Holes
897
+ * keep their opposite orientation, so the path fills correctly with either fill rule.
898
+ */
899
+ declare function polygonToSvgPath(input: MultiPolygon | PolygonRing[], options?: PolygonToPathOptions): string;
900
+
901
+ /**
902
+ * Shape nodes and Freeform-style shape boolean operations (Unite / Intersect / Subtract /
903
+ * Exclude / Divide) on canvas nodes.
904
+ *
905
+ * A shape node is any node whose `data.shape` is a `ShapeDescription`. Its geometry is
906
+ * relative to the node box, so moving or resizing the node moves / resizes the shape.
907
+ */
908
+
909
+ /** Data key holding a node's shape description. */
910
+ declare const SHAPE_DATA_KEY = "shape";
911
+ /** Node type suggested for shape nodes (register `ShapeNode` under it). */
912
+ declare const SHAPE_NODE_TYPE = "shape";
913
+ /** Freeform's shape operations. `exclude` is xor; `divide` splits into every region. */
914
+ type ShapeOperation = 'unite' | 'intersect' | 'subtract' | 'exclude' | 'divide';
915
+ declare const SHAPE_OPERATIONS: readonly ShapeOperation[];
916
+ /** Whether a node carries a supported `data.shape`. */
917
+ declare function isShapeNode(node: CanvasNode | undefined | null): boolean;
918
+ /** The node's shape description, or `null`. */
919
+ declare function getNodeShape(node: CanvasNode | undefined | null): ShapeDescription | null;
920
+ interface ShapeNodePolygonOptions extends NodeRectOptions {
921
+ /** Curve flattening tolerance in flow units (default 0.25). */
922
+ tolerance?: number;
923
+ }
924
+ /**
925
+ * A shape node's outline in absolute flow coordinates (parent offsets, size and rotation
926
+ * applied), or `null` for non-shape nodes.
927
+ */
928
+ declare function shapeNodeToPolygon(node: CanvasNode, lookup?: ReadonlyMap<string, CanvasNode>, options?: ShapeNodePolygonOptions): {
929
+ rings: PolygonRing[];
930
+ fillRule: PolygonFillRule;
931
+ } | null;
932
+ /** Why `combineShapeNodes` refused. */
933
+ type CombineShapeNodesRefusal =
934
+ /** A selected shape is locked; locked shapes are never consumed. */
935
+ 'locked'
936
+ /** Fewer than two shape nodes were selected. */
937
+ | 'too-few'
938
+ /** The operation leaves nothing (e.g. intersecting shapes that do not overlap). */
939
+ | 'empty';
940
+ interface CombineShapeNodesOptions extends ShapeNodePolygonOptions {
941
+ /**
942
+ * Which shape's node style and data the result inherits: `bottom` (default, like
943
+ * Freeform: the back-most shape), `top`, `first` (first id passed), or a node id.
944
+ * Divide pieces always take the style of the top-most shape covering them.
945
+ */
946
+ styleFrom?: 'bottom' | 'top' | 'first' | {
947
+ id: string;
948
+ };
949
+ /** Id factory for new nodes (default `crypto.randomUUID`, with a counter fallback). */
950
+ createId?: (index: number) => string;
951
+ /** Node `type` for the new nodes (default: the style source's type). */
952
+ nodeType?: string;
953
+ /** Decimal places in the path data (default 3). */
954
+ precision?: number;
955
+ /** Ramer–Douglas–Peucker simplification tolerance for the result (default 0, off). */
956
+ simplify?: number;
957
+ /**
958
+ * How the result's outline is written: `'fit'` (default) fits smooth cubic curves to the
959
+ * flattened result, so booleans of curved shapes stay curved (sharp corners and straight
960
+ * edges are kept); `'lines'` writes the polygon as straight segments.
961
+ */
962
+ curves?: 'lines' | 'fit';
963
+ /** Curve-fitting tolerance in flow units for `curves: 'fit'` (default: `tolerance`, 0.25). */
964
+ curveTolerance?: number;
965
+ /** Corner threshold in degrees for `curves: 'fit'` (default 30). */
966
+ cornerAngleDeg?: number;
967
+ /** Point-merge tolerance passed to the boolean engine. */
968
+ epsilon?: number;
969
+ /** Select the new nodes (default `true`). */
970
+ select?: boolean;
971
+ }
972
+ type CombineShapeNodesResult<TNode extends CanvasNode = CanvasNode> = {
973
+ ok: true;
974
+ operation: ShapeOperation;
975
+ /** Ids of the shape nodes consumed by the operation. */
976
+ removeIds: string[];
977
+ /** New path shape node(s): one, or one per region for `divide`. */
978
+ nodes: TNode[];
979
+ } | {
980
+ ok: false;
981
+ operation: ShapeOperation;
982
+ reason: CombineShapeNodesRefusal;
983
+ };
984
+ /**
985
+ * Checks whether `combineShapeNodes` can run, without computing geometry (it cannot
986
+ * predict an `empty` result). Use it to enable menu items.
987
+ */
988
+ declare function canCombineShapeNodes(nodes: CanvasNode[], ids: Iterable<string>): {
989
+ ok: true;
990
+ shapeIds: string[];
991
+ } | {
992
+ ok: false;
993
+ reason: Exclude<CombineShapeNodesRefusal, 'empty'>;
994
+ };
995
+ /**
996
+ * Freeform-style shape operations on canvas nodes.
997
+ *
998
+ * - Only shape nodes (`data.shape`) among `ids` take part; other selected nodes are ignored.
999
+ * - Refuses (`ok: false`) when a selected shape is locked, when fewer than two shapes are
1000
+ * selected, or when the result is empty. Nothing should be changed on refusal.
1001
+ * - `subtract` removes every other shape from the back-most one; the other operations are
1002
+ * symmetric. `divide` returns one node per distinct region of the overlay.
1003
+ * - Each new node is a `path` shape: its position / size is the result's bounding box and
1004
+ * its path data is node-local with `viewBox = [0, 0, width, height]`, so it scales with
1005
+ * the node. Rotation is baked into the path. Node fields and `data` (other than the
1006
+ * shape geometry, lock state and size) come from the style source.
1007
+ * - When all shapes share a parent, the new nodes keep it; otherwise they are top-level.
1008
+ *
1009
+ * Pure: returns the change; apply it with `applyShapeCombination` (or the arrange
1010
+ * controller's `combineShapes`, which also records one undo step).
1011
+ */
1012
+ declare function combineShapeNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>, operation: ShapeOperation, options?: CombineShapeNodesOptions): CombineShapeNodesResult<TNode>;
1013
+ /**
1014
+ * Applies a successful `combineShapeNodes` result: removes the consumed nodes and inserts
1015
+ * the new ones at the array index of the first consumed node.
1016
+ * With `deselect` (default `true`) other nodes are deselected when the result is selected.
1017
+ */
1018
+ declare function applyShapeCombination<TNode extends CanvasNode>(nodes: TNode[], change: {
1019
+ removeIds: Iterable<string>;
1020
+ nodes: TNode[];
1021
+ }, options?: {
1022
+ deselect?: boolean;
1023
+ }): TNode[];
1024
+ /**
1025
+ * Reverses `applyShapeCombination`: removes the created nodes and puts the consumed nodes
1026
+ * back at their original array indices (`previous` is the node array before the change).
1027
+ */
1028
+ declare function revertShapeCombination<TNode extends CanvasNode>(nodes: TNode[], change: {
1029
+ removeIds: Iterable<string>;
1030
+ nodes: TNode[];
1031
+ }, previous: TNode[]): TNode[];
1032
+
1033
+ /** A resize handle, named like xyflow's `ControlPosition`. */
1034
+ type ResizeHandle = 'top-left' | 'top' | 'top-right' | 'right' | 'bottom-right' | 'bottom' | 'bottom-left' | 'left';
1035
+ declare const RESIZE_HANDLES: readonly ResizeHandle[];
1036
+ /** Which edge of each axis a handle moves: -1 the start (left / top), 1 the end, 0 none. */
1037
+ declare function resizeHandleAxes(handle: ResizeHandle): {
1038
+ x: -1 | 0 | 1;
1039
+ y: -1 | 0 | 1;
1040
+ };
1041
+ /**
1042
+ * The handle a resize from `start` to `next` was made with, from the edges that moved, or
1043
+ * `null` when no edge moved.
1044
+ */
1045
+ declare function inferResizeHandle(start: Rect, next: Rect, eps?: number): ResizeHandle | null;
1046
+ interface SnapResizeOptions extends Omit<SnapGuidesOptions, 'spacing'> {
1047
+ /**
1048
+ * Keep the input box's aspect ratio. A corner snaps whichever axis is closer to a target
1049
+ * and the other follows; a side scales the other axis about its centre.
1050
+ */
1051
+ keepAspectRatio?: boolean;
1052
+ /** Smallest width a snap may leave (default: anything above 0). */
1053
+ minWidth?: number;
1054
+ /** Smallest height a snap may leave (default: anything above 0). */
1055
+ minHeight?: number;
1056
+ }
1057
+ interface SnapResizeResult {
1058
+ /** The snapped box. */
1059
+ rect: Rect;
1060
+ /** Guide segments for the shape snaps that were applied (grid snaps draw none). */
1061
+ guides: GuideLine[];
1062
+ /** Whether the moving x edge snapped to a shape. */
1063
+ snappedX: boolean;
1064
+ /** Whether the moving y edge snapped to a shape. */
1065
+ snappedY: boolean;
1066
+ /** Whether `rect` differs from the input. */
1067
+ changed: boolean;
1068
+ }
1069
+ /**
1070
+ * Snaps a resize of the axis-aligned box `rect` made with `handle` against `targets`. The
1071
+ * opposite edge (side handles) or corner (corner handles) stays fixed:
1072
+ *
1073
+ * - A **side** handle snaps its one moving edge to the nearest target edge or centre within
1074
+ * `threshold` screen pixels.
1075
+ * - A **corner** handle snaps x and y independently (unless `keepAspectRatio`).
1076
+ * - A snap that would collapse or flip the box (or go below `minWidth` / `minHeight`) is
1077
+ * rejected; the next nearest target is tried, else the edge stays where it was dragged.
1078
+ * - With `gridSize`, an axis without a shape snap snaps its moving edge to the grid.
1079
+ *
1080
+ * Returns the snapped box and the guides to draw (same shape as drag snapping).
1081
+ */
1082
+ declare function snapResize(rect: Rect, handle: ResizeHandle, targets: Rect[] | GuideIndex, options?: SnapResizeOptions): SnapResizeResult;
1083
+ interface SnapNodeResizeOptions extends SnapResizeOptions, NodeRectOptions {
1084
+ /** Index from `createNodeGuideIndex(nodes, [id])`, reused across resize frames. */
1085
+ index?: GuideIndex;
1086
+ }
1087
+ interface SnapNodeResizeResult extends SnapResizeResult {
1088
+ /** Position / size update for the node (parent-relative position), or none. */
1089
+ updates: CanvasNodeUpdate[];
1090
+ }
1091
+ /**
1092
+ * Snaps a node that is being resized. `rect` is the node's proposed absolute box (e.g. from
1093
+ * a resize event) and `handle` the handle being dragged. Locked nodes are never snapped.
1094
+ * The node itself and its descendants are not snap targets.
1095
+ */
1096
+ declare function snapNodeResize(nodes: CanvasNode[], id: string, rect: Rect, handle: ResizeHandle, options?: SnapNodeResizeOptions): SnapNodeResizeResult;
1097
+ /**
1098
+ * Absolute box of a node while it is being resized: flow libraries write the new size to
1099
+ * `width` / `height` before the DOM is re-measured, so those win over `measured` here.
1100
+ */
1101
+ declare function getResizingNodeRect(node: CanvasNode, lookup: ReadonlyMap<string, CanvasNode>, options?: NodeRectOptions): Rect;
767
1102
 
768
1103
  /** Minimal history surface (`HistoryManager` satisfies it). */
769
1104
  interface ArrangeHistoryTarget {
@@ -782,15 +1117,27 @@ interface CanvasArrangeControllerOptions<TNode extends CanvasNode = CanvasNode>
782
1117
  guides?: Omit<SnapGuidesOptions, 'zoom'>;
783
1118
  /** Turns drag snapping off while keeping the arrange actions (default `true`). */
784
1119
  snapping?: boolean;
1120
+ /** Turns resize snapping off (default: follows `snapping`). */
1121
+ resizeSnapping?: boolean;
1122
+ /** Resize options passed to `snapResize` (`keepAspectRatio`, `minWidth`, `minHeight`). */
1123
+ resize?: {
1124
+ keepAspectRatio?: boolean;
1125
+ minWidth?: number;
1126
+ minHeight?: number;
1127
+ };
785
1128
  /** Size resolution for unmeasured nodes. */
786
1129
  rectOptions?: NodeRectOptions;
787
1130
  /** Called whenever the guide segments change (set while dragging, cleared on drop). */
788
1131
  onGuidesChange?: (guides: GuideLine[]) => void;
789
1132
  }
790
- /** A React Flow / SvelteFlow style node change; only `position` changes are inspected. */
1133
+ /**
1134
+ * A React Flow / SvelteFlow style node change; only `position` changes are inspected.
1135
+ * `id` is optional because a batch can contain changes without one (React Flow's
1136
+ * `add` change carries an `item` instead), so `NodeChange[]` must be accepted as is.
1137
+ */
791
1138
  interface PositionChangeLike {
792
1139
  type: string;
793
- id: string;
1140
+ id?: string;
794
1141
  position?: XYPosition;
795
1142
  dragging?: boolean;
796
1143
  [key: string]: any;
@@ -815,6 +1162,20 @@ declare function createCanvasArrangeController<TNode extends CanvasNode = Canvas
815
1162
  unlock: (ids?: Iterable<string>) => string[];
816
1163
  toggleLock(ids?: Iterable<string>): string[];
817
1164
  isLocked: (id: string) => boolean;
1165
+ /**
1166
+ * Freeform shape operation (Unite / Intersect / Subtract / Exclude / Divide) on the
1167
+ * shape nodes among `ids` (default: the selection). Replaces them with the result as
1168
+ * one undoable step. Refuses without changing anything when a selected shape is
1169
+ * locked, fewer than two shapes are selected, or the result is empty.
1170
+ */
1171
+ combineShapes(operation: ShapeOperation, ids?: Iterable<string>, combineOptions?: CombineShapeNodesOptions): CombineShapeNodesResult<TNode>;
1172
+ unite: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => CombineShapeNodesResult<TNode>;
1173
+ intersect: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => CombineShapeNodesResult<TNode>;
1174
+ subtract: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => CombineShapeNodesResult<TNode>;
1175
+ exclude: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => CombineShapeNodesResult<TNode>;
1176
+ divide: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => CombineShapeNodesResult<TNode>;
1177
+ /** Whether the shape operations can run on `ids` (default: the selection). */
1178
+ canCombineShapes: (ids?: Iterable<string>) => boolean;
818
1179
  /**
819
1180
  * Runs a shortcut action. Scene actions are not handled here (see the scenes store);
820
1181
  * returns `false` for them and for unknown actions.
@@ -834,9 +1195,179 @@ declare function createCanvasArrangeController<TNode extends CanvasNode = Canvas
834
1195
  * A batch without a dragging position change ends the drag and clears the guides.
835
1196
  */
836
1197
  snapPositionChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[];
837
- /** Clears guides and the cached snap targets. */
1198
+ /**
1199
+ * Starts resize snapping for node `id`: records its box (to tell which handle is
1200
+ * dragged) and caches the snap targets. Optional: `resize` starts lazily.
1201
+ */
1202
+ beginResize(id: string): void;
1203
+ /**
1204
+ * Snaps node `id` as currently sized in `getNodes()` (call it after the flow wrote the
1205
+ * new size, e.g. SvelteFlow's `NodeResizer` `onResize`, deferred by a microtask) and
1206
+ * writes the snapped box back through `setNodes` (not recorded in history). `handle`
1207
+ * defaults to the one inferred from the edges that moved since `beginResize`. A locked
1208
+ * node is restored to its starting box.
1209
+ */
1210
+ resize(id: string, handle?: ResizeHandle): SnapNodeResizeResult | null;
1211
+ /**
1212
+ * Ends resize snapping: clears guides and re-applies the last snapped box if the flow
1213
+ * wrote the raw pointer size afterwards.
1214
+ */
1215
+ endResize(): void;
1216
+ /**
1217
+ * React Flow style: snaps the `dimensions` (and matching `position`) changes a
1218
+ * `NodeResizer` emits in an `onNodesChange` batch and updates `guides`. The resize-end
1219
+ * change (`resizing: false`) carries the raw pointer size, so the last snapped size is
1220
+ * kept. Resize changes of locked nodes are dropped. Returns the (possibly new) changes.
1221
+ */
1222
+ snapResizeChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[];
1223
+ /**
1224
+ * Clears guides and the cached snap targets. Call it from the flow's drag-stop
1225
+ * handler: flow libraries write the raw pointer position when a drag ends, so the
1226
+ * last snapped positions from `drag` are written back here.
1227
+ */
838
1228
  endDrag(): void;
839
1229
  };
840
1230
  type CanvasArrangeController<TNode extends CanvasNode = CanvasNode> = ReturnType<typeof createCanvasArrangeController<TNode>>;
841
1231
 
842
- export { type AlignEdge, type AlignNodesOptions, type ArrangeHistoryTarget, type ArrangeShortcutBinding, CONNECTION_LINE_TYPES, type CanvasArrangeAction, type CanvasArrangeController, type CanvasArrangeControllerOptions, CanvasClipboardPayload, type CanvasConnectionLineType, CanvasEdge, type CanvasHeightOptions, CanvasLayoutOptions, CanvasNode, CanvasNodeStyle, type CanvasNodeUpdate, type CanvasScene, type CanvasScenesOptions, type CanvasScenesState, CanvasScenesStore, type CanvasViewport, type Clock, type ConnectorSnap, type ConnectorSnapOptions, type ConnectorSnapTarget, DEFAULT_ARRANGE_SHORTCUTS, DEFAULT_NODE_HEIGHT, DEFAULT_NODE_WIDTH, DEFAULT_PLACEMENT_GAP, DirectionalDisplacementOptions, type DistributeAxis, type DistributeMode, type DistributeNodesOptions, FanOutTrajectoryOptions, type FitViewportOptions, type GuideIndex, type GuideLine, IncrementalLayoutOptions, LOCKED_DATA_KEY, LOCKED_PREVIOUS_FLAGS_KEY, ManualClock, type MatchSizeOptions, type MultiNodeTransitionOptions, type NodeRectOptions, NodeTransition, PlaceBesideOptions, Position, type PositionChangeLike, Rect, ReflowDisplacementOptions, type SerializedCanvasScenes, Side, type SizeDimension, type SnapGuidesOptions, type SnapNodeDragOptions, type SnapNodeDragResult, type SnapResult, StreamingLayoutManager, type StreamingLayoutManagerOptions, TrajectoryFunction, XYPosition, type ZOrderOperation, alignNodes, applyNodeUpdates, blendLayout, boundsOf, calculateDagreReflowDisplacement, calculateDirectionalDisplacement, canvasHeight, centerNodes, centre, collectDescendantIds, computeNodesBoundingBox, computeSnapGuides, connectedOnly, createBezierTrajectory, createCanvasArrangeController, createCanvasScenesStore, createFanOutTrajectory, createGuideIndex, createNodeGuideIndex, createNodeLookup, defaultEasing, distributeNodes, ensureLayout, estimateCanvasHeight, estimateNodeSize, findConnectorSnap, fitViewportToNodes, fitViewportToRect, flowSide, getAbsolutePosition, getCenteredNodePosition, getLayoutedNodes, getNodeRect, getNodeSize, handlePoint, invertNodeUpdates, isEventFromTextInput, isLocked, isValidCanvasEdge, isValidCanvasNode, isValidCanvasScene, isValidUrl, layoutBounds, linearPositionTrajectory, linearTrajectory, lockNodes, matchArrangeShortcut, matchSize, nearestHandlePositions, oppositePosition, overlaps, pageHidden, parseCanvasClipboardData, parseCanvasScenes, placeBeside, positionToSide, readStyleSize, realClock, remapPastedNodes, reorderNodes, repositionNodesTo, runMultiNodeTransition, serializeCanvasNodes, sideToPosition, snapNodeDrag, toggleLockNodes, unlockNodes, withStyleOpacity, withStyleSize };
1232
+ /**
1233
+ * A measuring line in flow coordinates. `measure` lines carry a `label` (the distance);
1234
+ * `extension` lines are dashed, unlabelled lines that carry a gap over to the measured box
1235
+ * when the gap line passes beside it.
1236
+ */
1237
+ interface MeasureGuide extends GuideLine {
1238
+ kind: 'measure' | 'extension';
1239
+ /** The distance as shown on the label (`measure` lines only). */
1240
+ label?: string;
1241
+ }
1242
+ /** A distance as shown on a label: absolute value, up to two decimals, no trailing zeros. */
1243
+ declare function measureLabel(distance: number): string;
1244
+ /**
1245
+ * Distance lines from box `a` (the selection) to box `b` (the hovered shape):
1246
+ *
1247
+ * - **One inside the other**: from the inner box to each edge of the outer one, along the
1248
+ * inner box's centre lines (the same lines either way round).
1249
+ * - **Overlapping**: between the matching edges (left to left, right to right, ...).
1250
+ * - **Apart**: the horizontal and/or vertical gap, across the shared rows / columns, or from
1251
+ * the middle of `a` with a dashed `extension` line to `b` when the gap line passes beside it.
1252
+ * Touching boxes have no gap to draw.
1253
+ */
1254
+ declare function measureBetween(a: Rect, b: Rect): MeasureGuide[];
1255
+ /**
1256
+ * Distance lines from box `a` to an infinite guide line (`vertical` at x = `at`, otherwise
1257
+ * horizontal at y = `at`): to the nearer side, or to both sides when the line crosses the box.
1258
+ */
1259
+ declare function measureToLine(a: Rect, orientation: 'vertical' | 'horizontal', at: number): MeasureGuide[];
1260
+ interface MeasureSelectionResult {
1261
+ /** Measuring lines in flow coordinates. */
1262
+ guides: MeasureGuide[];
1263
+ /** The box measured to (to outline), or `null` when nothing is measured. */
1264
+ target: Rect | null;
1265
+ /** The selection's bounding box, or `null` without a selection. */
1266
+ selection: Rect | null;
1267
+ }
1268
+ interface MeasureSelectionOptions extends NodeRectOptions {
1269
+ }
1270
+ /**
1271
+ * Alt-hover measuring on canvas nodes: from the bounding box of `selectedIds` (absolute,
1272
+ * union of several nodes) to the hovered node. Hovering the selection itself, or nothing
1273
+ * (`hoveredId` null), measures to the selection's parent (`parentId`) when it has one;
1274
+ * a top-level selection then measures nothing. Hidden nodes are ignored.
1275
+ */
1276
+ declare function measureSelection(nodes: CanvasNode[], selectedIds: Iterable<string>, hoveredId: string | null | undefined, options?: MeasureSelectionOptions): MeasureSelectionResult;
1277
+ /**
1278
+ * Whether the measuring modifier (Alt / Option) is held after a keyboard or pointer event.
1279
+ * `keyup` of Alt releases it; `keydown` of Alt or any event with `altKey` holds it.
1280
+ */
1281
+ declare function isMeasureModifierHeld(event: {
1282
+ type?: string;
1283
+ key?: string;
1284
+ altKey?: boolean;
1285
+ } | null | undefined): boolean;
1286
+
1287
+ /**
1288
+ * Least-squares cubic Bézier fitting of point sequences (Schneider-style: fit one cubic with
1289
+ * fixed end tangents, reparameterise with Newton steps, split at the worst point), with
1290
+ * corner preservation by a turning-angle test.
1291
+ *
1292
+ * Portions ported from vectorcraft-main/crates/pathops/src/fit.rs and
1293
+ * vectorcraft-main/crates/pathops/src/edit.rs (MIT OR Apache-2.0, © ArtCraft Team and
1294
+ * contributors). See THIRD_PARTY_NOTICES.md.
1295
+ */
1296
+
1297
+ /** One cubic Bézier segment. */
1298
+ interface CubicSegment {
1299
+ p0: Point;
1300
+ p1: Point;
1301
+ p2: Point;
1302
+ p3: Point;
1303
+ }
1304
+ interface FitCubicsOptions {
1305
+ /**
1306
+ * Vertices where the polyline turns by more than this many degrees stay sharp corners
1307
+ * (default 30). Use 180 to smooth through every vertex.
1308
+ */
1309
+ cornerAngleDeg?: number;
1310
+ /** Treat the points as a closed ring (the last point connects back to the first). */
1311
+ closed?: boolean;
1312
+ }
1313
+ /** Evaluates a cubic segment at `t` ∈ [0, 1]. */
1314
+ declare function cubicPointAt(c: CubicSegment, t: number): Point;
1315
+ /**
1316
+ * Fits a G1-continuous chain of cubics through `points` (an open run) with the unit
1317
+ * tangents `t0` (leaving the first point) and `t1` (arriving at the last point, forward);
1318
+ * both are estimated from the end points when omitted. Every point lies within `tolerance`
1319
+ * of the curve at its fitted parameter.
1320
+ */
1321
+ declare function fitCubicRun(points: readonly Point[], tolerance: number, t0?: Point, t1?: Point): CubicSegment[];
1322
+ /**
1323
+ * Fits cubic Bézier segments to a polyline (`closed` for a ring), keeping vertices that turn
1324
+ * by more than `cornerAngleDeg` (default 30°) as sharp corners. Each stretch between corners
1325
+ * is fitted Schneider-style so that every input point is within `tolerance` of the result.
1326
+ *
1327
+ * Returns the segments in order (consecutive segments share end points; a closed result ends
1328
+ * where it starts). Fewer than two distinct points give an empty array.
1329
+ */
1330
+ declare function fitCubics(points: readonly Point[], tolerance: number, options?: FitCubicsOptions): CubicSegment[];
1331
+ /** Whether a cubic is (within `tol`) the straight line from `p0` to `p3`. */
1332
+ declare function isStraightCubic(c: CubicSegment, tol: number): boolean;
1333
+ interface FitPathDataOptions extends FitCubicsOptions {
1334
+ /** Fitting tolerance in path units (default 0.5). */
1335
+ tolerance?: number;
1336
+ /** Decimal places (default 3). */
1337
+ precision?: number;
1338
+ /** Added to every point before formatting. */
1339
+ offset?: {
1340
+ x: number;
1341
+ y: number;
1342
+ };
1343
+ /**
1344
+ * Keep the original polyline when fitting would need more segments than it has edges
1345
+ * (default `true`), so fitting never makes a path heavier.
1346
+ */
1347
+ noWorse?: boolean;
1348
+ }
1349
+ /** SVG `d` for fitted segments (`L` for straight ones, `C` otherwise). */
1350
+ declare function cubicsToPathData(segments: readonly CubicSegment[], options?: {
1351
+ precision?: number;
1352
+ offset?: {
1353
+ x: number;
1354
+ y: number;
1355
+ };
1356
+ closed?: boolean;
1357
+ straightTolerance?: number;
1358
+ }): string;
1359
+ /**
1360
+ * Fits smooth curves to a polygon ring (closed by default) and returns SVG path data with
1361
+ * `C` curves for smooth stretches, `L` lines for straight ones and sharp corners kept.
1362
+ * Use it for boolean results of curved shapes or to smooth freehand strokes
1363
+ * (`{ closed: false }`, see `fitFreehandPath`).
1364
+ */
1365
+ declare function fitPathData(ring: readonly Point[], options?: FitPathDataOptions): string;
1366
+ /**
1367
+ * Smooths a freehand stroke (pointer samples) into an open SVG path of cubic curves.
1368
+ * `tolerance` is the largest distance (in the points' units) a sample may be from the
1369
+ * curve (default 1); sharp turns above `cornerAngleDeg` (default 30) stay corners.
1370
+ */
1371
+ declare function fitFreehandPath(points: readonly Point[], options?: Omit<FitPathDataOptions, 'closed'>): string;
1372
+
1373
+ export { AlignEdge, AlignNodesOptions, type ArrangeHistoryTarget, CONNECTION_LINE_TYPES, CanvasArrangeAction, type CanvasArrangeController, type CanvasArrangeControllerOptions, CanvasClipboardPayload, type CanvasConnectionLineType, CanvasEdge, type CanvasHeightOptions, CanvasLayoutOptions, CanvasNode, CanvasNodeStyle, CanvasNodeUpdate, type CanvasScene, type CanvasScenesOptions, type CanvasScenesState, CanvasScenesStore, type CanvasViewport, type Clock, type CombineShapeNodesOptions, type CombineShapeNodesRefusal, type CombineShapeNodesResult, type ConnectorSnap, type ConnectorSnapOptions, type ConnectorSnapTarget, type CubicSegment, DEFAULT_NODE_HEIGHT, DEFAULT_NODE_WIDTH, DEFAULT_PLACEMENT_GAP, DirectionalDisplacementOptions, DistributeAxis, DistributeMode, DistributeNodesOptions, type EllipseShape, FanOutTrajectoryOptions, type FitCubicsOptions, type FitPathDataOptions, type FitViewportOptions, type FlattenOptions, type GuideIndex, type GuideLine, IncrementalLayoutOptions, ManualClock, MatchSizeOptions, type MeasureGuide, type MeasureSelectionOptions, type MeasureSelectionResult, type MultiNodeTransitionOptions, type MultiPolygon, NodeRectOptions, NodeTransition, type PathCommand, type PathShape, PlaceBesideOptions, Point, type PolygonBooleanOperation, type PolygonBooleanOptions, type PolygonFillRule, type PolygonInput, type PolygonOperand, type PolygonPiece, type PolygonRing, type PolygonToPathOptions, type PolygonWithHoles, Position, type PositionChangeLike, RESIZE_HANDLES, Rect, type RectShape, ReflowDisplacementOptions, type RegularPolygonShape, type ResizeHandle, SHAPE_DATA_KEY, SHAPE_KINDS, SHAPE_NODE_TYPE, SHAPE_OPERATIONS, type SerializedCanvasScenes, type ShapeDescription, type ShapeKind, type ShapeNodePolygonOptions, type ShapeOperation, type ShapePaint, type ShapeRenderData, type ShapeSize, Side, SizeDimension, type SnapGuidesOptions, type SnapNodeDragOptions, type SnapNodeDragResult, type SnapNodeResizeOptions, type SnapNodeResizeResult, type SnapResizeOptions, type SnapResizeResult, type SnapResult, type StarShape, StreamingLayoutManager, type StreamingLayoutManagerOptions, TrajectoryFunction, XYPosition, ZOrderOperation, applyShapeCombination, blendLayout, boundsOf, calculateDagreReflowDisplacement, calculateDirectionalDisplacement, canCombineShapeNodes, canvasHeight, centerNodes, centre, collectDescendantIds, combineShapeNodes, computeNodesBoundingBox, computeSnapGuides, connectedOnly, createBezierTrajectory, createCanvasArrangeController, createCanvasScenesStore, createFanOutTrajectory, createGuideIndex, createNodeGuideIndex, cubicPointAt, cubicsToPathData, defaultEasing, dividePolygons, ensureLayout, estimateCanvasHeight, estimateNodeSize, findConnectorSnap, fitCubicRun, fitCubics, fitFreehandPath, fitPathData, fitViewportToNodes, fitViewportToRect, flattenSvgPath, flowSide, formatNumber, getCenteredNodePosition, getLayoutedNodes, getNodeShape, getResizingNodeRect, getShapeRenderData, getShapeSvg, handlePoint, inferResizeHandle, intersectPolygons, isEventFromTextInput, isMeasureModifierHeld, isShapeDescription, isShapeNode, isStraightCubic, isValidCanvasEdge, isValidCanvasNode, isValidCanvasScene, isValidUrl, layoutBounds, linearPositionTrajectory, linearTrajectory, measureBetween, measureLabel, measureSelection, measureToLine, multiPolygonArea, multiPolygonBounds, nearestHandlePositions, oppositePosition, overlaps, pageHidden, parseCanvasClipboardData, parseCanvasScenes, parseSvgPath, placeBeside, pointInMultiPolygon, pointInRings, polygonArea, polygonBoolean, polygonToSvgPath, positionToSide, realClock, remapPastedNodes, repositionNodesTo, resizeHandleAxes, revertShapeCombination, ringSignedArea, runMultiNodeTransition, serializeCanvasNodes, shapeNodeToPolygon, shapeToPolygon, sideToPosition, simplifyRing, snapNodeDrag, snapNodeResize, snapResize, snapToGrid, subtractPolygons, unionPolygons, withStyleOpacity, xorPolygons };