@pixerate/editor 0.19.0 → 0.20.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 +91 -2
- package/dist/agent/index.cjs +625 -2
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +286 -2
- package/dist/agent/index.d.ts +286 -2
- package/dist/agent/index.js +624 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/canvas/index.cjs +1240 -22
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +515 -3
- package/dist/canvas/index.d.ts +515 -3
- package/dist/canvas/index.js +1219 -1
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-3TYMNULT.js +1367 -0
- package/dist/chunk-3TYMNULT.js.map +1 -0
- package/dist/chunk-4QPLEUL2.cjs +4480 -0
- package/dist/chunk-4QPLEUL2.cjs.map +1 -0
- package/dist/chunk-4SUNJZWB.cjs +553 -0
- package/dist/chunk-4SUNJZWB.cjs.map +1 -0
- package/dist/chunk-A7DKIA72.js +2657 -0
- package/dist/chunk-A7DKIA72.js.map +1 -0
- package/dist/chunk-AXV2ZW34.cjs +118 -0
- package/dist/chunk-AXV2ZW34.cjs.map +1 -0
- package/dist/chunk-B2ZRLN4S.js +118 -0
- package/dist/chunk-B2ZRLN4S.js.map +1 -0
- package/dist/chunk-CYFJCKC4.js +4480 -0
- package/dist/chunk-CYFJCKC4.js.map +1 -0
- package/dist/chunk-GVW4P7BG.js +2706 -0
- package/dist/chunk-GVW4P7BG.js.map +1 -0
- package/dist/chunk-JACER6TE.cjs +1 -0
- package/dist/chunk-JACER6TE.cjs.map +1 -0
- package/dist/chunk-JYRIOFEN.cjs +2657 -0
- package/dist/chunk-JYRIOFEN.cjs.map +1 -0
- package/dist/chunk-KYRZDWM4.js +299 -0
- package/dist/chunk-KYRZDWM4.js.map +1 -0
- package/dist/chunk-LA7PEGU3.cjs +299 -0
- package/dist/chunk-LA7PEGU3.cjs.map +1 -0
- package/dist/chunk-N7FG6CBO.js +1 -0
- package/dist/chunk-N7FG6CBO.js.map +1 -0
- package/dist/chunk-OYXSKQFR.js +553 -0
- package/dist/chunk-OYXSKQFR.js.map +1 -0
- package/dist/chunk-TVRC4KVT.cjs +1367 -0
- package/dist/chunk-TVRC4KVT.cjs.map +1 -0
- package/dist/chunk-U6NDQFOK.cjs +2706 -0
- package/dist/chunk-U6NDQFOK.cjs.map +1 -0
- package/dist/controller-OUdxPY-X.d.cts +754 -0
- package/dist/controller-OUdxPY-X.d.ts +754 -0
- package/dist/extensions/index.cjs +144 -3
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +9 -144
- package/dist/extensions/index.d.ts +9 -144
- package/dist/extensions/index.js +145 -4
- package/dist/grammar/index.cjs +31 -3
- package/dist/grammar/index.cjs.map +1 -1
- package/dist/grammar/index.d.cts +239 -19
- package/dist/grammar/index.d.ts +239 -19
- package/dist/grammar/index.js +31 -3
- package/dist/history/index.d.cts +2 -41
- package/dist/history/index.d.ts +2 -41
- package/dist/index-Ng1pG7JE.d.ts +899 -0
- package/dist/index-k3mFbPx_.d.cts +899 -0
- package/dist/index.cjs +457 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -12
- package/dist/index.d.ts +99 -12
- package/dist/index.js +457 -20
- package/dist/index.js.map +1 -1
- package/dist/registry-iAHbqkYt.d.cts +128 -0
- package/dist/registry-iAHbqkYt.d.ts +128 -0
- package/dist/serializers/index.cjs +26 -3
- package/dist/serializers/index.cjs.map +1 -1
- package/dist/serializers/index.d.cts +119 -6
- package/dist/serializers/index.d.ts +119 -6
- package/dist/serializers/index.js +24 -1
- package/dist/spreadsheet/index.cjs +122 -2
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +371 -313
- package/dist/spreadsheet/index.d.ts +371 -313
- package/dist/spreadsheet/index.js +127 -7
- package/dist/tokens-CyLZmIei.d.cts +76 -0
- package/dist/tokens-CyLZmIei.d.ts +76 -0
- package/dist/track-changes-G8JcWB8b.d.cts +306 -0
- package/dist/track-changes-G8JcWB8b.d.ts +306 -0
- package/dist/types-BZgcDsC5.d.cts +42 -0
- package/dist/types-BZgcDsC5.d.ts +42 -0
- package/dist/{types-JV3S8VeR.d.cts → types-DMe0vFB1.d.cts} +1 -1
- package/dist/{types-JV3S8VeR.d.ts → types-DMe0vFB1.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/chunk-7FWEWE6T.js +0 -1888
- package/dist/chunk-7FWEWE6T.js.map +0 -1
- package/dist/chunk-AIZUKWTL.js +0 -1715
- package/dist/chunk-AIZUKWTL.js.map +0 -1
- package/dist/chunk-GLSIZKOX.js +0 -17
- package/dist/chunk-GLSIZKOX.js.map +0 -1
- package/dist/chunk-LDES3EOS.cjs +0 -1888
- package/dist/chunk-LDES3EOS.cjs.map +0 -1
- package/dist/chunk-LLUAKTLW.cjs +0 -532
- package/dist/chunk-LLUAKTLW.cjs.map +0 -1
- package/dist/chunk-MPPXH7U5.cjs +0 -336
- package/dist/chunk-MPPXH7U5.cjs.map +0 -1
- package/dist/chunk-O7LOPQMH.js +0 -336
- package/dist/chunk-O7LOPQMH.js.map +0 -1
- package/dist/chunk-QGVMBCT5.js +0 -532
- package/dist/chunk-QGVMBCT5.js.map +0 -1
- package/dist/chunk-SEDR75VH.cjs +0 -17
- package/dist/chunk-SEDR75VH.cjs.map +0 -1
- package/dist/chunk-YQCI4CCN.cjs +0 -1715
- package/dist/chunk-YQCI4CCN.cjs.map +0 -1
- package/dist/mention-Dcevh3ff.d.cts +0 -32
- package/dist/mention-Dcevh3ff.d.ts +0 -32
- package/dist/tokens-7kRTEtPb.d.cts +0 -39
- package/dist/tokens-7kRTEtPb.d.ts +0 -39
package/dist/canvas/index.js
CHANGED
|
@@ -719,12 +719,1198 @@ var StreamingLayoutManager = class {
|
|
|
719
719
|
}
|
|
720
720
|
}
|
|
721
721
|
};
|
|
722
|
+
|
|
723
|
+
// src/canvas/arrange.ts
|
|
724
|
+
var LOCKED_DATA_KEY = "locked";
|
|
725
|
+
var LOCKED_PREVIOUS_FLAGS_KEY = "lockedPrevious";
|
|
726
|
+
function parsePx(value) {
|
|
727
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
728
|
+
if (typeof value === "string") {
|
|
729
|
+
const match = /^\s*(-?\d+(?:\.\d+)?)(px)?\s*$/i.exec(value);
|
|
730
|
+
if (match) return Number(match[1]);
|
|
731
|
+
}
|
|
732
|
+
return void 0;
|
|
733
|
+
}
|
|
734
|
+
function readStyleSize(style) {
|
|
735
|
+
if (!style) return {};
|
|
736
|
+
if (typeof style === "object") {
|
|
737
|
+
return { width: parsePx(style.width), height: parsePx(style.height) };
|
|
738
|
+
}
|
|
739
|
+
const read = (prop) => {
|
|
740
|
+
const match = new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([^;]+)`, "i").exec(style);
|
|
741
|
+
return match ? parsePx(match[1]) : void 0;
|
|
742
|
+
};
|
|
743
|
+
return { width: read("width"), height: read("height") };
|
|
744
|
+
}
|
|
745
|
+
function withStyleSize(style, size) {
|
|
746
|
+
if (!style) return style ?? void 0;
|
|
747
|
+
if (typeof style === "object") {
|
|
748
|
+
const next2 = { ...style };
|
|
749
|
+
if (size.width !== void 0 && "width" in next2) next2.width = size.width;
|
|
750
|
+
if (size.height !== void 0 && "height" in next2) next2.height = size.height;
|
|
751
|
+
return next2;
|
|
752
|
+
}
|
|
753
|
+
let next = style;
|
|
754
|
+
for (const prop of ["width", "height"]) {
|
|
755
|
+
const value = size[prop];
|
|
756
|
+
if (value === void 0) continue;
|
|
757
|
+
next = next.replace(
|
|
758
|
+
new RegExp(`((?:^|;)\\s*${prop}\\s*:\\s*)[^;]+`, "i"),
|
|
759
|
+
`$1${value}px`
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
return next;
|
|
763
|
+
}
|
|
764
|
+
function getNodeSize(node, options = {}) {
|
|
765
|
+
const styleSize = readStyleSize(node.style);
|
|
766
|
+
let width = node.measured?.width ?? node.width ?? styleSize.width;
|
|
767
|
+
let height = node.measured?.height ?? node.height ?? styleSize.height;
|
|
768
|
+
if (width === void 0 || height === void 0) {
|
|
769
|
+
const fallback = options.estimate === false ? { width: options.defaultWidth ?? 200, height: options.defaultHeight ?? 60 } : estimateNodeSize(node, options.defaultWidth, options.defaultHeight);
|
|
770
|
+
width ??= fallback.width;
|
|
771
|
+
height ??= fallback.height;
|
|
772
|
+
}
|
|
773
|
+
return { width, height };
|
|
774
|
+
}
|
|
775
|
+
function getAbsolutePosition(node, lookup) {
|
|
776
|
+
let x = node.position.x;
|
|
777
|
+
let y = node.position.y;
|
|
778
|
+
const seen = /* @__PURE__ */ new Set([node.id]);
|
|
779
|
+
let parentId = node.parentId;
|
|
780
|
+
while (parentId && !seen.has(parentId)) {
|
|
781
|
+
const parent = lookup.get(parentId);
|
|
782
|
+
if (!parent) break;
|
|
783
|
+
seen.add(parentId);
|
|
784
|
+
x += parent.position.x;
|
|
785
|
+
y += parent.position.y;
|
|
786
|
+
parentId = parent.parentId;
|
|
787
|
+
}
|
|
788
|
+
return { x, y };
|
|
789
|
+
}
|
|
790
|
+
function createNodeLookup(nodes) {
|
|
791
|
+
return new Map(nodes.map((n) => [n.id, n]));
|
|
792
|
+
}
|
|
793
|
+
function getNodeRect(node, lookup, options) {
|
|
794
|
+
const { x, y } = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;
|
|
795
|
+
return { x, y, ...getNodeSize(node, options) };
|
|
796
|
+
}
|
|
797
|
+
function isLocked(node) {
|
|
798
|
+
return node?.data?.[LOCKED_DATA_KEY] === true;
|
|
799
|
+
}
|
|
800
|
+
function lockNodes(nodes, ids) {
|
|
801
|
+
const idSet = new Set(ids);
|
|
802
|
+
return nodes.map((node) => {
|
|
803
|
+
if (!idSet.has(node.id) || isLocked(node)) return node;
|
|
804
|
+
const previous = {};
|
|
805
|
+
if (node.draggable !== void 0) previous.draggable = node.draggable;
|
|
806
|
+
if (node.deletable !== void 0) previous.deletable = node.deletable;
|
|
807
|
+
return {
|
|
808
|
+
...node,
|
|
809
|
+
draggable: false,
|
|
810
|
+
deletable: false,
|
|
811
|
+
data: { ...node.data, [LOCKED_DATA_KEY]: true, [LOCKED_PREVIOUS_FLAGS_KEY]: previous }
|
|
812
|
+
};
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
function unlockNodes(nodes, ids) {
|
|
816
|
+
const idSet = new Set(ids);
|
|
817
|
+
return nodes.map((node) => {
|
|
818
|
+
if (!idSet.has(node.id) || !isLocked(node)) return node;
|
|
819
|
+
const {
|
|
820
|
+
[LOCKED_DATA_KEY]: _locked,
|
|
821
|
+
[LOCKED_PREVIOUS_FLAGS_KEY]: previous = {},
|
|
822
|
+
...data
|
|
823
|
+
} = node.data;
|
|
824
|
+
const next = { ...node, data };
|
|
825
|
+
delete next.draggable;
|
|
826
|
+
delete next.deletable;
|
|
827
|
+
if (previous.draggable !== void 0) next.draggable = previous.draggable;
|
|
828
|
+
if (previous.deletable !== void 0) next.deletable = previous.deletable;
|
|
829
|
+
return next;
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
function toggleLockNodes(nodes, ids) {
|
|
833
|
+
const idSet = new Set(ids);
|
|
834
|
+
const anyUnlocked = nodes.some((n) => idSet.has(n.id) && !isLocked(n));
|
|
835
|
+
return anyUnlocked ? lockNodes(nodes, idSet) : unlockNodes(nodes, idSet);
|
|
836
|
+
}
|
|
837
|
+
function collect(nodes, ids, options) {
|
|
838
|
+
const lookup = createNodeLookup(nodes);
|
|
839
|
+
const items = [];
|
|
840
|
+
const seen = /* @__PURE__ */ new Set();
|
|
841
|
+
for (const id of ids) {
|
|
842
|
+
const node = lookup.get(id);
|
|
843
|
+
if (!node || seen.has(id)) continue;
|
|
844
|
+
seen.add(id);
|
|
845
|
+
items.push({ node, rect: getNodeRect(node, lookup, options), locked: isLocked(node) });
|
|
846
|
+
}
|
|
847
|
+
return { lookup, items };
|
|
848
|
+
}
|
|
849
|
+
function toPositionUpdates(lookup, targets) {
|
|
850
|
+
const updates = [];
|
|
851
|
+
for (const [id, abs] of targets) {
|
|
852
|
+
const node = lookup.get(id);
|
|
853
|
+
let position = abs;
|
|
854
|
+
if (node.parentId && lookup.has(node.parentId)) {
|
|
855
|
+
const parent = lookup.get(node.parentId);
|
|
856
|
+
const parentAbs = targets.get(parent.id) ?? getAbsolutePosition(parent, lookup);
|
|
857
|
+
position = { x: abs.x - parentAbs.x, y: abs.y - parentAbs.y };
|
|
858
|
+
}
|
|
859
|
+
if (position.x !== node.position.x || position.y !== node.position.y) {
|
|
860
|
+
updates.push({ id, position });
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return updates;
|
|
864
|
+
}
|
|
865
|
+
function alignNodes(nodes, ids, edge, options = {}) {
|
|
866
|
+
const { lookup, items } = collect(nodes, ids, options);
|
|
867
|
+
const fixed = typeof options.to === "object" ? options.to : null;
|
|
868
|
+
if (items.length === 0 || !fixed && items.length < 2) return [];
|
|
869
|
+
const bounds = fixed ?? boundsOf(items.map((i) => i.rect));
|
|
870
|
+
const targets = /* @__PURE__ */ new Map();
|
|
871
|
+
for (const { node, rect, locked } of items) {
|
|
872
|
+
if (locked) continue;
|
|
873
|
+
let { x, y } = rect;
|
|
874
|
+
switch (edge) {
|
|
875
|
+
case "left":
|
|
876
|
+
x = bounds.x;
|
|
877
|
+
break;
|
|
878
|
+
case "center":
|
|
879
|
+
x = bounds.x + bounds.width / 2 - rect.width / 2;
|
|
880
|
+
break;
|
|
881
|
+
case "right":
|
|
882
|
+
x = bounds.x + bounds.width - rect.width;
|
|
883
|
+
break;
|
|
884
|
+
case "top":
|
|
885
|
+
y = bounds.y;
|
|
886
|
+
break;
|
|
887
|
+
case "middle":
|
|
888
|
+
y = bounds.y + bounds.height / 2 - rect.height / 2;
|
|
889
|
+
break;
|
|
890
|
+
case "bottom":
|
|
891
|
+
y = bounds.y + bounds.height - rect.height;
|
|
892
|
+
break;
|
|
893
|
+
}
|
|
894
|
+
targets.set(node.id, { x, y });
|
|
895
|
+
}
|
|
896
|
+
return toPositionUpdates(lookup, targets);
|
|
897
|
+
}
|
|
898
|
+
function distributeAxis(items, horizontal, mode, gapOverride, targets) {
|
|
899
|
+
const start = (r) => horizontal ? r.x : r.y;
|
|
900
|
+
const size = (r) => horizontal ? r.width : r.height;
|
|
901
|
+
const sorted = [...items].sort(
|
|
902
|
+
(a, b) => start(a.rect) + size(a.rect) / 2 - (start(b.rect) + size(b.rect) / 2)
|
|
903
|
+
);
|
|
904
|
+
const first = sorted[0].rect;
|
|
905
|
+
const last = sorted[sorted.length - 1].rect;
|
|
906
|
+
const n = sorted.length;
|
|
907
|
+
const set = (item, value) => {
|
|
908
|
+
if (item.locked) return;
|
|
909
|
+
const current = targets.get(item.node.id) ?? { x: item.rect.x, y: item.rect.y };
|
|
910
|
+
targets.set(item.node.id, horizontal ? { ...current, x: value } : { ...current, y: value });
|
|
911
|
+
};
|
|
912
|
+
if (mode === "centres") {
|
|
913
|
+
const c0 = start(first) + size(first) / 2;
|
|
914
|
+
const c1 = start(last) + size(last) / 2;
|
|
915
|
+
const step = (c1 - c0) / (n - 1);
|
|
916
|
+
sorted.forEach((item, i) => set(item, c0 + step * i - size(item.rect) / 2));
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
const span = start(last) + size(last) - start(first);
|
|
920
|
+
const total = sorted.reduce((sum, item) => sum + size(item.rect), 0);
|
|
921
|
+
const gap = gapOverride ?? (span - total) / (n - 1);
|
|
922
|
+
let cursor = start(first);
|
|
923
|
+
for (const item of sorted) {
|
|
924
|
+
set(item, cursor);
|
|
925
|
+
cursor += size(item.rect) + gap;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function distributeNodes(nodes, ids, axis, mode = "gaps", options = {}) {
|
|
929
|
+
const { lookup, items } = collect(nodes, ids, options);
|
|
930
|
+
const minimum = options.gap !== void 0 && mode === "gaps" ? 2 : 3;
|
|
931
|
+
if (items.length < minimum) return [];
|
|
932
|
+
const targets = /* @__PURE__ */ new Map();
|
|
933
|
+
if (axis !== "vertical") distributeAxis(items, true, mode, options.gap, targets);
|
|
934
|
+
if (axis !== "horizontal") distributeAxis(items, false, mode, options.gap, targets);
|
|
935
|
+
return toPositionUpdates(lookup, targets);
|
|
936
|
+
}
|
|
937
|
+
function matchSize(nodes, ids, dimension, options = {}) {
|
|
938
|
+
const { items } = collect(nodes, ids, options);
|
|
939
|
+
if (items.length < 2) return [];
|
|
940
|
+
const reference = options.reference ?? "largest";
|
|
941
|
+
const pick = (dim) => {
|
|
942
|
+
if (typeof reference === "object") {
|
|
943
|
+
const item = items.find((i) => i.node.id === reference.id);
|
|
944
|
+
if (item) return item.rect[dim];
|
|
945
|
+
}
|
|
946
|
+
if (reference === "first") return items[0].rect[dim];
|
|
947
|
+
const values = items.map((i) => i.rect[dim]);
|
|
948
|
+
return reference === "smallest" ? Math.min(...values) : Math.max(...values);
|
|
949
|
+
};
|
|
950
|
+
const width = dimension !== "height" ? pick("width") : void 0;
|
|
951
|
+
const height = dimension !== "width" ? pick("height") : void 0;
|
|
952
|
+
const updates = [];
|
|
953
|
+
for (const { node, rect, locked } of items) {
|
|
954
|
+
if (locked) continue;
|
|
955
|
+
const update = { id: node.id };
|
|
956
|
+
if (width !== void 0 && width !== rect.width) update.width = width;
|
|
957
|
+
if (height !== void 0 && height !== rect.height) update.height = height;
|
|
958
|
+
if (update.width === void 0 && update.height === void 0) continue;
|
|
959
|
+
if (options.keepCentre) {
|
|
960
|
+
update.position = {
|
|
961
|
+
x: node.position.x - ((update.width ?? rect.width) - rect.width) / 2,
|
|
962
|
+
y: node.position.y - ((update.height ?? rect.height) - rect.height) / 2
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
updates.push(update);
|
|
966
|
+
}
|
|
967
|
+
return updates;
|
|
968
|
+
}
|
|
969
|
+
function reorderNodes(nodes, ids, operation, options = {}) {
|
|
970
|
+
const lookup = createNodeLookup(nodes);
|
|
971
|
+
const targetIds = new Set(
|
|
972
|
+
[...ids].filter((id) => lookup.has(id) && !isLocked(lookup.get(id)))
|
|
973
|
+
);
|
|
974
|
+
if (targetIds.size === 0) return [];
|
|
975
|
+
const groups = /* @__PURE__ */ new Map();
|
|
976
|
+
for (const node of nodes) {
|
|
977
|
+
const key = node.parentId ?? "";
|
|
978
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
979
|
+
groups.get(key).push(node);
|
|
980
|
+
}
|
|
981
|
+
const updates = [];
|
|
982
|
+
for (const siblings of groups.values()) {
|
|
983
|
+
if (!siblings.some((n) => targetIds.has(n.id))) continue;
|
|
984
|
+
const order = siblings.map((node, index) => ({ node, index, z: typeof node.zIndex === "number" ? node.zIndex : 0 })).sort((a, b) => a.z - b.z || a.index - b.index).map((entry) => entry.node);
|
|
985
|
+
const isTarget = (n) => targetIds.has(n.id);
|
|
986
|
+
let next;
|
|
987
|
+
if (operation === "bringToFront") {
|
|
988
|
+
next = [...order.filter((n) => !isTarget(n)), ...order.filter(isTarget)];
|
|
989
|
+
} else if (operation === "sendToBack") {
|
|
990
|
+
next = [...order.filter(isTarget), ...order.filter((n) => !isTarget(n))];
|
|
991
|
+
} else {
|
|
992
|
+
next = [...order];
|
|
993
|
+
const forward = operation === "bringForward";
|
|
994
|
+
const rects = new Map(next.map((n) => [n.id, getNodeRect(n, lookup, options)]));
|
|
995
|
+
const intersects = (a, b) => a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
|
|
996
|
+
const indices = next.map((_, i) => i);
|
|
997
|
+
if (forward) indices.reverse();
|
|
998
|
+
for (const i of indices) {
|
|
999
|
+
const node = next[i];
|
|
1000
|
+
if (!isTarget(node)) continue;
|
|
1001
|
+
const step = forward ? 1 : -1;
|
|
1002
|
+
let j = i + step;
|
|
1003
|
+
let swapWith = -1;
|
|
1004
|
+
let firstNonTarget = -1;
|
|
1005
|
+
while (j >= 0 && j < next.length) {
|
|
1006
|
+
if (!isTarget(next[j])) {
|
|
1007
|
+
if (firstNonTarget === -1) firstNonTarget = j;
|
|
1008
|
+
if (intersects(rects.get(node.id), rects.get(next[j].id))) {
|
|
1009
|
+
swapWith = j;
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
j += step;
|
|
1014
|
+
}
|
|
1015
|
+
if (swapWith === -1) swapWith = firstNonTarget;
|
|
1016
|
+
if (swapWith === -1) continue;
|
|
1017
|
+
next.splice(i, 1);
|
|
1018
|
+
next.splice(swapWith, 0, node);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
if (next.every((node, i) => node === order[i])) continue;
|
|
1022
|
+
next.forEach((node, rank) => {
|
|
1023
|
+
const current = typeof node.zIndex === "number" ? node.zIndex : 0;
|
|
1024
|
+
if (current !== rank) updates.push({ id: node.id, zIndex: rank });
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
return updates;
|
|
1028
|
+
}
|
|
1029
|
+
function applyNodeUpdates(nodes, updates) {
|
|
1030
|
+
if (updates.length === 0) return nodes;
|
|
1031
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1032
|
+
for (const update of updates) {
|
|
1033
|
+
byId.set(update.id, { ...byId.get(update.id), ...update });
|
|
1034
|
+
}
|
|
1035
|
+
return nodes.map((node) => {
|
|
1036
|
+
const update = byId.get(node.id);
|
|
1037
|
+
if (!update) return node;
|
|
1038
|
+
const next = { ...node };
|
|
1039
|
+
if (update.position) next.position = { ...update.position };
|
|
1040
|
+
if (update.zIndex !== void 0) next.zIndex = update.zIndex;
|
|
1041
|
+
if (update.width !== void 0 || update.height !== void 0) {
|
|
1042
|
+
if (update.width !== void 0) next.width = update.width;
|
|
1043
|
+
if (update.height !== void 0) next.height = update.height;
|
|
1044
|
+
if (node.style) next.style = withStyleSize(node.style, update);
|
|
1045
|
+
if (node.measured) {
|
|
1046
|
+
next.measured = {
|
|
1047
|
+
...node.measured,
|
|
1048
|
+
...update.width !== void 0 ? { width: update.width } : {},
|
|
1049
|
+
...update.height !== void 0 ? { height: update.height } : {}
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return next;
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
function invertNodeUpdates(nodes, updates) {
|
|
1057
|
+
const lookup = createNodeLookup(nodes);
|
|
1058
|
+
const inverse = [];
|
|
1059
|
+
for (const update of updates) {
|
|
1060
|
+
const node = lookup.get(update.id);
|
|
1061
|
+
if (!node) continue;
|
|
1062
|
+
const back = { id: node.id };
|
|
1063
|
+
if (update.position) back.position = { ...node.position };
|
|
1064
|
+
if (update.zIndex !== void 0) back.zIndex = typeof node.zIndex === "number" ? node.zIndex : 0;
|
|
1065
|
+
if (update.width !== void 0 || update.height !== void 0) {
|
|
1066
|
+
const size = getNodeSize(node);
|
|
1067
|
+
if (update.width !== void 0) back.width = size.width;
|
|
1068
|
+
if (update.height !== void 0) back.height = size.height;
|
|
1069
|
+
}
|
|
1070
|
+
inverse.push(back);
|
|
1071
|
+
}
|
|
1072
|
+
return inverse;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// src/canvas/guides.ts
|
|
1076
|
+
function buildAxis(entries) {
|
|
1077
|
+
entries.sort((a, b) => a.value - b.value);
|
|
1078
|
+
return {
|
|
1079
|
+
values: Float64Array.from(entries.map((e) => e.value)),
|
|
1080
|
+
rects: entries.map((e) => e.rect)
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
function lowerBound(values, v) {
|
|
1084
|
+
let lo = 0;
|
|
1085
|
+
let hi = values.length;
|
|
1086
|
+
while (lo < hi) {
|
|
1087
|
+
const mid = lo + hi >>> 1;
|
|
1088
|
+
if (values[mid] < v) lo = mid + 1;
|
|
1089
|
+
else hi = mid;
|
|
1090
|
+
}
|
|
1091
|
+
return lo;
|
|
1092
|
+
}
|
|
1093
|
+
function createGuideIndex(rects) {
|
|
1094
|
+
const edgeX = [];
|
|
1095
|
+
const centreX = [];
|
|
1096
|
+
const edgeY = [];
|
|
1097
|
+
const centreY = [];
|
|
1098
|
+
for (const rect of rects) {
|
|
1099
|
+
edgeX.push({ value: rect.x, rect }, { value: rect.x + rect.width, rect });
|
|
1100
|
+
centreX.push({ value: rect.x + rect.width / 2, rect });
|
|
1101
|
+
edgeY.push({ value: rect.y, rect }, { value: rect.y + rect.height, rect });
|
|
1102
|
+
centreY.push({ value: rect.y + rect.height / 2, rect });
|
|
1103
|
+
}
|
|
1104
|
+
return {
|
|
1105
|
+
rects,
|
|
1106
|
+
edgeX: buildAxis(edgeX),
|
|
1107
|
+
centreX: buildAxis(centreX),
|
|
1108
|
+
edgeY: buildAxis(edgeY),
|
|
1109
|
+
centreY: buildAxis(centreY)
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
function nearest(axis, v, tolerance) {
|
|
1113
|
+
const { values } = axis;
|
|
1114
|
+
let best = null;
|
|
1115
|
+
for (let i = lowerBound(values, v - tolerance); i < values.length && values[i] <= v + tolerance; i++) {
|
|
1116
|
+
const delta = values[i] - v;
|
|
1117
|
+
if (best === null || Math.abs(delta) < Math.abs(best)) best = delta;
|
|
1118
|
+
}
|
|
1119
|
+
return best;
|
|
1120
|
+
}
|
|
1121
|
+
function matches(axis, v, eps) {
|
|
1122
|
+
const { values, rects } = axis;
|
|
1123
|
+
const out = [];
|
|
1124
|
+
for (let i = lowerBound(values, v - eps); i < values.length && values[i] <= v + eps; i++) {
|
|
1125
|
+
out.push(rects[i]);
|
|
1126
|
+
}
|
|
1127
|
+
return out;
|
|
1128
|
+
}
|
|
1129
|
+
function spacingCandidate(rects, moving, horizontal, tolerance) {
|
|
1130
|
+
const start = (r) => horizontal ? r.x : r.y;
|
|
1131
|
+
const end = (r) => horizontal ? r.x + r.width : r.y + r.height;
|
|
1132
|
+
const crossStart = (r) => horizontal ? r.y : r.x;
|
|
1133
|
+
const crossEnd = (r) => horizontal ? r.y + r.height : r.x + r.width;
|
|
1134
|
+
const size = end(moving) - start(moving);
|
|
1135
|
+
const inLane = (r, lane2) => crossStart(r) < crossEnd(lane2) && crossEnd(r) > crossStart(lane2);
|
|
1136
|
+
const lane = rects.filter((r) => inLane(r, moving));
|
|
1137
|
+
if (lane.length === 0) return null;
|
|
1138
|
+
let before = null;
|
|
1139
|
+
let after = null;
|
|
1140
|
+
for (const r of lane) {
|
|
1141
|
+
if (end(r) <= start(moving) + tolerance && (!before || end(r) > end(before))) before = r;
|
|
1142
|
+
if (start(r) >= end(moving) - tolerance && (!after || start(r) < start(after))) after = r;
|
|
1143
|
+
}
|
|
1144
|
+
const neighbourOf = (r, forward) => {
|
|
1145
|
+
let best2 = null;
|
|
1146
|
+
for (const other of lane) {
|
|
1147
|
+
if (other === r || !inLane(other, r)) continue;
|
|
1148
|
+
if (forward ? start(other) >= end(r) : end(other) <= start(r)) {
|
|
1149
|
+
if (!best2 || (forward ? start(other) < start(best2) : end(other) > end(best2))) best2 = other;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
return best2;
|
|
1153
|
+
};
|
|
1154
|
+
const crossMid = (a, b) => (Math.max(crossStart(a), crossStart(b)) + Math.min(crossEnd(a), crossEnd(b))) / 2;
|
|
1155
|
+
const segment = (from, to, cross) => horizontal ? { orientation: "horizontal", kind: "spacing", x1: from, y1: cross, x2: to, y2: cross } : { orientation: "vertical", kind: "spacing", x1: cross, y1: from, x2: cross, y2: to };
|
|
1156
|
+
const candidates = [];
|
|
1157
|
+
if (before && after) {
|
|
1158
|
+
const b = before;
|
|
1159
|
+
const a = after;
|
|
1160
|
+
const pos = (end(b) + start(a) - size) / 2;
|
|
1161
|
+
if (pos >= end(b)) {
|
|
1162
|
+
candidates.push({
|
|
1163
|
+
pos,
|
|
1164
|
+
guides: (p) => [
|
|
1165
|
+
segment(end(b), p, crossMid(b, moving)),
|
|
1166
|
+
segment(p + size, start(a), crossMid(a, moving))
|
|
1167
|
+
]
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
if (before) {
|
|
1172
|
+
const b = before;
|
|
1173
|
+
const prev = neighbourOf(b, false);
|
|
1174
|
+
if (prev) {
|
|
1175
|
+
const gap = start(b) - end(prev);
|
|
1176
|
+
candidates.push({
|
|
1177
|
+
pos: end(b) + gap,
|
|
1178
|
+
guides: (p) => [segment(end(prev), start(b), crossMid(prev, b)), segment(end(b), p, crossMid(b, moving))]
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
if (after) {
|
|
1183
|
+
const a = after;
|
|
1184
|
+
const next = neighbourOf(a, true);
|
|
1185
|
+
if (next) {
|
|
1186
|
+
const gap = start(next) - end(a);
|
|
1187
|
+
candidates.push({
|
|
1188
|
+
pos: start(a) - gap - size,
|
|
1189
|
+
guides: (p) => [segment(p + size, start(a), crossMid(a, moving)), segment(end(a), start(next), crossMid(a, next))]
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
let best = null;
|
|
1194
|
+
for (const c of candidates) {
|
|
1195
|
+
const delta = c.pos - start(moving);
|
|
1196
|
+
if (Math.abs(delta) <= tolerance && (!best || Math.abs(delta) < Math.abs(best.delta))) {
|
|
1197
|
+
best = { delta, guides: c.guides(c.pos) };
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return best;
|
|
1201
|
+
}
|
|
1202
|
+
function computeSnapGuides(moving, targets, options = {}) {
|
|
1203
|
+
const index = Array.isArray(targets) ? createGuideIndex(targets) : targets;
|
|
1204
|
+
const zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;
|
|
1205
|
+
const tolerance = (options.threshold ?? 6) / zoom;
|
|
1206
|
+
const useEdges = options.edges !== false;
|
|
1207
|
+
const useCentres = options.centres !== false;
|
|
1208
|
+
const useSpacing = options.spacing !== false;
|
|
1209
|
+
const eps = 0.5 / zoom;
|
|
1210
|
+
const snapAxis = (horizontal) => {
|
|
1211
|
+
const start = horizontal ? moving.x : moving.y;
|
|
1212
|
+
const size = horizontal ? moving.width : moving.height;
|
|
1213
|
+
const edgeAxis = horizontal ? index.edgeX : index.edgeY;
|
|
1214
|
+
const centreAxis = horizontal ? index.centreX : index.centreY;
|
|
1215
|
+
let delta = null;
|
|
1216
|
+
const consider = (d) => {
|
|
1217
|
+
if (d !== null && (delta === null || Math.abs(d) < Math.abs(delta))) delta = d;
|
|
1218
|
+
};
|
|
1219
|
+
if (useEdges) {
|
|
1220
|
+
consider(nearest(edgeAxis, start, tolerance));
|
|
1221
|
+
consider(nearest(edgeAxis, start + size, tolerance));
|
|
1222
|
+
}
|
|
1223
|
+
if (useCentres) consider(nearest(centreAxis, start + size / 2, tolerance));
|
|
1224
|
+
const spacing = useSpacing ? spacingCandidate(index.rects, moving, horizontal, tolerance) : null;
|
|
1225
|
+
if (spacing && (delta === null || Math.abs(spacing.delta) < Math.abs(delta) - eps)) {
|
|
1226
|
+
return { delta: spacing.delta, spacing };
|
|
1227
|
+
}
|
|
1228
|
+
return { delta, spacing: null };
|
|
1229
|
+
};
|
|
1230
|
+
const sx = snapAxis(true);
|
|
1231
|
+
const sy = snapAxis(false);
|
|
1232
|
+
const dx = sx.delta ?? 0;
|
|
1233
|
+
const dy = sy.delta ?? 0;
|
|
1234
|
+
const snapped = { x: moving.x + dx, y: moving.y + dy, width: moving.width, height: moving.height };
|
|
1235
|
+
const guides = [];
|
|
1236
|
+
const alignmentGuides = (horizontal) => {
|
|
1237
|
+
const start = horizontal ? snapped.x : snapped.y;
|
|
1238
|
+
const size = horizontal ? snapped.width : snapped.height;
|
|
1239
|
+
const lines = [];
|
|
1240
|
+
if (useEdges) {
|
|
1241
|
+
const axis = horizontal ? index.edgeX : index.edgeY;
|
|
1242
|
+
lines.push({ value: start, kind: "edge", axis }, { value: start + size, kind: "edge", axis });
|
|
1243
|
+
}
|
|
1244
|
+
if (useCentres) {
|
|
1245
|
+
lines.push({ value: start + size / 2, kind: "centre", axis: horizontal ? index.centreX : index.centreY });
|
|
1246
|
+
}
|
|
1247
|
+
for (const line of lines) {
|
|
1248
|
+
const aligned = matches(line.axis, line.value, eps);
|
|
1249
|
+
if (aligned.length === 0) continue;
|
|
1250
|
+
const span = boundsOf([snapped, ...aligned]);
|
|
1251
|
+
guides.push(
|
|
1252
|
+
horizontal ? { orientation: "vertical", kind: line.kind, x1: line.value, y1: span.y, x2: line.value, y2: span.y + span.height } : { orientation: "horizontal", kind: line.kind, x1: span.x, y1: line.value, x2: span.x + span.width, y2: line.value }
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
if (sx.delta !== null) {
|
|
1257
|
+
if (sx.spacing) guides.push(...shiftGuides(sx.spacing.guides, 0, dy));
|
|
1258
|
+
else alignmentGuides(true);
|
|
1259
|
+
}
|
|
1260
|
+
if (sy.delta !== null) {
|
|
1261
|
+
if (sy.spacing) guides.push(...shiftGuides(sy.spacing.guides, dx, 0));
|
|
1262
|
+
else alignmentGuides(false);
|
|
1263
|
+
}
|
|
1264
|
+
return {
|
|
1265
|
+
position: { x: snapped.x, y: snapped.y },
|
|
1266
|
+
offset: { x: dx, y: dy },
|
|
1267
|
+
guides,
|
|
1268
|
+
snappedX: sx.delta !== null,
|
|
1269
|
+
snappedY: sy.delta !== null
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
function shiftGuides(guides, dx, dy) {
|
|
1273
|
+
return guides.map(
|
|
1274
|
+
(g) => g.orientation === "horizontal" ? { ...g, y1: g.y1 + dy, y2: g.y2 + dy } : { ...g, x1: g.x1 + dx, x2: g.x2 + dx }
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
function collectDescendantIds(nodes, ids) {
|
|
1278
|
+
const result = new Set(ids);
|
|
1279
|
+
let grew = true;
|
|
1280
|
+
while (grew) {
|
|
1281
|
+
grew = false;
|
|
1282
|
+
for (const node of nodes) {
|
|
1283
|
+
if (node.parentId && result.has(node.parentId) && !result.has(node.id)) {
|
|
1284
|
+
result.add(node.id);
|
|
1285
|
+
grew = true;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
return result;
|
|
1290
|
+
}
|
|
1291
|
+
function createNodeGuideIndex(nodes, draggingIds, options = {}) {
|
|
1292
|
+
const exclude = collectDescendantIds(nodes, draggingIds);
|
|
1293
|
+
const lookup = createNodeLookup(nodes);
|
|
1294
|
+
const rects = [];
|
|
1295
|
+
for (const node of nodes) {
|
|
1296
|
+
if (exclude.has(node.id) || node.hidden === true) continue;
|
|
1297
|
+
if (options.filter && !options.filter(node)) continue;
|
|
1298
|
+
rects.push(getNodeRect(node, lookup, options));
|
|
1299
|
+
}
|
|
1300
|
+
return createGuideIndex(rects);
|
|
1301
|
+
}
|
|
1302
|
+
function snapNodeDrag(nodes, draggingIds, options = {}) {
|
|
1303
|
+
const ids = new Set(draggingIds);
|
|
1304
|
+
const lookup = createNodeLookup(nodes);
|
|
1305
|
+
const dragged = [...ids].map((id) => lookup.get(id)).filter((n) => !!n);
|
|
1306
|
+
const empty = {
|
|
1307
|
+
position: { x: 0, y: 0 },
|
|
1308
|
+
offset: { x: 0, y: 0 },
|
|
1309
|
+
guides: [],
|
|
1310
|
+
snappedX: false,
|
|
1311
|
+
snappedY: false,
|
|
1312
|
+
updates: []
|
|
1313
|
+
};
|
|
1314
|
+
if (dragged.length === 0) return empty;
|
|
1315
|
+
const bounds = boundsOf(dragged.map((n) => getNodeRect(n, lookup, options)));
|
|
1316
|
+
const index = options.index ?? createNodeGuideIndex(nodes, ids, options);
|
|
1317
|
+
const result = computeSnapGuides(bounds, index, options);
|
|
1318
|
+
const { x: dx, y: dy } = result.offset;
|
|
1319
|
+
const updates = [];
|
|
1320
|
+
if (dx !== 0 || dy !== 0) {
|
|
1321
|
+
for (const node of dragged) {
|
|
1322
|
+
if (node.parentId && ids.has(node.parentId)) continue;
|
|
1323
|
+
updates.push({ id: node.id, position: { x: node.position.x + dx, y: node.position.y + dy } });
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return { ...result, updates };
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// src/canvas/connectors.ts
|
|
1330
|
+
var CONNECTION_LINE_TYPES = [
|
|
1331
|
+
"default",
|
|
1332
|
+
"straight",
|
|
1333
|
+
"step",
|
|
1334
|
+
"smoothstep",
|
|
1335
|
+
"simplebezier"
|
|
1336
|
+
];
|
|
1337
|
+
function positionToSide(position) {
|
|
1338
|
+
switch (position) {
|
|
1339
|
+
case "top":
|
|
1340
|
+
return "above";
|
|
1341
|
+
case "bottom":
|
|
1342
|
+
return "below";
|
|
1343
|
+
default:
|
|
1344
|
+
return position;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
function sideToPosition(side) {
|
|
1348
|
+
switch (side) {
|
|
1349
|
+
case "above":
|
|
1350
|
+
return "top";
|
|
1351
|
+
case "below":
|
|
1352
|
+
return "bottom";
|
|
1353
|
+
default:
|
|
1354
|
+
return side;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
function oppositePosition(position) {
|
|
1358
|
+
switch (position) {
|
|
1359
|
+
case "top":
|
|
1360
|
+
return "bottom";
|
|
1361
|
+
case "bottom":
|
|
1362
|
+
return "top";
|
|
1363
|
+
case "left":
|
|
1364
|
+
return "right";
|
|
1365
|
+
default:
|
|
1366
|
+
return "left";
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
function handlePoint(rect, position) {
|
|
1370
|
+
switch (position) {
|
|
1371
|
+
case "top":
|
|
1372
|
+
return { x: rect.x + rect.width / 2, y: rect.y };
|
|
1373
|
+
case "bottom":
|
|
1374
|
+
return { x: rect.x + rect.width / 2, y: rect.y + rect.height };
|
|
1375
|
+
case "left":
|
|
1376
|
+
return { x: rect.x, y: rect.y + rect.height / 2 };
|
|
1377
|
+
default:
|
|
1378
|
+
return { x: rect.x + rect.width, y: rect.y + rect.height / 2 };
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
function findConnectorSnap(pointer, targets, options = {}) {
|
|
1382
|
+
const zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;
|
|
1383
|
+
const radius = (options.radius ?? 24) / zoom;
|
|
1384
|
+
const exclude = new Set(options.excludeIds ?? []);
|
|
1385
|
+
const all = ["top", "right", "bottom", "left"];
|
|
1386
|
+
let best = null;
|
|
1387
|
+
for (const target of targets) {
|
|
1388
|
+
if (exclude.has(target.id)) continue;
|
|
1389
|
+
const { rect } = target;
|
|
1390
|
+
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
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
const inside = pointer.x >= rect.x && pointer.x <= rect.x + rect.width && pointer.y >= rect.y && pointer.y <= rect.y + rect.height;
|
|
1394
|
+
for (const position of target.positions ?? all) {
|
|
1395
|
+
const point = options.mode === "edge" ? closestPointOnSide(rect, position, pointer) : handlePoint(rect, position);
|
|
1396
|
+
const sidePoint = closestPointOnSide(rect, position, pointer);
|
|
1397
|
+
const rank = Math.hypot(pointer.x - sidePoint.x, pointer.y - sidePoint.y);
|
|
1398
|
+
const distance = Math.hypot(pointer.x - point.x, pointer.y - point.y);
|
|
1399
|
+
if (!inside && rank > radius) continue;
|
|
1400
|
+
const better = !best || inside && !best.inside || inside === best.inside && rank < best.rank;
|
|
1401
|
+
if (better) {
|
|
1402
|
+
best = { nodeId: target.id, position, side: positionToSide(position), point, distance, inside, rank };
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
if (!best) return null;
|
|
1407
|
+
const { inside: _inside, rank: _rank, ...snap } = best;
|
|
1408
|
+
return snap;
|
|
1409
|
+
}
|
|
1410
|
+
function closestPointOnSide(rect, position, p) {
|
|
1411
|
+
const clampX = Math.min(rect.x + rect.width, Math.max(rect.x, p.x));
|
|
1412
|
+
const clampY = Math.min(rect.y + rect.height, Math.max(rect.y, p.y));
|
|
1413
|
+
switch (position) {
|
|
1414
|
+
case "top":
|
|
1415
|
+
return { x: clampX, y: rect.y };
|
|
1416
|
+
case "bottom":
|
|
1417
|
+
return { x: clampX, y: rect.y + rect.height };
|
|
1418
|
+
case "left":
|
|
1419
|
+
return { x: rect.x, y: clampY };
|
|
1420
|
+
default:
|
|
1421
|
+
return { x: rect.x + rect.width, y: clampY };
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
function nearestHandlePositions(source, target) {
|
|
1425
|
+
const a = centre(source);
|
|
1426
|
+
const b = centre(target);
|
|
1427
|
+
const dx = b.x - a.x;
|
|
1428
|
+
const dy = b.y - a.y;
|
|
1429
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
1430
|
+
return dx >= 0 ? { source: "right", target: "left" } : { source: "left", target: "right" };
|
|
1431
|
+
}
|
|
1432
|
+
return dy >= 0 ? { source: "bottom", target: "top" } : { source: "top", target: "bottom" };
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// src/canvas/scenes.ts
|
|
1436
|
+
var fallbackCounter = 0;
|
|
1437
|
+
function defaultId() {
|
|
1438
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
1439
|
+
return crypto.randomUUID();
|
|
1440
|
+
}
|
|
1441
|
+
fallbackCounter += 1;
|
|
1442
|
+
return `scene-${Date.now().toString(36)}-${fallbackCounter}`;
|
|
1443
|
+
}
|
|
1444
|
+
function isFiniteNumber(value) {
|
|
1445
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
1446
|
+
}
|
|
1447
|
+
function isValidCanvasScene(value) {
|
|
1448
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1449
|
+
const scene = value;
|
|
1450
|
+
const vp = scene.viewport;
|
|
1451
|
+
return typeof scene.id === "string" && typeof scene.name === "string" && typeof vp === "object" && vp !== null && isFiniteNumber(vp.x) && isFiniteNumber(vp.y) && isFiniteNumber(vp.zoom) && vp.zoom > 0 && (scene.nodeIds === void 0 || Array.isArray(scene.nodeIds) && scene.nodeIds.every((id) => typeof id === "string"));
|
|
1452
|
+
}
|
|
1453
|
+
function cloneScene(scene) {
|
|
1454
|
+
const copy = {
|
|
1455
|
+
id: scene.id,
|
|
1456
|
+
name: scene.name,
|
|
1457
|
+
viewport: { x: scene.viewport.x, y: scene.viewport.y, zoom: scene.viewport.zoom }
|
|
1458
|
+
};
|
|
1459
|
+
if (scene.nodeIds) copy.nodeIds = [...scene.nodeIds];
|
|
1460
|
+
return copy;
|
|
1461
|
+
}
|
|
1462
|
+
function parseCanvasScenes(input) {
|
|
1463
|
+
let value = input;
|
|
1464
|
+
if (typeof input === "string") {
|
|
1465
|
+
try {
|
|
1466
|
+
value = JSON.parse(input);
|
|
1467
|
+
} catch {
|
|
1468
|
+
return [];
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
const list = Array.isArray(value) ? value : typeof value === "object" && value !== null && Array.isArray(value.scenes) ? value.scenes : [];
|
|
1472
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1473
|
+
const scenes = [];
|
|
1474
|
+
for (const entry of list) {
|
|
1475
|
+
if (!isValidCanvasScene(entry) || seen.has(entry.id)) continue;
|
|
1476
|
+
seen.add(entry.id);
|
|
1477
|
+
scenes.push(cloneScene(entry));
|
|
1478
|
+
}
|
|
1479
|
+
return scenes;
|
|
1480
|
+
}
|
|
1481
|
+
var CanvasScenesStore = class {
|
|
1482
|
+
scenes;
|
|
1483
|
+
currentId = null;
|
|
1484
|
+
cached;
|
|
1485
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1486
|
+
createId;
|
|
1487
|
+
onChange;
|
|
1488
|
+
wrap;
|
|
1489
|
+
constructor(options = {}) {
|
|
1490
|
+
this.scenes = parseCanvasScenes(options.initialScenes ?? []);
|
|
1491
|
+
this.createId = options.createId ?? defaultId;
|
|
1492
|
+
this.wrap = options.wrap ?? false;
|
|
1493
|
+
this.onChange = options.onChange;
|
|
1494
|
+
this.cached = this.snapshot();
|
|
1495
|
+
}
|
|
1496
|
+
snapshot() {
|
|
1497
|
+
const currentIndex = this.currentId ? this.scenes.findIndex((s) => s.id === this.currentId) : -1;
|
|
1498
|
+
return {
|
|
1499
|
+
scenes: this.scenes,
|
|
1500
|
+
currentId: currentIndex === -1 ? null : this.currentId,
|
|
1501
|
+
currentIndex
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
commit(scenes = this.scenes) {
|
|
1505
|
+
this.scenes = scenes;
|
|
1506
|
+
if (this.currentId && !scenes.some((s) => s.id === this.currentId)) this.currentId = null;
|
|
1507
|
+
this.cached = this.snapshot();
|
|
1508
|
+
this.onChange?.(this.cached);
|
|
1509
|
+
for (const listener of this.listeners) listener(this.cached);
|
|
1510
|
+
}
|
|
1511
|
+
getState() {
|
|
1512
|
+
return this.cached;
|
|
1513
|
+
}
|
|
1514
|
+
getScene(id) {
|
|
1515
|
+
return this.scenes.find((s) => s.id === id);
|
|
1516
|
+
}
|
|
1517
|
+
subscribe(listener) {
|
|
1518
|
+
this.listeners.add(listener);
|
|
1519
|
+
return () => {
|
|
1520
|
+
this.listeners.delete(listener);
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
/** First unused "Scene N" name. */
|
|
1524
|
+
nextDefaultName() {
|
|
1525
|
+
const names = new Set(this.scenes.map((s) => s.name));
|
|
1526
|
+
let n = this.scenes.length + 1;
|
|
1527
|
+
while (names.has(`Scene ${n}`)) n += 1;
|
|
1528
|
+
return `Scene ${n}`;
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Adds a scene (appended, or inserted at `index`) and makes it current.
|
|
1532
|
+
*/
|
|
1533
|
+
add(input, index) {
|
|
1534
|
+
const scene = cloneScene({
|
|
1535
|
+
id: input.id && !this.getScene(input.id) ? input.id : this.createId(),
|
|
1536
|
+
name: input.name?.trim() || this.nextDefaultName(),
|
|
1537
|
+
viewport: input.viewport,
|
|
1538
|
+
nodeIds: input.nodeIds
|
|
1539
|
+
});
|
|
1540
|
+
if (!isValidCanvasScene(scene)) throw new Error("[CanvasScenesStore] Invalid scene viewport.");
|
|
1541
|
+
const scenes = [...this.scenes];
|
|
1542
|
+
const at = index === void 0 ? scenes.length : Math.max(0, Math.min(scenes.length, index));
|
|
1543
|
+
scenes.splice(at, 0, scene);
|
|
1544
|
+
this.currentId = scene.id;
|
|
1545
|
+
this.commit(scenes);
|
|
1546
|
+
return scene;
|
|
1547
|
+
}
|
|
1548
|
+
/** Patches a scene's name, viewport or node ids. Returns the updated scene. */
|
|
1549
|
+
update(id, patch) {
|
|
1550
|
+
const index = this.scenes.findIndex((s) => s.id === id);
|
|
1551
|
+
if (index === -1) return void 0;
|
|
1552
|
+
const current = this.scenes[index];
|
|
1553
|
+
const next = cloneScene({
|
|
1554
|
+
...current,
|
|
1555
|
+
...patch,
|
|
1556
|
+
name: patch.name !== void 0 ? patch.name.trim() || current.name : current.name,
|
|
1557
|
+
viewport: patch.viewport ?? current.viewport,
|
|
1558
|
+
id
|
|
1559
|
+
});
|
|
1560
|
+
if (!isValidCanvasScene(next)) throw new Error("[CanvasScenesStore] Invalid scene viewport.");
|
|
1561
|
+
const scenes = [...this.scenes];
|
|
1562
|
+
scenes[index] = next;
|
|
1563
|
+
this.commit(scenes);
|
|
1564
|
+
return next;
|
|
1565
|
+
}
|
|
1566
|
+
rename(id, name) {
|
|
1567
|
+
return this.update(id, { name });
|
|
1568
|
+
}
|
|
1569
|
+
/** Moves a scene to `toIndex` (clamped). */
|
|
1570
|
+
move(id, toIndex) {
|
|
1571
|
+
const from = this.scenes.findIndex((s) => s.id === id);
|
|
1572
|
+
if (from === -1) return;
|
|
1573
|
+
const to = Math.max(0, Math.min(this.scenes.length - 1, Math.trunc(toIndex)));
|
|
1574
|
+
if (from === to) return;
|
|
1575
|
+
const scenes = [...this.scenes];
|
|
1576
|
+
const [scene] = scenes.splice(from, 1);
|
|
1577
|
+
scenes.splice(to, 0, scene);
|
|
1578
|
+
this.commit(scenes);
|
|
1579
|
+
}
|
|
1580
|
+
/** Reorders scenes to match `ids`; unknown ids are ignored and missing scenes keep their relative order at the end. */
|
|
1581
|
+
reorder(ids) {
|
|
1582
|
+
const byId = new Map(this.scenes.map((s) => [s.id, s]));
|
|
1583
|
+
const ordered = [];
|
|
1584
|
+
for (const id of ids) {
|
|
1585
|
+
const scene = byId.get(id);
|
|
1586
|
+
if (scene) {
|
|
1587
|
+
ordered.push(scene);
|
|
1588
|
+
byId.delete(id);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
this.commit([...ordered, ...this.scenes.filter((s) => byId.has(s.id))]);
|
|
1592
|
+
}
|
|
1593
|
+
remove(id) {
|
|
1594
|
+
if (!this.getScene(id)) return;
|
|
1595
|
+
this.commit(this.scenes.filter((s) => s.id !== id));
|
|
1596
|
+
}
|
|
1597
|
+
clear() {
|
|
1598
|
+
this.currentId = null;
|
|
1599
|
+
this.commit([]);
|
|
1600
|
+
}
|
|
1601
|
+
/** Marks a scene as current and returns it. */
|
|
1602
|
+
goTo(id) {
|
|
1603
|
+
const scene = this.getScene(id);
|
|
1604
|
+
if (!scene) return void 0;
|
|
1605
|
+
if (this.currentId !== id) {
|
|
1606
|
+
this.currentId = id;
|
|
1607
|
+
this.commit();
|
|
1608
|
+
}
|
|
1609
|
+
return scene;
|
|
1610
|
+
}
|
|
1611
|
+
step(direction) {
|
|
1612
|
+
const count = this.scenes.length;
|
|
1613
|
+
if (count === 0) return void 0;
|
|
1614
|
+
const index = this.cached.currentIndex;
|
|
1615
|
+
let next;
|
|
1616
|
+
if (index === -1) next = direction === 1 ? 0 : count - 1;
|
|
1617
|
+
else next = index + direction;
|
|
1618
|
+
if (next < 0 || next >= count) {
|
|
1619
|
+
if (!this.wrap) return void 0;
|
|
1620
|
+
next = (next + count) % count;
|
|
1621
|
+
}
|
|
1622
|
+
return this.goTo(this.scenes[next].id);
|
|
1623
|
+
}
|
|
1624
|
+
/** Shows the next scene. Returns `undefined` at the end unless `wrap` is on. */
|
|
1625
|
+
next() {
|
|
1626
|
+
return this.step(1);
|
|
1627
|
+
}
|
|
1628
|
+
/** Shows the previous scene. Returns `undefined` at the start unless `wrap` is on. */
|
|
1629
|
+
previous() {
|
|
1630
|
+
return this.step(-1);
|
|
1631
|
+
}
|
|
1632
|
+
toJSON() {
|
|
1633
|
+
return { version: 1, scenes: this.scenes.map(cloneScene) };
|
|
1634
|
+
}
|
|
1635
|
+
serialize() {
|
|
1636
|
+
return JSON.stringify(this.toJSON());
|
|
1637
|
+
}
|
|
1638
|
+
/** Replaces all scenes from serialized data (see `parseCanvasScenes`). */
|
|
1639
|
+
load(input) {
|
|
1640
|
+
this.currentId = null;
|
|
1641
|
+
this.commit(parseCanvasScenes(input));
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
function createCanvasScenesStore(options) {
|
|
1645
|
+
return new CanvasScenesStore(options);
|
|
1646
|
+
}
|
|
1647
|
+
function fitViewportToRect(rect, size, options = {}) {
|
|
1648
|
+
const { padding = 0.1, minZoom = 0.1, maxZoom = 2 } = options;
|
|
1649
|
+
const paddedWidth = Math.max(rect.width * (1 + padding * 2), 1);
|
|
1650
|
+
const paddedHeight = Math.max(rect.height * (1 + padding * 2), 1);
|
|
1651
|
+
const raw = Math.min(size.width / paddedWidth, size.height / paddedHeight);
|
|
1652
|
+
const zoom = Math.min(maxZoom, Math.max(minZoom, raw));
|
|
1653
|
+
const cx = rect.x + rect.width / 2;
|
|
1654
|
+
const cy = rect.y + rect.height / 2;
|
|
1655
|
+
return { x: size.width / 2 - cx * zoom, y: size.height / 2 - cy * zoom, zoom };
|
|
1656
|
+
}
|
|
1657
|
+
function fitViewportToNodes(nodes, size, ids, options = {}) {
|
|
1658
|
+
const lookup = createNodeLookup(nodes);
|
|
1659
|
+
const wanted = ids ? new Set(ids) : null;
|
|
1660
|
+
const rects = nodes.filter((n) => wanted ? wanted.has(n.id) : true).map((n) => getNodeRect(n, lookup, options));
|
|
1661
|
+
const bounds = boundsOf(rects);
|
|
1662
|
+
return bounds ? fitViewportToRect(bounds, size, options) : null;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
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
|
+
// src/canvas/arrangeController.ts
|
|
1709
|
+
function createCanvasArrangeController(options) {
|
|
1710
|
+
let guides = [];
|
|
1711
|
+
let dragIndex = null;
|
|
1712
|
+
let dragKey = "";
|
|
1713
|
+
const selectedIds = (ids) => ids ? [...ids] : options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);
|
|
1714
|
+
const setGuides = (next) => {
|
|
1715
|
+
if (next.length === 0 && guides.length === 0) return;
|
|
1716
|
+
guides = next;
|
|
1717
|
+
options.onGuidesChange?.(guides);
|
|
1718
|
+
};
|
|
1719
|
+
function apply(name, updates) {
|
|
1720
|
+
if (updates.length === 0) return updates;
|
|
1721
|
+
const inverse = invertNodeUpdates(options.getNodes(), updates);
|
|
1722
|
+
const run = (u) => options.setNodes(applyNodeUpdates(options.getNodes(), u));
|
|
1723
|
+
if (options.history) {
|
|
1724
|
+
options.history.execute({ name, execute: () => run(updates), undo: () => run(inverse) });
|
|
1725
|
+
} else {
|
|
1726
|
+
run(updates);
|
|
1727
|
+
}
|
|
1728
|
+
return updates;
|
|
1729
|
+
}
|
|
1730
|
+
function setLocked(name, ids, locked) {
|
|
1731
|
+
const nodes = options.getNodes();
|
|
1732
|
+
const set = new Set(ids);
|
|
1733
|
+
const changed = nodes.filter((n) => set.has(n.id) && isLocked(n) !== locked).map((n) => n.id);
|
|
1734
|
+
if (changed.length === 0) return changed;
|
|
1735
|
+
const lock = () => options.setNodes(lockNodes(options.getNodes(), changed));
|
|
1736
|
+
const unlock = () => options.setNodes(unlockNodes(options.getNodes(), changed));
|
|
1737
|
+
if (options.history) {
|
|
1738
|
+
options.history.execute({ name, execute: locked ? lock : unlock, undo: locked ? unlock : lock });
|
|
1739
|
+
} else if (locked) lock();
|
|
1740
|
+
else unlock();
|
|
1741
|
+
return changed;
|
|
1742
|
+
}
|
|
1743
|
+
const rect = () => options.rectOptions ?? {};
|
|
1744
|
+
const controller = {
|
|
1745
|
+
get guides() {
|
|
1746
|
+
return guides;
|
|
1747
|
+
},
|
|
1748
|
+
apply,
|
|
1749
|
+
align(edge, ids, alignOptions = {}) {
|
|
1750
|
+
return apply(`Align ${edge}`, alignNodes(options.getNodes(), selectedIds(ids), edge, { ...rect(), ...alignOptions }));
|
|
1751
|
+
},
|
|
1752
|
+
distribute(axis, mode = "gaps", ids, distributeOptions = {}) {
|
|
1753
|
+
return apply(
|
|
1754
|
+
`Distribute ${axis}`,
|
|
1755
|
+
distributeNodes(options.getNodes(), selectedIds(ids), axis, mode, { ...rect(), ...distributeOptions })
|
|
1756
|
+
);
|
|
1757
|
+
},
|
|
1758
|
+
matchSize(dimension, ids, sizeOptions = {}) {
|
|
1759
|
+
return apply(
|
|
1760
|
+
"Make Same Size",
|
|
1761
|
+
matchSize(options.getNodes(), selectedIds(ids), dimension, { ...rect(), ...sizeOptions })
|
|
1762
|
+
);
|
|
1763
|
+
},
|
|
1764
|
+
reorder(operation, ids) {
|
|
1765
|
+
return apply(operation, reorderNodes(options.getNodes(), selectedIds(ids), operation, rect()));
|
|
1766
|
+
},
|
|
1767
|
+
bringForward: (ids) => controller.reorder("bringForward", ids),
|
|
1768
|
+
sendBackward: (ids) => controller.reorder("sendBackward", ids),
|
|
1769
|
+
bringToFront: (ids) => controller.reorder("bringToFront", ids),
|
|
1770
|
+
sendToBack: (ids) => controller.reorder("sendToBack", ids),
|
|
1771
|
+
lock: (ids) => setLocked("Lock", selectedIds(ids), true),
|
|
1772
|
+
unlock: (ids) => setLocked("Unlock", selectedIds(ids), false),
|
|
1773
|
+
toggleLock(ids) {
|
|
1774
|
+
const list = selectedIds(ids);
|
|
1775
|
+
const set = new Set(list);
|
|
1776
|
+
const anyUnlocked = options.getNodes().some((n) => set.has(n.id) && !isLocked(n));
|
|
1777
|
+
return anyUnlocked ? controller.lock(list) : controller.unlock(list);
|
|
1778
|
+
},
|
|
1779
|
+
isLocked: (id) => isLocked(options.getNodes().find((n) => n.id === id)),
|
|
1780
|
+
/**
|
|
1781
|
+
* Runs a shortcut action. Scene actions are not handled here (see the scenes store);
|
|
1782
|
+
* returns `false` for them and for unknown actions.
|
|
1783
|
+
*/
|
|
1784
|
+
runAction(action) {
|
|
1785
|
+
switch (action) {
|
|
1786
|
+
case "alignLeft":
|
|
1787
|
+
controller.align("left");
|
|
1788
|
+
return true;
|
|
1789
|
+
case "alignCenter":
|
|
1790
|
+
controller.align("center");
|
|
1791
|
+
return true;
|
|
1792
|
+
case "alignRight":
|
|
1793
|
+
controller.align("right");
|
|
1794
|
+
return true;
|
|
1795
|
+
case "alignTop":
|
|
1796
|
+
controller.align("top");
|
|
1797
|
+
return true;
|
|
1798
|
+
case "alignMiddle":
|
|
1799
|
+
controller.align("middle");
|
|
1800
|
+
return true;
|
|
1801
|
+
case "alignBottom":
|
|
1802
|
+
controller.align("bottom");
|
|
1803
|
+
return true;
|
|
1804
|
+
case "distributeHorizontal":
|
|
1805
|
+
controller.distribute("horizontal");
|
|
1806
|
+
return true;
|
|
1807
|
+
case "distributeVertical":
|
|
1808
|
+
controller.distribute("vertical");
|
|
1809
|
+
return true;
|
|
1810
|
+
case "lock":
|
|
1811
|
+
controller.lock();
|
|
1812
|
+
return true;
|
|
1813
|
+
case "unlock":
|
|
1814
|
+
controller.unlock();
|
|
1815
|
+
return true;
|
|
1816
|
+
case "bringForward":
|
|
1817
|
+
controller.bringForward();
|
|
1818
|
+
return true;
|
|
1819
|
+
case "sendBackward":
|
|
1820
|
+
controller.sendBackward();
|
|
1821
|
+
return true;
|
|
1822
|
+
case "bringToFront":
|
|
1823
|
+
controller.bringToFront();
|
|
1824
|
+
return true;
|
|
1825
|
+
case "sendToBack":
|
|
1826
|
+
controller.sendToBack();
|
|
1827
|
+
return true;
|
|
1828
|
+
default:
|
|
1829
|
+
return false;
|
|
1830
|
+
}
|
|
1831
|
+
},
|
|
1832
|
+
/** Caches the snap targets for a drag. Optional: `drag` builds them lazily. */
|
|
1833
|
+
beginDrag(ids) {
|
|
1834
|
+
const list = selectedIds(ids);
|
|
1835
|
+
dragKey = list.slice().sort().join("\0");
|
|
1836
|
+
dragIndex = createNodeGuideIndex(options.getNodes(), list, rect());
|
|
1837
|
+
},
|
|
1838
|
+
/**
|
|
1839
|
+
* Snaps the dragged nodes (as currently positioned in `getNodes()`) to the other
|
|
1840
|
+
* nodes, writes the snapped positions back through `setNodes` (not recorded in
|
|
1841
|
+
* history) and updates `guides`.
|
|
1842
|
+
*/
|
|
1843
|
+
drag(ids) {
|
|
1844
|
+
if (options.snapping === false) return null;
|
|
1845
|
+
const list = selectedIds(ids);
|
|
1846
|
+
const key = list.slice().sort().join("\0");
|
|
1847
|
+
if (!dragIndex || key !== dragKey) controller.beginDrag(list);
|
|
1848
|
+
const result = snapNodeDrag(options.getNodes(), list, {
|
|
1849
|
+
...rect(),
|
|
1850
|
+
...options.guides,
|
|
1851
|
+
zoom: options.getZoom?.() ?? 1,
|
|
1852
|
+
index: dragIndex
|
|
1853
|
+
});
|
|
1854
|
+
if (result.updates.length > 0) {
|
|
1855
|
+
options.setNodes(applyNodeUpdates(options.getNodes(), result.updates));
|
|
1856
|
+
}
|
|
1857
|
+
setGuides(result.guides);
|
|
1858
|
+
return result;
|
|
1859
|
+
},
|
|
1860
|
+
/**
|
|
1861
|
+
* React Flow style: adjusts dragging `position` changes in an `onNodesChange` batch so
|
|
1862
|
+
* the dragged nodes snap, and updates `guides`. Returns the (possibly new) changes array.
|
|
1863
|
+
* A batch without a dragging position change ends the drag and clears the guides.
|
|
1864
|
+
*/
|
|
1865
|
+
snapPositionChanges(changes) {
|
|
1866
|
+
const dragging = changes.filter((c) => c.type === "position" && c.position && c.dragging);
|
|
1867
|
+
if (dragging.length === 0) {
|
|
1868
|
+
if (changes.some((c) => c.type === "position" && c.dragging === false)) controller.endDrag();
|
|
1869
|
+
return changes;
|
|
1870
|
+
}
|
|
1871
|
+
if (options.snapping === false) return changes;
|
|
1872
|
+
const ids = dragging.map((c) => c.id);
|
|
1873
|
+
const key = ids.slice().sort().join("\0");
|
|
1874
|
+
if (!dragIndex || key !== dragKey) controller.beginDrag(ids);
|
|
1875
|
+
const proposed = new Map(dragging.map((c) => [c.id, c.position]));
|
|
1876
|
+
const nodes = options.getNodes().map((n) => proposed.has(n.id) ? { ...n, position: proposed.get(n.id) } : n);
|
|
1877
|
+
const result = snapNodeDrag(nodes, ids, {
|
|
1878
|
+
...rect(),
|
|
1879
|
+
...options.guides,
|
|
1880
|
+
zoom: options.getZoom?.() ?? 1,
|
|
1881
|
+
index: dragIndex
|
|
1882
|
+
});
|
|
1883
|
+
setGuides(result.guides);
|
|
1884
|
+
const { x: dx, y: dy } = result.offset;
|
|
1885
|
+
if (dx === 0 && dy === 0) return changes;
|
|
1886
|
+
const parents = new Map(nodes.map((n) => [n.id, n.parentId]));
|
|
1887
|
+
const shift = new Set(ids.filter((id) => !proposed.has(parents.get(id) ?? "")));
|
|
1888
|
+
return changes.map(
|
|
1889
|
+
(c) => c.type === "position" && c.position && c.dragging && shift.has(c.id) ? { ...c, position: { x: c.position.x + dx, y: c.position.y + dy } } : c
|
|
1890
|
+
);
|
|
1891
|
+
},
|
|
1892
|
+
/** Clears guides and the cached snap targets. */
|
|
1893
|
+
endDrag() {
|
|
1894
|
+
dragIndex = null;
|
|
1895
|
+
dragKey = "";
|
|
1896
|
+
setGuides([]);
|
|
1897
|
+
}
|
|
1898
|
+
};
|
|
1899
|
+
return controller;
|
|
1900
|
+
}
|
|
722
1901
|
export {
|
|
1902
|
+
CONNECTION_LINE_TYPES,
|
|
1903
|
+
CanvasScenesStore,
|
|
1904
|
+
DEFAULT_ARRANGE_SHORTCUTS,
|
|
723
1905
|
DEFAULT_NODE_HEIGHT,
|
|
724
1906
|
DEFAULT_NODE_WIDTH,
|
|
725
1907
|
DEFAULT_PLACEMENT_GAP,
|
|
1908
|
+
LOCKED_DATA_KEY,
|
|
1909
|
+
LOCKED_PREVIOUS_FLAGS_KEY,
|
|
726
1910
|
ManualClock,
|
|
727
1911
|
StreamingLayoutManager,
|
|
1912
|
+
alignNodes,
|
|
1913
|
+
applyNodeUpdates,
|
|
728
1914
|
blendLayout,
|
|
729
1915
|
boundsOf,
|
|
730
1916
|
calculateDagreReflowDisplacement,
|
|
@@ -732,33 +1918,65 @@ export {
|
|
|
732
1918
|
canvasHeight,
|
|
733
1919
|
centerNodes,
|
|
734
1920
|
centre,
|
|
1921
|
+
collectDescendantIds,
|
|
735
1922
|
computeNodesBoundingBox,
|
|
1923
|
+
computeSnapGuides,
|
|
736
1924
|
connectedOnly,
|
|
737
1925
|
createBezierTrajectory,
|
|
1926
|
+
createCanvasArrangeController,
|
|
1927
|
+
createCanvasScenesStore,
|
|
738
1928
|
createFanOutTrajectory,
|
|
1929
|
+
createGuideIndex,
|
|
1930
|
+
createNodeGuideIndex,
|
|
1931
|
+
createNodeLookup,
|
|
739
1932
|
defaultEasing,
|
|
1933
|
+
distributeNodes,
|
|
740
1934
|
ensureLayout,
|
|
741
1935
|
estimateCanvasHeight,
|
|
742
1936
|
estimateNodeSize,
|
|
1937
|
+
findConnectorSnap,
|
|
1938
|
+
fitViewportToNodes,
|
|
1939
|
+
fitViewportToRect,
|
|
743
1940
|
flowSide,
|
|
1941
|
+
getAbsolutePosition,
|
|
744
1942
|
getCenteredNodePosition,
|
|
745
1943
|
getLayoutedNodes,
|
|
1944
|
+
getNodeRect,
|
|
1945
|
+
getNodeSize,
|
|
1946
|
+
handlePoint,
|
|
1947
|
+
invertNodeUpdates,
|
|
746
1948
|
isEventFromTextInput,
|
|
1949
|
+
isLocked,
|
|
747
1950
|
isValidCanvasEdge,
|
|
748
1951
|
isValidCanvasNode,
|
|
1952
|
+
isValidCanvasScene,
|
|
749
1953
|
isValidUrl,
|
|
750
1954
|
layoutBounds,
|
|
751
1955
|
linearPositionTrajectory,
|
|
752
1956
|
linearTrajectory,
|
|
1957
|
+
lockNodes,
|
|
1958
|
+
matchArrangeShortcut,
|
|
1959
|
+
matchSize,
|
|
1960
|
+
nearestHandlePositions,
|
|
1961
|
+
oppositePosition,
|
|
753
1962
|
overlaps,
|
|
754
1963
|
pageHidden,
|
|
755
1964
|
parseCanvasClipboardData,
|
|
1965
|
+
parseCanvasScenes,
|
|
756
1966
|
placeBeside,
|
|
1967
|
+
positionToSide,
|
|
1968
|
+
readStyleSize,
|
|
757
1969
|
realClock,
|
|
758
1970
|
remapPastedNodes,
|
|
1971
|
+
reorderNodes,
|
|
759
1972
|
repositionNodesTo,
|
|
760
1973
|
runMultiNodeTransition,
|
|
761
1974
|
serializeCanvasNodes,
|
|
762
|
-
|
|
1975
|
+
sideToPosition,
|
|
1976
|
+
snapNodeDrag,
|
|
1977
|
+
toggleLockNodes,
|
|
1978
|
+
unlockNodes,
|
|
1979
|
+
withStyleOpacity,
|
|
1980
|
+
withStyleSize
|
|
763
1981
|
};
|
|
764
1982
|
//# sourceMappingURL=index.js.map
|