@pixerate/editor 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -2
- package/THIRD_PARTY_NOTICES.md +139 -0
- package/dist/agent/index.cjs +87 -111
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +70 -211
- package/dist/agent/index.d.ts +70 -211
- package/dist/agent/index.js +78 -102
- package/dist/agent/index.js.map +1 -1
- package/dist/arrangeShortcuts-B8O3f8-P.d.cts +184 -0
- package/dist/arrangeShortcuts-sLlD2rSX.d.ts +184 -0
- package/dist/canvas/index.cjs +2075 -86
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +608 -186
- package/dist/canvas/index.d.ts +608 -186
- package/dist/canvas/index.js +2060 -71
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-26BVFKHU.cjs +453 -0
- package/dist/chunk-26BVFKHU.cjs.map +1 -0
- package/dist/chunk-2H7ZHPEJ.js +762 -0
- package/dist/chunk-2H7ZHPEJ.js.map +1 -0
- package/dist/{chunk-DENSCKFC.js → chunk-3CN2WD3X.js} +1730 -189
- package/dist/chunk-3CN2WD3X.js.map +1 -0
- package/dist/{chunk-JJDLPEWL.js → chunk-3LV6RRPH.js} +2 -2
- package/dist/{chunk-MJZBNWG7.js → chunk-5S3Z4PPF.js} +78 -557
- package/dist/chunk-5S3Z4PPF.js.map +1 -0
- package/dist/chunk-664LQYFJ.js +115 -0
- package/dist/chunk-664LQYFJ.js.map +1 -0
- package/dist/chunk-7DWMJCAT.js +453 -0
- package/dist/chunk-7DWMJCAT.js.map +1 -0
- package/dist/chunk-ARRTP33I.js +101 -0
- package/dist/chunk-ARRTP33I.js.map +1 -0
- package/dist/chunk-CPOKFNBG.cjs +101 -0
- package/dist/chunk-CPOKFNBG.cjs.map +1 -0
- package/dist/{chunk-AA4ZEVCC.cjs → chunk-D27YFC46.cjs} +101 -580
- package/dist/chunk-D27YFC46.cjs.map +1 -0
- package/dist/chunk-D6FH2YAX.cjs +131 -0
- package/dist/chunk-D6FH2YAX.cjs.map +1 -0
- package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
- package/dist/chunk-FD4LPFAE.cjs.map +1 -0
- package/dist/chunk-FODNLX5N.js +749 -0
- package/dist/chunk-FODNLX5N.js.map +1 -0
- package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
- package/dist/chunk-FPENXAEJ.js.map +1 -0
- package/dist/chunk-G2I4QJOT.cjs +762 -0
- package/dist/chunk-G2I4QJOT.cjs.map +1 -0
- package/dist/chunk-HLIMTC6N.js +1 -0
- package/dist/chunk-HLIMTC6N.js.map +1 -0
- package/dist/{chunk-QGHGJ56O.js → chunk-ITBSKI26.js} +1483 -19
- package/dist/chunk-ITBSKI26.js.map +1 -0
- package/dist/{chunk-6M3BCTBD.cjs → chunk-MA2F5KHF.cjs} +470 -242
- package/dist/chunk-MA2F5KHF.cjs.map +1 -0
- package/dist/{chunk-SAZRENEZ.cjs → chunk-MCSJFWAO.cjs} +1733 -192
- package/dist/chunk-MCSJFWAO.cjs.map +1 -0
- package/dist/chunk-MKA3AHLV.cjs +256 -0
- package/dist/chunk-MKA3AHLV.cjs.map +1 -0
- package/dist/chunk-OLZJXL4R.cjs +749 -0
- package/dist/chunk-OLZJXL4R.cjs.map +1 -0
- package/dist/chunk-OXSV64JX.js +689 -0
- package/dist/chunk-OXSV64JX.js.map +1 -0
- package/dist/chunk-P3RQ5UJE.cjs +689 -0
- package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
- package/dist/chunk-P6ODPZ4W.js +256 -0
- package/dist/chunk-P6ODPZ4W.js.map +1 -0
- package/dist/chunk-PGNZKFQS.js +685 -0
- package/dist/chunk-PGNZKFQS.js.map +1 -0
- package/dist/chunk-PL4JB3BF.cjs +1 -0
- package/dist/chunk-PL4JB3BF.cjs.map +1 -0
- package/dist/{chunk-GVSMRPHB.cjs → chunk-SBCZ6RN5.cjs} +1568 -104
- package/dist/chunk-SBCZ6RN5.cjs.map +1 -0
- package/dist/{chunk-CL37JGFW.cjs → chunk-SML64VJG.cjs} +4 -4
- package/dist/{chunk-CL37JGFW.cjs.map → chunk-SML64VJG.cjs.map} +1 -1
- package/dist/chunk-TNNJXF46.cjs +115 -0
- package/dist/chunk-TNNJXF46.cjs.map +1 -0
- package/dist/chunk-U5HZSFCS.js +131 -0
- package/dist/chunk-U5HZSFCS.js.map +1 -0
- package/dist/{chunk-RU52I7QO.js → chunk-U5KWZU4X.js} +388 -160
- package/dist/chunk-U5KWZU4X.js.map +1 -0
- package/dist/chunk-XNORY7M6.cjs +685 -0
- package/dist/chunk-XNORY7M6.cjs.map +1 -0
- package/dist/commands/index.cjs +860 -0
- package/dist/commands/index.cjs.map +1 -0
- package/dist/commands/index.d.cts +244 -0
- package/dist/commands/index.d.ts +244 -0
- package/dist/commands/index.js +860 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/comment-store-BEMgVlAA.d.cts +135 -0
- package/dist/comment-store-BEMgVlAA.d.ts +135 -0
- package/dist/compare/index.cjs +15 -0
- package/dist/compare/index.cjs.map +1 -0
- package/dist/compare/index.d.cts +58 -0
- package/dist/compare/index.d.ts +58 -0
- package/dist/compare/index.js +15 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/{controller-B7GIybc4.d.cts → controller-BhLLAoi8.d.cts} +119 -5
- package/dist/{controller-B7GIybc4.d.ts → controller-BhLLAoi8.d.ts} +119 -5
- package/dist/documents-BQjzJNe2.d.ts +76 -0
- package/dist/documents-KRTPhvst.d.cts +76 -0
- package/dist/extensions/index.cjs +53 -5
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +7 -3
- package/dist/extensions/index.d.ts +7 -3
- package/dist/extensions/index.js +52 -4
- package/dist/generation/index.cjs +1373 -0
- package/dist/generation/index.cjs.map +1 -0
- package/dist/generation/index.d.cts +554 -0
- package/dist/generation/index.d.ts +554 -0
- package/dist/generation/index.js +1373 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/grammar/index.cjs +48 -3
- package/dist/grammar/index.cjs.map +1 -1
- package/dist/grammar/index.d.cts +322 -100
- package/dist/grammar/index.d.ts +322 -100
- package/dist/grammar/index.js +54 -9
- package/dist/history/index.cjs +12 -2
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +211 -7
- package/dist/history/index.d.ts +211 -7
- package/dist/history/index.js +13 -3
- package/dist/image-editor/index.cjs +2244 -38
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +742 -4
- package/dist/image-editor/index.d.ts +742 -4
- package/dist/image-editor/index.js +2222 -16
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{index-CCr_z9kF.d.cts → index-BhJBSm2d.d.cts} +370 -4
- package/dist/{index-Ck6ajwXh.d.ts → index-fJrlkJCd.d.ts} +370 -4
- package/dist/index.cjs +193 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -48
- package/dist/index.d.ts +17 -48
- package/dist/index.js +198 -103
- package/dist/index.js.map +1 -1
- package/dist/migrate-iTlm3a8h.d.cts +112 -0
- package/dist/migrate-iTlm3a8h.d.ts +112 -0
- package/dist/persistence/index.cjs +676 -0
- package/dist/persistence/index.cjs.map +1 -0
- package/dist/persistence/index.d.cts +347 -0
- package/dist/persistence/index.d.ts +347 -0
- package/dist/persistence/index.js +676 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/registry-BDe-vlv3.d.ts +171 -0
- package/dist/registry-D32LpIc4.d.cts +354 -0
- package/dist/registry-DnzpgY27.d.cts +171 -0
- package/dist/registry-evKTP4ls.d.ts +354 -0
- package/dist/schema-DYDGdYL9.d.cts +41 -0
- package/dist/schema-DYDGdYL9.d.ts +41 -0
- package/dist/serializers/index.cjs +4 -3
- package/dist/serializers/index.cjs.map +1 -1
- package/dist/serializers/index.d.cts +2 -1
- package/dist/serializers/index.d.ts +2 -1
- package/dist/serializers/index.js +3 -2
- package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
- package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
- package/dist/spreadsheet/index.cjs +53 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +247 -18
- package/dist/spreadsheet/index.d.ts +247 -18
- package/dist/spreadsheet/index.js +58 -8
- package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
- package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
- package/dist/{track-changes-BSF58RK0.d.cts → track-changes-BK5sVf4_.d.cts} +18 -136
- package/dist/{track-changes-BSF58RK0.d.ts → track-changes-CDjCrwKe.d.ts} +18 -136
- package/dist/{types-CJ4U-HHI.d.ts → types-DX4Mh4SL.d.cts} +1 -1
- package/dist/{types-CJ4U-HHI.d.cts → types-DX4Mh4SL.d.ts} +1 -1
- package/dist/types-GT5a5YAX.d.cts +86 -0
- package/dist/types-GT5a5YAX.d.ts +86 -0
- package/package.json +23 -2
- package/dist/chunk-2TWGLC5K.cjs +0 -262
- package/dist/chunk-2TWGLC5K.cjs.map +0 -1
- package/dist/chunk-4SUNJZWB.cjs.map +0 -1
- package/dist/chunk-6M3BCTBD.cjs.map +0 -1
- package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
- package/dist/chunk-AXV2ZW34.cjs +0 -118
- package/dist/chunk-AXV2ZW34.cjs.map +0 -1
- package/dist/chunk-B2ZRLN4S.js +0 -118
- package/dist/chunk-B2ZRLN4S.js.map +0 -1
- package/dist/chunk-DENSCKFC.js.map +0 -1
- package/dist/chunk-GVSMRPHB.cjs.map +0 -1
- package/dist/chunk-KAADNAPF.js +0 -262
- package/dist/chunk-KAADNAPF.js.map +0 -1
- package/dist/chunk-MJZBNWG7.js.map +0 -1
- package/dist/chunk-OYXSKQFR.js.map +0 -1
- package/dist/chunk-QGHGJ56O.js.map +0 -1
- package/dist/chunk-RU52I7QO.js.map +0 -1
- package/dist/chunk-SAZRENEZ.cjs.map +0 -1
- package/dist/types-BZgcDsC5.d.cts +0 -42
- package/dist/types-BZgcDsC5.d.ts +0 -42
- /package/dist/{chunk-JJDLPEWL.js.map → chunk-3LV6RRPH.js.map} +0 -0
package/dist/canvas/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_ARRANGE_SHORTCUTS,
|
|
3
|
+
matchArrangeShortcut
|
|
4
|
+
} from "../chunk-7DWMJCAT.js";
|
|
1
5
|
import {
|
|
2
6
|
DEFAULT_NODE_HEIGHT,
|
|
3
7
|
DEFAULT_NODE_WIDTH,
|
|
@@ -1073,6 +1077,10 @@ function invertNodeUpdates(nodes, updates) {
|
|
|
1073
1077
|
}
|
|
1074
1078
|
|
|
1075
1079
|
// src/canvas/guides.ts
|
|
1080
|
+
function snapToGrid(value, spacing, origin = 0) {
|
|
1081
|
+
if (!(spacing > 0) || !Number.isFinite(spacing)) return value;
|
|
1082
|
+
return Math.round((value - origin) / spacing) * spacing + origin;
|
|
1083
|
+
}
|
|
1076
1084
|
function buildAxis(entries) {
|
|
1077
1085
|
entries.sort((a, b) => a.value - b.value);
|
|
1078
1086
|
return {
|
|
@@ -1229,8 +1237,10 @@ function computeSnapGuides(moving, targets, options = {}) {
|
|
|
1229
1237
|
};
|
|
1230
1238
|
const sx = snapAxis(true);
|
|
1231
1239
|
const sy = snapAxis(false);
|
|
1232
|
-
const
|
|
1233
|
-
const
|
|
1240
|
+
const grid = options.gridSize ?? 0;
|
|
1241
|
+
const gridDelta = (v, origin) => snapToGrid(v, grid, origin) - v;
|
|
1242
|
+
const dx = sx.delta ?? (grid > 0 ? gridDelta(moving.x, options.gridOrigin?.x ?? 0) : 0);
|
|
1243
|
+
const dy = sy.delta ?? (grid > 0 ? gridDelta(moving.y, options.gridOrigin?.y ?? 0) : 0);
|
|
1234
1244
|
const snapped = { x: moving.x + dx, y: moving.y + dy, width: moving.width, height: moving.height };
|
|
1235
1245
|
const guides = [];
|
|
1236
1246
|
const alignmentGuides = (horizontal) => {
|
|
@@ -1244,12 +1254,12 @@ function computeSnapGuides(moving, targets, options = {}) {
|
|
|
1244
1254
|
if (useCentres) {
|
|
1245
1255
|
lines.push({ value: start + size / 2, kind: "centre", axis: horizontal ? index.centreX : index.centreY });
|
|
1246
1256
|
}
|
|
1247
|
-
for (const
|
|
1248
|
-
const aligned = matches(
|
|
1257
|
+
for (const line2 of lines) {
|
|
1258
|
+
const aligned = matches(line2.axis, line2.value, eps);
|
|
1249
1259
|
if (aligned.length === 0) continue;
|
|
1250
1260
|
const span = boundsOf([snapped, ...aligned]);
|
|
1251
1261
|
guides.push(
|
|
1252
|
-
horizontal ? { orientation: "vertical", kind:
|
|
1262
|
+
horizontal ? { orientation: "vertical", kind: line2.kind, x1: line2.value, y1: span.y, x2: line2.value, y2: span.y + span.height } : { orientation: "horizontal", kind: line2.kind, x1: span.x, y1: line2.value, x2: span.x + span.width, y2: line2.value }
|
|
1253
1263
|
);
|
|
1254
1264
|
}
|
|
1255
1265
|
};
|
|
@@ -1390,16 +1400,16 @@ function findConnectorSnap(pointer, targets, options = {}) {
|
|
|
1390
1400
|
if (pointer.x < rect.x - radius || pointer.x > rect.x + rect.width + radius || pointer.y < rect.y - radius || pointer.y > rect.y + rect.height + radius) {
|
|
1391
1401
|
continue;
|
|
1392
1402
|
}
|
|
1393
|
-
const
|
|
1403
|
+
const inside2 = pointer.x >= rect.x && pointer.x <= rect.x + rect.width && pointer.y >= rect.y && pointer.y <= rect.y + rect.height;
|
|
1394
1404
|
for (const position of target.positions ?? all) {
|
|
1395
1405
|
const point = options.mode === "edge" ? closestPointOnSide(rect, position, pointer) : handlePoint(rect, position);
|
|
1396
1406
|
const sidePoint = closestPointOnSide(rect, position, pointer);
|
|
1397
1407
|
const rank = Math.hypot(pointer.x - sidePoint.x, pointer.y - sidePoint.y);
|
|
1398
1408
|
const distance = Math.hypot(pointer.x - point.x, pointer.y - point.y);
|
|
1399
|
-
if (!
|
|
1400
|
-
const better = !best ||
|
|
1409
|
+
if (!inside2 && rank > radius) continue;
|
|
1410
|
+
const better = !best || inside2 && !best.inside || inside2 === best.inside && rank < best.rank;
|
|
1401
1411
|
if (better) {
|
|
1402
|
-
best = { nodeId: target.id, position, side: positionToSide(position), point, distance, inside, rank };
|
|
1412
|
+
best = { nodeId: target.id, position, side: positionToSide(position), point, distance, inside: inside2, rank };
|
|
1403
1413
|
}
|
|
1404
1414
|
}
|
|
1405
1415
|
}
|
|
@@ -1567,10 +1577,10 @@ var CanvasScenesStore = class {
|
|
|
1567
1577
|
return this.update(id, { name });
|
|
1568
1578
|
}
|
|
1569
1579
|
/** Moves a scene to `toIndex` (clamped). */
|
|
1570
|
-
move(id,
|
|
1580
|
+
move(id, toIndex2) {
|
|
1571
1581
|
const from = this.scenes.findIndex((s) => s.id === id);
|
|
1572
1582
|
if (from === -1) return;
|
|
1573
|
-
const to = Math.max(0, Math.min(this.scenes.length - 1, Math.trunc(
|
|
1583
|
+
const to = Math.max(0, Math.min(this.scenes.length - 1, Math.trunc(toIndex2)));
|
|
1574
1584
|
if (from === to) return;
|
|
1575
1585
|
const scenes = [...this.scenes];
|
|
1576
1586
|
const [scene] = scenes.splice(from, 1);
|
|
@@ -1662,49 +1672,6 @@ function fitViewportToNodes(nodes, size, ids, options = {}) {
|
|
|
1662
1672
|
return bounds ? fitViewportToRect(bounds, size, options) : null;
|
|
1663
1673
|
}
|
|
1664
1674
|
|
|
1665
|
-
// src/canvas/arrangeShortcuts.ts
|
|
1666
|
-
var DEFAULT_ARRANGE_SHORTCUTS = [
|
|
1667
|
-
{ action: "alignLeft", code: "KeyA", alt: true, label: "Alt+A" },
|
|
1668
|
-
{ action: "alignCenter", code: "KeyH", alt: true, label: "Alt+H" },
|
|
1669
|
-
{ action: "alignRight", code: "KeyD", alt: true, label: "Alt+D" },
|
|
1670
|
-
{ action: "alignTop", code: "KeyW", alt: true, label: "Alt+W" },
|
|
1671
|
-
{ action: "alignMiddle", code: "KeyV", alt: true, label: "Alt+V" },
|
|
1672
|
-
{ action: "alignBottom", code: "KeyS", alt: true, label: "Alt+S" },
|
|
1673
|
-
{ action: "distributeHorizontal", code: "KeyH", alt: true, shift: true, label: "Alt+Shift+H" },
|
|
1674
|
-
{ action: "distributeVertical", code: "KeyV", alt: true, shift: true, label: "Alt+Shift+V" },
|
|
1675
|
-
{ action: "lock", code: "KeyL", mod: true, label: "Mod+L" },
|
|
1676
|
-
{ action: "unlock", code: "KeyL", mod: true, alt: true, label: "Mod+Alt+L" },
|
|
1677
|
-
{ action: "bringForward", code: "BracketRight", mod: true, label: "Mod+]" },
|
|
1678
|
-
{ action: "sendBackward", code: "BracketLeft", mod: true, label: "Mod+[" },
|
|
1679
|
-
{ action: "bringToFront", code: "BracketRight", mod: true, alt: true, label: "Mod+Alt+]" },
|
|
1680
|
-
{ action: "sendToBack", code: "BracketLeft", mod: true, alt: true, label: "Mod+Alt+[" },
|
|
1681
|
-
{ action: "nextScene", code: "PageDown", label: "PageDown" },
|
|
1682
|
-
{ action: "previousScene", code: "PageUp", label: "PageUp" }
|
|
1683
|
-
];
|
|
1684
|
-
var KEY_TO_CODE = {
|
|
1685
|
-
"]": "BracketRight",
|
|
1686
|
-
"[": "BracketLeft",
|
|
1687
|
-
PageDown: "PageDown",
|
|
1688
|
-
PageUp: "PageUp"
|
|
1689
|
-
};
|
|
1690
|
-
function eventCode(event) {
|
|
1691
|
-
if (event.code) return event.code;
|
|
1692
|
-
if (KEY_TO_CODE[event.key]) return KEY_TO_CODE[event.key];
|
|
1693
|
-
if (/^[a-z]$/i.test(event.key)) return `Key${event.key.toUpperCase()}`;
|
|
1694
|
-
return event.key;
|
|
1695
|
-
}
|
|
1696
|
-
function matchArrangeShortcut(event, bindings = DEFAULT_ARRANGE_SHORTCUTS) {
|
|
1697
|
-
if (isEventFromTextInput(event)) return null;
|
|
1698
|
-
const code = eventCode(event);
|
|
1699
|
-
const mod = event.metaKey || event.ctrlKey;
|
|
1700
|
-
for (const binding of bindings) {
|
|
1701
|
-
if (binding.code === code && !!binding.mod === mod && !!binding.alt === event.altKey && !!binding.shift === event.shiftKey) {
|
|
1702
|
-
return binding.action;
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
return null;
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
1675
|
// src/canvas/polygonBoolean.ts
|
|
1709
1676
|
function ringSignedArea(ring) {
|
|
1710
1677
|
let sum = 0;
|
|
@@ -1885,9 +1852,9 @@ function splitPass(segments, pool, eps) {
|
|
|
1885
1852
|
]) {
|
|
1886
1853
|
for (const p of [other.a, other.b]) {
|
|
1887
1854
|
if (p.id === seg.a.id || p.id === seg.b.id) continue;
|
|
1888
|
-
const { t: t2, dist } = project(seg.a, seg.b, p);
|
|
1855
|
+
const { t: t2, dist: dist2 } = project(seg.a, seg.b, p);
|
|
1889
1856
|
const len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);
|
|
1890
|
-
if (
|
|
1857
|
+
if (dist2 <= eps && t2 * len > eps && (1 - t2) * len > eps) {
|
|
1891
1858
|
addSplit(split, t2, p);
|
|
1892
1859
|
touched = true;
|
|
1893
1860
|
}
|
|
@@ -2077,8 +2044,8 @@ function removeCollinear(ring, eps) {
|
|
|
2077
2044
|
const dy = nxt[1] - prev[1];
|
|
2078
2045
|
const len = Math.hypot(dx, dy);
|
|
2079
2046
|
const cross = (cur[0] - prev[0]) * dy - (cur[1] - prev[1]) * dx;
|
|
2080
|
-
const
|
|
2081
|
-
if (len > 0 && Math.abs(cross) / len <= eps &&
|
|
2047
|
+
const dot2 = (cur[0] - prev[0]) * dx + (cur[1] - prev[1]) * dy;
|
|
2048
|
+
if (len > 0 && Math.abs(cross) / len <= eps && dot2 > 0 && dot2 < len * len) {
|
|
2082
2049
|
changed = true;
|
|
2083
2050
|
continue;
|
|
2084
2051
|
}
|
|
@@ -2174,6 +2141,347 @@ function dividePolygons(operands, options = {}) {
|
|
|
2174
2141
|
return pieces;
|
|
2175
2142
|
}
|
|
2176
2143
|
|
|
2144
|
+
// src/canvas/curveFit.ts
|
|
2145
|
+
function formatNumber(value, precision) {
|
|
2146
|
+
const factor = 10 ** precision;
|
|
2147
|
+
const rounded = Math.round(value * factor) / factor;
|
|
2148
|
+
return Object.is(rounded, -0) ? "0" : String(rounded);
|
|
2149
|
+
}
|
|
2150
|
+
var MAX_REPARAM = 6;
|
|
2151
|
+
var sub = (a, b) => [a[0] - b[0], a[1] - b[1]];
|
|
2152
|
+
var add = (a, v, s = 1) => [a[0] + v[0] * s, a[1] + v[1] * s];
|
|
2153
|
+
var dot = (a, b) => a[0] * b[0] + a[1] * b[1];
|
|
2154
|
+
var dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
2155
|
+
function unit(v) {
|
|
2156
|
+
const l = Math.hypot(v[0], v[1]);
|
|
2157
|
+
return l > 1e-12 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;
|
|
2158
|
+
}
|
|
2159
|
+
function evalCubic(c, t) {
|
|
2160
|
+
const s = 1 - t;
|
|
2161
|
+
const b0 = s * s * s;
|
|
2162
|
+
const b1 = 3 * s * s * t;
|
|
2163
|
+
const b2 = 3 * s * t * t;
|
|
2164
|
+
const b3 = t * t * t;
|
|
2165
|
+
return [
|
|
2166
|
+
b0 * c.p0[0] + b1 * c.p1[0] + b2 * c.p2[0] + b3 * c.p3[0],
|
|
2167
|
+
b0 * c.p0[1] + b1 * c.p1[1] + b2 * c.p2[1] + b3 * c.p3[1]
|
|
2168
|
+
];
|
|
2169
|
+
}
|
|
2170
|
+
function cubicPointAt(c, t) {
|
|
2171
|
+
return evalCubic(c, t);
|
|
2172
|
+
}
|
|
2173
|
+
function dedup(points) {
|
|
2174
|
+
const out = [];
|
|
2175
|
+
for (const p of points) {
|
|
2176
|
+
if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
|
|
2177
|
+
if (out.length === 0 || dist(out[out.length - 1], p) > 1e-12) out.push(p);
|
|
2178
|
+
}
|
|
2179
|
+
return out;
|
|
2180
|
+
}
|
|
2181
|
+
function chordParams(pts) {
|
|
2182
|
+
const u = [0];
|
|
2183
|
+
let acc = 0;
|
|
2184
|
+
for (let i = 1; i < pts.length; i++) {
|
|
2185
|
+
acc += dist(pts[i - 1], pts[i]);
|
|
2186
|
+
u.push(acc);
|
|
2187
|
+
}
|
|
2188
|
+
return acc > 0 ? u.map((x) => x / acc) : u;
|
|
2189
|
+
}
|
|
2190
|
+
function generate(pts, u, t0, t1) {
|
|
2191
|
+
const p0 = pts[0];
|
|
2192
|
+
const p3 = pts[pts.length - 1];
|
|
2193
|
+
const dirIn = [-t1[0], -t1[1]];
|
|
2194
|
+
let c00 = 0;
|
|
2195
|
+
let c01 = 0;
|
|
2196
|
+
let c11 = 0;
|
|
2197
|
+
let x0 = 0;
|
|
2198
|
+
let x1 = 0;
|
|
2199
|
+
for (let i = 0; i < pts.length; i++) {
|
|
2200
|
+
const t = u[i];
|
|
2201
|
+
const s = 1 - t;
|
|
2202
|
+
const b0 = s * s * s;
|
|
2203
|
+
const b1 = 3 * s * s * t;
|
|
2204
|
+
const b2 = 3 * s * t * t;
|
|
2205
|
+
const b3 = t * t * t;
|
|
2206
|
+
const a0 = [t0[0] * b1, t0[1] * b1];
|
|
2207
|
+
const a1 = [dirIn[0] * b2, dirIn[1] * b2];
|
|
2208
|
+
c00 += dot(a0, a0);
|
|
2209
|
+
c01 += dot(a0, a1);
|
|
2210
|
+
c11 += dot(a1, a1);
|
|
2211
|
+
const tmp = [
|
|
2212
|
+
pts[i][0] - (p0[0] * (b0 + b1) + p3[0] * (b2 + b3)),
|
|
2213
|
+
pts[i][1] - (p0[1] * (b0 + b1) + p3[1] * (b2 + b3))
|
|
2214
|
+
];
|
|
2215
|
+
x0 += dot(a0, tmp);
|
|
2216
|
+
x1 += dot(a1, tmp);
|
|
2217
|
+
}
|
|
2218
|
+
const det = c00 * c11 - c01 * c01;
|
|
2219
|
+
const seg = dist(p0, p3);
|
|
2220
|
+
const eps = 1e-6 * seg;
|
|
2221
|
+
let al = seg / 3;
|
|
2222
|
+
let ar = seg / 3;
|
|
2223
|
+
if (Math.abs(det) > 1e-12) {
|
|
2224
|
+
al = (x0 * c11 - x1 * c01) / det;
|
|
2225
|
+
ar = (c00 * x1 - c01 * x0) / det;
|
|
2226
|
+
}
|
|
2227
|
+
if (!Number.isFinite(al) || !Number.isFinite(ar) || al < eps || ar < eps) {
|
|
2228
|
+
al = seg / 3;
|
|
2229
|
+
ar = seg / 3;
|
|
2230
|
+
}
|
|
2231
|
+
const cap = seg * 4 + 1e-9;
|
|
2232
|
+
al = Math.min(al, cap);
|
|
2233
|
+
ar = Math.min(ar, cap);
|
|
2234
|
+
return { p0, p1: add(p0, t0, al), p2: add(p3, dirIn, ar), p3 };
|
|
2235
|
+
}
|
|
2236
|
+
function maxError(pts, c, u) {
|
|
2237
|
+
let e = 0;
|
|
2238
|
+
let idx = pts.length >> 1;
|
|
2239
|
+
for (let i = 0; i < pts.length; i++) {
|
|
2240
|
+
const d = dist(evalCubic(c, u[i]), pts[i]);
|
|
2241
|
+
if (d > e) {
|
|
2242
|
+
e = d;
|
|
2243
|
+
idx = i;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
return [e, idx];
|
|
2247
|
+
}
|
|
2248
|
+
function newton(c, p, t) {
|
|
2249
|
+
const s = 1 - t;
|
|
2250
|
+
const q = evalCubic(c, t);
|
|
2251
|
+
const d1 = [
|
|
2252
|
+
3 * (s * s * (c.p1[0] - c.p0[0]) + 2 * s * t * (c.p2[0] - c.p1[0]) + t * t * (c.p3[0] - c.p2[0])),
|
|
2253
|
+
3 * (s * s * (c.p1[1] - c.p0[1]) + 2 * s * t * (c.p2[1] - c.p1[1]) + t * t * (c.p3[1] - c.p2[1]))
|
|
2254
|
+
];
|
|
2255
|
+
const d2 = [
|
|
2256
|
+
6 * (s * (c.p2[0] - 2 * c.p1[0] + c.p0[0]) + t * (c.p3[0] - 2 * c.p2[0] + c.p1[0])),
|
|
2257
|
+
6 * (s * (c.p2[1] - 2 * c.p1[1] + c.p0[1]) + t * (c.p3[1] - 2 * c.p2[1] + c.p1[1]))
|
|
2258
|
+
];
|
|
2259
|
+
const diff = [q[0] - p[0], q[1] - p[1]];
|
|
2260
|
+
const num = dot(diff, d1);
|
|
2261
|
+
const den = dot(d1, d1) + dot(diff, d2);
|
|
2262
|
+
if (Math.abs(den) < 1e-12) return t;
|
|
2263
|
+
const nt = t - num / den;
|
|
2264
|
+
return Number.isFinite(nt) ? Math.min(1, Math.max(0, nt)) : t;
|
|
2265
|
+
}
|
|
2266
|
+
function fitSingleFrom(pts, u, t0, t1) {
|
|
2267
|
+
let best = generate(pts, u, t0, t1);
|
|
2268
|
+
let [bestErr, bestSplit] = maxError(pts, best, u);
|
|
2269
|
+
const params = u.slice();
|
|
2270
|
+
for (let round = 0; round < MAX_REPARAM; round++) {
|
|
2271
|
+
for (let i = 0; i < params.length; i++) params[i] = newton(best, pts[i], params[i]);
|
|
2272
|
+
const c = generate(pts, params, t0, t1);
|
|
2273
|
+
const [e, s] = maxError(pts, c, params);
|
|
2274
|
+
if (e < bestErr) {
|
|
2275
|
+
best = c;
|
|
2276
|
+
bestErr = e;
|
|
2277
|
+
bestSplit = s;
|
|
2278
|
+
} else break;
|
|
2279
|
+
}
|
|
2280
|
+
return [best, bestErr, bestSplit];
|
|
2281
|
+
}
|
|
2282
|
+
function fitRec(pts, t0, t1, tol, out, depth) {
|
|
2283
|
+
const n = pts.length;
|
|
2284
|
+
if (n === 2) {
|
|
2285
|
+
const d = dist(pts[0], pts[1]) / 3;
|
|
2286
|
+
out.push({ p0: pts[0], p1: add(pts[0], t0, d), p2: add(pts[1], t1, -d), p3: pts[1] });
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
const [c, err, splitAt] = fitSingleFrom(pts, chordParams(pts), t0, t1);
|
|
2290
|
+
if (err <= tol || depth > 40) {
|
|
2291
|
+
out.push(c);
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
const split = Math.min(n - 2, Math.max(1, splitAt));
|
|
2295
|
+
const tc = unit(sub(pts[split + 1], pts[split - 1])) ?? t0;
|
|
2296
|
+
fitRec(pts.slice(0, split + 1), t0, tc, tol, out, depth + 1);
|
|
2297
|
+
fitRec(pts.slice(split), tc, t1, tol, out, depth + 1);
|
|
2298
|
+
}
|
|
2299
|
+
function tangentFrom(a, b, c) {
|
|
2300
|
+
const chord = unit(sub(b, a));
|
|
2301
|
+
if (!c) return chord;
|
|
2302
|
+
const d1 = dist(a, b);
|
|
2303
|
+
const d2 = dist(b, c);
|
|
2304
|
+
if (!(d1 > 0) || !(d2 > 0)) return chord;
|
|
2305
|
+
const ka = -(2 * d1 + d2) / (d1 * (d1 + d2));
|
|
2306
|
+
const kb = (d1 + d2) / (d1 * d2);
|
|
2307
|
+
const kc = -d1 / (d2 * (d1 + d2));
|
|
2308
|
+
const est = unit([ka * a[0] + kb * b[0] + kc * c[0], ka * a[1] + kb * b[1] + kc * c[1]]);
|
|
2309
|
+
return est && chord && dot(est, chord) > Math.cos(Math.PI / 4) ? est : chord;
|
|
2310
|
+
}
|
|
2311
|
+
function fitCubicRun(points, tolerance, t0, t1) {
|
|
2312
|
+
const pts = dedup(points);
|
|
2313
|
+
const out = [];
|
|
2314
|
+
if (pts.length < 2) return out;
|
|
2315
|
+
const n = pts.length;
|
|
2316
|
+
const start = (t0 && unit([t0[0], t0[1]])) ?? tangentFrom(pts[0], pts[1], pts[2]) ?? [1, 0];
|
|
2317
|
+
const back = tangentFrom(pts[n - 1], pts[n - 2], pts[n - 3]);
|
|
2318
|
+
const end = (t1 && unit([t1[0], t1[1]])) ?? (back ? [-back[0], -back[1]] : [1, 0]);
|
|
2319
|
+
fitRec(pts, start, end, Math.max(tolerance, 1e-9), out, 0);
|
|
2320
|
+
return out;
|
|
2321
|
+
}
|
|
2322
|
+
function turnDeg(a, b, c) {
|
|
2323
|
+
const u = unit(sub(b, a));
|
|
2324
|
+
const v = unit(sub(c, b));
|
|
2325
|
+
if (!u || !v) return 0;
|
|
2326
|
+
return Math.acos(Math.min(1, Math.max(-1, dot(u, v)))) * 180 / Math.PI;
|
|
2327
|
+
}
|
|
2328
|
+
function findCorners(pts, closed, cornerAngle, span) {
|
|
2329
|
+
const n = pts.length;
|
|
2330
|
+
const at = (i) => pts[closed ? (i % n + n) % n : i];
|
|
2331
|
+
const reach = (i, step) => {
|
|
2332
|
+
for (let k = 1; k < (closed ? n : Infinity); k++) {
|
|
2333
|
+
const j = i + step * k;
|
|
2334
|
+
if (!closed && (j < 0 || j >= n)) return k > 1 ? j - step : null;
|
|
2335
|
+
if (dist(at(j), pts[i]) >= span) return j;
|
|
2336
|
+
}
|
|
2337
|
+
return null;
|
|
2338
|
+
};
|
|
2339
|
+
const turns = new Float64Array(n);
|
|
2340
|
+
const windows = [];
|
|
2341
|
+
for (let i = 0; i < n; i++) {
|
|
2342
|
+
windows.push([i, i]);
|
|
2343
|
+
if (!closed && (i === 0 || i === n - 1)) continue;
|
|
2344
|
+
const prev = reach(i, -1);
|
|
2345
|
+
const next = reach(i, 1);
|
|
2346
|
+
if (prev === null || next === null) continue;
|
|
2347
|
+
windows[i] = [prev, next];
|
|
2348
|
+
turns[i] = turnDeg(at(prev), pts[i], at(next));
|
|
2349
|
+
}
|
|
2350
|
+
const corners = [];
|
|
2351
|
+
for (let i = 0; i < n; i++) {
|
|
2352
|
+
if (!(turns[i] > cornerAngle)) continue;
|
|
2353
|
+
const [lo, hi] = windows[i];
|
|
2354
|
+
let sharpest = true;
|
|
2355
|
+
for (let j = lo; j <= hi && sharpest; j++) {
|
|
2356
|
+
const k = closed ? (j % n + n) % n : j;
|
|
2357
|
+
if (k === i || dist(pts[k], pts[i]) >= span) continue;
|
|
2358
|
+
if (turns[k] > turns[i] || turns[k] === turns[i] && k < i) sharpest = false;
|
|
2359
|
+
}
|
|
2360
|
+
if (sharpest) corners.push(i);
|
|
2361
|
+
}
|
|
2362
|
+
return corners;
|
|
2363
|
+
}
|
|
2364
|
+
function densify(run) {
|
|
2365
|
+
if (run.length < 3) return run;
|
|
2366
|
+
const lengths = [];
|
|
2367
|
+
for (let i = 1; i < run.length; i++) lengths.push(dist(run[i - 1], run[i]));
|
|
2368
|
+
const sorted = [...lengths].sort((a, b) => a - b);
|
|
2369
|
+
const median = sorted[sorted.length >> 1];
|
|
2370
|
+
if (!(median > 0)) return run;
|
|
2371
|
+
const out = [run[0]];
|
|
2372
|
+
for (let i = 1; i < run.length; i++) {
|
|
2373
|
+
const len = lengths[i - 1];
|
|
2374
|
+
if (len > 2 * median) {
|
|
2375
|
+
const steps = Math.min(64, Math.ceil(len / median));
|
|
2376
|
+
const a = run[i - 1];
|
|
2377
|
+
const b = run[i];
|
|
2378
|
+
for (let k = 1; k < steps; k++) {
|
|
2379
|
+
const t = k / steps;
|
|
2380
|
+
out.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
out.push(run[i]);
|
|
2384
|
+
}
|
|
2385
|
+
return out;
|
|
2386
|
+
}
|
|
2387
|
+
function fitCubics(points, tolerance, options = {}) {
|
|
2388
|
+
let pts = dedup(points);
|
|
2389
|
+
const closed = options.closed === true;
|
|
2390
|
+
if (closed && pts.length > 1 && dist(pts[0], pts[pts.length - 1]) <= 1e-12) pts = pts.slice(0, -1);
|
|
2391
|
+
if (pts.length < 2) return [];
|
|
2392
|
+
const tol = Math.max(tolerance, 1e-6);
|
|
2393
|
+
const cornerAngle = options.cornerAngleDeg ?? 30;
|
|
2394
|
+
const n = pts.length;
|
|
2395
|
+
const span = 4 * tol;
|
|
2396
|
+
if (!closed || n < 3) {
|
|
2397
|
+
const corners2 = [0, ...n > 2 ? findCorners(pts, false, cornerAngle, span) : [], n - 1];
|
|
2398
|
+
const out2 = [];
|
|
2399
|
+
for (let k = 1; k < corners2.length; k++) {
|
|
2400
|
+
const run = pts.slice(corners2[k - 1], corners2[k] + 1);
|
|
2401
|
+
out2.push(...fitCubicRun(densify(run), tol));
|
|
2402
|
+
}
|
|
2403
|
+
if (closed && n === 2) out2.push(...fitCubicRun([pts[1], pts[0]], tol));
|
|
2404
|
+
return out2;
|
|
2405
|
+
}
|
|
2406
|
+
const corners = findCorners(pts, true, cornerAngle, span);
|
|
2407
|
+
if (corners.length === 0) {
|
|
2408
|
+
const ring = [...pts, pts[0]];
|
|
2409
|
+
const t = unit(sub(pts[1], pts[n - 1])) ?? [1, 0];
|
|
2410
|
+
return fitCubicRun(densify(ring), tol, t, t);
|
|
2411
|
+
}
|
|
2412
|
+
const out = [];
|
|
2413
|
+
for (let k = 0; k < corners.length; k++) {
|
|
2414
|
+
const from = corners[k];
|
|
2415
|
+
const to = corners[(k + 1) % corners.length];
|
|
2416
|
+
const run = [];
|
|
2417
|
+
for (let i = from; ; i = (i + 1) % n) {
|
|
2418
|
+
run.push(pts[i]);
|
|
2419
|
+
if (run.length > 1 && i === to) break;
|
|
2420
|
+
}
|
|
2421
|
+
out.push(...fitCubicRun(densify(run), tol));
|
|
2422
|
+
}
|
|
2423
|
+
return out;
|
|
2424
|
+
}
|
|
2425
|
+
function isStraightCubic(c, tol) {
|
|
2426
|
+
const chord = sub(c.p3, c.p0);
|
|
2427
|
+
const len = Math.hypot(chord[0], chord[1]);
|
|
2428
|
+
if (len < 1e-12) return dist(c.p1, c.p0) <= tol && dist(c.p2, c.p0) <= tol;
|
|
2429
|
+
const dir = [chord[0] / len, chord[1] / len];
|
|
2430
|
+
return [c.p1, c.p2].every((h) => {
|
|
2431
|
+
const v = sub(h, c.p0);
|
|
2432
|
+
const along = dot(v, dir);
|
|
2433
|
+
const across = Math.abs(v[0] * dir[1] - v[1] * dir[0]);
|
|
2434
|
+
return across <= tol && along >= -tol && along <= len + tol;
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
function cubicsToPathData(segments, options = {}) {
|
|
2438
|
+
if (segments.length === 0) return "";
|
|
2439
|
+
const f = (n) => formatNumber(n, options.precision ?? 3);
|
|
2440
|
+
const dx = options.offset?.x ?? 0;
|
|
2441
|
+
const dy = options.offset?.y ?? 0;
|
|
2442
|
+
const pt = (p) => `${f(p[0] + dx)} ${f(p[1] + dy)}`;
|
|
2443
|
+
const straight = options.straightTolerance ?? 1e-3;
|
|
2444
|
+
let d = `M${pt(segments[0].p0)}`;
|
|
2445
|
+
segments.forEach((c, i) => {
|
|
2446
|
+
const last = options.closed && i === segments.length - 1;
|
|
2447
|
+
if (isStraightCubic(c, straight)) {
|
|
2448
|
+
if (!last) d += `L${pt(c.p3)}`;
|
|
2449
|
+
} else d += `C${pt(c.p1)} ${pt(c.p2)} ${pt(c.p3)}`;
|
|
2450
|
+
});
|
|
2451
|
+
return options.closed ? `${d}Z` : d;
|
|
2452
|
+
}
|
|
2453
|
+
function polylineToPathData(points, closed, options) {
|
|
2454
|
+
if (points.length < 2) return "";
|
|
2455
|
+
const f = (n) => formatNumber(n, options.precision ?? 3);
|
|
2456
|
+
const dx = options.offset?.x ?? 0;
|
|
2457
|
+
const dy = options.offset?.y ?? 0;
|
|
2458
|
+
let d = `M${f(points[0][0] + dx)} ${f(points[0][1] + dy)}`;
|
|
2459
|
+
for (let i = 1; i < points.length; i++) d += `L${f(points[i][0] + dx)} ${f(points[i][1] + dy)}`;
|
|
2460
|
+
return closed ? `${d}Z` : d;
|
|
2461
|
+
}
|
|
2462
|
+
function fitPathData(ring, options = {}) {
|
|
2463
|
+
const closed = options.closed ?? true;
|
|
2464
|
+
const tolerance = options.tolerance ?? 0.5;
|
|
2465
|
+
const pts = dedup(ring);
|
|
2466
|
+
const segments = fitCubics(pts, tolerance, { ...options, closed });
|
|
2467
|
+
if (segments.length === 0) return "";
|
|
2468
|
+
const edges = closed ? pts.length : pts.length - 1;
|
|
2469
|
+
if (options.noWorse !== false) {
|
|
2470
|
+
const straight = segments.filter((c) => isStraightCubic(c, tolerance * 0.02)).length;
|
|
2471
|
+
if (segments.length >= edges && straight === segments.length) return polylineToPathData(pts, closed, options);
|
|
2472
|
+
if (segments.length > edges) return polylineToPathData(pts, closed, options);
|
|
2473
|
+
}
|
|
2474
|
+
return cubicsToPathData(segments, {
|
|
2475
|
+
precision: options.precision,
|
|
2476
|
+
offset: options.offset,
|
|
2477
|
+
closed,
|
|
2478
|
+
straightTolerance: tolerance * 0.02
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
function fitFreehandPath(points, options = {}) {
|
|
2482
|
+
return fitPathData(points, { tolerance: 1, ...options, closed: false });
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2177
2485
|
// src/canvas/shapeGeometry.ts
|
|
2178
2486
|
var SHAPE_KINDS = ["rect", "ellipse", "polygon", "star", "path"];
|
|
2179
2487
|
function isShapeDescription(value) {
|
|
@@ -2540,7 +2848,7 @@ function rotationTransform(shape, size) {
|
|
|
2540
2848
|
}
|
|
2541
2849
|
function getShapeSvg(shape, size) {
|
|
2542
2850
|
const { width: w, height: h } = size;
|
|
2543
|
-
const f = (n) =>
|
|
2851
|
+
const f = (n) => formatNumber2(n, 3);
|
|
2544
2852
|
const transforms = [];
|
|
2545
2853
|
if (shape.rotation) transforms.push(`rotate(${f(shape.rotation)} ${f(w / 2)} ${f(h / 2)})`);
|
|
2546
2854
|
let d = "";
|
|
@@ -2589,7 +2897,7 @@ function getShapeRenderData(shape, size = {}) {
|
|
|
2589
2897
|
const height = size.height && size.height > 0 ? size.height : vb ? vb[3] : 100;
|
|
2590
2898
|
const svg = getShapeSvg(shape, { width, height });
|
|
2591
2899
|
const data = {
|
|
2592
|
-
viewBox: `0 0 ${
|
|
2900
|
+
viewBox: `0 0 ${formatNumber2(width, 3)} ${formatNumber2(height, 3)}`,
|
|
2593
2901
|
width,
|
|
2594
2902
|
height,
|
|
2595
2903
|
d: svg.d,
|
|
@@ -2636,7 +2944,7 @@ function shapeToPolygon(shape, size, options = {}) {
|
|
|
2636
2944
|
if (rotate) rings = rings.map((ring) => ring.map(rotate));
|
|
2637
2945
|
return { rings, fillRule };
|
|
2638
2946
|
}
|
|
2639
|
-
function
|
|
2947
|
+
function formatNumber2(value, precision = 3) {
|
|
2640
2948
|
const factor = 10 ** precision;
|
|
2641
2949
|
const rounded = Math.round(value * factor) / factor;
|
|
2642
2950
|
return Object.is(rounded, -0) ? "0" : String(rounded);
|
|
@@ -2670,9 +2978,9 @@ function rdp(points, tolerance) {
|
|
|
2670
2978
|
let maxDist = -1;
|
|
2671
2979
|
let index = -1;
|
|
2672
2980
|
for (let i = s + 1; i < e; i++) {
|
|
2673
|
-
const
|
|
2674
|
-
if (
|
|
2675
|
-
maxDist =
|
|
2981
|
+
const dist2 = segmentDistance(points[i], points[s], points[e]);
|
|
2982
|
+
if (dist2 > maxDist) {
|
|
2983
|
+
maxDist = dist2;
|
|
2676
2984
|
index = i;
|
|
2677
2985
|
}
|
|
2678
2986
|
}
|
|
@@ -2688,9 +2996,9 @@ function simplifyRing(ring, tolerance) {
|
|
|
2688
2996
|
let far = 0;
|
|
2689
2997
|
let farDist = -1;
|
|
2690
2998
|
for (let i = 1; i < ring.length; i++) {
|
|
2691
|
-
const
|
|
2692
|
-
if (
|
|
2693
|
-
farDist =
|
|
2999
|
+
const dist2 = Math.hypot(ring[i][0] - ring[0][0], ring[i][1] - ring[0][1]);
|
|
3000
|
+
if (dist2 > farDist) {
|
|
3001
|
+
farDist = dist2;
|
|
2694
3002
|
far = i;
|
|
2695
3003
|
}
|
|
2696
3004
|
}
|
|
@@ -2701,20 +3009,31 @@ function simplifyRing(ring, tolerance) {
|
|
|
2701
3009
|
}
|
|
2702
3010
|
function polygonToSvgPath(input, options = {}) {
|
|
2703
3011
|
const precision = options.precision ?? 3;
|
|
2704
|
-
const f = (n) =>
|
|
3012
|
+
const f = (n) => formatNumber2(n, precision);
|
|
2705
3013
|
const dx = options.offset?.x ?? 0;
|
|
2706
3014
|
const dy = options.offset?.y ?? 0;
|
|
2707
3015
|
const rings = [];
|
|
3016
|
+
const fit = options.curves === "fit";
|
|
3017
|
+
let fitted = "";
|
|
2708
3018
|
for (const item of input) {
|
|
2709
3019
|
const list = item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0]) ? item : [item];
|
|
2710
3020
|
for (const ring of list) {
|
|
2711
3021
|
let r = ring;
|
|
2712
3022
|
if (options.simplify && options.simplify > 0) r = simplifyRing(r, options.simplify);
|
|
2713
3023
|
if (r.length < 3) continue;
|
|
3024
|
+
if (fit) {
|
|
3025
|
+
fitted += fitPathData(r, {
|
|
3026
|
+
tolerance: options.curveTolerance ?? 0.25,
|
|
3027
|
+
cornerAngleDeg: options.cornerAngleDeg,
|
|
3028
|
+
precision,
|
|
3029
|
+
offset: { x: dx, y: dy }
|
|
3030
|
+
});
|
|
3031
|
+
continue;
|
|
3032
|
+
}
|
|
2714
3033
|
rings.push(dx || dy ? r.map(([x, y]) => [x + dx, y + dy]) : r);
|
|
2715
3034
|
}
|
|
2716
3035
|
}
|
|
2717
|
-
return ringsToPath(rings, f);
|
|
3036
|
+
return fit ? fitted : ringsToPath(rings, f);
|
|
2718
3037
|
}
|
|
2719
3038
|
|
|
2720
3039
|
// src/canvas/shapeNodes.ts
|
|
@@ -2792,7 +3111,10 @@ function combineShapeNodes(nodes, ids, operation, options = {}) {
|
|
|
2792
3111
|
const d = polygonToSvgPath(polygon, {
|
|
2793
3112
|
precision: options.precision,
|
|
2794
3113
|
simplify: options.simplify,
|
|
2795
|
-
offset: { x: -bounds.minX, y: -bounds.minY }
|
|
3114
|
+
offset: { x: -bounds.minX, y: -bounds.minY },
|
|
3115
|
+
curves: options.curves ?? "fit",
|
|
3116
|
+
curveTolerance: options.curveTolerance ?? options.tolerance ?? 0.25,
|
|
3117
|
+
cornerAngleDeg: options.cornerAngleDeg
|
|
2796
3118
|
});
|
|
2797
3119
|
if (!d) return null;
|
|
2798
3120
|
const sourceShape = getNodeShape(source);
|
|
@@ -2876,12 +3198,1088 @@ function revertShapeCombination(nodes, change, previous) {
|
|
|
2876
3198
|
return out;
|
|
2877
3199
|
}
|
|
2878
3200
|
|
|
3201
|
+
// src/canvas/resizeSnap.ts
|
|
3202
|
+
var RESIZE_HANDLES = [
|
|
3203
|
+
"top-left",
|
|
3204
|
+
"top",
|
|
3205
|
+
"top-right",
|
|
3206
|
+
"right",
|
|
3207
|
+
"bottom-right",
|
|
3208
|
+
"bottom",
|
|
3209
|
+
"bottom-left",
|
|
3210
|
+
"left"
|
|
3211
|
+
];
|
|
3212
|
+
function resizeHandleAxes(handle) {
|
|
3213
|
+
const x = handle.includes("left") ? -1 : handle.includes("right") ? 1 : 0;
|
|
3214
|
+
const y = handle.includes("top") ? -1 : handle.includes("bottom") ? 1 : 0;
|
|
3215
|
+
return { x, y };
|
|
3216
|
+
}
|
|
3217
|
+
function inferResizeHandle(start, next, eps = 1e-6) {
|
|
3218
|
+
const moved = (a, b) => Math.abs(a - b) > eps;
|
|
3219
|
+
const left = moved(start.x, next.x);
|
|
3220
|
+
const right = moved(start.x + start.width, next.x + next.width);
|
|
3221
|
+
const top = moved(start.y, next.y);
|
|
3222
|
+
const bottom = moved(start.y + start.height, next.y + next.height);
|
|
3223
|
+
const h = left ? "left" : right ? "right" : "";
|
|
3224
|
+
const v = top ? "top" : bottom ? "bottom" : "";
|
|
3225
|
+
if (h && v) return `${v}-${h}`;
|
|
3226
|
+
return h || v || null;
|
|
3227
|
+
}
|
|
3228
|
+
function within(axis, v, tolerance) {
|
|
3229
|
+
const { values } = axis;
|
|
3230
|
+
let lo = 0;
|
|
3231
|
+
let hi = values.length;
|
|
3232
|
+
while (lo < hi) {
|
|
3233
|
+
const mid = lo + hi >>> 1;
|
|
3234
|
+
if (values[mid] < v - tolerance) lo = mid + 1;
|
|
3235
|
+
else hi = mid;
|
|
3236
|
+
}
|
|
3237
|
+
const out = [];
|
|
3238
|
+
for (let i = lo; i < values.length && values[i] <= v + tolerance; i++) out.push(values[i]);
|
|
3239
|
+
return out;
|
|
3240
|
+
}
|
|
3241
|
+
function matching(axis, v, eps) {
|
|
3242
|
+
const { values, rects } = axis;
|
|
3243
|
+
const out = [];
|
|
3244
|
+
for (let i = 0; i < values.length; i++) if (Math.abs(values[i] - v) <= eps) out.push(rects[i]);
|
|
3245
|
+
return out;
|
|
3246
|
+
}
|
|
3247
|
+
function snapResize(rect, handle, targets, options = {}) {
|
|
3248
|
+
const index = Array.isArray(targets) ? createGuideIndex(targets) : targets;
|
|
3249
|
+
const zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;
|
|
3250
|
+
const tolerance = (options.threshold ?? 6) / zoom;
|
|
3251
|
+
const eps = 0.5 / zoom;
|
|
3252
|
+
const useEdges = options.edges !== false;
|
|
3253
|
+
const useCentres = options.centres !== false;
|
|
3254
|
+
const grid = options.gridSize ?? 0;
|
|
3255
|
+
const axes = resizeHandleAxes(handle);
|
|
3256
|
+
const unchanged = { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false };
|
|
3257
|
+
if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) return unchanged;
|
|
3258
|
+
const x0 = rect.x;
|
|
3259
|
+
const x1 = rect.x + rect.width;
|
|
3260
|
+
const y0 = rect.y;
|
|
3261
|
+
const y1 = rect.y + rect.height;
|
|
3262
|
+
const bestHit = (horizontal) => {
|
|
3263
|
+
const dir = horizontal ? axes.x : axes.y;
|
|
3264
|
+
if (dir === 0) return null;
|
|
3265
|
+
const moving = horizontal ? dir < 0 ? x0 : x1 : dir < 0 ? y0 : y1;
|
|
3266
|
+
const fixed = horizontal ? dir < 0 ? x1 : x0 : dir < 0 ? y1 : y0;
|
|
3267
|
+
const min = (horizontal ? options.minWidth : options.minHeight) ?? 0;
|
|
3268
|
+
const valid = (t) => {
|
|
3269
|
+
const size = dir < 0 ? fixed - t : t - fixed;
|
|
3270
|
+
return Number.isFinite(size) && size > 1e-9 && size >= min;
|
|
3271
|
+
};
|
|
3272
|
+
let best = null;
|
|
3273
|
+
const consider = (values, kind) => {
|
|
3274
|
+
for (const t of values) {
|
|
3275
|
+
if (!valid(t)) continue;
|
|
3276
|
+
if (!best || Math.abs(t - moving) < Math.abs(best.value - moving)) best = { value: t, kind };
|
|
3277
|
+
}
|
|
3278
|
+
};
|
|
3279
|
+
if (useEdges) consider(within(horizontal ? index.edgeX : index.edgeY, moving, tolerance), "edge");
|
|
3280
|
+
if (useCentres) consider(within(horizontal ? index.centreX : index.centreY, moving, tolerance), "centre");
|
|
3281
|
+
if (!best && grid > 0) {
|
|
3282
|
+
const origin = horizontal ? options.gridOrigin?.x ?? 0 : options.gridOrigin?.y ?? 0;
|
|
3283
|
+
consider([snapToGrid(moving, grid, origin)], "grid");
|
|
3284
|
+
}
|
|
3285
|
+
return best;
|
|
3286
|
+
};
|
|
3287
|
+
let nx0 = x0;
|
|
3288
|
+
let nx1 = x1;
|
|
3289
|
+
let ny0 = y0;
|
|
3290
|
+
let ny1 = y1;
|
|
3291
|
+
const setEdge = (horizontal, value) => {
|
|
3292
|
+
const dir = horizontal ? axes.x : axes.y;
|
|
3293
|
+
if (horizontal) {
|
|
3294
|
+
if (dir < 0) nx0 = value;
|
|
3295
|
+
else nx1 = value;
|
|
3296
|
+
} else if (dir < 0) ny0 = value;
|
|
3297
|
+
else ny1 = value;
|
|
3298
|
+
};
|
|
3299
|
+
let hitX = bestHit(true);
|
|
3300
|
+
let hitY = bestHit(false);
|
|
3301
|
+
const ratio = rect.height > 0 ? rect.width / rect.height : 0;
|
|
3302
|
+
if (options.keepAspectRatio && ratio > 0 && (hitX || hitY)) {
|
|
3303
|
+
if (axes.x !== 0 && axes.y !== 0) {
|
|
3304
|
+
const dist2 = (h, horizontal) => h ? Math.abs(h.value - (horizontal ? axes.x < 0 ? x0 : x1 : axes.y < 0 ? y0 : y1)) : Infinity;
|
|
3305
|
+
const useX = dist2(hitX, true) <= dist2(hitY, false);
|
|
3306
|
+
if (useX && hitX) {
|
|
3307
|
+
setEdge(true, hitX.value);
|
|
3308
|
+
const height = (nx1 - nx0) / ratio;
|
|
3309
|
+
if (axes.y < 0) ny0 = ny1 - height;
|
|
3310
|
+
else ny1 = ny0 + height;
|
|
3311
|
+
hitY = null;
|
|
3312
|
+
} else if (hitY) {
|
|
3313
|
+
setEdge(false, hitY.value);
|
|
3314
|
+
const width = (ny1 - ny0) * ratio;
|
|
3315
|
+
if (axes.x < 0) nx0 = nx1 - width;
|
|
3316
|
+
else nx1 = nx0 + width;
|
|
3317
|
+
hitX = null;
|
|
3318
|
+
}
|
|
3319
|
+
const min = (v) => v ?? 0;
|
|
3320
|
+
if (nx1 - nx0 < min(options.minWidth) || ny1 - ny0 < min(options.minHeight) || !(nx1 > nx0 && ny1 > ny0)) {
|
|
3321
|
+
return unchanged;
|
|
3322
|
+
}
|
|
3323
|
+
} else if (axes.x !== 0 && hitX) {
|
|
3324
|
+
setEdge(true, hitX.value);
|
|
3325
|
+
const height = (nx1 - nx0) / ratio;
|
|
3326
|
+
const cy = (y0 + y1) / 2;
|
|
3327
|
+
ny0 = cy - height / 2;
|
|
3328
|
+
ny1 = cy + height / 2;
|
|
3329
|
+
} else if (axes.y !== 0 && hitY) {
|
|
3330
|
+
setEdge(false, hitY.value);
|
|
3331
|
+
const width = (ny1 - ny0) * ratio;
|
|
3332
|
+
const cx = (x0 + x1) / 2;
|
|
3333
|
+
nx0 = cx - width / 2;
|
|
3334
|
+
nx1 = cx + width / 2;
|
|
3335
|
+
}
|
|
3336
|
+
} else {
|
|
3337
|
+
if (hitX) setEdge(true, hitX.value);
|
|
3338
|
+
if (hitY) setEdge(false, hitY.value);
|
|
3339
|
+
}
|
|
3340
|
+
const out = { x: nx0, y: ny0, width: nx1 - nx0, height: ny1 - ny0 };
|
|
3341
|
+
const guides = [];
|
|
3342
|
+
const guideFor = (horizontal, hit) => {
|
|
3343
|
+
if (!hit || hit.kind === "grid") return;
|
|
3344
|
+
const axis = hit.kind === "edge" ? horizontal ? index.edgeX : index.edgeY : horizontal ? index.centreX : index.centreY;
|
|
3345
|
+
const aligned = matching(axis, hit.value, eps);
|
|
3346
|
+
const span = boundsOf([out, ...aligned]);
|
|
3347
|
+
guides.push(
|
|
3348
|
+
horizontal ? { orientation: "vertical", kind: hit.kind, x1: hit.value, y1: span.y, x2: hit.value, y2: span.y + span.height } : { orientation: "horizontal", kind: hit.kind, x1: span.x, y1: hit.value, x2: span.x + span.width, y2: hit.value }
|
|
3349
|
+
);
|
|
3350
|
+
};
|
|
3351
|
+
guideFor(true, hitX);
|
|
3352
|
+
guideFor(false, hitY);
|
|
3353
|
+
const changed = out.x !== rect.x || out.y !== rect.y || out.width !== rect.width || out.height !== rect.height;
|
|
3354
|
+
return {
|
|
3355
|
+
rect: out,
|
|
3356
|
+
guides,
|
|
3357
|
+
snappedX: !!hitX && hitX.kind !== "grid",
|
|
3358
|
+
snappedY: !!hitY && hitY.kind !== "grid",
|
|
3359
|
+
changed
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
function snapNodeResize(nodes, id, rect, handle, options = {}) {
|
|
3363
|
+
const lookup = createNodeLookup(nodes);
|
|
3364
|
+
const node = lookup.get(id);
|
|
3365
|
+
if (!node || isLocked(node)) {
|
|
3366
|
+
return { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false, updates: [] };
|
|
3367
|
+
}
|
|
3368
|
+
const index = options.index ?? createNodeGuideIndex(nodes, [id], options);
|
|
3369
|
+
const result = snapResize(rect, handle, index, options);
|
|
3370
|
+
if (!result.changed) return { ...result, updates: [] };
|
|
3371
|
+
const parent = node.parentId ? lookup.get(node.parentId) : void 0;
|
|
3372
|
+
const origin = parent ? getAbsolutePosition(parent, lookup) : { x: 0, y: 0 };
|
|
3373
|
+
return {
|
|
3374
|
+
...result,
|
|
3375
|
+
updates: [
|
|
3376
|
+
{
|
|
3377
|
+
id,
|
|
3378
|
+
position: { x: result.rect.x - origin.x, y: result.rect.y - origin.y },
|
|
3379
|
+
width: result.rect.width,
|
|
3380
|
+
height: result.rect.height
|
|
3381
|
+
}
|
|
3382
|
+
]
|
|
3383
|
+
};
|
|
3384
|
+
}
|
|
3385
|
+
function getResizingNodeRect(node, lookup, options = {}) {
|
|
3386
|
+
const { x, y } = node.parentId ? getAbsolutePosition(node, lookup) : node.position;
|
|
3387
|
+
const size = getNodeSize(node, options);
|
|
3388
|
+
return { x, y, width: node.width ?? size.width, height: node.height ?? size.height };
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
// src/canvas/autoLayout.ts
|
|
3392
|
+
var AUTO_LAYOUT_DATA_KEY = "layout";
|
|
3393
|
+
var CHILD_LAYOUT_DATA_KEY = "childLayout";
|
|
3394
|
+
var MODES = /* @__PURE__ */ new Set(["horizontal", "vertical", "grid"]);
|
|
3395
|
+
var EPS = 1e-6;
|
|
3396
|
+
var MAX_TRACKS = 1e3;
|
|
3397
|
+
var MAX_GRID_CELLS = 1e6;
|
|
3398
|
+
function clampLayoutSize(value, min, max) {
|
|
3399
|
+
let v = Number.isFinite(value) ? Math.max(value, 0) : 0;
|
|
3400
|
+
if (typeof max === "number" && Number.isFinite(max)) v = Math.min(v, max);
|
|
3401
|
+
if (typeof min === "number" && Number.isFinite(min)) v = Math.max(v, min);
|
|
3402
|
+
return v;
|
|
3403
|
+
}
|
|
3404
|
+
var finite = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
3405
|
+
function getAutoLayout(node) {
|
|
3406
|
+
const layout2 = node?.data?.[AUTO_LAYOUT_DATA_KEY];
|
|
3407
|
+
return layout2 && typeof layout2 === "object" && MODES.has(layout2.mode) ? layout2 : void 0;
|
|
3408
|
+
}
|
|
3409
|
+
function isAutoLayoutFrame(node) {
|
|
3410
|
+
return getAutoLayout(node) !== void 0;
|
|
3411
|
+
}
|
|
3412
|
+
function getChildLayout(node) {
|
|
3413
|
+
const child = node?.data?.[CHILD_LAYOUT_DATA_KEY];
|
|
3414
|
+
return child && typeof child === "object" ? child : {};
|
|
3415
|
+
}
|
|
3416
|
+
function getLayoutSizing(node, axis) {
|
|
3417
|
+
const value = getChildLayout(node).sizing?.[axis] ?? getAutoLayout(node)?.sizing?.[axis];
|
|
3418
|
+
return value === "hug" || value === "fill" ? value : "fixed";
|
|
3419
|
+
}
|
|
3420
|
+
function limitsOf(node) {
|
|
3421
|
+
const child = getChildLayout(node);
|
|
3422
|
+
const layout2 = getAutoLayout(node);
|
|
3423
|
+
const min = child.min ?? layout2?.min ?? {};
|
|
3424
|
+
const max = child.max ?? layout2?.max ?? {};
|
|
3425
|
+
return { minW: min.width, maxW: max.width, minH: min.height, maxH: max.height };
|
|
3426
|
+
}
|
|
3427
|
+
function resolvePadding(padding) {
|
|
3428
|
+
let sides;
|
|
3429
|
+
if (typeof padding === "number") sides = [padding, padding, padding, padding];
|
|
3430
|
+
else if (Array.isArray(padding)) {
|
|
3431
|
+
sides = padding.length === 2 ? [padding[0], padding[1], padding[0], padding[1]] : [padding[0], padding[1], padding[2], padding[3]];
|
|
3432
|
+
} else if (padding && typeof padding === "object") sides = [padding.top, padding.right, padding.bottom, padding.left];
|
|
3433
|
+
else sides = [0, 0, 0, 0];
|
|
3434
|
+
return sides.map((p) => Math.max(finite(p), 0));
|
|
3435
|
+
}
|
|
3436
|
+
var LayoutState = class {
|
|
3437
|
+
boxes = /* @__PURE__ */ new Map();
|
|
3438
|
+
measure;
|
|
3439
|
+
visiting = /* @__PURE__ */ new Set();
|
|
3440
|
+
original = /* @__PURE__ */ new Map();
|
|
3441
|
+
constructor(nodes, options = {}) {
|
|
3442
|
+
this.measure = options.measure ?? {};
|
|
3443
|
+
for (const node of nodes) {
|
|
3444
|
+
const size = this.measure.size?.(node) ?? getNodeSize(node, options.rectOptions);
|
|
3445
|
+
const box = {
|
|
3446
|
+
id: node.id,
|
|
3447
|
+
node,
|
|
3448
|
+
parent: node.parentId,
|
|
3449
|
+
x: finite(node.position?.x),
|
|
3450
|
+
y: finite(node.position?.y),
|
|
3451
|
+
w: finite(size.width),
|
|
3452
|
+
h: finite(size.height),
|
|
3453
|
+
kids: []
|
|
3454
|
+
};
|
|
3455
|
+
this.boxes.set(node.id, box);
|
|
3456
|
+
this.original.set(node.id, { x: box.x, y: box.y, w: box.w, h: box.h });
|
|
3457
|
+
}
|
|
3458
|
+
for (const node of nodes) {
|
|
3459
|
+
if (node.parentId && node.parentId !== node.id) this.boxes.get(node.parentId)?.kids.push(node.id);
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
updates() {
|
|
3463
|
+
const out = [];
|
|
3464
|
+
for (const [id, box] of this.boxes) {
|
|
3465
|
+
const o = this.original.get(id);
|
|
3466
|
+
const update = { id };
|
|
3467
|
+
if (Math.abs(box.x - o.x) > EPS || Math.abs(box.y - o.y) > EPS) update.position = { x: box.x, y: box.y };
|
|
3468
|
+
if (Math.abs(box.w - o.w) > EPS) update.width = box.w;
|
|
3469
|
+
if (Math.abs(box.h - o.h) > EPS) update.height = box.h;
|
|
3470
|
+
if (update.position || update.width !== void 0 || update.height !== void 0) out.push(update);
|
|
3471
|
+
}
|
|
3472
|
+
return out;
|
|
3473
|
+
}
|
|
3474
|
+
/** Runs `fn` for `id` unless it is already on the stack (guards `parentId` cycles). */
|
|
3475
|
+
guard(id, fn) {
|
|
3476
|
+
if (this.visiting.has(id)) return;
|
|
3477
|
+
this.visiting.add(id);
|
|
3478
|
+
try {
|
|
3479
|
+
fn();
|
|
3480
|
+
} finally {
|
|
3481
|
+
this.visiting.delete(id);
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
ancestors(id) {
|
|
3485
|
+
const out = [];
|
|
3486
|
+
const seen = /* @__PURE__ */ new Set([id]);
|
|
3487
|
+
let parent = this.boxes.get(id)?.parent;
|
|
3488
|
+
while (parent && !seen.has(parent) && this.boxes.has(parent)) {
|
|
3489
|
+
out.push(parent);
|
|
3490
|
+
seen.add(parent);
|
|
3491
|
+
parent = this.boxes.get(parent).parent;
|
|
3492
|
+
}
|
|
3493
|
+
return out;
|
|
3494
|
+
}
|
|
3495
|
+
};
|
|
3496
|
+
function setSize(s, id, w, h) {
|
|
3497
|
+
const b = s.boxes.get(id);
|
|
3498
|
+
if (!b) return;
|
|
3499
|
+
b.w = Number.isFinite(w) ? Math.max(w, 0) : b.w;
|
|
3500
|
+
b.h = Number.isFinite(h) ? Math.max(h, 0) : b.h;
|
|
3501
|
+
}
|
|
3502
|
+
function place(s, id, x, y) {
|
|
3503
|
+
const b = s.boxes.get(id);
|
|
3504
|
+
if (!b) return;
|
|
3505
|
+
b.x = x;
|
|
3506
|
+
b.y = y;
|
|
3507
|
+
}
|
|
3508
|
+
var isText = (s, b) => !!s.measure.textSize && s.measure.textSize(b.node) !== void 0;
|
|
3509
|
+
function flowChildren(s, frame) {
|
|
3510
|
+
const f = s.boxes.get(frame);
|
|
3511
|
+
if (!f) return [];
|
|
3512
|
+
return f.kids.filter((k) => {
|
|
3513
|
+
const n = s.boxes.get(k).node;
|
|
3514
|
+
return n.hidden !== true && !getChildLayout(n).absolute;
|
|
3515
|
+
});
|
|
3516
|
+
}
|
|
3517
|
+
function fitHugText(s, id) {
|
|
3518
|
+
const b = s.boxes.get(id);
|
|
3519
|
+
if (!b || !isText(s, b)) return;
|
|
3520
|
+
const natural = s.measure.textSize(b.node);
|
|
3521
|
+
const { minW, maxW } = limitsOf(b.node);
|
|
3522
|
+
if (getLayoutSizing(b.node, "width") === "hug") {
|
|
3523
|
+
const w = clampLayoutSize(natural.width, minW, maxW);
|
|
3524
|
+
const limited = Math.abs(w - natural.width) > EPS;
|
|
3525
|
+
const h = limited ? s.measure.textHeight?.(b.node, w) ?? natural.height : natural.height;
|
|
3526
|
+
setSize(s, id, w, h);
|
|
3527
|
+
} else if (getLayoutSizing(b.node, "height") === "hug") {
|
|
3528
|
+
setSize(s, id, b.w, s.measure.textHeight?.(b.node, b.w) ?? b.h);
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
function enforceLimits(s, id) {
|
|
3532
|
+
const b = s.boxes.get(id);
|
|
3533
|
+
if (!b) return;
|
|
3534
|
+
const { minW, maxW, minH, maxH } = limitsOf(b.node);
|
|
3535
|
+
if (minW === void 0 && maxW === void 0 && minH === void 0 && maxH === void 0) return;
|
|
3536
|
+
const nw = clampLayoutSize(b.w, minW, maxW);
|
|
3537
|
+
const nh = clampLayoutSize(b.h, minH, maxH);
|
|
3538
|
+
if (Math.abs(nw - b.w) < 1e-9 && Math.abs(nh - b.h) < 1e-9) return;
|
|
3539
|
+
if (isText(s, b)) setSize(s, id, nw, nh);
|
|
3540
|
+
else resizeChild(s, id, nw, nh);
|
|
3541
|
+
}
|
|
3542
|
+
function itemBaseline(s, id, depth) {
|
|
3543
|
+
const b = s.boxes.get(id);
|
|
3544
|
+
if (!b) return void 0;
|
|
3545
|
+
if (isText(s, b)) {
|
|
3546
|
+
const v = s.measure.textBaseline?.(b.node);
|
|
3547
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
3548
|
+
}
|
|
3549
|
+
const layout2 = getAutoLayout(b.node);
|
|
3550
|
+
if (!layout2 || layout2.mode === "grid" || depth >= 32) return void 0;
|
|
3551
|
+
for (const k of flowChildren(s, id)) {
|
|
3552
|
+
const base = itemBaseline(s, k, depth + 1);
|
|
3553
|
+
if (base !== void 0) return s.boxes.get(k).y + base;
|
|
3554
|
+
}
|
|
3555
|
+
return void 0;
|
|
3556
|
+
}
|
|
3557
|
+
function resizeChild(s, id, w, h) {
|
|
3558
|
+
const b = s.boxes.get(id);
|
|
3559
|
+
if (!b) return;
|
|
3560
|
+
if (isText(s, b) && getLayoutSizing(b.node, "height") === "hug") {
|
|
3561
|
+
setSize(s, id, w, s.measure.textHeight?.(b.node, w) ?? h);
|
|
3562
|
+
return;
|
|
3563
|
+
}
|
|
3564
|
+
const ow = b.w;
|
|
3565
|
+
const oh = b.h;
|
|
3566
|
+
setSize(s, id, w, h);
|
|
3567
|
+
if (b.kids.length > 0) constrain(s, id, ow, oh);
|
|
3568
|
+
if (getAutoLayout(b.node)) layout(s, id);
|
|
3569
|
+
}
|
|
3570
|
+
function mainOffsets(align, inner, total, n, gap) {
|
|
3571
|
+
const gaps = gap * Math.max(n - 1, 0);
|
|
3572
|
+
const free = inner - total;
|
|
3573
|
+
const k = Math.max(n, 1);
|
|
3574
|
+
switch (align) {
|
|
3575
|
+
case "center":
|
|
3576
|
+
return [(free - gaps) / 2, gap];
|
|
3577
|
+
case "end":
|
|
3578
|
+
return [free - gaps, gap];
|
|
3579
|
+
case "space-between":
|
|
3580
|
+
return [0, n > 1 ? Math.max(free / (n - 1), 0) : 0];
|
|
3581
|
+
case "space-around": {
|
|
3582
|
+
const g = Math.max(free / k, 0);
|
|
3583
|
+
return [g / 2, g];
|
|
3584
|
+
}
|
|
3585
|
+
case "space-evenly": {
|
|
3586
|
+
const g = Math.max(free / (k + 1), 0);
|
|
3587
|
+
return [g, g];
|
|
3588
|
+
}
|
|
3589
|
+
default:
|
|
3590
|
+
return [0, gap];
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
function distribute(items, open, free) {
|
|
3594
|
+
free = Math.max(free, 0);
|
|
3595
|
+
while (open.length > 0) {
|
|
3596
|
+
const share = free / open.length;
|
|
3597
|
+
const sized = open.map((i) => [i, clampLayoutSize(share, items[i].minMain, items[i].maxMain)]);
|
|
3598
|
+
const violation = sized.reduce((sum, [, v]) => sum + (v - share), 0);
|
|
3599
|
+
const frozen = (v) => Math.abs(violation) < 1e-9 || violation > 0 && v > share + 1e-9 || violation < 0 && v < share - 1e-9;
|
|
3600
|
+
for (const [i, v] of sized) {
|
|
3601
|
+
if (frozen(v)) {
|
|
3602
|
+
items[i].main = v;
|
|
3603
|
+
free -= v;
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
free = Math.max(free, 0);
|
|
3607
|
+
open = sized.filter(([, v]) => !frozen(v)).map(([i]) => i);
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
function layout(s, frame) {
|
|
3611
|
+
s.guard(frame, () => layoutFrameInner(s, frame));
|
|
3612
|
+
}
|
|
3613
|
+
function layoutFrameInner(s, frame) {
|
|
3614
|
+
const f = s.boxes.get(frame);
|
|
3615
|
+
const al = f && getAutoLayout(f.node);
|
|
3616
|
+
if (!f || !al) return;
|
|
3617
|
+
const horizontal = al.mode !== "vertical";
|
|
3618
|
+
const hugW = getLayoutSizing(f.node, "width") === "hug";
|
|
3619
|
+
const hugH = getLayoutSizing(f.node, "height") === "hug";
|
|
3620
|
+
const ow = f.w;
|
|
3621
|
+
const oh = f.h;
|
|
3622
|
+
const fl = limitsOf(f.node);
|
|
3623
|
+
const [pt, pr, pb, pl] = resolvePadding(al.padding);
|
|
3624
|
+
const fw = Math.max(clampLayoutSize(ow, fl.minW, fl.maxW), pl + pr);
|
|
3625
|
+
const fh = Math.max(clampLayoutSize(oh, fl.minH, fl.maxH), pt + pb);
|
|
3626
|
+
const kids = flowChildren(s, frame);
|
|
3627
|
+
for (const c of kids) {
|
|
3628
|
+
fitHugText(s, c);
|
|
3629
|
+
enforceLimits(s, c);
|
|
3630
|
+
}
|
|
3631
|
+
if (al.mode === "grid") {
|
|
3632
|
+
layoutGrid(s, frame, al);
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3635
|
+
const innerMainFixed = horizontal ? fw - pl - pr : fh - pt - pb;
|
|
3636
|
+
const innerCrossFixed = horizontal ? fh - pt - pb : fw - pl - pr;
|
|
3637
|
+
const hugMain = horizontal ? hugW : hugH;
|
|
3638
|
+
const hugCross = horizontal ? hugH : hugW;
|
|
3639
|
+
const alignCross = al.alignCross ?? "start";
|
|
3640
|
+
const baseline = horizontal && alignCross === "baseline";
|
|
3641
|
+
const stretch = alignCross === "stretch";
|
|
3642
|
+
const items = [];
|
|
3643
|
+
for (const c of kids) {
|
|
3644
|
+
const b = s.boxes.get(c);
|
|
3645
|
+
const lim = limitsOf(b.node);
|
|
3646
|
+
const sm = getLayoutSizing(b.node, horizontal ? "width" : "height");
|
|
3647
|
+
const sc = getLayoutSizing(b.node, horizontal ? "height" : "width");
|
|
3648
|
+
items.push({
|
|
3649
|
+
id: c,
|
|
3650
|
+
main: horizontal ? b.w : b.h,
|
|
3651
|
+
cross: horizontal ? b.h : b.w,
|
|
3652
|
+
// Fill needs room to fill: on an axis the frame hugs it reads as fixed.
|
|
3653
|
+
fillMain: sm === "fill" && !hugMain,
|
|
3654
|
+
fillCross: (sc === "fill" || stretch && sc !== "hug") && !hugCross,
|
|
3655
|
+
minMain: horizontal ? lim.minW : lim.minH,
|
|
3656
|
+
maxMain: horizontal ? lim.maxW : lim.maxH,
|
|
3657
|
+
minCross: horizontal ? lim.minH : lim.minW,
|
|
3658
|
+
maxCross: horizontal ? lim.maxH : lim.maxW,
|
|
3659
|
+
baseOff: 0
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3662
|
+
const gap = finite(al.gap);
|
|
3663
|
+
const alignMain = al.alignMain ?? "start";
|
|
3664
|
+
const autoGap = alignMain === "space-between" || alignMain === "space-around" || alignMain === "space-evenly";
|
|
3665
|
+
const wraps = !!al.wrap && !hugMain;
|
|
3666
|
+
const sizeOf = (id) => {
|
|
3667
|
+
const b = s.boxes.get(id);
|
|
3668
|
+
return horizontal ? [b.w, b.h] : [b.h, b.w];
|
|
3669
|
+
};
|
|
3670
|
+
const resize = (it, main, cross) => resizeChild(s, it.id, horizontal ? main : cross, horizontal ? cross : main);
|
|
3671
|
+
if (!wraps) {
|
|
3672
|
+
for (const it of items) {
|
|
3673
|
+
if (!it.fillCross || it.fillMain) continue;
|
|
3674
|
+
resize(it, it.main, clampLayoutSize(innerCrossFixed, it.minCross, it.maxCross));
|
|
3675
|
+
it.main = sizeOf(it.id)[0];
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
const lines = [];
|
|
3679
|
+
if (wraps) {
|
|
3680
|
+
let cur = [];
|
|
3681
|
+
let used = 0;
|
|
3682
|
+
items.forEach((it, i) => {
|
|
3683
|
+
const sz = it.fillMain ? it.minMain ?? 0 : it.main;
|
|
3684
|
+
const add2 = cur.length === 0 ? sz : Math.max(gap, 0) + sz;
|
|
3685
|
+
if (cur.length > 0 && used + add2 > innerMainFixed + 1e-6) {
|
|
3686
|
+
lines.push(cur);
|
|
3687
|
+
cur = [];
|
|
3688
|
+
used = sz;
|
|
3689
|
+
} else {
|
|
3690
|
+
used += add2;
|
|
3691
|
+
}
|
|
3692
|
+
cur.push(i);
|
|
3693
|
+
});
|
|
3694
|
+
if (cur.length > 0) lines.push(cur);
|
|
3695
|
+
} else {
|
|
3696
|
+
lines.push(items.map((_, i) => i));
|
|
3697
|
+
}
|
|
3698
|
+
for (const line2 of lines) {
|
|
3699
|
+
const fixed = line2.filter((i) => !items[i].fillMain).reduce((sum, i) => sum + items[i].main, 0);
|
|
3700
|
+
const gaps = gap * Math.max(line2.length - 1, 0);
|
|
3701
|
+
const fills = line2.filter((i) => items[i].fillMain);
|
|
3702
|
+
if (fills.length > 0) distribute(items, fills, innerMainFixed - fixed - (autoGap ? 0 : gaps));
|
|
3703
|
+
}
|
|
3704
|
+
for (const it of items) {
|
|
3705
|
+
if (!it.fillMain) continue;
|
|
3706
|
+
const cross = it.fillCross && !wraps ? clampLayoutSize(innerCrossFixed, it.minCross, it.maxCross) : it.cross;
|
|
3707
|
+
resize(it, it.main, cross);
|
|
3708
|
+
it.cross = sizeOf(it.id)[1];
|
|
3709
|
+
}
|
|
3710
|
+
if (baseline) {
|
|
3711
|
+
for (const it of items) if (!it.fillCross) it.baseline = itemBaseline(s, it.id, 0);
|
|
3712
|
+
}
|
|
3713
|
+
const extents = [];
|
|
3714
|
+
for (const line2 of lines) {
|
|
3715
|
+
const gaps = gap * Math.max(line2.length - 1, 0);
|
|
3716
|
+
const lineBase = line2.reduce((m, i) => Math.max(m, items[i].baseline ?? 0), 0);
|
|
3717
|
+
for (const i of line2) items[i].baseOff = items[i].baseline !== void 0 ? lineBase - items[i].baseline : 0;
|
|
3718
|
+
const inLine = line2.map((i) => items[i]);
|
|
3719
|
+
const mainUsed = inLine.reduce((sum, it) => sum + it.main, 0) + gaps;
|
|
3720
|
+
const allFill = inLine.every((it) => it.fillCross);
|
|
3721
|
+
const cross = inLine.filter((it) => allFill || !it.fillCross).reduce((m, it) => Math.max(m, it.baseOff + it.cross), 0);
|
|
3722
|
+
extents.push([mainUsed, cross]);
|
|
3723
|
+
}
|
|
3724
|
+
const contentMain = extents.reduce((m, l) => Math.max(m, l[0]), 0);
|
|
3725
|
+
let crossGap = al.rowGap !== void 0 ? finite(al.rowGap) : gap;
|
|
3726
|
+
const contentCross = extents.reduce((sum, l) => sum + l[1], 0) + crossGap * Math.max(lines.length - 1, 0);
|
|
3727
|
+
let nw = fw;
|
|
3728
|
+
let nh = fh;
|
|
3729
|
+
if (hugMain) {
|
|
3730
|
+
const v = contentMain + (horizontal ? pl + pr : pt + pb);
|
|
3731
|
+
if (horizontal) nw = v;
|
|
3732
|
+
else nh = v;
|
|
3733
|
+
}
|
|
3734
|
+
if (hugCross) {
|
|
3735
|
+
const v = contentCross + (horizontal ? pt + pb : pl + pr);
|
|
3736
|
+
if (horizontal) nh = v;
|
|
3737
|
+
else nw = v;
|
|
3738
|
+
}
|
|
3739
|
+
nw = Math.max(clampLayoutSize(nw, fl.minW, fl.maxW), pl + pr);
|
|
3740
|
+
nh = Math.max(clampLayoutSize(nh, fl.minH, fl.maxH), pt + pb);
|
|
3741
|
+
setSize(s, frame, nw, nh);
|
|
3742
|
+
constrain(s, frame, ow, oh);
|
|
3743
|
+
const innerMain = horizontal ? nw - pl - pr : nh - pt - pb;
|
|
3744
|
+
const innerCross = horizontal ? nh - pt - pb : nw - pl - pr;
|
|
3745
|
+
let crossPos = horizontal ? pt : pl;
|
|
3746
|
+
const single = lines.length === 1;
|
|
3747
|
+
if (lines.length > 1) {
|
|
3748
|
+
const linesCross = extents.reduce((sum, l) => sum + l[1], 0);
|
|
3749
|
+
const between = lines.length - 1;
|
|
3750
|
+
const spread = (innerCross - linesCross) / between;
|
|
3751
|
+
const spaceBetween = al.alignContent === "space-between";
|
|
3752
|
+
if (spaceBetween && spread > 0) {
|
|
3753
|
+
crossGap = spread;
|
|
3754
|
+
} else {
|
|
3755
|
+
if (spaceBetween) crossGap = 0;
|
|
3756
|
+
const free = innerCross - linesCross - crossGap * between;
|
|
3757
|
+
if (free > 0 && items.every((it) => it.fillCross)) {
|
|
3758
|
+
const extra = free / lines.length;
|
|
3759
|
+
for (const l of extents) l[1] += extra;
|
|
3760
|
+
} else if (Number.isFinite(free)) {
|
|
3761
|
+
crossPos += alignCross === "center" ? free / 2 : alignCross === "end" ? free : 0;
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
lines.forEach((line2, li) => {
|
|
3766
|
+
const [used, ownCross] = extents[li];
|
|
3767
|
+
const lineCross = single ? innerCross : ownCross;
|
|
3768
|
+
const total = used - gap * Math.max(line2.length - 1, 0);
|
|
3769
|
+
let [pos, step] = mainOffsets(alignMain, innerMain, total, line2.length, gap);
|
|
3770
|
+
pos += horizontal ? pl : pt;
|
|
3771
|
+
for (const i of line2) {
|
|
3772
|
+
const it = items[i];
|
|
3773
|
+
const crossSize = it.fillCross ? clampLayoutSize(lineCross, it.minCross, it.maxCross) : it.cross;
|
|
3774
|
+
let off = 0;
|
|
3775
|
+
if (alignCross === "baseline") off = it.baseOff;
|
|
3776
|
+
else if (alignCross === "center") off = (lineCross - crossSize) / 2;
|
|
3777
|
+
else if (alignCross === "end") off = lineCross - crossSize;
|
|
3778
|
+
if (it.fillCross && wraps) resize(it, it.main, crossSize);
|
|
3779
|
+
const x = horizontal ? pos : crossPos + off;
|
|
3780
|
+
const y = horizontal ? crossPos + off : pos;
|
|
3781
|
+
place(s, it.id, finite(x), finite(y));
|
|
3782
|
+
pos += it.main + step;
|
|
3783
|
+
}
|
|
3784
|
+
crossPos += lineCross + crossGap;
|
|
3785
|
+
});
|
|
3786
|
+
}
|
|
3787
|
+
function parseTrack(track) {
|
|
3788
|
+
if (typeof track === "number") return { sizing: "fixed", value: track };
|
|
3789
|
+
if (track === "hug") return { sizing: "hug", value: 0 };
|
|
3790
|
+
if (typeof track === "string") {
|
|
3791
|
+
const match = /^\s*(-?\d*\.?\d+)\s*fr\s*$/i.exec(track);
|
|
3792
|
+
if (match) return { sizing: "fill", value: Number(match[1]) };
|
|
3793
|
+
}
|
|
3794
|
+
if (track && typeof track === "object" && "fr" in track) return { sizing: "fill", value: track.fr };
|
|
3795
|
+
return { sizing: "fill", value: 1 };
|
|
3796
|
+
}
|
|
3797
|
+
function trackList(spec, fallback) {
|
|
3798
|
+
if (Array.isArray(spec)) return { count: Math.min(Math.max(spec.length, 1), MAX_TRACKS), tracks: spec.map(parseTrack) };
|
|
3799
|
+
const n = typeof spec === "number" && Number.isFinite(spec) ? Math.floor(spec) : fallback;
|
|
3800
|
+
return { count: Math.min(Math.max(n, 1), MAX_TRACKS), tracks: [] };
|
|
3801
|
+
}
|
|
3802
|
+
function trackSpec(tracks, i) {
|
|
3803
|
+
const t = tracks[i] ?? { sizing: "fill", value: 1 };
|
|
3804
|
+
const value = Number.isFinite(t.value) ? Math.min(Math.max(t.value, 0), 1e6) : 0;
|
|
3805
|
+
return { sizing: t.sizing, value };
|
|
3806
|
+
}
|
|
3807
|
+
function cellRect(g, c) {
|
|
3808
|
+
const x0 = g.columns[c.column]?.[0] ?? 0;
|
|
3809
|
+
const x1 = g.columns[c.column + c.columnSpan - 1]?.[1] ?? x0;
|
|
3810
|
+
const y0 = g.rows[c.row]?.[0] ?? 0;
|
|
3811
|
+
const y1 = g.rows[c.row + c.rowSpan - 1]?.[1] ?? y0;
|
|
3812
|
+
return { x: x0, y: y0, width: Math.max(x1, x0) - x0, height: Math.max(y1, y0) - y0 };
|
|
3813
|
+
}
|
|
3814
|
+
var toIndex = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.floor(v) : void 0;
|
|
3815
|
+
function gridPlace(s, al, kids) {
|
|
3816
|
+
const cols = trackList(al.grid?.columns, 2).count;
|
|
3817
|
+
const minRows = trackList(al.grid?.rows, 1).count;
|
|
3818
|
+
const maxRows = Math.min(Math.max(Math.floor(MAX_GRID_CELLS / cols), 1), MAX_TRACKS);
|
|
3819
|
+
const occ = [];
|
|
3820
|
+
const free = (c, r, cs, rs) => {
|
|
3821
|
+
for (let ri = r; ri < r + rs; ri++) {
|
|
3822
|
+
const row = occ[ri];
|
|
3823
|
+
if (!row) continue;
|
|
3824
|
+
for (let ci = c; ci < c + cs; ci++) if (row[ci]) return false;
|
|
3825
|
+
}
|
|
3826
|
+
return true;
|
|
3827
|
+
};
|
|
3828
|
+
const mark = (c, r, cs, rs) => {
|
|
3829
|
+
while (occ.length < r + rs) occ.push(new Array(cols).fill(false));
|
|
3830
|
+
for (let ri = r; ri < r + rs; ri++) for (let ci = c; ci < c + cs; ci++) occ[ri][ci] = true;
|
|
3831
|
+
};
|
|
3832
|
+
const spans = (id) => {
|
|
3833
|
+
const area = getChildLayout(s.boxes.get(id).node).gridArea ?? {};
|
|
3834
|
+
const cs = Math.min(Math.max(toIndex(area.columnSpan) ?? 1, 1), cols);
|
|
3835
|
+
const rs = Math.min(Math.max(toIndex(area.rowSpan) ?? 1, 1), maxRows);
|
|
3836
|
+
const col = toIndex(area.column);
|
|
3837
|
+
const row = toIndex(area.row);
|
|
3838
|
+
return {
|
|
3839
|
+
col: col === void 0 ? void 0 : Math.min(col, cols - cs),
|
|
3840
|
+
row: row === void 0 ? void 0 : Math.min(row, maxRows - rs),
|
|
3841
|
+
cs,
|
|
3842
|
+
rs
|
|
3843
|
+
};
|
|
3844
|
+
};
|
|
3845
|
+
const placed = new Array(kids.length).fill(void 0);
|
|
3846
|
+
kids.forEach((id, i) => {
|
|
3847
|
+
const { col, row, cs, rs } = spans(id);
|
|
3848
|
+
if (col !== void 0 && row !== void 0) {
|
|
3849
|
+
mark(col, row, cs, rs);
|
|
3850
|
+
placed[i] = { id, column: col, row, columnSpan: cs, rowSpan: rs };
|
|
3851
|
+
}
|
|
3852
|
+
});
|
|
3853
|
+
let cursor = [0, 0];
|
|
3854
|
+
kids.forEach((id, i) => {
|
|
3855
|
+
if (placed[i]) return;
|
|
3856
|
+
const { col, row, cs, rs } = spans(id);
|
|
3857
|
+
const lastRow = maxRows - rs;
|
|
3858
|
+
let c = 0;
|
|
3859
|
+
let r = 0;
|
|
3860
|
+
if (col !== void 0) {
|
|
3861
|
+
r = lastRow;
|
|
3862
|
+
for (let ri = 0; ri <= lastRow; ri++) if (free(col, ri, cs, rs)) {
|
|
3863
|
+
r = ri;
|
|
3864
|
+
break;
|
|
3865
|
+
}
|
|
3866
|
+
c = col;
|
|
3867
|
+
} else if (row !== void 0) {
|
|
3868
|
+
r = row;
|
|
3869
|
+
for (let ci = 0; ci <= cols - cs; ci++) if (free(ci, row, cs, rs)) {
|
|
3870
|
+
c = ci;
|
|
3871
|
+
break;
|
|
3872
|
+
}
|
|
3873
|
+
} else {
|
|
3874
|
+
let [rr, cc] = cursor;
|
|
3875
|
+
let found;
|
|
3876
|
+
while (rr <= lastRow) {
|
|
3877
|
+
if (cc + cs <= cols && free(cc, rr, cs, rs)) {
|
|
3878
|
+
found = [cc, rr];
|
|
3879
|
+
break;
|
|
3880
|
+
}
|
|
3881
|
+
cc += 1;
|
|
3882
|
+
if (cc + cs > cols) {
|
|
3883
|
+
cc = 0;
|
|
3884
|
+
rr += 1;
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
[c, r] = found ?? [0, lastRow];
|
|
3888
|
+
cursor = [r, c + cs];
|
|
3889
|
+
}
|
|
3890
|
+
mark(c, r, cs, rs);
|
|
3891
|
+
placed[i] = { id, column: c, row: r, columnSpan: cs, rowSpan: rs };
|
|
3892
|
+
});
|
|
3893
|
+
const cells = placed.filter((c) => !!c);
|
|
3894
|
+
const rows = Math.max(cells.reduce((m, c) => Math.max(m, c.row + c.rowSpan), 0), minRows);
|
|
3895
|
+
return { cells, rows: Math.min(rows, MAX_TRACKS) };
|
|
3896
|
+
}
|
|
3897
|
+
function resolveTracks(tracks, content, avail) {
|
|
3898
|
+
const out = content.map((c, i) => {
|
|
3899
|
+
const t = trackSpec(tracks, i);
|
|
3900
|
+
return t.sizing === "fixed" ? t.value : c;
|
|
3901
|
+
});
|
|
3902
|
+
if (avail === void 0) return out;
|
|
3903
|
+
const fr = (i) => trackSpec(tracks, i).value;
|
|
3904
|
+
let flex = out.map((_, i) => i).filter((i) => trackSpec(tracks, i).sizing === "fill");
|
|
3905
|
+
let used = out.reduce((sum, v, i) => trackSpec(tracks, i).sizing === "fill" ? sum : sum + v, 0);
|
|
3906
|
+
while (flex.length > 0) {
|
|
3907
|
+
const total = flex.reduce((sum, i) => sum + fr(i), 0);
|
|
3908
|
+
const unit2 = total > 1e-12 ? Math.max(avail - used, 0) / total : 0;
|
|
3909
|
+
const open = flex.filter((i) => unit2 * fr(i) >= out[i]);
|
|
3910
|
+
const frozen = flex.filter((i) => unit2 * fr(i) < out[i]);
|
|
3911
|
+
if (frozen.length === 0) {
|
|
3912
|
+
for (const i of open) out[i] = unit2 * fr(i);
|
|
3913
|
+
break;
|
|
3914
|
+
}
|
|
3915
|
+
used += frozen.reduce((sum, i) => sum + out[i], 0);
|
|
3916
|
+
flex = open;
|
|
3917
|
+
}
|
|
3918
|
+
return out;
|
|
3919
|
+
}
|
|
3920
|
+
function trackContent(cells, sizes, tracks, n, gap, columns, hug) {
|
|
3921
|
+
const byContent = (i) => trackSpec(tracks, i).sizing !== "fixed";
|
|
3922
|
+
const content = Array.from({ length: n }, (_, i) => {
|
|
3923
|
+
const t = trackSpec(tracks, i);
|
|
3924
|
+
return t.sizing === "fixed" ? t.value : 0;
|
|
3925
|
+
});
|
|
3926
|
+
const span = (c) => columns ? [c.column, c.columnSpan] : [c.row, c.rowSpan];
|
|
3927
|
+
cells.forEach((c, k) => {
|
|
3928
|
+
const [start, len] = span(c);
|
|
3929
|
+
if (len === 1 && byContent(start) && start < n) content[start] = Math.max(content[start], sizes[k]);
|
|
3930
|
+
});
|
|
3931
|
+
cells.forEach((c, k) => {
|
|
3932
|
+
const [start, len] = span(c);
|
|
3933
|
+
if (len < 2) return;
|
|
3934
|
+
const range = Array.from({ length: len }, (_, j) => start + j).filter((i) => i < n);
|
|
3935
|
+
if (!hug && range.some((i) => trackSpec(tracks, i).sizing === "fill")) return;
|
|
3936
|
+
const have = range.reduce((sum, i) => sum + content[i], 0) + gap * (len - 1);
|
|
3937
|
+
const grow = range.filter(byContent);
|
|
3938
|
+
if (sizes[k] > have && grow.length > 0) {
|
|
3939
|
+
const extra = (sizes[k] - have) / grow.length;
|
|
3940
|
+
for (const i of grow) content[i] += extra;
|
|
3941
|
+
}
|
|
3942
|
+
});
|
|
3943
|
+
return content;
|
|
3944
|
+
}
|
|
3945
|
+
function trackRanges(start, sizes, gap) {
|
|
3946
|
+
let x = start;
|
|
3947
|
+
return sizes.map((size) => {
|
|
3948
|
+
const r = [x, x + size];
|
|
3949
|
+
x += size + gap;
|
|
3950
|
+
return r;
|
|
3951
|
+
});
|
|
3952
|
+
}
|
|
3953
|
+
function gridGeometryOf(s, frame) {
|
|
3954
|
+
const f = s.boxes.get(frame);
|
|
3955
|
+
const al = f && getAutoLayout(f.node);
|
|
3956
|
+
if (!f || !al || al.mode !== "grid") return void 0;
|
|
3957
|
+
const [pt, pr, pb, pl] = resolvePadding(al.padding);
|
|
3958
|
+
const kids = flowChildren(s, frame);
|
|
3959
|
+
const { cells, rows } = gridPlace(s, al, kids);
|
|
3960
|
+
const colSpec = trackList(al.grid?.columns, 2);
|
|
3961
|
+
const rowSpec = trackList(al.grid?.rows, 1);
|
|
3962
|
+
const cols = colSpec.count;
|
|
3963
|
+
const gx = Math.max(finite(al.gap), 0);
|
|
3964
|
+
const gy = Math.max(al.rowGap !== void 0 ? finite(al.rowGap) : gx, 0);
|
|
3965
|
+
const hugW = getLayoutSizing(f.node, "width") === "hug";
|
|
3966
|
+
const hugH = getLayoutSizing(f.node, "height") === "hug";
|
|
3967
|
+
const ws = [];
|
|
3968
|
+
const hs = [];
|
|
3969
|
+
for (const c of cells) {
|
|
3970
|
+
const b = s.boxes.get(c.id);
|
|
3971
|
+
const lim2 = limitsOf(b.node);
|
|
3972
|
+
ws.push(getLayoutSizing(b.node, "width") === "fill" && !hugW ? lim2.minW ?? 0 : b.w);
|
|
3973
|
+
hs.push(getLayoutSizing(b.node, "height") === "fill" && !hugH ? lim2.minH ?? 0 : b.h);
|
|
3974
|
+
}
|
|
3975
|
+
const colContent = trackContent(cells, ws, colSpec.tracks, cols, gx, true, hugW);
|
|
3976
|
+
const rowContent = trackContent(cells, hs, rowSpec.tracks, rows, gy, false, hugH);
|
|
3977
|
+
const gapsW = gx * Math.max(cols - 1, 0);
|
|
3978
|
+
const gapsH = gy * Math.max(rows - 1, 0);
|
|
3979
|
+
const colW = resolveTracks(colSpec.tracks, colContent, hugW ? void 0 : finite(f.w) - pl - pr - gapsW);
|
|
3980
|
+
const rowH = resolveTracks(rowSpec.tracks, rowContent, hugH ? void 0 : finite(f.h) - pt - pb - gapsH);
|
|
3981
|
+
const lim = limitsOf(f.node);
|
|
3982
|
+
const width = hugW ? colW.reduce((a, b) => a + b, 0) + gapsW + pl + pr : finite(f.w);
|
|
3983
|
+
const height = hugH ? rowH.reduce((a, b) => a + b, 0) + gapsH + pt + pb : finite(f.h);
|
|
3984
|
+
return {
|
|
3985
|
+
columns: trackRanges(pl, colW, gx),
|
|
3986
|
+
rows: trackRanges(pt, rowH, gy),
|
|
3987
|
+
cells,
|
|
3988
|
+
width: Math.max(clampLayoutSize(width, lim.minW, lim.maxW), pl + pr),
|
|
3989
|
+
height: Math.max(clampLayoutSize(height, lim.minH, lim.maxH), pt + pb)
|
|
3990
|
+
};
|
|
3991
|
+
}
|
|
3992
|
+
function layoutGrid(s, frame, al) {
|
|
3993
|
+
const f = s.boxes.get(frame);
|
|
3994
|
+
const fw = f.w;
|
|
3995
|
+
const fh = f.h;
|
|
3996
|
+
const hugW = getLayoutSizing(f.node, "width") === "hug";
|
|
3997
|
+
const hugH = getLayoutSizing(f.node, "height") === "hug";
|
|
3998
|
+
const lim = limitsOf(f.node);
|
|
3999
|
+
const [pt, pr, pb, pl] = resolvePadding(al.padding);
|
|
4000
|
+
setSize(
|
|
4001
|
+
s,
|
|
4002
|
+
frame,
|
|
4003
|
+
hugW ? fw : Math.max(clampLayoutSize(fw, lim.minW, lim.maxW), pl + pr),
|
|
4004
|
+
hugH ? fh : Math.max(clampLayoutSize(fh, lim.minH, lim.maxH), pt + pb)
|
|
4005
|
+
);
|
|
4006
|
+
let g = gridGeometryOf(s, frame);
|
|
4007
|
+
if (!g) return;
|
|
4008
|
+
let widened = false;
|
|
4009
|
+
for (const c of g.cells) {
|
|
4010
|
+
const b = s.boxes.get(c.id);
|
|
4011
|
+
if (getLayoutSizing(b.node, "width") === "fill" && !hugW) {
|
|
4012
|
+
const l = limitsOf(b.node);
|
|
4013
|
+
resizeChild(s, c.id, clampLayoutSize(cellRect(g, c).width, l.minW, l.maxW), b.h);
|
|
4014
|
+
widened = true;
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
if (widened) g = gridGeometryOf(s, frame) ?? g;
|
|
4018
|
+
setSize(s, frame, g.width, g.height);
|
|
4019
|
+
constrain(s, frame, fw, fh);
|
|
4020
|
+
const frameX = al.alignMain === "center" ? "center" : al.alignMain === "end" ? "end" : "start";
|
|
4021
|
+
const frameY = al.alignCross === "center" ? "center" : al.alignCross === "end" ? "end" : "start";
|
|
4022
|
+
const offset = (a, free) => a === "center" ? free / 2 : a === "end" ? free : 0;
|
|
4023
|
+
for (const c of g.cells) {
|
|
4024
|
+
const cell = cellRect(g, c);
|
|
4025
|
+
const b = s.boxes.get(c.id);
|
|
4026
|
+
const l = limitsOf(b.node);
|
|
4027
|
+
const area = getChildLayout(b.node).gridArea ?? {};
|
|
4028
|
+
const fillW = getLayoutSizing(b.node, "width") === "fill" && !hugW;
|
|
4029
|
+
const fillH = getLayoutSizing(b.node, "height") === "fill" && !hugH;
|
|
4030
|
+
if (fillW || fillH) {
|
|
4031
|
+
resizeChild(
|
|
4032
|
+
s,
|
|
4033
|
+
c.id,
|
|
4034
|
+
fillW ? clampLayoutSize(cell.width, l.minW, l.maxW) : b.w,
|
|
4035
|
+
fillH ? clampLayoutSize(cell.height, l.minH, l.maxH) : b.h
|
|
4036
|
+
);
|
|
4037
|
+
}
|
|
4038
|
+
const ox = offset(area.alignX ?? frameX, cell.width - b.w);
|
|
4039
|
+
const oy = offset(area.alignY ?? frameY, cell.height - b.h);
|
|
4040
|
+
place(s, c.id, finite(cell.x + ox), finite(cell.y + oy));
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
function constrain(s, id, ow, oh) {
|
|
4044
|
+
const f = s.boxes.get(id);
|
|
4045
|
+
if (!f) return;
|
|
4046
|
+
const nw = f.w;
|
|
4047
|
+
const nh = f.h;
|
|
4048
|
+
if (Math.abs(nw - ow) < 1e-9 && Math.abs(nh - oh) < 1e-9) return;
|
|
4049
|
+
const absoluteOnly = !!getAutoLayout(f.node);
|
|
4050
|
+
const sx = ow > 1e-9 ? nw / ow : 1;
|
|
4051
|
+
const sy = oh > 1e-9 ? nh / oh : 1;
|
|
4052
|
+
s.guard(`constrain:${id}`, () => {
|
|
4053
|
+
for (const k of f.kids) {
|
|
4054
|
+
const c = s.boxes.get(k);
|
|
4055
|
+
const child = getChildLayout(c.node);
|
|
4056
|
+
if (absoluteOnly && !child.absolute) continue;
|
|
4057
|
+
let x0 = c.x;
|
|
4058
|
+
let x1 = c.x + c.w;
|
|
4059
|
+
let y0 = c.y;
|
|
4060
|
+
let y1 = c.y + c.h;
|
|
4061
|
+
const dx = nw - ow;
|
|
4062
|
+
const dy = nh - oh;
|
|
4063
|
+
switch (child.constraints?.horizontal ?? "left") {
|
|
4064
|
+
case "right":
|
|
4065
|
+
x0 += dx;
|
|
4066
|
+
x1 += dx;
|
|
4067
|
+
break;
|
|
4068
|
+
case "leftRight":
|
|
4069
|
+
x1 += dx;
|
|
4070
|
+
break;
|
|
4071
|
+
case "center":
|
|
4072
|
+
x0 += dx / 2;
|
|
4073
|
+
x1 += dx / 2;
|
|
4074
|
+
break;
|
|
4075
|
+
case "scale":
|
|
4076
|
+
x0 *= sx;
|
|
4077
|
+
x1 *= sx;
|
|
4078
|
+
break;
|
|
4079
|
+
}
|
|
4080
|
+
switch (child.constraints?.vertical ?? "top") {
|
|
4081
|
+
case "bottom":
|
|
4082
|
+
y0 += dy;
|
|
4083
|
+
y1 += dy;
|
|
4084
|
+
break;
|
|
4085
|
+
case "topBottom":
|
|
4086
|
+
y1 += dy;
|
|
4087
|
+
break;
|
|
4088
|
+
case "center":
|
|
4089
|
+
y0 += dy / 2;
|
|
4090
|
+
y1 += dy / 2;
|
|
4091
|
+
break;
|
|
4092
|
+
case "scale":
|
|
4093
|
+
y0 *= sy;
|
|
4094
|
+
y1 *= sy;
|
|
4095
|
+
break;
|
|
4096
|
+
}
|
|
4097
|
+
const cw = c.w;
|
|
4098
|
+
const ch = c.h;
|
|
4099
|
+
const resized = Math.abs(x1 - x0 - cw) > 1e-9 || Math.abs(y1 - y0 - ch) > 1e-9;
|
|
4100
|
+
place(s, k, finite(x0), finite(y0));
|
|
4101
|
+
if (resized) {
|
|
4102
|
+
setSize(s, k, x1 - x0, y1 - y0);
|
|
4103
|
+
if (c.kids.length > 0) constrain(s, k, cw, ch);
|
|
4104
|
+
if (getAutoLayout(c.node)) layout(s, k);
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
});
|
|
4108
|
+
}
|
|
4109
|
+
function layoutFrame(nodes, frameId, options = {}) {
|
|
4110
|
+
const s = new LayoutState(nodes, options);
|
|
4111
|
+
layout(s, frameId);
|
|
4112
|
+
return s.updates();
|
|
4113
|
+
}
|
|
4114
|
+
function postOrder(s, nodes) {
|
|
4115
|
+
const out = [];
|
|
4116
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4117
|
+
const visit = (id) => {
|
|
4118
|
+
if (seen.has(id)) return;
|
|
4119
|
+
seen.add(id);
|
|
4120
|
+
for (const k of s.boxes.get(id)?.kids ?? []) visit(k);
|
|
4121
|
+
out.push(id);
|
|
4122
|
+
};
|
|
4123
|
+
const roots = nodes.filter((n) => !n.parentId || !s.boxes.has(n.parentId));
|
|
4124
|
+
for (const r of roots) visit(r.id);
|
|
4125
|
+
for (const n of nodes) visit(n.id);
|
|
4126
|
+
return out;
|
|
4127
|
+
}
|
|
4128
|
+
function layoutAll(nodes, options = {}) {
|
|
4129
|
+
const s = new LayoutState(nodes, options);
|
|
4130
|
+
for (const id of postOrder(s, nodes)) if (getAutoLayout(s.boxes.get(id).node)) layout(s, id);
|
|
4131
|
+
return s.updates();
|
|
4132
|
+
}
|
|
4133
|
+
function layoutUp(nodes, changedId, options = {}) {
|
|
4134
|
+
return layoutUpMany(nodes, [changedId], options);
|
|
4135
|
+
}
|
|
4136
|
+
function layoutUpMany(nodes, changedIds, options = {}) {
|
|
4137
|
+
const s = new LayoutState(nodes, options);
|
|
4138
|
+
const chains = [...new Set(changedIds)].filter((id) => s.boxes.has(id)).map((id) => [id, ...s.ancestors(id)]);
|
|
4139
|
+
const depth = /* @__PURE__ */ new Map();
|
|
4140
|
+
for (const chain of chains) chain.forEach((id, i) => depth.set(id, Math.max(depth.get(id) ?? 0, chain.length - 1 - i)));
|
|
4141
|
+
const order = [...depth.keys()].sort((a, b) => depth.get(b) - depth.get(a));
|
|
4142
|
+
for (const id of order) if (getAutoLayout(s.boxes.get(id).node)) layout(s, id);
|
|
4143
|
+
return s.updates();
|
|
4144
|
+
}
|
|
4145
|
+
function applyConstraints(nodes, parentId, previousSize, options = {}) {
|
|
4146
|
+
const s = new LayoutState(nodes, options);
|
|
4147
|
+
constrain(s, parentId, previousSize.width, previousSize.height);
|
|
4148
|
+
return s.updates();
|
|
4149
|
+
}
|
|
4150
|
+
function getAutoLayoutGrid(nodes, frameId, options = {}) {
|
|
4151
|
+
return gridGeometryOf(new LayoutState(nodes, options), frameId);
|
|
4152
|
+
}
|
|
4153
|
+
function getFlowChildren(nodes, frameId) {
|
|
4154
|
+
return nodes.filter((n) => n.parentId === frameId && n.id !== frameId && n.hidden !== true && !getChildLayout(n).absolute).map((n) => n.id);
|
|
4155
|
+
}
|
|
4156
|
+
function getLayoutParent(nodes, id) {
|
|
4157
|
+
const lookup = createNodeLookup(nodes);
|
|
4158
|
+
const node = lookup.get(id);
|
|
4159
|
+
if (!node?.parentId || getChildLayout(node).absolute) return void 0;
|
|
4160
|
+
const parent = lookup.get(node.parentId);
|
|
4161
|
+
return isAutoLayoutFrame(parent) ? parent : void 0;
|
|
4162
|
+
}
|
|
4163
|
+
function makeSizingCoherent(nodes, id, options = {}) {
|
|
4164
|
+
const lookup = createNodeLookup(nodes);
|
|
4165
|
+
const targets = /* @__PURE__ */ new Set([id, ...nodes.filter((n) => n.parentId === id).map((n) => n.id)]);
|
|
4166
|
+
if (!lookup.has(id)) return nodes;
|
|
4167
|
+
let changed = false;
|
|
4168
|
+
const next = nodes.map((node) => {
|
|
4169
|
+
if (!targets.has(node.id)) return node;
|
|
4170
|
+
const parent = node.parentId ? lookup.get(node.parentId) : void 0;
|
|
4171
|
+
const flowParent = parent && isAutoLayoutFrame(parent) && !getChildLayout(node).absolute ? parent : void 0;
|
|
4172
|
+
const canHug = isAutoLayoutFrame(node) || options.measure?.textSize?.(node) !== void 0;
|
|
4173
|
+
const coherent = (axis) => {
|
|
4174
|
+
const sizing = getLayoutSizing(node, axis);
|
|
4175
|
+
if (sizing === "fill" && (!flowParent || getLayoutSizing(flowParent, axis) === "hug")) return "fixed";
|
|
4176
|
+
if (sizing === "hug" && !canHug) return "fixed";
|
|
4177
|
+
return sizing;
|
|
4178
|
+
};
|
|
4179
|
+
const width = coherent("width");
|
|
4180
|
+
const height = coherent("height");
|
|
4181
|
+
if (width === getLayoutSizing(node, "width") && height === getLayoutSizing(node, "height")) return node;
|
|
4182
|
+
changed = true;
|
|
4183
|
+
const child = getChildLayout(node);
|
|
4184
|
+
return {
|
|
4185
|
+
...node,
|
|
4186
|
+
data: { ...node.data, [CHILD_LAYOUT_DATA_KEY]: { ...child, sizing: { ...child.sizing, width, height } } }
|
|
4187
|
+
};
|
|
4188
|
+
});
|
|
4189
|
+
return changed ? next : nodes;
|
|
4190
|
+
}
|
|
4191
|
+
function getAutoLayoutInsertTarget(nodes, frameId, point, options = {}) {
|
|
4192
|
+
const lookup = createNodeLookup(nodes);
|
|
4193
|
+
const frame = lookup.get(frameId);
|
|
4194
|
+
const al = getAutoLayout(frame);
|
|
4195
|
+
if (!frame || !al) return void 0;
|
|
4196
|
+
const s = new LayoutState(nodes, options);
|
|
4197
|
+
const origin = getAbsolutePosition(frame, lookup);
|
|
4198
|
+
const px = point.x - origin.x;
|
|
4199
|
+
const py = point.y - origin.y;
|
|
4200
|
+
const siblings = flowChildren(s, frameId).filter((id) => id !== options.excludeId).map((id) => s.boxes.get(id));
|
|
4201
|
+
const horizontal = al.mode !== "vertical";
|
|
4202
|
+
const main = (b) => horizontal ? b.x : b.y;
|
|
4203
|
+
const mainSize = (b) => horizontal ? b.w : b.h;
|
|
4204
|
+
const cross = (b) => horizontal ? b.y : b.x;
|
|
4205
|
+
const crossSize = (b) => horizontal ? b.h : b.w;
|
|
4206
|
+
const p = { x: px, y: py };
|
|
4207
|
+
const multiLine = al.mode === "grid" || !!al.wrap;
|
|
4208
|
+
let index = 0;
|
|
4209
|
+
for (const b of siblings) {
|
|
4210
|
+
const before = multiLine ? cross(b) + crossSize(b) <= cross(p) || cross(b) <= cross(p) && main(b) + mainSize(b) / 2 < main(p) : main(b) + mainSize(b) / 2 < main(p);
|
|
4211
|
+
if (before) index += 1;
|
|
4212
|
+
}
|
|
4213
|
+
const [pt, pr, pb, pl] = resolvePadding(al.padding);
|
|
4214
|
+
const prev = siblings[index - 1];
|
|
4215
|
+
const next = siblings[index];
|
|
4216
|
+
const f = s.boxes.get(frameId);
|
|
4217
|
+
let guide;
|
|
4218
|
+
if (horizontal) {
|
|
4219
|
+
const x = prev && next && Math.abs(cross(prev) - cross(next)) < 1e-6 ? (prev.x + prev.w + next.x) / 2 : prev ? prev.x + prev.w : next ? next.x : pl;
|
|
4220
|
+
const ref = next ?? prev;
|
|
4221
|
+
const y1 = ref ? ref.y : pt;
|
|
4222
|
+
const y2 = ref ? ref.y + ref.h : f.h - pb;
|
|
4223
|
+
guide = { orientation: "vertical", kind: "edge", x1: origin.x + x, y1: origin.y + y1, x2: origin.x + x, y2: origin.y + y2 };
|
|
4224
|
+
} else {
|
|
4225
|
+
const y = prev && next && Math.abs(cross(prev) - cross(next)) < 1e-6 ? (prev.y + prev.h + next.y) / 2 : prev ? prev.y + prev.h : next ? next.y : pt;
|
|
4226
|
+
const ref = next ?? prev;
|
|
4227
|
+
const x1 = ref ? ref.x : pl;
|
|
4228
|
+
const x2 = ref ? ref.x + ref.w : f.w - pr;
|
|
4229
|
+
guide = { orientation: "horizontal", kind: "edge", x1: origin.x + x1, y1: origin.y + y, x2: origin.x + x2, y2: origin.y + y };
|
|
4230
|
+
}
|
|
4231
|
+
return { frameId, index, guide };
|
|
4232
|
+
}
|
|
4233
|
+
function moveLayoutChild(nodes, childId, index) {
|
|
4234
|
+
const child = nodes.find((n) => n.id === childId);
|
|
4235
|
+
if (!child?.parentId) return nodes;
|
|
4236
|
+
const siblings = nodes.filter((n) => n.parentId === child.parentId && n.id !== child.parentId);
|
|
4237
|
+
const flow = siblings.filter((n) => n.hidden !== true && !getChildLayout(n).absolute && n.id !== childId);
|
|
4238
|
+
const clamped = Math.min(Math.max(Math.floor(Number.isFinite(index) ? index : 0), 0), flow.length);
|
|
4239
|
+
const order = siblings.filter((n) => n.id !== childId);
|
|
4240
|
+
const anchor = flow[clamped];
|
|
4241
|
+
const at = anchor ? order.indexOf(anchor) : flow.length > 0 ? order.indexOf(flow[flow.length - 1]) + 1 : order.length;
|
|
4242
|
+
order.splice(at, 0, child);
|
|
4243
|
+
if (order.every((n, i) => n === siblings[i])) return nodes;
|
|
4244
|
+
let k = 0;
|
|
4245
|
+
const parentId = child.parentId;
|
|
4246
|
+
const placed = nodes.map((n) => n.parentId === parentId && n.id !== parentId ? order[k++] : n);
|
|
4247
|
+
return parentsFirst(placed);
|
|
4248
|
+
}
|
|
4249
|
+
function parentsFirst(nodes) {
|
|
4250
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
4251
|
+
const pending = /* @__PURE__ */ new Map();
|
|
4252
|
+
const done = /* @__PURE__ */ new Set();
|
|
4253
|
+
const out = [];
|
|
4254
|
+
const emit = (n) => {
|
|
4255
|
+
out.push(n);
|
|
4256
|
+
done.add(n.id);
|
|
4257
|
+
const waiting = pending.get(n.id);
|
|
4258
|
+
if (waiting) {
|
|
4259
|
+
pending.delete(n.id);
|
|
4260
|
+
for (const w of waiting) emit(w);
|
|
4261
|
+
}
|
|
4262
|
+
};
|
|
4263
|
+
for (const n of nodes) {
|
|
4264
|
+
if (n.parentId && ids.has(n.parentId) && !done.has(n.parentId) && n.parentId !== n.id) {
|
|
4265
|
+
if (!pending.has(n.parentId)) pending.set(n.parentId, []);
|
|
4266
|
+
pending.get(n.parentId).push(n);
|
|
4267
|
+
} else emit(n);
|
|
4268
|
+
}
|
|
4269
|
+
for (const waiting of pending.values()) for (const n of waiting) if (!done.has(n.id)) emit(n);
|
|
4270
|
+
return out;
|
|
4271
|
+
}
|
|
4272
|
+
|
|
2879
4273
|
// src/canvas/arrangeController.ts
|
|
2880
4274
|
function createCanvasArrangeController(options) {
|
|
2881
4275
|
let guides = [];
|
|
2882
4276
|
let dragIndex = null;
|
|
2883
4277
|
let dragKey = "";
|
|
2884
4278
|
let lastSnapped = /* @__PURE__ */ new Map();
|
|
4279
|
+
let resizeId = null;
|
|
4280
|
+
let resizeStart = null;
|
|
4281
|
+
let resizeIndex = null;
|
|
4282
|
+
let lastResized = null;
|
|
2885
4283
|
const selectedIds = (ids) => ids ? [...ids] : options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);
|
|
2886
4284
|
const setGuides = (next) => {
|
|
2887
4285
|
if (next.length === 0 && guides.length === 0) return;
|
|
@@ -2890,6 +4288,16 @@ function createCanvasArrangeController(options) {
|
|
|
2890
4288
|
};
|
|
2891
4289
|
function apply(name, updates) {
|
|
2892
4290
|
if (updates.length === 0) return updates;
|
|
4291
|
+
if (options.autoLayout) {
|
|
4292
|
+
const layoutOptions = options.autoLayout === true ? { rectOptions: options.rectOptions } : options.autoLayout;
|
|
4293
|
+
const moved = applyNodeUpdates(options.getNodes(), updates);
|
|
4294
|
+
const layout2 = layoutUpMany(moved, updates.map((u) => u.id), layoutOptions);
|
|
4295
|
+
if (layout2.length > 0) {
|
|
4296
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4297
|
+
for (const u of [...updates, ...layout2]) byId.set(u.id, { ...byId.get(u.id), ...u });
|
|
4298
|
+
updates = [...byId.values()];
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
2893
4301
|
const inverse = invertNodeUpdates(options.getNodes(), updates);
|
|
2894
4302
|
const run = (u) => options.setNodes(applyNodeUpdates(options.getNodes(), u));
|
|
2895
4303
|
if (options.history) {
|
|
@@ -3098,6 +4506,150 @@ function createCanvasArrangeController(options) {
|
|
|
3098
4506
|
return { ...c, position };
|
|
3099
4507
|
});
|
|
3100
4508
|
},
|
|
4509
|
+
/**
|
|
4510
|
+
* Starts resize snapping for node `id`: records its box (to tell which handle is
|
|
4511
|
+
* dragged) and caches the snap targets. Optional: `resize` starts lazily.
|
|
4512
|
+
*/
|
|
4513
|
+
beginResize(id) {
|
|
4514
|
+
const nodes = options.getNodes();
|
|
4515
|
+
const lookup = createNodeLookup(nodes);
|
|
4516
|
+
const node = lookup.get(id);
|
|
4517
|
+
resizeId = id;
|
|
4518
|
+
resizeStart = node ? getResizingNodeRect(node, lookup, rect()) : null;
|
|
4519
|
+
resizeIndex = createNodeGuideIndex(nodes, [id], rect());
|
|
4520
|
+
lastResized = null;
|
|
4521
|
+
},
|
|
4522
|
+
/**
|
|
4523
|
+
* Snaps node `id` as currently sized in `getNodes()` (call it after the flow wrote the
|
|
4524
|
+
* new size, e.g. SvelteFlow's `NodeResizer` `onResize`, deferred by a microtask) and
|
|
4525
|
+
* writes the snapped box back through `setNodes` (not recorded in history). `handle`
|
|
4526
|
+
* defaults to the one inferred from the edges that moved since `beginResize`. A locked
|
|
4527
|
+
* node is restored to its starting box.
|
|
4528
|
+
*/
|
|
4529
|
+
resize(id, handle) {
|
|
4530
|
+
if (resizeId !== id) controller.beginResize(id);
|
|
4531
|
+
const nodes = options.getNodes();
|
|
4532
|
+
const lookup = createNodeLookup(nodes);
|
|
4533
|
+
const node = lookup.get(id);
|
|
4534
|
+
if (!node || !resizeStart) return null;
|
|
4535
|
+
const proposed = getResizingNodeRect(node, lookup, rect());
|
|
4536
|
+
if (isLocked(node)) {
|
|
4537
|
+
const start = resizeStart;
|
|
4538
|
+
const parent = node.parentId ? lookup.get(node.parentId) : void 0;
|
|
4539
|
+
const origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };
|
|
4540
|
+
if (proposed.x !== start.x || proposed.y !== start.y || proposed.width !== start.width || proposed.height !== start.height) {
|
|
4541
|
+
options.setNodes(
|
|
4542
|
+
applyNodeUpdates(nodes, [
|
|
4543
|
+
{ id, position: { x: start.x - origin.x, y: start.y - origin.y }, width: start.width, height: start.height }
|
|
4544
|
+
])
|
|
4545
|
+
);
|
|
4546
|
+
}
|
|
4547
|
+
return null;
|
|
4548
|
+
}
|
|
4549
|
+
if ((options.resizeSnapping ?? options.snapping) === false) return null;
|
|
4550
|
+
const h = handle ?? inferResizeHandle(resizeStart, proposed);
|
|
4551
|
+
if (!h) {
|
|
4552
|
+
setGuides([]);
|
|
4553
|
+
return null;
|
|
4554
|
+
}
|
|
4555
|
+
const result = snapNodeResize(nodes, id, proposed, h, {
|
|
4556
|
+
...rect(),
|
|
4557
|
+
...options.guides,
|
|
4558
|
+
...options.resize,
|
|
4559
|
+
zoom: options.getZoom?.() ?? 1,
|
|
4560
|
+
index: resizeIndex
|
|
4561
|
+
});
|
|
4562
|
+
lastResized = result.updates.length > 0 ? result : null;
|
|
4563
|
+
if (result.updates.length > 0) options.setNodes(applyNodeUpdates(nodes, result.updates));
|
|
4564
|
+
setGuides(result.guides);
|
|
4565
|
+
return result;
|
|
4566
|
+
},
|
|
4567
|
+
/**
|
|
4568
|
+
* Ends resize snapping: clears guides and re-applies the last snapped box if the flow
|
|
4569
|
+
* wrote the raw pointer size afterwards.
|
|
4570
|
+
*/
|
|
4571
|
+
endResize() {
|
|
4572
|
+
const last = lastResized;
|
|
4573
|
+
resizeId = null;
|
|
4574
|
+
resizeStart = null;
|
|
4575
|
+
resizeIndex = null;
|
|
4576
|
+
lastResized = null;
|
|
4577
|
+
setGuides([]);
|
|
4578
|
+
const update = last?.updates[0];
|
|
4579
|
+
if (!update) return;
|
|
4580
|
+
const node = options.getNodes().find((n) => n.id === update.id);
|
|
4581
|
+
if (!node) return;
|
|
4582
|
+
const drift = node.position.x !== update.position.x || node.position.y !== update.position.y || node.width !== update.width || node.height !== update.height;
|
|
4583
|
+
if (drift) options.setNodes(applyNodeUpdates(options.getNodes(), [update]));
|
|
4584
|
+
},
|
|
4585
|
+
/**
|
|
4586
|
+
* React Flow style: snaps the `dimensions` (and matching `position`) changes a
|
|
4587
|
+
* `NodeResizer` emits in an `onNodesChange` batch and updates `guides`. The resize-end
|
|
4588
|
+
* change (`resizing: false`) carries the raw pointer size, so the last snapped size is
|
|
4589
|
+
* kept. Resize changes of locked nodes are dropped. Returns the (possibly new) changes.
|
|
4590
|
+
*/
|
|
4591
|
+
snapResizeChanges(changes) {
|
|
4592
|
+
const dims = changes.find(
|
|
4593
|
+
(c) => c.type === "dimensions" && typeof c.id === "string" && !!c.dimensions && typeof c.resizing === "boolean"
|
|
4594
|
+
);
|
|
4595
|
+
if (!dims) return changes;
|
|
4596
|
+
const id = dims.id;
|
|
4597
|
+
const nodes = options.getNodes();
|
|
4598
|
+
const lookup = createNodeLookup(nodes);
|
|
4599
|
+
const node = lookup.get(id);
|
|
4600
|
+
const isResizeChange = (c) => c.id === id && (c.type === "dimensions" || c.type === "position");
|
|
4601
|
+
if (node && isLocked(node)) {
|
|
4602
|
+
if (dims.resizing === false) controller.endResize();
|
|
4603
|
+
return changes.filter((c) => !isResizeChange(c));
|
|
4604
|
+
}
|
|
4605
|
+
if (dims.resizing === false) {
|
|
4606
|
+
const last = lastResized?.updates[0];
|
|
4607
|
+
lastResized = null;
|
|
4608
|
+
controller.endResize();
|
|
4609
|
+
if (!last || last.id !== id) return changes;
|
|
4610
|
+
return changes.map(
|
|
4611
|
+
(c) => c === dims ? { ...c, dimensions: { width: last.width, height: last.height } } : c
|
|
4612
|
+
);
|
|
4613
|
+
}
|
|
4614
|
+
if (!node || (options.resizeSnapping ?? options.snapping) === false) return changes;
|
|
4615
|
+
if (resizeId !== id) controller.beginResize(id);
|
|
4616
|
+
if (!resizeStart) return changes;
|
|
4617
|
+
const posChange = changes.find((c) => c.id === id && c.type === "position" && !!c.position);
|
|
4618
|
+
const parent = node.parentId ? lookup.get(node.parentId) : void 0;
|
|
4619
|
+
const origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };
|
|
4620
|
+
const position = posChange?.position ?? node.position;
|
|
4621
|
+
const proposed = {
|
|
4622
|
+
x: position.x + origin.x,
|
|
4623
|
+
y: position.y + origin.y,
|
|
4624
|
+
width: dims.dimensions.width,
|
|
4625
|
+
height: dims.dimensions.height
|
|
4626
|
+
};
|
|
4627
|
+
const handle = inferResizeHandle(resizeStart, proposed);
|
|
4628
|
+
if (!handle) {
|
|
4629
|
+
setGuides([]);
|
|
4630
|
+
return changes;
|
|
4631
|
+
}
|
|
4632
|
+
const result = snapNodeResize(nodes, id, proposed, handle, {
|
|
4633
|
+
...rect(),
|
|
4634
|
+
...options.guides,
|
|
4635
|
+
...options.resize,
|
|
4636
|
+
zoom: options.getZoom?.() ?? 1,
|
|
4637
|
+
index: resizeIndex
|
|
4638
|
+
});
|
|
4639
|
+
setGuides(result.guides);
|
|
4640
|
+
const update = result.updates[0];
|
|
4641
|
+
lastResized = update ? result : null;
|
|
4642
|
+
if (!update) return changes;
|
|
4643
|
+
const next = changes.map((c) => {
|
|
4644
|
+
if (c === dims) return { ...c, dimensions: { width: update.width, height: update.height } };
|
|
4645
|
+
if (c === posChange) return { ...c, position: update.position };
|
|
4646
|
+
return c;
|
|
4647
|
+
});
|
|
4648
|
+
if (!posChange && (update.position.x !== node.position.x || update.position.y !== node.position.y)) {
|
|
4649
|
+
next.push({ id, type: "position", position: update.position });
|
|
4650
|
+
}
|
|
4651
|
+
return next;
|
|
4652
|
+
},
|
|
3101
4653
|
/**
|
|
3102
4654
|
* Clears guides and the cached snap targets. Call it from the flow's drag-stop
|
|
3103
4655
|
* handler: flow libraries write the raw pointer position when a drag ends, so the
|
|
@@ -3122,7 +4674,405 @@ function createCanvasArrangeController(options) {
|
|
|
3122
4674
|
};
|
|
3123
4675
|
return controller;
|
|
3124
4676
|
}
|
|
4677
|
+
|
|
4678
|
+
// src/canvas/measure.ts
|
|
4679
|
+
var EPS2 = 1e-6;
|
|
4680
|
+
function measureLabel(distance) {
|
|
4681
|
+
const v = Math.round(Math.abs(distance) * 100) / 100;
|
|
4682
|
+
return Object.is(v, -0) ? "0" : String(v);
|
|
4683
|
+
}
|
|
4684
|
+
var toBox = (r) => ({ x0: r.x, y0: r.y, x1: r.x + r.width, y1: r.y + r.height });
|
|
4685
|
+
var finiteBox = (b) => [b.x0, b.y0, b.x1, b.y1].every(Number.isFinite);
|
|
4686
|
+
function line(kind, x1, y1, x2, y2) {
|
|
4687
|
+
return { orientation: x1 === x2 ? "vertical" : "horizontal", kind, x1, y1, x2, y2 };
|
|
4688
|
+
}
|
|
4689
|
+
function measured(out, x1, y1, x2, y2) {
|
|
4690
|
+
const d = Math.hypot(x2 - x1, y2 - y1);
|
|
4691
|
+
if (d > EPS2 && Number.isFinite(d)) out.push({ ...line("measure", x1, y1, x2, y2), label: measureLabel(d) });
|
|
4692
|
+
}
|
|
4693
|
+
function extension(out, x1, y1, x2, y2) {
|
|
4694
|
+
if (Math.hypot(x2 - x1, y2 - y1) > EPS2) out.push(line("extension", x1, y1, x2, y2));
|
|
4695
|
+
}
|
|
4696
|
+
function contains(outer, inner) {
|
|
4697
|
+
return outer.x0 <= inner.x0 + EPS2 && outer.y0 <= inner.y0 + EPS2 && outer.x1 >= inner.x1 - EPS2 && outer.y1 >= inner.y1 - EPS2;
|
|
4698
|
+
}
|
|
4699
|
+
function overlapMid(a0, a1, b0, b1) {
|
|
4700
|
+
const lo = Math.max(a0, b0);
|
|
4701
|
+
const hi = Math.min(a1, b1);
|
|
4702
|
+
return lo <= hi ? (lo + hi) / 2 : null;
|
|
4703
|
+
}
|
|
4704
|
+
function inside(out, inner, outer) {
|
|
4705
|
+
const cx = (inner.x0 + inner.x1) / 2;
|
|
4706
|
+
const cy = (inner.y0 + inner.y1) / 2;
|
|
4707
|
+
measured(out, outer.x0, cy, inner.x0, cy);
|
|
4708
|
+
measured(out, inner.x1, cy, outer.x1, cy);
|
|
4709
|
+
measured(out, cx, outer.y0, cx, inner.y0);
|
|
4710
|
+
measured(out, cx, inner.y1, cx, outer.y1);
|
|
4711
|
+
}
|
|
4712
|
+
function measureBetween(a, b) {
|
|
4713
|
+
const out = [];
|
|
4714
|
+
const A = toBox(a);
|
|
4715
|
+
const B = toBox(b);
|
|
4716
|
+
if (!finiteBox(A) || !finiteBox(B)) return out;
|
|
4717
|
+
if (contains(B, A)) {
|
|
4718
|
+
inside(out, A, B);
|
|
4719
|
+
return out;
|
|
4720
|
+
}
|
|
4721
|
+
if (contains(A, B)) {
|
|
4722
|
+
inside(out, B, A);
|
|
4723
|
+
return out;
|
|
4724
|
+
}
|
|
4725
|
+
const ox = overlapMid(A.x0, A.x1, B.x0, B.x1);
|
|
4726
|
+
const oy = overlapMid(A.y0, A.y1, B.y0, B.y1);
|
|
4727
|
+
const overlapping = A.x0 < B.x1 && B.x0 < A.x1 && A.y0 < B.y1 && B.y0 < A.y1;
|
|
4728
|
+
if (overlapping && ox !== null && oy !== null) {
|
|
4729
|
+
measured(out, A.x0, oy, B.x0, oy);
|
|
4730
|
+
measured(out, A.x1, oy, B.x1, oy);
|
|
4731
|
+
measured(out, ox, A.y0, ox, B.y0);
|
|
4732
|
+
measured(out, ox, A.y1, ox, B.y1);
|
|
4733
|
+
return out;
|
|
4734
|
+
}
|
|
4735
|
+
const cx = (A.x0 + A.x1) / 2;
|
|
4736
|
+
const cy = (A.y0 + A.y1) / 2;
|
|
4737
|
+
const gapX = B.x0 >= A.x1 ? [A.x1, B.x0] : B.x1 <= A.x0 ? [A.x0, B.x1] : null;
|
|
4738
|
+
if (gapX) {
|
|
4739
|
+
const [from, to] = gapX;
|
|
4740
|
+
const y = oy ?? cy;
|
|
4741
|
+
measured(out, from, y, to, y);
|
|
4742
|
+
if (oy === null) extension(out, to, y, to, y < B.y0 ? B.y0 : B.y1);
|
|
4743
|
+
}
|
|
4744
|
+
const gapY = B.y0 >= A.y1 ? [A.y1, B.y0] : B.y1 <= A.y0 ? [A.y0, B.y1] : null;
|
|
4745
|
+
if (gapY) {
|
|
4746
|
+
const [from, to] = gapY;
|
|
4747
|
+
const x = ox ?? cx;
|
|
4748
|
+
measured(out, x, from, x, to);
|
|
4749
|
+
if (ox === null) extension(out, x, to, x < B.x0 ? B.x0 : B.x1, to);
|
|
4750
|
+
}
|
|
4751
|
+
return out;
|
|
4752
|
+
}
|
|
4753
|
+
function measureToLine(a, orientation, at) {
|
|
4754
|
+
const out = [];
|
|
4755
|
+
const A = toBox(a);
|
|
4756
|
+
if (!finiteBox(A) || !Number.isFinite(at)) return out;
|
|
4757
|
+
const vertical = orientation === "vertical";
|
|
4758
|
+
const cx = (A.x0 + A.x1) / 2;
|
|
4759
|
+
const cy = (A.y0 + A.y1) / 2;
|
|
4760
|
+
const [lo, hi] = vertical ? [A.x0, A.x1] : [A.y0, A.y1];
|
|
4761
|
+
const seg = (from, to) => vertical ? measured(out, from, cy, to, cy) : measured(out, cx, from, cx, to);
|
|
4762
|
+
if (at >= hi) seg(hi, at);
|
|
4763
|
+
else if (at <= lo) seg(at, lo);
|
|
4764
|
+
else {
|
|
4765
|
+
seg(lo, at);
|
|
4766
|
+
seg(at, hi);
|
|
4767
|
+
}
|
|
4768
|
+
return out;
|
|
4769
|
+
}
|
|
4770
|
+
function measureSelection(nodes, selectedIds, hoveredId, options = {}) {
|
|
4771
|
+
const none = (selection2 = null) => ({ guides: [], target: null, selection: selection2 });
|
|
4772
|
+
const lookup = createNodeLookup(nodes);
|
|
4773
|
+
const selected = [...new Set(selectedIds)].map((id) => lookup.get(id)).filter((n) => !!n && n.hidden !== true);
|
|
4774
|
+
if (selected.length === 0) return none();
|
|
4775
|
+
const selection = boundsOf(selected.map((n) => getNodeRect(n, lookup, options)));
|
|
4776
|
+
if (!selection) return none();
|
|
4777
|
+
const selectedSet = new Set(selected.map((n) => n.id));
|
|
4778
|
+
let targetId = hoveredId ?? null;
|
|
4779
|
+
if (targetId === null || selectedSet.has(targetId)) {
|
|
4780
|
+
const parentId = selected[0].parentId;
|
|
4781
|
+
const sameParent = selected.every((n) => n.parentId === parentId);
|
|
4782
|
+
targetId = parentId && sameParent && !selectedSet.has(parentId) ? parentId : null;
|
|
4783
|
+
}
|
|
4784
|
+
const target = targetId !== null ? lookup.get(targetId) : void 0;
|
|
4785
|
+
if (!target || target.hidden === true) return none(selection);
|
|
4786
|
+
const rect = getNodeRect(target, lookup, options);
|
|
4787
|
+
return { guides: measureBetween(selection, rect), target: rect, selection };
|
|
4788
|
+
}
|
|
4789
|
+
function isMeasureModifierHeld(event) {
|
|
4790
|
+
if (!event) return false;
|
|
4791
|
+
if (event.type === "keyup" && event.key === "Alt") return false;
|
|
4792
|
+
return event.altKey === true || event.type === "keydown" && event.key === "Alt";
|
|
4793
|
+
}
|
|
4794
|
+
|
|
4795
|
+
// src/canvas/autoLayoutController.ts
|
|
4796
|
+
function mergeNodeUpdates(...lists) {
|
|
4797
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4798
|
+
for (const list of lists) for (const u of list) byId.set(u.id, { ...byId.get(u.id), ...u });
|
|
4799
|
+
return [...byId.values()];
|
|
4800
|
+
}
|
|
4801
|
+
function relayoutUpdates(nodes, ids, options = {}) {
|
|
4802
|
+
return layoutUpMany(nodes, ids, options);
|
|
4803
|
+
}
|
|
4804
|
+
function createCanvasAutoLayoutController(options) {
|
|
4805
|
+
const layoutOptions = () => ({ measure: options.measure, rectOptions: options.rectOptions });
|
|
4806
|
+
const enabled = () => options.enabled !== false;
|
|
4807
|
+
const pendingIds = /* @__PURE__ */ new Set();
|
|
4808
|
+
const pendingResizes = /* @__PURE__ */ new Map();
|
|
4809
|
+
const resizeStarts = /* @__PURE__ */ new Map();
|
|
4810
|
+
let pendingAll = false;
|
|
4811
|
+
const dragStarts = /* @__PURE__ */ new Map();
|
|
4812
|
+
const pendingDrops = /* @__PURE__ */ new Set();
|
|
4813
|
+
let guides = [];
|
|
4814
|
+
const setGuides = (next) => {
|
|
4815
|
+
if (next.length === 0 && guides.length === 0) return;
|
|
4816
|
+
guides = next;
|
|
4817
|
+
options.onGuidesChange?.(guides);
|
|
4818
|
+
};
|
|
4819
|
+
function run(name, forward, backward, ids, extra = [], restore) {
|
|
4820
|
+
const before = options.getNodes();
|
|
4821
|
+
const structural = forward(before);
|
|
4822
|
+
const withExtra = applyNodeUpdates(structural, extra);
|
|
4823
|
+
const layout2 = enabled() || name === "Auto Layout" ? relayoutUpdates(withExtra, ids(withExtra), layoutOptions()) : [];
|
|
4824
|
+
const updates = mergeNodeUpdates(extra, layout2);
|
|
4825
|
+
if (structural === before && updates.length === 0) return updates;
|
|
4826
|
+
let inverse = invertNodeUpdates(structural, updates);
|
|
4827
|
+
if (restore) {
|
|
4828
|
+
inverse = mergeNodeUpdates(inverse, [{ id: restore.id, position: { ...restore.position } }]);
|
|
4829
|
+
}
|
|
4830
|
+
const execute = () => options.setNodes(applyNodeUpdates(forward(options.getNodes()), updates));
|
|
4831
|
+
const undo = () => options.setNodes(backward(applyNodeUpdates(options.getNodes(), inverse)));
|
|
4832
|
+
if (options.history) options.history.execute({ name, execute, undo });
|
|
4833
|
+
else execute();
|
|
4834
|
+
return updates;
|
|
4835
|
+
}
|
|
4836
|
+
const identity = (nodes) => nodes;
|
|
4837
|
+
const controller = {
|
|
4838
|
+
/** Drop marker of the active reorder drag (flow coordinates); empty otherwise. */
|
|
4839
|
+
get guides() {
|
|
4840
|
+
return guides;
|
|
4841
|
+
},
|
|
4842
|
+
/**
|
|
4843
|
+
* Lays out frame `frameId` and its auto layout ancestors (default: every frame) as one
|
|
4844
|
+
* (optionally undoable) step. Returns the updates.
|
|
4845
|
+
*/
|
|
4846
|
+
applyAutoLayout(frameId) {
|
|
4847
|
+
if (frameId === void 0) {
|
|
4848
|
+
const updates = layoutAll(options.getNodes(), layoutOptions());
|
|
4849
|
+
return run("Auto Layout", identity, identity, () => [], updates);
|
|
4850
|
+
}
|
|
4851
|
+
return run("Auto Layout", identity, identity, () => [frameId]);
|
|
4852
|
+
},
|
|
4853
|
+
/** Updates that relayout around `ids` on the current nodes, without applying them. */
|
|
4854
|
+
layoutUpdates(ids) {
|
|
4855
|
+
return relayoutUpdates(options.getNodes(), ids, layoutOptions());
|
|
4856
|
+
},
|
|
4857
|
+
/**
|
|
4858
|
+
* Applies the relayout around `ids` (frames, or nodes inside frames) directly, not
|
|
4859
|
+
* recorded in history: auto layout is derived state, re-run after any change.
|
|
4860
|
+
*/
|
|
4861
|
+
relayout(ids) {
|
|
4862
|
+
const updates = relayoutUpdates(options.getNodes(), ids, layoutOptions());
|
|
4863
|
+
if (updates.length > 0) options.setNodes(applyNodeUpdates(options.getNodes(), updates));
|
|
4864
|
+
return updates;
|
|
4865
|
+
},
|
|
4866
|
+
/**
|
|
4867
|
+
* Adds `node` (into the frame named by its `parentId`, at flow `index` when given) and
|
|
4868
|
+
* lays out its frame, as one step.
|
|
4869
|
+
*/
|
|
4870
|
+
addNode(node, addOptions = {}) {
|
|
4871
|
+
const forward = (nodes) => {
|
|
4872
|
+
if (nodes.some((n) => n.id === node.id)) return nodes;
|
|
4873
|
+
let next = [...nodes, node];
|
|
4874
|
+
if (addOptions.index !== void 0 && node.parentId) next = moveLayoutChild(next, node.id, addOptions.index);
|
|
4875
|
+
return next;
|
|
4876
|
+
};
|
|
4877
|
+
const backward = (nodes) => nodes.filter((n) => n.id !== node.id);
|
|
4878
|
+
return run("Add Node", forward, backward, () => [node.id]);
|
|
4879
|
+
},
|
|
4880
|
+
/** Removes nodes (and their descendants) and lays out the frames they leave, as one step. */
|
|
4881
|
+
removeNodes(ids) {
|
|
4882
|
+
const before = options.getNodes();
|
|
4883
|
+
const remove = new Set(ids);
|
|
4884
|
+
let grew = true;
|
|
4885
|
+
while (grew) {
|
|
4886
|
+
grew = false;
|
|
4887
|
+
for (const n of before) {
|
|
4888
|
+
if (n.parentId && remove.has(n.parentId) && !remove.has(n.id)) {
|
|
4889
|
+
remove.add(n.id);
|
|
4890
|
+
grew = true;
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
}
|
|
4894
|
+
const removed = before.map((n, i) => [i, n]).filter(([, n]) => remove.has(n.id));
|
|
4895
|
+
if (removed.length === 0) return [];
|
|
4896
|
+
const parents = [...new Set(removed.map(([, n]) => n.parentId).filter((p) => !!p && !remove.has(p)))];
|
|
4897
|
+
const forward = (nodes) => nodes.filter((n) => !remove.has(n.id));
|
|
4898
|
+
const backward = (nodes) => {
|
|
4899
|
+
const next = [...nodes];
|
|
4900
|
+
for (const [i, n] of removed) next.splice(Math.min(i, next.length), 0, n);
|
|
4901
|
+
return next;
|
|
4902
|
+
};
|
|
4903
|
+
return run("Delete", forward, backward, () => parents);
|
|
4904
|
+
},
|
|
4905
|
+
/**
|
|
4906
|
+
* Resizes node `id` (its children follow their constraints) and lays out around it,
|
|
4907
|
+
* as one step.
|
|
4908
|
+
*/
|
|
4909
|
+
resizeNode(id, size) {
|
|
4910
|
+
const nodes = options.getNodes();
|
|
4911
|
+
const node = nodes.find((n) => n.id === id);
|
|
4912
|
+
if (!node) return [];
|
|
4913
|
+
const old = getNodeSize(node, options.rectOptions);
|
|
4914
|
+
const update = { id };
|
|
4915
|
+
if (size.width !== void 0) update.width = size.width;
|
|
4916
|
+
if (size.height !== void 0) update.height = size.height;
|
|
4917
|
+
const resized = applyNodeUpdates(nodes, [update]);
|
|
4918
|
+
const constrained = mergeNodeUpdates([update], applyConstraints(resized, id, old, layoutOptions()));
|
|
4919
|
+
return run("Resize", identity, identity, () => [id], constrained);
|
|
4920
|
+
},
|
|
4921
|
+
/** Moves flow child `childId` to flow index `index` in its frame and lays it out, as one step. */
|
|
4922
|
+
moveChild(childId, index) {
|
|
4923
|
+
const before = options.getNodes();
|
|
4924
|
+
const order = before.map((n) => n.id);
|
|
4925
|
+
const forward = (nodes) => moveLayoutChild(nodes, childId, index);
|
|
4926
|
+
const backward = (nodes) => restoreOrder(nodes, order);
|
|
4927
|
+
if (forward(before) === before) return [];
|
|
4928
|
+
return run("Reorder", forward, backward, () => [childId]);
|
|
4929
|
+
},
|
|
4930
|
+
/** Where flow child `id` would drop if released at `point` (default: its centre). */
|
|
4931
|
+
getInsertTarget(id, point) {
|
|
4932
|
+
const nodes = options.getNodes();
|
|
4933
|
+
const frame = getLayoutParent(nodes, id);
|
|
4934
|
+
if (!frame) return void 0;
|
|
4935
|
+
const lookup = createNodeLookup(nodes);
|
|
4936
|
+
const rect = getNodeRect(lookup.get(id), lookup, options.rectOptions);
|
|
4937
|
+
const at = point ?? { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
|
|
4938
|
+
return getAutoLayoutInsertTarget(nodes, frame.id, at, { ...layoutOptions(), excludeId: id });
|
|
4939
|
+
},
|
|
4940
|
+
/** Records the start position of a reorder drag. Optional: `dragReorder` records it lazily. */
|
|
4941
|
+
beginReorderDrag(id) {
|
|
4942
|
+
const node = options.getNodes().find((n) => n.id === id);
|
|
4943
|
+
if (node && getLayoutParent(options.getNodes(), id)) dragStarts.set(id, { ...node.position });
|
|
4944
|
+
},
|
|
4945
|
+
/**
|
|
4946
|
+
* While flow child `id` is dragged (as currently positioned in `getNodes()`): updates the
|
|
4947
|
+
* drop marker. Returns the insert target.
|
|
4948
|
+
*/
|
|
4949
|
+
dragReorder(id, point) {
|
|
4950
|
+
if (options.dragReorder === false) return void 0;
|
|
4951
|
+
if (!dragStarts.has(id)) controller.beginReorderDrag(id);
|
|
4952
|
+
const target = controller.getInsertTarget(id, point);
|
|
4953
|
+
setGuides(target ? [target.guide] : []);
|
|
4954
|
+
return target;
|
|
4955
|
+
},
|
|
4956
|
+
/**
|
|
4957
|
+
* Ends a reorder drag: moves `id` to the slot under `point` (default: its centre) and lays
|
|
4958
|
+
* the frame out, as one undoable step whose undo puts the node back where the drag began.
|
|
4959
|
+
*/
|
|
4960
|
+
endReorderDrag(id, point) {
|
|
4961
|
+
setGuides([]);
|
|
4962
|
+
const start = dragStarts.get(id);
|
|
4963
|
+
dragStarts.delete(id);
|
|
4964
|
+
if (options.dragReorder === false || !getLayoutParent(options.getNodes(), id)) return [];
|
|
4965
|
+
const target = controller.getInsertTarget(id, point);
|
|
4966
|
+
if (!target) return [];
|
|
4967
|
+
const before = options.getNodes();
|
|
4968
|
+
const order = before.map((n) => n.id);
|
|
4969
|
+
const forward = (nodes) => moveLayoutChild(nodes, id, target.index);
|
|
4970
|
+
const backward = (nodes) => restoreOrder(nodes, order);
|
|
4971
|
+
return run("Reorder", forward, backward, () => [id], [], start && { id, position: start });
|
|
4972
|
+
},
|
|
4973
|
+
/**
|
|
4974
|
+
* Notes a batch of React Flow / SvelteFlow style node changes (before or after they are
|
|
4975
|
+
* applied) so `flush` can relayout: dimension changes (measured sizes, resize ends),
|
|
4976
|
+
* added and removed nodes, and position changes of flow children (reorder drags).
|
|
4977
|
+
* Returns the changes unchanged.
|
|
4978
|
+
*/
|
|
4979
|
+
noteChanges(changes) {
|
|
4980
|
+
const nodes = options.getNodes();
|
|
4981
|
+
const lookup = createNodeLookup(nodes);
|
|
4982
|
+
for (const c of changes) {
|
|
4983
|
+
const id = c.id ?? c.item?.id;
|
|
4984
|
+
if (c.type === "add" && c.item) {
|
|
4985
|
+
pendingIds.add(c.item.id);
|
|
4986
|
+
} else if (c.type === "remove" && id) {
|
|
4987
|
+
const parent = lookup.get(id)?.parentId;
|
|
4988
|
+
if (parent) pendingIds.add(parent);
|
|
4989
|
+
else pendingAll = true;
|
|
4990
|
+
} else if (c.type === "replace" && id) {
|
|
4991
|
+
pendingIds.add(id);
|
|
4992
|
+
} else if (c.type === "dimensions" && id) {
|
|
4993
|
+
const node = lookup.get(id);
|
|
4994
|
+
if (c.resizing === true) {
|
|
4995
|
+
if (node && !resizeStarts.has(id)) resizeStarts.set(id, getNodeSize(node, options.rectOptions));
|
|
4996
|
+
continue;
|
|
4997
|
+
}
|
|
4998
|
+
const start = resizeStarts.get(id);
|
|
4999
|
+
resizeStarts.delete(id);
|
|
5000
|
+
if (start) pendingResizes.set(id, start);
|
|
5001
|
+
pendingIds.add(id);
|
|
5002
|
+
} else if (c.type === "position" && id && options.dragReorder !== false) {
|
|
5003
|
+
if (!getLayoutParent(nodes, id)) continue;
|
|
5004
|
+
if (c.dragging === true) {
|
|
5005
|
+
if (!dragStarts.has(id)) {
|
|
5006
|
+
const node = lookup.get(id);
|
|
5007
|
+
if (node) dragStarts.set(id, { ...node.position });
|
|
5008
|
+
}
|
|
5009
|
+
} else if (c.dragging === false || dragStarts.has(id)) {
|
|
5010
|
+
pendingDrops.add(id);
|
|
5011
|
+
}
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
return changes;
|
|
5015
|
+
},
|
|
5016
|
+
/**
|
|
5017
|
+
* Notes nodes that were already removed (e.g. SvelteFlow's `ondelete`), so `flush`
|
|
5018
|
+
* relayouts the frames they left. The nodes must still carry their `parentId`.
|
|
5019
|
+
*/
|
|
5020
|
+
noteRemoved(removed) {
|
|
5021
|
+
const gone = /* @__PURE__ */ new Set();
|
|
5022
|
+
const list = [...removed];
|
|
5023
|
+
for (const n of list) gone.add(n.id);
|
|
5024
|
+
for (const n of list) if (n.parentId && !gone.has(n.parentId)) pendingIds.add(n.parentId);
|
|
5025
|
+
},
|
|
5026
|
+
/** Whether `noteChanges` left work for `flush`. */
|
|
5027
|
+
get hasPending() {
|
|
5028
|
+
return pendingAll || pendingIds.size > 0 || pendingResizes.size > 0 || pendingDrops.size > 0 || dragStarts.size > 0;
|
|
5029
|
+
},
|
|
5030
|
+
/**
|
|
5031
|
+
* Applies the work noted by `noteChanges` to the current nodes: drop markers while
|
|
5032
|
+
* dragging, committed reorders on drop (undoable), and a relayout (not recorded) for
|
|
5033
|
+
* size, add and remove changes. Call it once the changes are applied.
|
|
5034
|
+
*/
|
|
5035
|
+
flush() {
|
|
5036
|
+
let applied = [];
|
|
5037
|
+
for (const id of pendingDrops) applied = mergeNodeUpdates(applied, controller.endReorderDrag(id));
|
|
5038
|
+
pendingDrops.clear();
|
|
5039
|
+
for (const id of dragStarts.keys()) controller.dragReorder(id);
|
|
5040
|
+
if (!enabled()) {
|
|
5041
|
+
pendingIds.clear();
|
|
5042
|
+
pendingResizes.clear();
|
|
5043
|
+
pendingAll = false;
|
|
5044
|
+
return applied;
|
|
5045
|
+
}
|
|
5046
|
+
let nodes = options.getNodes();
|
|
5047
|
+
let updates = [];
|
|
5048
|
+
for (const [id, previous] of pendingResizes) {
|
|
5049
|
+
if (!nodes.some((n) => n.id === id)) continue;
|
|
5050
|
+
const constrained = applyConstraints(nodes, id, previous, layoutOptions());
|
|
5051
|
+
nodes = applyNodeUpdates(nodes, constrained);
|
|
5052
|
+
updates = mergeNodeUpdates(updates, constrained);
|
|
5053
|
+
}
|
|
5054
|
+
const layout2 = pendingAll ? layoutAll(nodes, layoutOptions()) : relayoutUpdates(nodes, [...pendingIds].filter((id) => nodes.some((n) => n.id === id)), layoutOptions());
|
|
5055
|
+
updates = mergeNodeUpdates(updates, layout2);
|
|
5056
|
+
pendingIds.clear();
|
|
5057
|
+
pendingResizes.clear();
|
|
5058
|
+
pendingAll = false;
|
|
5059
|
+
if (updates.length > 0) options.setNodes(applyNodeUpdates(options.getNodes(), updates));
|
|
5060
|
+
return mergeNodeUpdates(applied, updates);
|
|
5061
|
+
},
|
|
5062
|
+
/** Whether `id` is an auto layout frame. */
|
|
5063
|
+
isFrame: (id) => isAutoLayoutFrame(options.getNodes().find((n) => n.id === id))
|
|
5064
|
+
};
|
|
5065
|
+
return controller;
|
|
5066
|
+
}
|
|
5067
|
+
function restoreOrder(nodes, order) {
|
|
5068
|
+
const rank = new Map(order.map((id, i) => [id, i]));
|
|
5069
|
+
const indexed = nodes.map((n, i) => ({ n, r: rank.get(n.id) ?? order.length + i }));
|
|
5070
|
+
indexed.sort((a, b) => a.r - b.r);
|
|
5071
|
+
return indexed.every((e, i) => e.n === nodes[i]) ? nodes : indexed.map((e) => e.n);
|
|
5072
|
+
}
|
|
3125
5073
|
export {
|
|
5074
|
+
AUTO_LAYOUT_DATA_KEY,
|
|
5075
|
+
CHILD_LAYOUT_DATA_KEY,
|
|
3126
5076
|
CONNECTION_LINE_TYPES,
|
|
3127
5077
|
CanvasScenesStore,
|
|
3128
5078
|
DEFAULT_ARRANGE_SHORTCUTS,
|
|
@@ -3132,12 +5082,14 @@ export {
|
|
|
3132
5082
|
LOCKED_DATA_KEY,
|
|
3133
5083
|
LOCKED_PREVIOUS_FLAGS_KEY,
|
|
3134
5084
|
ManualClock,
|
|
5085
|
+
RESIZE_HANDLES,
|
|
3135
5086
|
SHAPE_DATA_KEY,
|
|
3136
5087
|
SHAPE_KINDS,
|
|
3137
5088
|
SHAPE_NODE_TYPE,
|
|
3138
5089
|
SHAPE_OPERATIONS,
|
|
3139
5090
|
StreamingLayoutManager,
|
|
3140
5091
|
alignNodes,
|
|
5092
|
+
applyConstraints,
|
|
3141
5093
|
applyNodeUpdates,
|
|
3142
5094
|
applyShapeCombination,
|
|
3143
5095
|
blendLayout,
|
|
@@ -3148,6 +5100,7 @@ export {
|
|
|
3148
5100
|
canvasHeight,
|
|
3149
5101
|
centerNodes,
|
|
3150
5102
|
centre,
|
|
5103
|
+
clampLayoutSize,
|
|
3151
5104
|
collectDescendantIds,
|
|
3152
5105
|
combineShapeNodes,
|
|
3153
5106
|
computeNodesBoundingBox,
|
|
@@ -3155,11 +5108,14 @@ export {
|
|
|
3155
5108
|
connectedOnly,
|
|
3156
5109
|
createBezierTrajectory,
|
|
3157
5110
|
createCanvasArrangeController,
|
|
5111
|
+
createCanvasAutoLayoutController,
|
|
3158
5112
|
createCanvasScenesStore,
|
|
3159
5113
|
createFanOutTrajectory,
|
|
3160
5114
|
createGuideIndex,
|
|
3161
5115
|
createNodeGuideIndex,
|
|
3162
5116
|
createNodeLookup,
|
|
5117
|
+
cubicPointAt,
|
|
5118
|
+
cubicsToPathData,
|
|
3163
5119
|
defaultEasing,
|
|
3164
5120
|
distributeNodes,
|
|
3165
5121
|
dividePolygons,
|
|
@@ -3167,36 +5123,63 @@ export {
|
|
|
3167
5123
|
estimateCanvasHeight,
|
|
3168
5124
|
estimateNodeSize,
|
|
3169
5125
|
findConnectorSnap,
|
|
5126
|
+
fitCubicRun,
|
|
5127
|
+
fitCubics,
|
|
5128
|
+
fitFreehandPath,
|
|
5129
|
+
fitPathData,
|
|
3170
5130
|
fitViewportToNodes,
|
|
3171
5131
|
fitViewportToRect,
|
|
3172
5132
|
flattenSvgPath,
|
|
3173
5133
|
flowSide,
|
|
3174
|
-
formatNumber,
|
|
5134
|
+
formatNumber2 as formatNumber,
|
|
3175
5135
|
getAbsolutePosition,
|
|
5136
|
+
getAutoLayout,
|
|
5137
|
+
getAutoLayoutGrid,
|
|
5138
|
+
getAutoLayoutInsertTarget,
|
|
3176
5139
|
getCenteredNodePosition,
|
|
5140
|
+
getChildLayout,
|
|
5141
|
+
getFlowChildren,
|
|
5142
|
+
getLayoutParent,
|
|
5143
|
+
getLayoutSizing,
|
|
3177
5144
|
getLayoutedNodes,
|
|
3178
5145
|
getNodeRect,
|
|
3179
5146
|
getNodeShape,
|
|
3180
5147
|
getNodeSize,
|
|
5148
|
+
getResizingNodeRect,
|
|
3181
5149
|
getShapeRenderData,
|
|
3182
5150
|
getShapeSvg,
|
|
3183
5151
|
handlePoint,
|
|
5152
|
+
inferResizeHandle,
|
|
3184
5153
|
intersectPolygons,
|
|
3185
5154
|
invertNodeUpdates,
|
|
5155
|
+
isAutoLayoutFrame,
|
|
3186
5156
|
isEventFromTextInput,
|
|
3187
5157
|
isLocked,
|
|
5158
|
+
isMeasureModifierHeld,
|
|
3188
5159
|
isShapeDescription,
|
|
3189
5160
|
isShapeNode,
|
|
5161
|
+
isStraightCubic,
|
|
3190
5162
|
isValidCanvasEdge,
|
|
3191
5163
|
isValidCanvasNode,
|
|
3192
5164
|
isValidCanvasScene,
|
|
3193
5165
|
isValidUrl,
|
|
5166
|
+
layoutAll,
|
|
3194
5167
|
layoutBounds,
|
|
5168
|
+
layoutFrame,
|
|
5169
|
+
layoutUp,
|
|
5170
|
+
layoutUpMany,
|
|
3195
5171
|
linearPositionTrajectory,
|
|
3196
5172
|
linearTrajectory,
|
|
3197
5173
|
lockNodes,
|
|
5174
|
+
makeSizingCoherent,
|
|
3198
5175
|
matchArrangeShortcut,
|
|
3199
5176
|
matchSize,
|
|
5177
|
+
measureBetween,
|
|
5178
|
+
measureLabel,
|
|
5179
|
+
measureSelection,
|
|
5180
|
+
measureToLine,
|
|
5181
|
+
mergeNodeUpdates,
|
|
5182
|
+
moveLayoutChild,
|
|
3200
5183
|
multiPolygonArea,
|
|
3201
5184
|
multiPolygonBounds,
|
|
3202
5185
|
nearestHandlePositions,
|
|
@@ -3215,9 +5198,12 @@ export {
|
|
|
3215
5198
|
positionToSide,
|
|
3216
5199
|
readStyleSize,
|
|
3217
5200
|
realClock,
|
|
5201
|
+
relayoutUpdates,
|
|
3218
5202
|
remapPastedNodes,
|
|
3219
5203
|
reorderNodes,
|
|
3220
5204
|
repositionNodesTo,
|
|
5205
|
+
resizeHandleAxes,
|
|
5206
|
+
resolvePadding,
|
|
3221
5207
|
revertShapeCombination,
|
|
3222
5208
|
ringSignedArea,
|
|
3223
5209
|
runMultiNodeTransition,
|
|
@@ -3227,6 +5213,9 @@ export {
|
|
|
3227
5213
|
sideToPosition,
|
|
3228
5214
|
simplifyRing,
|
|
3229
5215
|
snapNodeDrag,
|
|
5216
|
+
snapNodeResize,
|
|
5217
|
+
snapResize,
|
|
5218
|
+
snapToGrid,
|
|
3230
5219
|
subtractPolygons,
|
|
3231
5220
|
toggleLockNodes,
|
|
3232
5221
|
unionPolygons,
|