@pixerate/editor 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -2
- package/THIRD_PARTY_NOTICES.md +88 -0
- package/dist/agent/index.cjs +83 -108
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +69 -211
- package/dist/agent/index.d.ts +69 -211
- package/dist/agent/index.js +76 -101
- 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 +879 -83
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +291 -186
- package/dist/canvas/index.d.ts +291 -186
- package/dist/canvas/index.js +865 -69
- 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-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-G2I4QJOT.cjs +762 -0
- package/dist/chunk-G2I4QJOT.cjs.map +1 -0
- package/dist/{chunk-RU52I7QO.js → chunk-K3R5NG2D.js} +147 -42
- package/dist/chunk-K3R5NG2D.js.map +1 -0
- package/dist/{chunk-DENSCKFC.js → chunk-QBJ22YZR.js} +423 -30
- package/dist/chunk-QBJ22YZR.js.map +1 -0
- package/dist/{chunk-SAZRENEZ.cjs → chunk-RIKBJLS2.cjs} +424 -31
- package/dist/chunk-RIKBJLS2.cjs.map +1 -0
- package/dist/{chunk-6M3BCTBD.cjs → chunk-XDMXXC4Z.cjs} +230 -125
- package/dist/chunk-XDMXXC4Z.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 +592 -0
- package/dist/commands/index.d.ts +592 -0
- package/dist/commands/index.js +860 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/{controller-B7GIybc4.d.cts → controller-pePCJLT1.d.cts} +78 -5
- package/dist/{controller-B7GIybc4.d.ts → controller-pePCJLT1.d.ts} +78 -5
- 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 +418 -13
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +154 -3
- package/dist/image-editor/index.d.ts +154 -3
- package/dist/image-editor/index.js +417 -12
- package/dist/image-editor/index.js.map +1 -1
- package/dist/index.cjs +34 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -42
- package/dist/index.d.ts +5 -42
- package/dist/index.js +27 -69
- package/dist/index.js.map +1 -1
- package/dist/registry-D5-R90p0.d.cts +209 -0
- package/dist/registry-D5-R90p0.d.ts +209 -0
- package/dist/spreadsheet/index.cjs +19 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +67 -9
- package/dist/spreadsheet/index.d.ts +67 -9
- package/dist/spreadsheet/index.js +18 -2
- package/dist/{types-CJ4U-HHI.d.cts → types-DX4Mh4SL.d.cts} +1 -1
- package/dist/{types-CJ4U-HHI.d.ts → 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 +8 -2
- package/dist/chunk-2TWGLC5K.cjs +0 -262
- package/dist/chunk-2TWGLC5K.cjs.map +0 -1
- package/dist/chunk-6M3BCTBD.cjs.map +0 -1
- package/dist/chunk-DENSCKFC.js.map +0 -1
- package/dist/chunk-KAADNAPF.js +0 -262
- package/dist/chunk-KAADNAPF.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/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
|
}
|
|
@@ -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,206 @@ 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
|
+
|
|
2879
3391
|
// src/canvas/arrangeController.ts
|
|
2880
3392
|
function createCanvasArrangeController(options) {
|
|
2881
3393
|
let guides = [];
|
|
2882
3394
|
let dragIndex = null;
|
|
2883
3395
|
let dragKey = "";
|
|
2884
3396
|
let lastSnapped = /* @__PURE__ */ new Map();
|
|
3397
|
+
let resizeId = null;
|
|
3398
|
+
let resizeStart = null;
|
|
3399
|
+
let resizeIndex = null;
|
|
3400
|
+
let lastResized = null;
|
|
2885
3401
|
const selectedIds = (ids) => ids ? [...ids] : options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);
|
|
2886
3402
|
const setGuides = (next) => {
|
|
2887
3403
|
if (next.length === 0 && guides.length === 0) return;
|
|
@@ -3098,6 +3614,150 @@ function createCanvasArrangeController(options) {
|
|
|
3098
3614
|
return { ...c, position };
|
|
3099
3615
|
});
|
|
3100
3616
|
},
|
|
3617
|
+
/**
|
|
3618
|
+
* Starts resize snapping for node `id`: records its box (to tell which handle is
|
|
3619
|
+
* dragged) and caches the snap targets. Optional: `resize` starts lazily.
|
|
3620
|
+
*/
|
|
3621
|
+
beginResize(id) {
|
|
3622
|
+
const nodes = options.getNodes();
|
|
3623
|
+
const lookup = createNodeLookup(nodes);
|
|
3624
|
+
const node = lookup.get(id);
|
|
3625
|
+
resizeId = id;
|
|
3626
|
+
resizeStart = node ? getResizingNodeRect(node, lookup, rect()) : null;
|
|
3627
|
+
resizeIndex = createNodeGuideIndex(nodes, [id], rect());
|
|
3628
|
+
lastResized = null;
|
|
3629
|
+
},
|
|
3630
|
+
/**
|
|
3631
|
+
* Snaps node `id` as currently sized in `getNodes()` (call it after the flow wrote the
|
|
3632
|
+
* new size, e.g. SvelteFlow's `NodeResizer` `onResize`, deferred by a microtask) and
|
|
3633
|
+
* writes the snapped box back through `setNodes` (not recorded in history). `handle`
|
|
3634
|
+
* defaults to the one inferred from the edges that moved since `beginResize`. A locked
|
|
3635
|
+
* node is restored to its starting box.
|
|
3636
|
+
*/
|
|
3637
|
+
resize(id, handle) {
|
|
3638
|
+
if (resizeId !== id) controller.beginResize(id);
|
|
3639
|
+
const nodes = options.getNodes();
|
|
3640
|
+
const lookup = createNodeLookup(nodes);
|
|
3641
|
+
const node = lookup.get(id);
|
|
3642
|
+
if (!node || !resizeStart) return null;
|
|
3643
|
+
const proposed = getResizingNodeRect(node, lookup, rect());
|
|
3644
|
+
if (isLocked(node)) {
|
|
3645
|
+
const start = resizeStart;
|
|
3646
|
+
const parent = node.parentId ? lookup.get(node.parentId) : void 0;
|
|
3647
|
+
const origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };
|
|
3648
|
+
if (proposed.x !== start.x || proposed.y !== start.y || proposed.width !== start.width || proposed.height !== start.height) {
|
|
3649
|
+
options.setNodes(
|
|
3650
|
+
applyNodeUpdates(nodes, [
|
|
3651
|
+
{ id, position: { x: start.x - origin.x, y: start.y - origin.y }, width: start.width, height: start.height }
|
|
3652
|
+
])
|
|
3653
|
+
);
|
|
3654
|
+
}
|
|
3655
|
+
return null;
|
|
3656
|
+
}
|
|
3657
|
+
if ((options.resizeSnapping ?? options.snapping) === false) return null;
|
|
3658
|
+
const h = handle ?? inferResizeHandle(resizeStart, proposed);
|
|
3659
|
+
if (!h) {
|
|
3660
|
+
setGuides([]);
|
|
3661
|
+
return null;
|
|
3662
|
+
}
|
|
3663
|
+
const result = snapNodeResize(nodes, id, proposed, h, {
|
|
3664
|
+
...rect(),
|
|
3665
|
+
...options.guides,
|
|
3666
|
+
...options.resize,
|
|
3667
|
+
zoom: options.getZoom?.() ?? 1,
|
|
3668
|
+
index: resizeIndex
|
|
3669
|
+
});
|
|
3670
|
+
lastResized = result.updates.length > 0 ? result : null;
|
|
3671
|
+
if (result.updates.length > 0) options.setNodes(applyNodeUpdates(nodes, result.updates));
|
|
3672
|
+
setGuides(result.guides);
|
|
3673
|
+
return result;
|
|
3674
|
+
},
|
|
3675
|
+
/**
|
|
3676
|
+
* Ends resize snapping: clears guides and re-applies the last snapped box if the flow
|
|
3677
|
+
* wrote the raw pointer size afterwards.
|
|
3678
|
+
*/
|
|
3679
|
+
endResize() {
|
|
3680
|
+
const last = lastResized;
|
|
3681
|
+
resizeId = null;
|
|
3682
|
+
resizeStart = null;
|
|
3683
|
+
resizeIndex = null;
|
|
3684
|
+
lastResized = null;
|
|
3685
|
+
setGuides([]);
|
|
3686
|
+
const update = last?.updates[0];
|
|
3687
|
+
if (!update) return;
|
|
3688
|
+
const node = options.getNodes().find((n) => n.id === update.id);
|
|
3689
|
+
if (!node) return;
|
|
3690
|
+
const drift = node.position.x !== update.position.x || node.position.y !== update.position.y || node.width !== update.width || node.height !== update.height;
|
|
3691
|
+
if (drift) options.setNodes(applyNodeUpdates(options.getNodes(), [update]));
|
|
3692
|
+
},
|
|
3693
|
+
/**
|
|
3694
|
+
* React Flow style: snaps the `dimensions` (and matching `position`) changes a
|
|
3695
|
+
* `NodeResizer` emits in an `onNodesChange` batch and updates `guides`. The resize-end
|
|
3696
|
+
* change (`resizing: false`) carries the raw pointer size, so the last snapped size is
|
|
3697
|
+
* kept. Resize changes of locked nodes are dropped. Returns the (possibly new) changes.
|
|
3698
|
+
*/
|
|
3699
|
+
snapResizeChanges(changes) {
|
|
3700
|
+
const dims = changes.find(
|
|
3701
|
+
(c) => c.type === "dimensions" && typeof c.id === "string" && !!c.dimensions && typeof c.resizing === "boolean"
|
|
3702
|
+
);
|
|
3703
|
+
if (!dims) return changes;
|
|
3704
|
+
const id = dims.id;
|
|
3705
|
+
const nodes = options.getNodes();
|
|
3706
|
+
const lookup = createNodeLookup(nodes);
|
|
3707
|
+
const node = lookup.get(id);
|
|
3708
|
+
const isResizeChange = (c) => c.id === id && (c.type === "dimensions" || c.type === "position");
|
|
3709
|
+
if (node && isLocked(node)) {
|
|
3710
|
+
if (dims.resizing === false) controller.endResize();
|
|
3711
|
+
return changes.filter((c) => !isResizeChange(c));
|
|
3712
|
+
}
|
|
3713
|
+
if (dims.resizing === false) {
|
|
3714
|
+
const last = lastResized?.updates[0];
|
|
3715
|
+
lastResized = null;
|
|
3716
|
+
controller.endResize();
|
|
3717
|
+
if (!last || last.id !== id) return changes;
|
|
3718
|
+
return changes.map(
|
|
3719
|
+
(c) => c === dims ? { ...c, dimensions: { width: last.width, height: last.height } } : c
|
|
3720
|
+
);
|
|
3721
|
+
}
|
|
3722
|
+
if (!node || (options.resizeSnapping ?? options.snapping) === false) return changes;
|
|
3723
|
+
if (resizeId !== id) controller.beginResize(id);
|
|
3724
|
+
if (!resizeStart) return changes;
|
|
3725
|
+
const posChange = changes.find((c) => c.id === id && c.type === "position" && !!c.position);
|
|
3726
|
+
const parent = node.parentId ? lookup.get(node.parentId) : void 0;
|
|
3727
|
+
const origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };
|
|
3728
|
+
const position = posChange?.position ?? node.position;
|
|
3729
|
+
const proposed = {
|
|
3730
|
+
x: position.x + origin.x,
|
|
3731
|
+
y: position.y + origin.y,
|
|
3732
|
+
width: dims.dimensions.width,
|
|
3733
|
+
height: dims.dimensions.height
|
|
3734
|
+
};
|
|
3735
|
+
const handle = inferResizeHandle(resizeStart, proposed);
|
|
3736
|
+
if (!handle) {
|
|
3737
|
+
setGuides([]);
|
|
3738
|
+
return changes;
|
|
3739
|
+
}
|
|
3740
|
+
const result = snapNodeResize(nodes, id, proposed, handle, {
|
|
3741
|
+
...rect(),
|
|
3742
|
+
...options.guides,
|
|
3743
|
+
...options.resize,
|
|
3744
|
+
zoom: options.getZoom?.() ?? 1,
|
|
3745
|
+
index: resizeIndex
|
|
3746
|
+
});
|
|
3747
|
+
setGuides(result.guides);
|
|
3748
|
+
const update = result.updates[0];
|
|
3749
|
+
lastResized = update ? result : null;
|
|
3750
|
+
if (!update) return changes;
|
|
3751
|
+
const next = changes.map((c) => {
|
|
3752
|
+
if (c === dims) return { ...c, dimensions: { width: update.width, height: update.height } };
|
|
3753
|
+
if (c === posChange) return { ...c, position: update.position };
|
|
3754
|
+
return c;
|
|
3755
|
+
});
|
|
3756
|
+
if (!posChange && (update.position.x !== node.position.x || update.position.y !== node.position.y)) {
|
|
3757
|
+
next.push({ id, type: "position", position: update.position });
|
|
3758
|
+
}
|
|
3759
|
+
return next;
|
|
3760
|
+
},
|
|
3101
3761
|
/**
|
|
3102
3762
|
* Clears guides and the cached snap targets. Call it from the flow's drag-stop
|
|
3103
3763
|
* handler: flow libraries write the raw pointer position when a drag ends, so the
|
|
@@ -3122,6 +3782,123 @@ function createCanvasArrangeController(options) {
|
|
|
3122
3782
|
};
|
|
3123
3783
|
return controller;
|
|
3124
3784
|
}
|
|
3785
|
+
|
|
3786
|
+
// src/canvas/measure.ts
|
|
3787
|
+
var EPS = 1e-6;
|
|
3788
|
+
function measureLabel(distance) {
|
|
3789
|
+
const v = Math.round(Math.abs(distance) * 100) / 100;
|
|
3790
|
+
return Object.is(v, -0) ? "0" : String(v);
|
|
3791
|
+
}
|
|
3792
|
+
var toBox = (r) => ({ x0: r.x, y0: r.y, x1: r.x + r.width, y1: r.y + r.height });
|
|
3793
|
+
var finiteBox = (b) => [b.x0, b.y0, b.x1, b.y1].every(Number.isFinite);
|
|
3794
|
+
function line(kind, x1, y1, x2, y2) {
|
|
3795
|
+
return { orientation: x1 === x2 ? "vertical" : "horizontal", kind, x1, y1, x2, y2 };
|
|
3796
|
+
}
|
|
3797
|
+
function measured(out, x1, y1, x2, y2) {
|
|
3798
|
+
const d = Math.hypot(x2 - x1, y2 - y1);
|
|
3799
|
+
if (d > EPS && Number.isFinite(d)) out.push({ ...line("measure", x1, y1, x2, y2), label: measureLabel(d) });
|
|
3800
|
+
}
|
|
3801
|
+
function extension(out, x1, y1, x2, y2) {
|
|
3802
|
+
if (Math.hypot(x2 - x1, y2 - y1) > EPS) out.push(line("extension", x1, y1, x2, y2));
|
|
3803
|
+
}
|
|
3804
|
+
function contains(outer, inner) {
|
|
3805
|
+
return outer.x0 <= inner.x0 + EPS && outer.y0 <= inner.y0 + EPS && outer.x1 >= inner.x1 - EPS && outer.y1 >= inner.y1 - EPS;
|
|
3806
|
+
}
|
|
3807
|
+
function overlapMid(a0, a1, b0, b1) {
|
|
3808
|
+
const lo = Math.max(a0, b0);
|
|
3809
|
+
const hi = Math.min(a1, b1);
|
|
3810
|
+
return lo <= hi ? (lo + hi) / 2 : null;
|
|
3811
|
+
}
|
|
3812
|
+
function inside(out, inner, outer) {
|
|
3813
|
+
const cx = (inner.x0 + inner.x1) / 2;
|
|
3814
|
+
const cy = (inner.y0 + inner.y1) / 2;
|
|
3815
|
+
measured(out, outer.x0, cy, inner.x0, cy);
|
|
3816
|
+
measured(out, inner.x1, cy, outer.x1, cy);
|
|
3817
|
+
measured(out, cx, outer.y0, cx, inner.y0);
|
|
3818
|
+
measured(out, cx, inner.y1, cx, outer.y1);
|
|
3819
|
+
}
|
|
3820
|
+
function measureBetween(a, b) {
|
|
3821
|
+
const out = [];
|
|
3822
|
+
const A = toBox(a);
|
|
3823
|
+
const B = toBox(b);
|
|
3824
|
+
if (!finiteBox(A) || !finiteBox(B)) return out;
|
|
3825
|
+
if (contains(B, A)) {
|
|
3826
|
+
inside(out, A, B);
|
|
3827
|
+
return out;
|
|
3828
|
+
}
|
|
3829
|
+
if (contains(A, B)) {
|
|
3830
|
+
inside(out, B, A);
|
|
3831
|
+
return out;
|
|
3832
|
+
}
|
|
3833
|
+
const ox = overlapMid(A.x0, A.x1, B.x0, B.x1);
|
|
3834
|
+
const oy = overlapMid(A.y0, A.y1, B.y0, B.y1);
|
|
3835
|
+
const overlapping = A.x0 < B.x1 && B.x0 < A.x1 && A.y0 < B.y1 && B.y0 < A.y1;
|
|
3836
|
+
if (overlapping && ox !== null && oy !== null) {
|
|
3837
|
+
measured(out, A.x0, oy, B.x0, oy);
|
|
3838
|
+
measured(out, A.x1, oy, B.x1, oy);
|
|
3839
|
+
measured(out, ox, A.y0, ox, B.y0);
|
|
3840
|
+
measured(out, ox, A.y1, ox, B.y1);
|
|
3841
|
+
return out;
|
|
3842
|
+
}
|
|
3843
|
+
const cx = (A.x0 + A.x1) / 2;
|
|
3844
|
+
const cy = (A.y0 + A.y1) / 2;
|
|
3845
|
+
const gapX = B.x0 >= A.x1 ? [A.x1, B.x0] : B.x1 <= A.x0 ? [A.x0, B.x1] : null;
|
|
3846
|
+
if (gapX) {
|
|
3847
|
+
const [from, to] = gapX;
|
|
3848
|
+
const y = oy ?? cy;
|
|
3849
|
+
measured(out, from, y, to, y);
|
|
3850
|
+
if (oy === null) extension(out, to, y, to, y < B.y0 ? B.y0 : B.y1);
|
|
3851
|
+
}
|
|
3852
|
+
const gapY = B.y0 >= A.y1 ? [A.y1, B.y0] : B.y1 <= A.y0 ? [A.y0, B.y1] : null;
|
|
3853
|
+
if (gapY) {
|
|
3854
|
+
const [from, to] = gapY;
|
|
3855
|
+
const x = ox ?? cx;
|
|
3856
|
+
measured(out, x, from, x, to);
|
|
3857
|
+
if (ox === null) extension(out, x, to, x < B.x0 ? B.x0 : B.x1, to);
|
|
3858
|
+
}
|
|
3859
|
+
return out;
|
|
3860
|
+
}
|
|
3861
|
+
function measureToLine(a, orientation, at) {
|
|
3862
|
+
const out = [];
|
|
3863
|
+
const A = toBox(a);
|
|
3864
|
+
if (!finiteBox(A) || !Number.isFinite(at)) return out;
|
|
3865
|
+
const vertical = orientation === "vertical";
|
|
3866
|
+
const cx = (A.x0 + A.x1) / 2;
|
|
3867
|
+
const cy = (A.y0 + A.y1) / 2;
|
|
3868
|
+
const [lo, hi] = vertical ? [A.x0, A.x1] : [A.y0, A.y1];
|
|
3869
|
+
const seg = (from, to) => vertical ? measured(out, from, cy, to, cy) : measured(out, cx, from, cx, to);
|
|
3870
|
+
if (at >= hi) seg(hi, at);
|
|
3871
|
+
else if (at <= lo) seg(at, lo);
|
|
3872
|
+
else {
|
|
3873
|
+
seg(lo, at);
|
|
3874
|
+
seg(at, hi);
|
|
3875
|
+
}
|
|
3876
|
+
return out;
|
|
3877
|
+
}
|
|
3878
|
+
function measureSelection(nodes, selectedIds, hoveredId, options = {}) {
|
|
3879
|
+
const none = (selection2 = null) => ({ guides: [], target: null, selection: selection2 });
|
|
3880
|
+
const lookup = createNodeLookup(nodes);
|
|
3881
|
+
const selected = [...new Set(selectedIds)].map((id) => lookup.get(id)).filter((n) => !!n && n.hidden !== true);
|
|
3882
|
+
if (selected.length === 0) return none();
|
|
3883
|
+
const selection = boundsOf(selected.map((n) => getNodeRect(n, lookup, options)));
|
|
3884
|
+
if (!selection) return none();
|
|
3885
|
+
const selectedSet = new Set(selected.map((n) => n.id));
|
|
3886
|
+
let targetId = hoveredId ?? null;
|
|
3887
|
+
if (targetId === null || selectedSet.has(targetId)) {
|
|
3888
|
+
const parentId = selected[0].parentId;
|
|
3889
|
+
const sameParent = selected.every((n) => n.parentId === parentId);
|
|
3890
|
+
targetId = parentId && sameParent && !selectedSet.has(parentId) ? parentId : null;
|
|
3891
|
+
}
|
|
3892
|
+
const target = targetId !== null ? lookup.get(targetId) : void 0;
|
|
3893
|
+
if (!target || target.hidden === true) return none(selection);
|
|
3894
|
+
const rect = getNodeRect(target, lookup, options);
|
|
3895
|
+
return { guides: measureBetween(selection, rect), target: rect, selection };
|
|
3896
|
+
}
|
|
3897
|
+
function isMeasureModifierHeld(event) {
|
|
3898
|
+
if (!event) return false;
|
|
3899
|
+
if (event.type === "keyup" && event.key === "Alt") return false;
|
|
3900
|
+
return event.altKey === true || event.type === "keydown" && event.key === "Alt";
|
|
3901
|
+
}
|
|
3125
3902
|
export {
|
|
3126
3903
|
CONNECTION_LINE_TYPES,
|
|
3127
3904
|
CanvasScenesStore,
|
|
@@ -3132,6 +3909,7 @@ export {
|
|
|
3132
3909
|
LOCKED_DATA_KEY,
|
|
3133
3910
|
LOCKED_PREVIOUS_FLAGS_KEY,
|
|
3134
3911
|
ManualClock,
|
|
3912
|
+
RESIZE_HANDLES,
|
|
3135
3913
|
SHAPE_DATA_KEY,
|
|
3136
3914
|
SHAPE_KINDS,
|
|
3137
3915
|
SHAPE_NODE_TYPE,
|
|
@@ -3160,6 +3938,8 @@ export {
|
|
|
3160
3938
|
createGuideIndex,
|
|
3161
3939
|
createNodeGuideIndex,
|
|
3162
3940
|
createNodeLookup,
|
|
3941
|
+
cubicPointAt,
|
|
3942
|
+
cubicsToPathData,
|
|
3163
3943
|
defaultEasing,
|
|
3164
3944
|
distributeNodes,
|
|
3165
3945
|
dividePolygons,
|
|
@@ -3167,26 +3947,34 @@ export {
|
|
|
3167
3947
|
estimateCanvasHeight,
|
|
3168
3948
|
estimateNodeSize,
|
|
3169
3949
|
findConnectorSnap,
|
|
3950
|
+
fitCubicRun,
|
|
3951
|
+
fitCubics,
|
|
3952
|
+
fitFreehandPath,
|
|
3953
|
+
fitPathData,
|
|
3170
3954
|
fitViewportToNodes,
|
|
3171
3955
|
fitViewportToRect,
|
|
3172
3956
|
flattenSvgPath,
|
|
3173
3957
|
flowSide,
|
|
3174
|
-
formatNumber,
|
|
3958
|
+
formatNumber2 as formatNumber,
|
|
3175
3959
|
getAbsolutePosition,
|
|
3176
3960
|
getCenteredNodePosition,
|
|
3177
3961
|
getLayoutedNodes,
|
|
3178
3962
|
getNodeRect,
|
|
3179
3963
|
getNodeShape,
|
|
3180
3964
|
getNodeSize,
|
|
3965
|
+
getResizingNodeRect,
|
|
3181
3966
|
getShapeRenderData,
|
|
3182
3967
|
getShapeSvg,
|
|
3183
3968
|
handlePoint,
|
|
3969
|
+
inferResizeHandle,
|
|
3184
3970
|
intersectPolygons,
|
|
3185
3971
|
invertNodeUpdates,
|
|
3186
3972
|
isEventFromTextInput,
|
|
3187
3973
|
isLocked,
|
|
3974
|
+
isMeasureModifierHeld,
|
|
3188
3975
|
isShapeDescription,
|
|
3189
3976
|
isShapeNode,
|
|
3977
|
+
isStraightCubic,
|
|
3190
3978
|
isValidCanvasEdge,
|
|
3191
3979
|
isValidCanvasNode,
|
|
3192
3980
|
isValidCanvasScene,
|
|
@@ -3197,6 +3985,10 @@ export {
|
|
|
3197
3985
|
lockNodes,
|
|
3198
3986
|
matchArrangeShortcut,
|
|
3199
3987
|
matchSize,
|
|
3988
|
+
measureBetween,
|
|
3989
|
+
measureLabel,
|
|
3990
|
+
measureSelection,
|
|
3991
|
+
measureToLine,
|
|
3200
3992
|
multiPolygonArea,
|
|
3201
3993
|
multiPolygonBounds,
|
|
3202
3994
|
nearestHandlePositions,
|
|
@@ -3218,6 +4010,7 @@ export {
|
|
|
3218
4010
|
remapPastedNodes,
|
|
3219
4011
|
reorderNodes,
|
|
3220
4012
|
repositionNodesTo,
|
|
4013
|
+
resizeHandleAxes,
|
|
3221
4014
|
revertShapeCombination,
|
|
3222
4015
|
ringSignedArea,
|
|
3223
4016
|
runMultiNodeTransition,
|
|
@@ -3227,6 +4020,9 @@ export {
|
|
|
3227
4020
|
sideToPosition,
|
|
3228
4021
|
simplifyRing,
|
|
3229
4022
|
snapNodeDrag,
|
|
4023
|
+
snapNodeResize,
|
|
4024
|
+
snapResize,
|
|
4025
|
+
snapToGrid,
|
|
3230
4026
|
subtractPolygons,
|
|
3231
4027
|
toggleLockNodes,
|
|
3232
4028
|
unionPolygons,
|