@lupinum/board-core 0.1.0 → 1.0.0-beta.1
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/dist/chunk-45V474TA.js +308 -0
- package/dist/dom-attributes.d.ts +13 -0
- package/dist/engine/json-canvas-extras.d.ts +12 -0
- package/dist/engine/persistence.d.ts +3 -1
- package/dist/engine/public-engine.d.ts +8 -0
- package/dist/helpers/json.d.ts +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +300 -161
- package/dist/internal.d.ts +4 -1
- package/dist/internal.js +29 -3
- package/dist/state/types.d.ts +7 -1
- package/dist/types.d.ts +38 -21
- package/package.json +1 -1
- package/dist/chunk-5JZXHZWR.js +0 -68
package/dist/index.js
CHANGED
|
@@ -5,8 +5,14 @@ import {
|
|
|
5
5
|
BoardInputError,
|
|
6
6
|
BoardNotFoundError,
|
|
7
7
|
assertInternalBoardPlugin,
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
collectJsonObjectExtras,
|
|
9
|
+
freezeClone,
|
|
10
|
+
freezeJsonObject,
|
|
11
|
+
readonlyMapView,
|
|
12
|
+
readonlySetView,
|
|
13
|
+
registerBoardInteractionAdapter,
|
|
14
|
+
sameArray
|
|
15
|
+
} from "./chunk-45V474TA.js";
|
|
10
16
|
|
|
11
17
|
// src/math.ts
|
|
12
18
|
function clamp(value, min, max) {
|
|
@@ -528,112 +534,6 @@ function snapPositionToEdges(bounds, otherEdges, threshold, excludeIds) {
|
|
|
528
534
|
return { dx, dy, guides };
|
|
529
535
|
}
|
|
530
536
|
|
|
531
|
-
// src/helpers/clone.ts
|
|
532
|
-
function freezeClone(value) {
|
|
533
|
-
if (Array.isArray(value)) {
|
|
534
|
-
for (const entry of value) {
|
|
535
|
-
freezeClone(entry);
|
|
536
|
-
}
|
|
537
|
-
return Object.freeze(value);
|
|
538
|
-
}
|
|
539
|
-
if (value && typeof value === "object") {
|
|
540
|
-
for (const child of Object.values(value)) {
|
|
541
|
-
freezeClone(child);
|
|
542
|
-
}
|
|
543
|
-
return Object.freeze(value);
|
|
544
|
-
}
|
|
545
|
-
return value;
|
|
546
|
-
}
|
|
547
|
-
function sameArray(a, b) {
|
|
548
|
-
if (a.length !== b.length) {
|
|
549
|
-
return false;
|
|
550
|
-
}
|
|
551
|
-
return a.every((value, index) => value === b[index]);
|
|
552
|
-
}
|
|
553
|
-
function readonlyMapView(source) {
|
|
554
|
-
let view;
|
|
555
|
-
view = Object.freeze({
|
|
556
|
-
get size() {
|
|
557
|
-
return source.size;
|
|
558
|
-
},
|
|
559
|
-
get: (key) => source.get(key),
|
|
560
|
-
has: (key) => source.has(key),
|
|
561
|
-
entries: () => source.entries(),
|
|
562
|
-
keys: () => source.keys(),
|
|
563
|
-
values: () => source.values(),
|
|
564
|
-
forEach: (callback, thisArg) => {
|
|
565
|
-
source.forEach((value, key) => callback.call(thisArg, value, key, view));
|
|
566
|
-
},
|
|
567
|
-
[Symbol.iterator]: () => source[Symbol.iterator]()
|
|
568
|
-
});
|
|
569
|
-
return view;
|
|
570
|
-
}
|
|
571
|
-
function readonlySetView(source) {
|
|
572
|
-
let view;
|
|
573
|
-
view = Object.freeze({
|
|
574
|
-
get size() {
|
|
575
|
-
return source.size;
|
|
576
|
-
},
|
|
577
|
-
has: (value) => source.has(value),
|
|
578
|
-
entries: () => source.entries(),
|
|
579
|
-
keys: () => source.keys(),
|
|
580
|
-
values: () => source.values(),
|
|
581
|
-
forEach: (callback, thisArg) => {
|
|
582
|
-
source.forEach((value) => callback.call(thisArg, value, value, view));
|
|
583
|
-
},
|
|
584
|
-
[Symbol.iterator]: () => source[Symbol.iterator](),
|
|
585
|
-
union: (other) => {
|
|
586
|
-
const result = new Set(source);
|
|
587
|
-
for (const value of iteratorValues(other.keys())) result.add(value);
|
|
588
|
-
return result;
|
|
589
|
-
},
|
|
590
|
-
intersection: (other) => {
|
|
591
|
-
const result = /* @__PURE__ */ new Set();
|
|
592
|
-
for (const value of source) {
|
|
593
|
-
if (other.has(value)) result.add(value);
|
|
594
|
-
}
|
|
595
|
-
return result;
|
|
596
|
-
},
|
|
597
|
-
difference: (other) => {
|
|
598
|
-
const result = /* @__PURE__ */ new Set();
|
|
599
|
-
for (const value of source) {
|
|
600
|
-
if (!other.has(value)) result.add(value);
|
|
601
|
-
}
|
|
602
|
-
return result;
|
|
603
|
-
},
|
|
604
|
-
symmetricDifference: (other) => {
|
|
605
|
-
const result = new Set(source);
|
|
606
|
-
for (const value of iteratorValues(other.keys())) {
|
|
607
|
-
if (source.has(value)) result.delete(value);
|
|
608
|
-
else result.add(value);
|
|
609
|
-
}
|
|
610
|
-
return result;
|
|
611
|
-
},
|
|
612
|
-
isSubsetOf: (other) => {
|
|
613
|
-
for (const value of source) if (!other.has(value)) return false;
|
|
614
|
-
return true;
|
|
615
|
-
},
|
|
616
|
-
isSupersetOf: (other) => {
|
|
617
|
-
for (const value of iteratorValues(other.keys())) {
|
|
618
|
-
if (!source.has(value)) return false;
|
|
619
|
-
}
|
|
620
|
-
return true;
|
|
621
|
-
},
|
|
622
|
-
isDisjointFrom: (other) => {
|
|
623
|
-
for (const value of source) if (other.has(value)) return false;
|
|
624
|
-
return true;
|
|
625
|
-
}
|
|
626
|
-
});
|
|
627
|
-
return view;
|
|
628
|
-
}
|
|
629
|
-
function* iteratorValues(iterator) {
|
|
630
|
-
while (true) {
|
|
631
|
-
const result = iterator.next();
|
|
632
|
-
if (result.done) return;
|
|
633
|
-
yield result.value;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
537
|
// src/helpers/ids.ts
|
|
638
538
|
function createNodeId() {
|
|
639
539
|
return crypto.randomUUID();
|
|
@@ -1593,7 +1493,11 @@ function stagePersistentRoots(roots) {
|
|
|
1593
1493
|
selection: new Set(roots.state.selection),
|
|
1594
1494
|
interaction: cloneInteraction(roots.state.interaction),
|
|
1595
1495
|
snapGuides: roots.state.snapGuides.map((guide) => ({ ...guide })),
|
|
1596
|
-
nextZIndex: roots.state.nextZIndex
|
|
1496
|
+
nextZIndex: roots.state.nextZIndex,
|
|
1497
|
+
jsonCanvas: {
|
|
1498
|
+
document: roots.state.jsonCanvas.document,
|
|
1499
|
+
nodes: new Map(roots.state.jsonCanvas.nodes)
|
|
1500
|
+
}
|
|
1597
1501
|
},
|
|
1598
1502
|
grid: { ...roots.grid },
|
|
1599
1503
|
pluginStates: new Map(
|
|
@@ -1639,6 +1543,29 @@ var JSON_CANVAS_SIDES = /* @__PURE__ */ new Set([
|
|
|
1639
1543
|
]);
|
|
1640
1544
|
var JSON_CANVAS_EDGE_ENDS = /* @__PURE__ */ new Set(["none", "arrow"]);
|
|
1641
1545
|
var JSON_CANVAS_BACKGROUND_STYLES = /* @__PURE__ */ new Set(["cover", "ratio", "repeat"]);
|
|
1546
|
+
var LEGACY_BOARD_METADATA_KEY = "x-vue-board";
|
|
1547
|
+
var DOCUMENT_FIELDS = /* @__PURE__ */ new Set([
|
|
1548
|
+
"nodes",
|
|
1549
|
+
"edges",
|
|
1550
|
+
"x-lupinum-board",
|
|
1551
|
+
LEGACY_BOARD_METADATA_KEY
|
|
1552
|
+
]);
|
|
1553
|
+
var NODE_FIELDS = /* @__PURE__ */ new Set([
|
|
1554
|
+
"id",
|
|
1555
|
+
"type",
|
|
1556
|
+
"x",
|
|
1557
|
+
"y",
|
|
1558
|
+
"width",
|
|
1559
|
+
"height",
|
|
1560
|
+
"color",
|
|
1561
|
+
"text",
|
|
1562
|
+
"file",
|
|
1563
|
+
"subpath",
|
|
1564
|
+
"url",
|
|
1565
|
+
"label",
|
|
1566
|
+
"background",
|
|
1567
|
+
"backgroundStyle"
|
|
1568
|
+
]);
|
|
1642
1569
|
function isJsonCanvasNodeType(value) {
|
|
1643
1570
|
return typeof value === "string" && JSON_CANVAS_NODE_TYPES.has(value);
|
|
1644
1571
|
}
|
|
@@ -1716,8 +1643,9 @@ function jsonNodeToBoardNode(node, meta, index) {
|
|
|
1716
1643
|
};
|
|
1717
1644
|
return withNodeFields(base, node);
|
|
1718
1645
|
}
|
|
1719
|
-
function boardNodeToJsonNode(node) {
|
|
1646
|
+
function boardNodeToJsonNode(node, extras) {
|
|
1720
1647
|
const base = {
|
|
1648
|
+
...extras ?? {},
|
|
1721
1649
|
id: node.id,
|
|
1722
1650
|
type: node.type,
|
|
1723
1651
|
x: node.x,
|
|
@@ -1759,9 +1687,10 @@ function mergeMetadata(base, patch) {
|
|
|
1759
1687
|
};
|
|
1760
1688
|
}
|
|
1761
1689
|
function getDocumentMetadata(document) {
|
|
1762
|
-
|
|
1690
|
+
const record = document;
|
|
1691
|
+
return document["x-lupinum-board"] ?? record[LEGACY_BOARD_METADATA_KEY];
|
|
1763
1692
|
}
|
|
1764
|
-
function toPersistedDocument(snapshot, featureDocuments) {
|
|
1693
|
+
function toPersistedDocument(snapshot, featureDocuments, passthrough) {
|
|
1765
1694
|
let metadata = {
|
|
1766
1695
|
camera: snapshot.camera,
|
|
1767
1696
|
grid: snapshot.grid,
|
|
@@ -1787,13 +1716,18 @@ function toPersistedDocument(snapshot, featureDocuments) {
|
|
|
1787
1716
|
metadata = mergeMetadata(metadata, getDocumentMetadata(featureDocument));
|
|
1788
1717
|
}
|
|
1789
1718
|
return {
|
|
1790
|
-
|
|
1719
|
+
...passthrough.document,
|
|
1720
|
+
nodes: snapshot.nodes.map(
|
|
1721
|
+
(node) => boardNodeToJsonNode(node, passthrough.nodes.get(node.id))
|
|
1722
|
+
),
|
|
1791
1723
|
...edges !== void 0 ? { edges } : {},
|
|
1792
|
-
"x-
|
|
1724
|
+
"x-lupinum-board": metadata
|
|
1793
1725
|
};
|
|
1794
1726
|
}
|
|
1795
1727
|
function isRecord(value) {
|
|
1796
|
-
|
|
1728
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1729
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1730
|
+
return prototype === Object.prototype || prototype === null;
|
|
1797
1731
|
}
|
|
1798
1732
|
function assertRecord(value, message) {
|
|
1799
1733
|
if (!isRecord(value)) {
|
|
@@ -1921,14 +1855,21 @@ function validateDocumentMetadata(metadata) {
|
|
|
1921
1855
|
`Invalid board document: metadata for edge "${id}" has invalid data.`
|
|
1922
1856
|
);
|
|
1923
1857
|
}
|
|
1858
|
+
if (edge.data !== void 0) {
|
|
1859
|
+
freezeJsonObject(
|
|
1860
|
+
edge.data,
|
|
1861
|
+
`Invalid board document: metadata for edge "${id}" data`
|
|
1862
|
+
);
|
|
1863
|
+
}
|
|
1924
1864
|
}
|
|
1925
1865
|
}
|
|
1926
1866
|
}
|
|
1927
1867
|
function normalizeDocumentForImport(raw) {
|
|
1928
|
-
const
|
|
1868
|
+
const parsedRecord = assertRecord(
|
|
1929
1869
|
raw,
|
|
1930
1870
|
"Invalid board document: document must be an object."
|
|
1931
1871
|
);
|
|
1872
|
+
const parsed = parsedRecord;
|
|
1932
1873
|
if (!Array.isArray(parsed.nodes)) {
|
|
1933
1874
|
throw new BoardInputError("Invalid board document: missing nodes array.");
|
|
1934
1875
|
}
|
|
@@ -1942,11 +1883,16 @@ function normalizeDocumentForImport(raw) {
|
|
|
1942
1883
|
]) {
|
|
1943
1884
|
if (key in parsed) {
|
|
1944
1885
|
throw new BoardInputError(
|
|
1945
|
-
`Invalid board document: runtime field "${key}" belongs under x-
|
|
1886
|
+
`Invalid board document: runtime field "${key}" belongs under x-lupinum-board.`
|
|
1946
1887
|
);
|
|
1947
1888
|
}
|
|
1948
1889
|
}
|
|
1949
|
-
|
|
1890
|
+
const rawMetadata = getDocumentMetadata(parsed);
|
|
1891
|
+
validateDocumentMetadata(rawMetadata);
|
|
1892
|
+
const metadata = rawMetadata === void 0 ? void 0 : freezeJsonObject(
|
|
1893
|
+
rawMetadata,
|
|
1894
|
+
"Invalid board document: board metadata"
|
|
1895
|
+
);
|
|
1950
1896
|
const seenNodes = /* @__PURE__ */ new Set();
|
|
1951
1897
|
const nodes = parsed.nodes.map((node) => {
|
|
1952
1898
|
if (!isRecord(node)) {
|
|
@@ -1969,7 +1915,10 @@ function normalizeDocumentForImport(raw) {
|
|
|
1969
1915
|
`Invalid board document: node "${String(node.id)}" has unsupported backgroundStyle "${String(node.backgroundStyle)}".`
|
|
1970
1916
|
);
|
|
1971
1917
|
}
|
|
1972
|
-
const normalized =
|
|
1918
|
+
const normalized = freezeJsonObject(
|
|
1919
|
+
node,
|
|
1920
|
+
`Invalid board document: node "${String(node.id ?? "?")}"`
|
|
1921
|
+
);
|
|
1973
1922
|
validateJsonCanvasNodeFields(normalized);
|
|
1974
1923
|
if (seenNodes.has(normalized.id)) {
|
|
1975
1924
|
throw new BoardInputError(
|
|
@@ -2037,13 +1986,41 @@ function normalizeDocumentForImport(raw) {
|
|
|
2037
1986
|
`Invalid board document: edge "${id}" has invalid label.`
|
|
2038
1987
|
);
|
|
2039
1988
|
}
|
|
2040
|
-
return
|
|
1989
|
+
return freezeJsonObject(
|
|
1990
|
+
{ ...edge, id, fromNode, toNode },
|
|
1991
|
+
`Invalid board document: edge "${id}"`
|
|
1992
|
+
);
|
|
2041
1993
|
});
|
|
2042
1994
|
}
|
|
2043
1995
|
return {
|
|
1996
|
+
...collectJsonObjectExtras(
|
|
1997
|
+
parsedRecord,
|
|
1998
|
+
DOCUMENT_FIELDS,
|
|
1999
|
+
"Invalid board document"
|
|
2000
|
+
),
|
|
2044
2001
|
nodes,
|
|
2045
2002
|
...edges !== void 0 ? { edges } : {},
|
|
2046
|
-
...
|
|
2003
|
+
...metadata !== void 0 ? { "x-lupinum-board": metadata } : {}
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
function extractJsonCanvasPassthrough(document) {
|
|
2007
|
+
const record = document;
|
|
2008
|
+
return {
|
|
2009
|
+
document: collectJsonObjectExtras(
|
|
2010
|
+
record,
|
|
2011
|
+
DOCUMENT_FIELDS,
|
|
2012
|
+
"Invalid board document"
|
|
2013
|
+
),
|
|
2014
|
+
nodes: new Map(
|
|
2015
|
+
document.nodes.map((node) => [
|
|
2016
|
+
node.id,
|
|
2017
|
+
collectJsonObjectExtras(
|
|
2018
|
+
node,
|
|
2019
|
+
NODE_FIELDS,
|
|
2020
|
+
`Invalid board document: node "${node.id}"`
|
|
2021
|
+
)
|
|
2022
|
+
])
|
|
2023
|
+
)
|
|
2047
2024
|
};
|
|
2048
2025
|
}
|
|
2049
2026
|
function materializeSnapshotNodes(snapshot) {
|
|
@@ -2163,6 +2140,110 @@ function createCameraSession(deps) {
|
|
|
2163
2140
|
return { animateTo, cancelAnimations, computeFit };
|
|
2164
2141
|
}
|
|
2165
2142
|
|
|
2143
|
+
// src/engine/public-engine.ts
|
|
2144
|
+
function createPublicEngine(engine) {
|
|
2145
|
+
return {
|
|
2146
|
+
plugins: engine.plugins,
|
|
2147
|
+
$camera: engine.$camera,
|
|
2148
|
+
$grid: engine.$grid,
|
|
2149
|
+
$nodes: engine.$nodes,
|
|
2150
|
+
$selection: engine.$selection,
|
|
2151
|
+
$interaction: engine.$interaction,
|
|
2152
|
+
$snapGuides: engine.$snapGuides,
|
|
2153
|
+
destroy: engine.destroy,
|
|
2154
|
+
batch: engine.batch,
|
|
2155
|
+
getState: engine.getState,
|
|
2156
|
+
getGridSettings: engine.getGridSettings,
|
|
2157
|
+
getViewportSize: engine.getViewportSize,
|
|
2158
|
+
updateGridSettings: engine.updateGridSettings,
|
|
2159
|
+
setViewportSize: engine.setViewportSize,
|
|
2160
|
+
on: engine.on,
|
|
2161
|
+
once: engine.once,
|
|
2162
|
+
off: engine.off,
|
|
2163
|
+
exportTrace: engine.exportTrace,
|
|
2164
|
+
addCommandGuard: engine.addCommandGuard,
|
|
2165
|
+
screenToWorld: engine.screenToWorld,
|
|
2166
|
+
worldToScreen: engine.worldToScreen,
|
|
2167
|
+
getVisibleBounds: engine.getVisibleBounds,
|
|
2168
|
+
getNode: engine.getNode,
|
|
2169
|
+
findNode: engine.findNode,
|
|
2170
|
+
hasNode: engine.hasNode,
|
|
2171
|
+
getNodeAt: engine.getNodeAt,
|
|
2172
|
+
getNodesInBounds: engine.getNodesInBounds,
|
|
2173
|
+
panBy: engine.panBy,
|
|
2174
|
+
panTo: engine.panTo,
|
|
2175
|
+
zoomAt: engine.zoomAt,
|
|
2176
|
+
zoomTo: engine.zoomTo,
|
|
2177
|
+
zoomToFit: engine.zoomToFit,
|
|
2178
|
+
zoomToNodes: engine.zoomToNodes,
|
|
2179
|
+
createNode: engine.createNode,
|
|
2180
|
+
updateNode: engine.updateNode,
|
|
2181
|
+
deleteNode: engine.deleteNode,
|
|
2182
|
+
moveNode: engine.moveNode,
|
|
2183
|
+
translateSelectedNodes: engine.translateSelectedNodes,
|
|
2184
|
+
resizeNode: engine.resizeNode,
|
|
2185
|
+
bringToFront: engine.bringToFront,
|
|
2186
|
+
sendToBack: engine.sendToBack,
|
|
2187
|
+
lockNode: engine.lockNode,
|
|
2188
|
+
unlockNode: engine.unlockNode,
|
|
2189
|
+
duplicateNodes: engine.duplicateNodes,
|
|
2190
|
+
copySelected: engine.copySelected,
|
|
2191
|
+
pasteClipboard: engine.pasteClipboard,
|
|
2192
|
+
pasteData: engine.pasteData,
|
|
2193
|
+
select: engine.select,
|
|
2194
|
+
selectAll: engine.selectAll,
|
|
2195
|
+
clearSelection: engine.clearSelection,
|
|
2196
|
+
deleteSelected: engine.deleteSelected,
|
|
2197
|
+
getSelection: engine.getSelection,
|
|
2198
|
+
beginTextEdit: engine.beginTextEdit,
|
|
2199
|
+
commitTextEdit: engine.commitTextEdit,
|
|
2200
|
+
cancelTextEdit: engine.cancelTextEdit,
|
|
2201
|
+
exportDocument: engine.exportDocument,
|
|
2202
|
+
loadDocument: engine.loadDocument
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// src/engine/json-canvas-extras.ts
|
|
2207
|
+
function removeNodeExtras(current, id) {
|
|
2208
|
+
if (!current.nodes.has(id)) return current;
|
|
2209
|
+
const nodes = new Map(current.nodes);
|
|
2210
|
+
nodes.delete(id);
|
|
2211
|
+
return { ...current, nodes };
|
|
2212
|
+
}
|
|
2213
|
+
function copyNodeExtras(current, idMap, source = current.nodes) {
|
|
2214
|
+
const nodes = new Map(current.nodes);
|
|
2215
|
+
for (const [sourceId, targetId] of idMap) {
|
|
2216
|
+
const extras = source.get(sourceId);
|
|
2217
|
+
if (extras) nodes.set(targetId, extras);
|
|
2218
|
+
}
|
|
2219
|
+
return { ...current, nodes };
|
|
2220
|
+
}
|
|
2221
|
+
function replaceJsonCanvasExtras(incoming) {
|
|
2222
|
+
return {
|
|
2223
|
+
document: incoming.document,
|
|
2224
|
+
nodes: new Map(incoming.nodes)
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
function mergeJsonCanvasExtras(current, incoming, idMap) {
|
|
2228
|
+
const document = Object.freeze({
|
|
2229
|
+
...current.document,
|
|
2230
|
+
...incoming.document
|
|
2231
|
+
});
|
|
2232
|
+
const nodes = new Map(current.nodes);
|
|
2233
|
+
for (const [id, extras] of incoming.nodes) {
|
|
2234
|
+
nodes.set(idMap.get(id) ?? id, extras);
|
|
2235
|
+
}
|
|
2236
|
+
return { document, nodes };
|
|
2237
|
+
}
|
|
2238
|
+
function selectNodeExtras(source, ids) {
|
|
2239
|
+
const selected = /* @__PURE__ */ new Map();
|
|
2240
|
+
for (const id of ids) {
|
|
2241
|
+
const extras = source.get(id);
|
|
2242
|
+
if (extras) selected.set(id, extras);
|
|
2243
|
+
}
|
|
2244
|
+
return selected;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2166
2247
|
// src/engine/node-shape.ts
|
|
2167
2248
|
function assertValidNodeGeometry(id, geometry) {
|
|
2168
2249
|
if (!Number.isFinite(geometry.x) || !Number.isFinite(geometry.y) || !Number.isFinite(geometry.width) || !Number.isFinite(geometry.height) || geometry.width <= 0 || geometry.height <= 0) {
|
|
@@ -2368,6 +2449,7 @@ function createBoardEngine(options = {}) {
|
|
|
2368
2449
|
const { emit, emitImmediate, on, once, off, reportUnhandledError } = eventBus;
|
|
2369
2450
|
const commandGuards = createCommandGuardRegistry();
|
|
2370
2451
|
const commitProjectors = /* @__PURE__ */ new Set();
|
|
2452
|
+
const projectedSubscribables = /* @__PURE__ */ new Set();
|
|
2371
2453
|
const nodeDeletedHooks = /* @__PURE__ */ new Set();
|
|
2372
2454
|
const pluginCleanups = /* @__PURE__ */ new Map();
|
|
2373
2455
|
let pluginStates = /* @__PURE__ */ new Map();
|
|
@@ -2376,6 +2458,7 @@ function createBoardEngine(options = {}) {
|
|
|
2376
2458
|
for (const hook of nodeDeletedHooks) hook(nodeId);
|
|
2377
2459
|
}
|
|
2378
2460
|
const clipboard = [];
|
|
2461
|
+
const clipboardPassthrough = /* @__PURE__ */ new Map();
|
|
2379
2462
|
const plugins = {};
|
|
2380
2463
|
let viewportSize = { ...DEFAULT_VIEWPORT_SIZE };
|
|
2381
2464
|
let destroyed = false;
|
|
@@ -2414,11 +2497,13 @@ function createBoardEngine(options = {}) {
|
|
|
2414
2497
|
selection: /* @__PURE__ */ new Set(),
|
|
2415
2498
|
interaction: { mode: "idle" },
|
|
2416
2499
|
snapGuides: [],
|
|
2417
|
-
nextZIndex: 1
|
|
2500
|
+
nextZIndex: 1,
|
|
2501
|
+
jsonCanvas: { document: Object.freeze({}), nodes: /* @__PURE__ */ new Map() }
|
|
2418
2502
|
};
|
|
2419
2503
|
const initialDocument = options.initialDocument ? normalizeDocumentForImport(options.initialDocument) : null;
|
|
2420
2504
|
if (initialDocument) {
|
|
2421
2505
|
const initial = documentToSnapshot(initialDocument);
|
|
2506
|
+
state.jsonCanvas = extractJsonCanvasPassthrough(initialDocument);
|
|
2422
2507
|
state.camera = { ...initial.camera };
|
|
2423
2508
|
state.selection = /* @__PURE__ */ new Set();
|
|
2424
2509
|
state.nextZIndex = initial.nextZIndex;
|
|
@@ -2518,6 +2603,7 @@ function createBoardEngine(options = {}) {
|
|
|
2518
2603
|
const checkpoint = {
|
|
2519
2604
|
roots,
|
|
2520
2605
|
clipboard: [...clipboard],
|
|
2606
|
+
clipboardPassthrough: new Map(clipboardPassthrough),
|
|
2521
2607
|
nodeOverrides: new Map(nodeOverrides),
|
|
2522
2608
|
activeGestureHistoryRoot,
|
|
2523
2609
|
activeBoxSelectionBefore
|
|
@@ -2533,6 +2619,10 @@ function createBoardEngine(options = {}) {
|
|
|
2533
2619
|
grid = checkpoint.roots.grid;
|
|
2534
2620
|
pluginStates = checkpoint.roots.pluginStates;
|
|
2535
2621
|
clipboard.splice(0, clipboard.length, ...checkpoint.clipboard);
|
|
2622
|
+
clipboardPassthrough.clear();
|
|
2623
|
+
for (const [id, extras] of checkpoint.clipboardPassthrough) {
|
|
2624
|
+
clipboardPassthrough.set(id, extras);
|
|
2625
|
+
}
|
|
2536
2626
|
nodeOverrides.clear();
|
|
2537
2627
|
for (const [id, node] of checkpoint.nodeOverrides) {
|
|
2538
2628
|
nodeOverrides.set(id, node);
|
|
@@ -2547,6 +2637,7 @@ function createBoardEngine(options = {}) {
|
|
|
2547
2637
|
grid: freezeClone({ ...grid }),
|
|
2548
2638
|
selection: state.selection,
|
|
2549
2639
|
nextZIndex: state.nextZIndex,
|
|
2640
|
+
jsonCanvas: state.jsonCanvas,
|
|
2550
2641
|
pluginSlices: new Map(
|
|
2551
2642
|
Array.from(pluginStates, ([name, pluginState]) => [
|
|
2552
2643
|
name,
|
|
@@ -2557,6 +2648,11 @@ function createBoardEngine(options = {}) {
|
|
|
2557
2648
|
}
|
|
2558
2649
|
function sameHistoryRoot(left, right) {
|
|
2559
2650
|
if (left.nextZIndex !== right.nextZIndex) return false;
|
|
2651
|
+
if (left.jsonCanvas.document !== right.jsonCanvas.document) return false;
|
|
2652
|
+
if (left.jsonCanvas.nodes.size !== right.jsonCanvas.nodes.size) return false;
|
|
2653
|
+
for (const [id, extras] of left.jsonCanvas.nodes) {
|
|
2654
|
+
if (right.jsonCanvas.nodes.get(id) !== extras) return false;
|
|
2655
|
+
}
|
|
2560
2656
|
if (left.grid.size !== right.grid.size || left.grid.majorEvery !== right.grid.majorEvery || left.grid.snap !== right.grid.snap || left.grid.edgeSnap !== right.grid.edgeSnap || left.grid.edgeSnapThreshold !== right.grid.edgeSnapThreshold || left.grid.pattern !== right.grid.pattern) {
|
|
2561
2657
|
return false;
|
|
2562
2658
|
}
|
|
@@ -2863,7 +2959,13 @@ function createBoardEngine(options = {}) {
|
|
|
2863
2959
|
Array.from(state.selection.values()).filter((id) => state.nodes.has(id))
|
|
2864
2960
|
);
|
|
2865
2961
|
}
|
|
2866
|
-
function
|
|
2962
|
+
function deleteNodePassthrough(id) {
|
|
2963
|
+
state.jsonCanvas = removeNodeExtras(state.jsonCanvas, id);
|
|
2964
|
+
}
|
|
2965
|
+
function copyNodePassthrough(idMap, source = state.jsonCanvas.nodes) {
|
|
2966
|
+
state.jsonCanvas = copyNodeExtras(state.jsonCanvas, idMap, source);
|
|
2967
|
+
}
|
|
2968
|
+
function restoreSnapshot(snapshot, mode, passthrough) {
|
|
2867
2969
|
const snapshotNodes = materializeSnapshotNodes(snapshot);
|
|
2868
2970
|
const idMap = /* @__PURE__ */ new Map();
|
|
2869
2971
|
if (mode === "replace") {
|
|
@@ -2874,6 +2976,7 @@ function createBoardEngine(options = {}) {
|
|
|
2874
2976
|
continue;
|
|
2875
2977
|
}
|
|
2876
2978
|
state.nodes.delete(id);
|
|
2979
|
+
deleteNodePassthrough(id);
|
|
2877
2980
|
notifyNodeDeletedPlugins(prevNode.id);
|
|
2878
2981
|
emit("node:deleted", id, materializeNode2(prevNode));
|
|
2879
2982
|
}
|
|
@@ -2884,6 +2987,7 @@ function createBoardEngine(options = {}) {
|
|
|
2884
2987
|
idMap.set(rawNode.id, node.id);
|
|
2885
2988
|
emit("node:created", materializeNode2(node));
|
|
2886
2989
|
}
|
|
2990
|
+
state.jsonCanvas = replaceJsonCanvasExtras(passthrough);
|
|
2887
2991
|
state.selection = new Set(
|
|
2888
2992
|
snapshot.selection.filter((id) => state.nodes.has(id))
|
|
2889
2993
|
);
|
|
@@ -2904,6 +3008,11 @@ function createBoardEngine(options = {}) {
|
|
|
2904
3008
|
state.nodes.set(id, { ...node, id, zIndex: state.nextZIndex++ });
|
|
2905
3009
|
idMap.set(node.id, id);
|
|
2906
3010
|
}
|
|
3011
|
+
state.jsonCanvas = mergeJsonCanvasExtras(
|
|
3012
|
+
state.jsonCanvas,
|
|
3013
|
+
passthrough,
|
|
3014
|
+
idMap
|
|
3015
|
+
);
|
|
2907
3016
|
notifyNodesChanged();
|
|
2908
3017
|
return idMap;
|
|
2909
3018
|
}
|
|
@@ -2998,6 +3107,8 @@ function createBoardEngine(options = {}) {
|
|
|
2998
3107
|
pluginStates.clear();
|
|
2999
3108
|
pluginPersistence.clear();
|
|
3000
3109
|
commitProjectors.clear();
|
|
3110
|
+
for (const projection of projectedSubscribables) projection.destroy();
|
|
3111
|
+
projectedSubscribables.clear();
|
|
3001
3112
|
nodeDeletedHooks.clear();
|
|
3002
3113
|
commandGuards.clear();
|
|
3003
3114
|
eventBus.clear();
|
|
@@ -3011,7 +3122,7 @@ function createBoardEngine(options = {}) {
|
|
|
3011
3122
|
},
|
|
3012
3123
|
extend(key, value) {
|
|
3013
3124
|
assertMutationAllowed();
|
|
3014
|
-
plugins
|
|
3125
|
+
Reflect.set(plugins, key, value);
|
|
3015
3126
|
},
|
|
3016
3127
|
batch(fn) {
|
|
3017
3128
|
assertCommandReady();
|
|
@@ -3120,6 +3231,27 @@ function createBoardEngine(options = {}) {
|
|
|
3120
3231
|
commitProjectors.add(projector);
|
|
3121
3232
|
return () => commitProjectors.delete(projector);
|
|
3122
3233
|
},
|
|
3234
|
+
createCommitSubscribable(select, channel) {
|
|
3235
|
+
assertAlive();
|
|
3236
|
+
assertMutationAllowed();
|
|
3237
|
+
let selected = select();
|
|
3238
|
+
const projection = createSubscribable(
|
|
3239
|
+
selected,
|
|
3240
|
+
createBatchController(),
|
|
3241
|
+
(error) => reportUnhandledError(error, { source: "subscriber", channel })
|
|
3242
|
+
);
|
|
3243
|
+
const projector = () => {
|
|
3244
|
+
const next = select();
|
|
3245
|
+
if (Object.is(next, selected)) return () => void 0;
|
|
3246
|
+
return () => {
|
|
3247
|
+
selected = next;
|
|
3248
|
+
projection.set(next);
|
|
3249
|
+
};
|
|
3250
|
+
};
|
|
3251
|
+
commitProjectors.add(projector);
|
|
3252
|
+
projectedSubscribables.add(projection);
|
|
3253
|
+
return projection;
|
|
3254
|
+
},
|
|
3123
3255
|
restoreHistoryRoot(root) {
|
|
3124
3256
|
runCommand(
|
|
3125
3257
|
"history:restore",
|
|
@@ -3132,6 +3264,10 @@ function createBoardEngine(options = {}) {
|
|
|
3132
3264
|
Array.from(root.selection).filter((id) => state.nodes.has(id))
|
|
3133
3265
|
);
|
|
3134
3266
|
state.nextZIndex = root.nextZIndex;
|
|
3267
|
+
state.jsonCanvas = {
|
|
3268
|
+
document: root.jsonCanvas.document,
|
|
3269
|
+
nodes: new Map(root.jsonCanvas.nodes)
|
|
3270
|
+
};
|
|
3135
3271
|
Object.assign(grid, root.grid);
|
|
3136
3272
|
for (const [name, pluginState] of pluginStates) {
|
|
3137
3273
|
if (root.pluginSlices.has(name)) {
|
|
@@ -3147,16 +3283,6 @@ function createBoardEngine(options = {}) {
|
|
|
3147
3283
|
IGNORE_COMMAND
|
|
3148
3284
|
);
|
|
3149
3285
|
},
|
|
3150
|
-
getPluginState() {
|
|
3151
|
-
throw new Error(
|
|
3152
|
-
"getPluginState is only available inside an internal plugin install() context."
|
|
3153
|
-
);
|
|
3154
|
-
},
|
|
3155
|
-
updatePluginState(_update) {
|
|
3156
|
-
throw new Error(
|
|
3157
|
-
"updatePluginState is only available inside an internal plugin install() context."
|
|
3158
|
-
);
|
|
3159
|
-
},
|
|
3160
3286
|
screenToWorld(point) {
|
|
3161
3287
|
assertAlive();
|
|
3162
3288
|
return screenToWorld(point, state.camera);
|
|
@@ -3344,6 +3470,7 @@ function createBoardEngine(options = {}) {
|
|
|
3344
3470
|
continue;
|
|
3345
3471
|
}
|
|
3346
3472
|
state.nodes.delete(deleteId);
|
|
3473
|
+
deleteNodePassthrough(deleteId);
|
|
3347
3474
|
notifyNodeDeletedPlugins(prevNode.id);
|
|
3348
3475
|
emit("node:deleted", deleteId, materializeNode2(prevNode));
|
|
3349
3476
|
}
|
|
@@ -3501,6 +3628,7 @@ function createBoardEngine(options = {}) {
|
|
|
3501
3628
|
const forest = forestIdsFromSeeds(ids);
|
|
3502
3629
|
const source = Array.from(forest).map((id) => state.nodes.get(id)).filter((node) => Boolean(node)).sort((a, b) => a.zIndex - b.zIndex);
|
|
3503
3630
|
const duplicated = duplicateForest2(source, offset);
|
|
3631
|
+
copyNodePassthrough(duplicated.idMap);
|
|
3504
3632
|
const created = duplicated.nodes;
|
|
3505
3633
|
for (const node of created) {
|
|
3506
3634
|
state.nodes.set(node.id, node);
|
|
@@ -3517,15 +3645,45 @@ function createBoardEngine(options = {}) {
|
|
|
3517
3645
|
copySelected() {
|
|
3518
3646
|
return runCommand("copySelected", [], () => {
|
|
3519
3647
|
clipboard.length = 0;
|
|
3520
|
-
|
|
3648
|
+
clipboardPassthrough.clear();
|
|
3649
|
+
const copied = getCopyClosureNodes2();
|
|
3650
|
+
for (const node of copied) {
|
|
3521
3651
|
clipboard.push({ ...node });
|
|
3522
3652
|
}
|
|
3653
|
+
for (const [id, extras] of selectNodeExtras(
|
|
3654
|
+
state.jsonCanvas.nodes,
|
|
3655
|
+
copied.map((node) => node.id)
|
|
3656
|
+
)) {
|
|
3657
|
+
clipboardPassthrough.set(id, extras);
|
|
3658
|
+
}
|
|
3523
3659
|
return clipboard.map((node) => materializeNode2(node));
|
|
3524
3660
|
});
|
|
3525
3661
|
},
|
|
3526
3662
|
pasteClipboard(offset = { x: grid.size, y: grid.size }) {
|
|
3527
3663
|
return runCommand("pasteClipboard", [offset], () => {
|
|
3528
|
-
const
|
|
3664
|
+
const duplicated = duplicateForest2(clipboard, offset);
|
|
3665
|
+
copyNodePassthrough(duplicated.idMap, clipboardPassthrough);
|
|
3666
|
+
const created = duplicated.nodes;
|
|
3667
|
+
for (const node of created) {
|
|
3668
|
+
state.nodes.set(node.id, node);
|
|
3669
|
+
emit("node:created", materializeNode2(node));
|
|
3670
|
+
}
|
|
3671
|
+
notifyNodesChanged();
|
|
3672
|
+
setSelection(created.map((node) => node.id));
|
|
3673
|
+
return created.map((node) => materializeNode2(node));
|
|
3674
|
+
});
|
|
3675
|
+
},
|
|
3676
|
+
pasteData(payload, offset = { x: grid.size, y: grid.size }) {
|
|
3677
|
+
return runCommand("pasteData", [payload, offset], () => {
|
|
3678
|
+
const inputs = options.clipboard?.deserialize(payload) ?? null;
|
|
3679
|
+
if (!inputs || inputs.length === 0) return null;
|
|
3680
|
+
const created = inputs.map(
|
|
3681
|
+
(input) => normalizeNode({
|
|
3682
|
+
...input,
|
|
3683
|
+
x: input.x ?? offset.x,
|
|
3684
|
+
y: input.y ?? offset.y
|
|
3685
|
+
})
|
|
3686
|
+
);
|
|
3529
3687
|
for (const node of created) {
|
|
3530
3688
|
state.nodes.set(node.id, node);
|
|
3531
3689
|
emit("node:created", materializeNode2(node));
|
|
@@ -3600,6 +3758,7 @@ function createBoardEngine(options = {}) {
|
|
|
3600
3758
|
continue;
|
|
3601
3759
|
}
|
|
3602
3760
|
state.nodes.delete(deleteId);
|
|
3761
|
+
deleteNodePassthrough(deleteId);
|
|
3603
3762
|
notifyNodeDeletedPlugins(prevNode.id);
|
|
3604
3763
|
emit("node:deleted", deleteId, materializeNode2(prevNode));
|
|
3605
3764
|
}
|
|
@@ -4038,7 +4197,8 @@ function createBoardEngine(options = {}) {
|
|
|
4038
4197
|
);
|
|
4039
4198
|
return toPersistedDocument(
|
|
4040
4199
|
buildSnapshot(state, grid, buildPublicNodeMap(state)),
|
|
4041
|
-
pluginDocuments
|
|
4200
|
+
pluginDocuments,
|
|
4201
|
+
state.jsonCanvas
|
|
4042
4202
|
);
|
|
4043
4203
|
},
|
|
4044
4204
|
loadDocument(document, options2 = {}) {
|
|
@@ -4050,10 +4210,11 @@ function createBoardEngine(options = {}) {
|
|
|
4050
4210
|
const normalized = normalizeDocumentForImport(document);
|
|
4051
4211
|
assertCanRestoreDocument(normalized);
|
|
4052
4212
|
const snapshot = documentToSnapshot(normalized);
|
|
4213
|
+
const passthrough = extractJsonCanvasPassthrough(normalized);
|
|
4053
4214
|
nodeOverrides.clear();
|
|
4054
4215
|
activeGestureHistoryRoot = null;
|
|
4055
4216
|
activeBoxSelectionBefore = null;
|
|
4056
|
-
const idMap = restoreSnapshot(snapshot, mode);
|
|
4217
|
+
const idMap = restoreSnapshot(snapshot, mode, passthrough);
|
|
4057
4218
|
restorePluginDocuments(normalized, mode, idMap);
|
|
4058
4219
|
},
|
|
4059
4220
|
IGNORE_COMMAND
|
|
@@ -4075,29 +4236,7 @@ function createBoardEngine(options = {}) {
|
|
|
4075
4236
|
);
|
|
4076
4237
|
}
|
|
4077
4238
|
validate("createBoardEngine");
|
|
4078
|
-
const
|
|
4079
|
-
"emit",
|
|
4080
|
-
"assertActive",
|
|
4081
|
-
"isBatching",
|
|
4082
|
-
"extend",
|
|
4083
|
-
"runCommand",
|
|
4084
|
-
"projectCommit",
|
|
4085
|
-
"restoreHistoryRoot",
|
|
4086
|
-
"getPluginState",
|
|
4087
|
-
"updatePluginState",
|
|
4088
|
-
"beginPan",
|
|
4089
|
-
"beginNodeDrag",
|
|
4090
|
-
"beginResize",
|
|
4091
|
-
"beginBoxSelect",
|
|
4092
|
-
"updatePointer",
|
|
4093
|
-
"endInteraction",
|
|
4094
|
-
"cancelInteraction",
|
|
4095
|
-
"getUniformTranslationTargets",
|
|
4096
|
-
"syncGroupZOrder"
|
|
4097
|
-
]);
|
|
4098
|
-
const publicEngine = Object.fromEntries(
|
|
4099
|
-
Object.entries(engine).filter(([key]) => !internalKeys.has(key))
|
|
4100
|
-
);
|
|
4239
|
+
const publicEngine = createPublicEngine(engine);
|
|
4101
4240
|
registerBoardInteractionAdapter(publicEngine, engine);
|
|
4102
4241
|
return publicEngine;
|
|
4103
4242
|
}
|