@dou-ui/flow 0.0.0-stage → 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/collaboration/index.js +360 -0
  4. package/dist/collaboration/index.js.map +1 -0
  5. package/dist/collaboration/peers.js +76 -0
  6. package/dist/collaboration/peers.js.map +1 -0
  7. package/dist/components/default-node.js +6 -0
  8. package/dist/components/default-node.js.map +1 -0
  9. package/dist/components/default-node.vue_vue_type_script_setup_true_lang.js +87 -0
  10. package/dist/components/default-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
  11. package/dist/components/default-node2.js +116 -0
  12. package/dist/components/default-node2.js.map +1 -0
  13. package/dist/components/flow-controls.js +6 -0
  14. package/dist/components/flow-controls.js.map +1 -0
  15. package/dist/components/flow-controls.vue_vue_type_script_setup_true_lang.js +148 -0
  16. package/dist/components/flow-controls.vue_vue_type_script_setup_true_lang.js.map +1 -0
  17. package/dist/components/flow-menu.js +6 -0
  18. package/dist/components/flow-menu.js.map +1 -0
  19. package/dist/components/flow-menu.vue_vue_type_script_setup_true_lang.js +52 -0
  20. package/dist/components/flow-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  21. package/dist/components/flow-minimap.js +6 -0
  22. package/dist/components/flow-minimap.js.map +1 -0
  23. package/dist/components/flow-minimap.vue_vue_type_script_setup_true_lang.js +238 -0
  24. package/dist/components/flow-minimap.vue_vue_type_script_setup_true_lang.js.map +1 -0
  25. package/dist/components/flow-port.js +6 -0
  26. package/dist/components/flow-port.js.map +1 -0
  27. package/dist/components/flow-port.vue_vue_type_script_setup_true_lang.js +57 -0
  28. package/dist/components/flow-port.vue_vue_type_script_setup_true_lang.js.map +1 -0
  29. package/dist/components/flow-quick-add.js +6 -0
  30. package/dist/components/flow-quick-add.js.map +1 -0
  31. package/dist/components/flow-quick-add.vue_vue_type_script_setup_true_lang.js +126 -0
  32. package/dist/components/flow-quick-add.vue_vue_type_script_setup_true_lang.js.map +1 -0
  33. package/dist/components/flow-toolbar.js +6 -0
  34. package/dist/components/flow-toolbar.js.map +1 -0
  35. package/dist/components/flow-toolbar.vue_vue_type_script_setup_true_lang.js +161 -0
  36. package/dist/components/flow-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  37. package/dist/components/group-node.js +6 -0
  38. package/dist/components/group-node.js.map +1 -0
  39. package/dist/components/group-node.vue_vue_type_script_setup_true_lang.js +84 -0
  40. package/dist/components/group-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/components/group-node2.js +42 -0
  42. package/dist/components/group-node2.js.map +1 -0
  43. package/dist/components/node-view.js +6 -0
  44. package/dist/components/node-view.js.map +1 -0
  45. package/dist/components/node-view.vue_vue_type_script_setup_true_lang.js +179 -0
  46. package/dist/components/node-view.vue_vue_type_script_setup_true_lang.js.map +1 -0
  47. package/dist/context.js +13 -0
  48. package/dist/context.js.map +1 -0
  49. package/dist/core/clipboard.js +37 -0
  50. package/dist/core/clipboard.js.map +1 -0
  51. package/dist/core/connect.js +74 -0
  52. package/dist/core/connect.js.map +1 -0
  53. package/dist/core/edge-types.js +240 -0
  54. package/dist/core/edge-types.js.map +1 -0
  55. package/dist/core/extension.js +63 -0
  56. package/dist/core/extension.js.map +1 -0
  57. package/dist/core/geometry.js +232 -0
  58. package/dist/core/geometry.js.map +1 -0
  59. package/dist/core/groups.js +76 -0
  60. package/dist/core/groups.js.map +1 -0
  61. package/dist/core/history.js +79 -0
  62. package/dist/core/history.js.map +1 -0
  63. package/dist/core/model.js +21 -0
  64. package/dist/core/model.js.map +1 -0
  65. package/dist/core/node-types.js +32 -0
  66. package/dist/core/node-types.js.map +1 -0
  67. package/dist/core/order.js +42 -0
  68. package/dist/core/order.js.map +1 -0
  69. package/dist/core/transaction.js +288 -0
  70. package/dist/core/transaction.js.map +1 -0
  71. package/dist/document.js +220 -0
  72. package/dist/document.js.map +1 -0
  73. package/dist/export/color.js +38 -0
  74. package/dist/export/color.js.map +1 -0
  75. package/dist/export/context.js +223 -0
  76. package/dist/export/context.js.map +1 -0
  77. package/dist/export/draw.js +249 -0
  78. package/dist/export/draw.js.map +1 -0
  79. package/dist/flow-editor.js +6 -0
  80. package/dist/flow-editor.js.map +1 -0
  81. package/dist/flow-editor.vue_vue_type_script_setup_true_lang.js +3721 -0
  82. package/dist/flow-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
  83. package/dist/index.js +31 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/kit.js +234 -0
  86. package/dist/kit.js.map +1 -0
  87. package/dist/layers.js +150 -0
  88. package/dist/layers.js.map +1 -0
  89. package/dist/locale.js +426 -0
  90. package/dist/locale.js.map +1 -0
  91. package/dist/style.css +1 -0
  92. package/dist/style.d.ts +1 -0
  93. package/dist/style.js +8 -0
  94. package/dist/types/collaboration/index.d.ts +42 -0
  95. package/dist/types/collaboration/peers.d.ts +27 -0
  96. package/dist/types/components/default-node.d.ts +18 -0
  97. package/dist/types/components/default-node.vue.d.ts +6 -0
  98. package/dist/types/components/flow-controls.vue.d.ts +8 -0
  99. package/dist/types/components/flow-menu.vue.d.ts +29 -0
  100. package/dist/types/components/flow-minimap.vue.d.ts +8 -0
  101. package/dist/types/components/flow-port.vue.d.ts +9 -0
  102. package/dist/types/components/flow-quick-add.vue.d.ts +31 -0
  103. package/dist/types/components/flow-toolbar.vue.d.ts +25 -0
  104. package/dist/types/components/group-node.d.ts +13 -0
  105. package/dist/types/components/group-node.vue.d.ts +6 -0
  106. package/dist/types/components/node-view.vue.d.ts +28 -0
  107. package/dist/types/context.d.ts +362 -0
  108. package/dist/types/core/clipboard.d.ts +9 -0
  109. package/dist/types/core/connect.d.ts +63 -0
  110. package/dist/types/core/edge-types.d.ts +50 -0
  111. package/dist/types/core/extension.d.ts +141 -0
  112. package/dist/types/core/geometry.d.ts +63 -0
  113. package/dist/types/core/groups.d.ts +23 -0
  114. package/dist/types/core/history.d.ts +24 -0
  115. package/dist/types/core/menu.d.ts +40 -0
  116. package/dist/types/core/model.d.ts +87 -0
  117. package/dist/types/core/node-types.d.ts +103 -0
  118. package/dist/types/core/order.d.ts +7 -0
  119. package/dist/types/core/transaction.d.ts +97 -0
  120. package/dist/types/document.d.ts +57 -0
  121. package/dist/types/export/color.d.ts +7 -0
  122. package/dist/types/export/context.d.ts +90 -0
  123. package/dist/types/export/draw.d.ts +72 -0
  124. package/dist/types/flow-editor.vue.d.ts +63 -0
  125. package/dist/types/index.d.ts +202 -0
  126. package/dist/types/kit.d.ts +22 -0
  127. package/dist/types/layers.d.ts +24 -0
  128. package/dist/types/locale.d.ts +172 -0
  129. package/dist/types/whiteboard/board-draft.vue.d.ts +3 -0
  130. package/dist/types/whiteboard/board-node.vue.d.ts +19 -0
  131. package/dist/types/whiteboard/connector.d.ts +12 -0
  132. package/dist/types/whiteboard/draw.d.ts +5 -0
  133. package/dist/types/whiteboard/image.d.ts +27 -0
  134. package/dist/types/whiteboard/index.d.ts +6 -0
  135. package/dist/types/whiteboard/ink.d.ts +30 -0
  136. package/dist/types/whiteboard/kit.d.ts +36 -0
  137. package/dist/types/whiteboard/model.d.ts +79 -0
  138. package/dist/types/whiteboard/pen.d.ts +7 -0
  139. package/dist/types/whiteboard/state.d.ts +37 -0
  140. package/dist/types/whiteboard/transform.d.ts +67 -0
  141. package/dist/types/whiteboard/whiteboard-toolbar.vue.d.ts +3 -0
  142. package/dist/whiteboard/board-draft.js +6 -0
  143. package/dist/whiteboard/board-draft.js.map +1 -0
  144. package/dist/whiteboard/board-draft.vue_vue_type_script_setup_true_lang.js +58 -0
  145. package/dist/whiteboard/board-draft.vue_vue_type_script_setup_true_lang.js.map +1 -0
  146. package/dist/whiteboard/board-node.js +6 -0
  147. package/dist/whiteboard/board-node.js.map +1 -0
  148. package/dist/whiteboard/board-node.vue_vue_type_script_setup_true_lang.js +332 -0
  149. package/dist/whiteboard/board-node.vue_vue_type_script_setup_true_lang.js.map +1 -0
  150. package/dist/whiteboard/connector.js +180 -0
  151. package/dist/whiteboard/connector.js.map +1 -0
  152. package/dist/whiteboard/draw.js +68 -0
  153. package/dist/whiteboard/draw.js.map +1 -0
  154. package/dist/whiteboard/image.js +104 -0
  155. package/dist/whiteboard/image.js.map +1 -0
  156. package/dist/whiteboard/index.js +5 -0
  157. package/dist/whiteboard/ink.js +168 -0
  158. package/dist/whiteboard/ink.js.map +1 -0
  159. package/dist/whiteboard/kit.js +406 -0
  160. package/dist/whiteboard/kit.js.map +1 -0
  161. package/dist/whiteboard/model.js +52 -0
  162. package/dist/whiteboard/model.js.map +1 -0
  163. package/dist/whiteboard/pen.js +88 -0
  164. package/dist/whiteboard/pen.js.map +1 -0
  165. package/dist/whiteboard/state.js +26 -0
  166. package/dist/whiteboard/state.js.map +1 -0
  167. package/dist/whiteboard/transform.js +256 -0
  168. package/dist/whiteboard/transform.js.map +1 -0
  169. package/dist/whiteboard/whiteboard-toolbar.js +6 -0
  170. package/dist/whiteboard/whiteboard-toolbar.js.map +1 -0
  171. package/dist/whiteboard/whiteboard-toolbar.vue_vue_type_script_setup_true_lang.js +171 -0
  172. package/dist/whiteboard/whiteboard-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  173. package/package.json +74 -4
@@ -0,0 +1,288 @@
1
+ // src/core/transaction.ts
2
+ function patched(item, patch) {
3
+ const next = { ...item };
4
+ for (const key of Object.keys(patch)) {
5
+ const value = patch[key];
6
+ if (value === void 0) delete next[key];
7
+ else next[key] = value;
8
+ }
9
+ return next;
10
+ }
11
+ /** The values `patch` would replace, so applying them restores `item`. */
12
+ function previous(item, patch) {
13
+ const old = {};
14
+ for (const key of Object.keys(patch)) old[key] = item[key];
15
+ return old;
16
+ }
17
+ var sameValue = (a, b) => a === b;
18
+ /**
19
+ * A batch of changes to a document. Steps apply as they are added, so
20
+ * later calls see earlier ones; `doc` is the result.
21
+ */
22
+ var FlowTransaction = class {
23
+ before;
24
+ steps = [];
25
+ /** The steps that undo this transaction, in the order to apply them. */
26
+ inverse = [];
27
+ meta = {};
28
+ nodes;
29
+ edges;
30
+ copied = {
31
+ nodes: false,
32
+ edges: false
33
+ };
34
+ constructor(before) {
35
+ this.before = before;
36
+ this.nodes = before.nodes;
37
+ this.edges = before.edges;
38
+ }
39
+ get doc() {
40
+ if (!this.steps.length) return this.before;
41
+ return {
42
+ ...this.before,
43
+ nodes: this.nodes,
44
+ edges: this.edges
45
+ };
46
+ }
47
+ /** Whether the change makes any change: it has steps and wasn't cancelled. */
48
+ get changed() {
49
+ return this.steps.length > 0 && !this.stopped;
50
+ }
51
+ stopped = null;
52
+ /**
53
+ * Stops the change: none of it is applied or kept for undo, and the
54
+ * editor fires `transactionCancel` and says why. For an extension's `transaction`
55
+ * hook, e.g. to keep people from changing what they may not; hooks after
56
+ * it don't run. A change kept out of the history (`meta.history: false`),
57
+ * such as undo, redo or another person's, can't be cancelled and goes ahead.
58
+ */
59
+ cancel(reason) {
60
+ if (this.meta.history !== false) this.stopped = { reason };
61
+ else warnUncancellable();
62
+ return this;
63
+ }
64
+ /** Whether `cancel` stopped it. */
65
+ get cancelled() {
66
+ return this.stopped !== null;
67
+ }
68
+ /** What `cancel` was given, to tell people why. */
69
+ get cancelReason() {
70
+ return this.stopped?.reason;
71
+ }
72
+ getNode(id) {
73
+ return this.nodes.find((node) => node.id === id);
74
+ }
75
+ getEdge(id) {
76
+ return this.edges.find((edge) => edge.id === id);
77
+ }
78
+ /** Edges that start or end at a node. */
79
+ edgesOf(id) {
80
+ return this.edges.filter((edge) => edge.source.node === id || edge.target.node === id);
81
+ }
82
+ setMeta(meta) {
83
+ Object.assign(this.meta, meta);
84
+ return this;
85
+ }
86
+ step(step) {
87
+ const inverse = this.apply(step);
88
+ if (inverse) {
89
+ this.steps.push(step);
90
+ this.inverse.unshift(inverse);
91
+ }
92
+ return this;
93
+ }
94
+ addNode(node, index) {
95
+ return this.step({
96
+ type: "addNode",
97
+ node,
98
+ index
99
+ });
100
+ }
101
+ /** Removes a node and every edge attached to it. */
102
+ removeNode(id) {
103
+ for (const edge of this.edgesOf(id)) this.removeEdge(edge.id);
104
+ return this.step({
105
+ type: "removeNode",
106
+ id
107
+ });
108
+ }
109
+ updateNode(id, patch) {
110
+ return this.step({
111
+ type: "updateNode",
112
+ id,
113
+ patch
114
+ });
115
+ }
116
+ moveNode(id, x, y) {
117
+ return this.updateNode(id, {
118
+ x,
119
+ y
120
+ });
121
+ }
122
+ /** Merges `patch` into the node's `data`. */
123
+ updateData(id, patch) {
124
+ const node = this.getNode(id);
125
+ if (!node) return this;
126
+ return this.updateNode(id, { data: {
127
+ ...node.data,
128
+ ...patch
129
+ } });
130
+ }
131
+ addEdge(edge, index) {
132
+ return this.step({
133
+ type: "addEdge",
134
+ edge,
135
+ index
136
+ });
137
+ }
138
+ removeEdge(id) {
139
+ return this.step({
140
+ type: "removeEdge",
141
+ id
142
+ });
143
+ }
144
+ updateEdge(id, patch) {
145
+ return this.step({
146
+ type: "updateEdge",
147
+ id,
148
+ patch
149
+ });
150
+ }
151
+ writable(kind) {
152
+ if (!this.copied[kind]) {
153
+ this.copied[kind] = true;
154
+ if (kind === "nodes") this.nodes = this.nodes.slice();
155
+ else this.edges = this.edges.slice();
156
+ }
157
+ return kind === "nodes" ? this.nodes : this.edges;
158
+ }
159
+ /** Applies a step and returns its inverse, or `null` when it changes nothing. */
160
+ apply(step) {
161
+ switch (step.type) {
162
+ case "addNode": {
163
+ if (this.getNode(step.node.id)) return null;
164
+ const list = this.writable("nodes");
165
+ const index = clampIndex(step.index, list.length);
166
+ list.splice(index, 0, step.node);
167
+ return {
168
+ type: "removeNode",
169
+ id: step.node.id
170
+ };
171
+ }
172
+ case "removeNode": {
173
+ const index = this.nodes.findIndex((node) => node.id === step.id);
174
+ if (index < 0) return null;
175
+ const [node] = this.writable("nodes").splice(index, 1);
176
+ return {
177
+ type: "addNode",
178
+ node,
179
+ index
180
+ };
181
+ }
182
+ case "updateNode": {
183
+ const index = this.nodes.findIndex((node) => node.id === step.id);
184
+ if (index < 0) return null;
185
+ const node = this.nodes[index];
186
+ if (Object.keys(step.patch).every((key) => sameValue(node[key], step.patch[key]))) return null;
187
+ const list = this.writable("nodes");
188
+ list[index] = patched(node, step.patch);
189
+ return {
190
+ type: "updateNode",
191
+ id: step.id,
192
+ patch: previous(node, step.patch)
193
+ };
194
+ }
195
+ case "addEdge": {
196
+ if (this.getEdge(step.edge.id)) return null;
197
+ const list = this.writable("edges");
198
+ list.splice(clampIndex(step.index, list.length), 0, step.edge);
199
+ return {
200
+ type: "removeEdge",
201
+ id: step.edge.id
202
+ };
203
+ }
204
+ case "removeEdge": {
205
+ const index = this.edges.findIndex((edge) => edge.id === step.id);
206
+ if (index < 0) return null;
207
+ const [edge] = this.writable("edges").splice(index, 1);
208
+ return {
209
+ type: "addEdge",
210
+ edge,
211
+ index
212
+ };
213
+ }
214
+ case "updateEdge": {
215
+ const index = this.edges.findIndex((edge) => edge.id === step.id);
216
+ if (index < 0) return null;
217
+ const edge = this.edges[index];
218
+ if (Object.keys(step.patch).every((key) => sameValue(edge[key], step.patch[key]))) return null;
219
+ const list = this.writable("edges");
220
+ list[index] = patched(edge, step.patch);
221
+ return {
222
+ type: "updateEdge",
223
+ id: step.id,
224
+ patch: previous(edge, step.patch)
225
+ };
226
+ }
227
+ }
228
+ }
229
+ };
230
+ function clampIndex(index, length) {
231
+ if (index == null) return length;
232
+ return Math.max(0, Math.min(length, index));
233
+ }
234
+ /** Applies steps to a document; steps that no longer apply are skipped. */
235
+ function applyFlowSteps(doc, steps) {
236
+ const tr = new FlowTransaction(doc);
237
+ for (const step of steps) tr.step(step);
238
+ return tr;
239
+ }
240
+ var targetOf = (step) => {
241
+ switch (step.type) {
242
+ case "addNode": return `n:${step.node.id}`;
243
+ case "addEdge": return `e:${step.edge.id}`;
244
+ case "removeNode":
245
+ case "updateNode": return `n:${step.id}`;
246
+ case "removeEdge":
247
+ case "updateEdge": return `e:${step.id}`;
248
+ }
249
+ };
250
+ /**
251
+ * Folds updates of the same node or edge into one, as long as nothing
252
+ * adds or removes it in between. Updates of different targets commute, so
253
+ * the result applies the same as the original list.
254
+ */
255
+ function compactFlowSteps(steps) {
256
+ const out = [];
257
+ const lastUpdate = /* @__PURE__ */ new Map();
258
+ for (const step of steps) {
259
+ const target = targetOf(step);
260
+ if (step.type === "updateNode" || step.type === "updateEdge") {
261
+ const at = lastUpdate.get(target);
262
+ if (at != null) {
263
+ const prev = out[at];
264
+ out[at] = {
265
+ ...prev,
266
+ patch: {
267
+ ...prev.patch,
268
+ ...step.patch
269
+ }
270
+ };
271
+ continue;
272
+ }
273
+ lastUpdate.set(target, out.length);
274
+ } else lastUpdate.delete(target);
275
+ out.push(step);
276
+ }
277
+ return out.filter((step) => step != null);
278
+ }
279
+ var warnedUncancellable = false;
280
+ /** Says once that cancelling a change kept out of the history does nothing. */
281
+ function warnUncancellable() {
282
+ if (process.env.NODE_ENV === "production" || warnedUncancellable) return;
283
+ warnedUncancellable = true;
284
+ console.warn("[@dou-ui/flow] a change kept out of the history (undo, redo or another person's) can't be cancelled; it goes ahead");
285
+ }
286
+ export { FlowTransaction, applyFlowSteps, compactFlowSteps };
287
+
288
+ //# sourceMappingURL=transaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction.js","names":[],"sources":["../../src/core/transaction.ts"],"sourcesContent":["import type { FlowDocument, FlowEdge, FlowNode } from './model'\n\n/*\n * Every change to a document is a list of steps. Applying a step also\n * gives the step that undoes it, so history, collaboration and the\n * `transaction` event all work on the same small vocabulary.\n */\n\ntype Patch<T> = { [K in keyof T]?: T[K] | undefined }\n\nexport type FlowStep =\n | { type: 'addNode'; node: FlowNode; index?: number }\n | { type: 'removeNode'; id: string }\n | { type: 'updateNode'; id: string; patch: Patch<FlowNode> }\n | { type: 'addEdge'; edge: FlowEdge; index?: number }\n | { type: 'removeEdge'; id: string }\n | { type: 'updateEdge'; id: string; patch: Patch<FlowEdge> }\n\nexport interface FlowTransactionMeta {\n /** Keep out of the undo history, e.g. changes from another client. */\n history?: boolean\n /**\n * Consecutive transactions in the same group within a second undo as\n * one, e.g. the frames of a drag or repeated arrow key nudges.\n */\n group?: string\n /** Who made the change, passed on to the `transaction` event. */\n origin?: string\n}\n\nfunction patched<T extends object>(item: T, patch: Patch<T>): T {\n const next = { ...item } as Record<string, unknown>\n for (const key of Object.keys(patch)) {\n const value = (patch as Record<string, unknown>)[key]\n if (value === undefined) delete next[key]\n else next[key] = value\n }\n return next as T\n}\n\n/** The values `patch` would replace, so applying them restores `item`. */\nfunction previous<T extends object>(item: T, patch: Patch<T>): Patch<T> {\n const old: Record<string, unknown> = {}\n for (const key of Object.keys(patch)) old[key] = (item as Record<string, unknown>)[key]\n return old as Patch<T>\n}\n\nconst sameValue = (a: unknown, b: unknown) => a === b\n\n/**\n * A batch of changes to a document. Steps apply as they are added, so\n * later calls see earlier ones; `doc` is the result.\n */\nexport class FlowTransaction {\n readonly steps: FlowStep[] = []\n /** The steps that undo this transaction, in the order to apply them. */\n readonly inverse: FlowStep[] = []\n meta: FlowTransactionMeta = {}\n private nodes: FlowNode[]\n private edges: FlowEdge[]\n private copied = { nodes: false, edges: false }\n\n constructor(readonly before: FlowDocument) {\n this.nodes = before.nodes\n this.edges = before.edges\n }\n\n get doc(): FlowDocument {\n if (!this.steps.length) return this.before\n return { ...this.before, nodes: this.nodes, edges: this.edges }\n }\n\n /** Whether the change makes any change: it has steps and wasn't cancelled. */\n get changed() {\n return this.steps.length > 0 && !this.stopped\n }\n\n private stopped: { reason?: string } | null = null\n\n /**\n * Stops the change: none of it is applied or kept for undo, and the\n * editor fires `transactionCancel` and says why. For an extension's `transaction`\n * hook, e.g. to keep people from changing what they may not; hooks after\n * it don't run. A change kept out of the history (`meta.history: false`),\n * such as undo, redo or another person's, can't be cancelled and goes ahead.\n */\n cancel(reason?: string) {\n if (this.meta.history !== false) this.stopped = { reason }\n else warnUncancellable()\n return this\n }\n\n /** Whether `cancel` stopped it. */\n get cancelled() {\n return this.stopped !== null\n }\n\n /** What `cancel` was given, to tell people why. */\n get cancelReason() {\n return this.stopped?.reason\n }\n\n getNode(id: string) {\n return this.nodes.find((node) => node.id === id)\n }\n\n getEdge(id: string) {\n return this.edges.find((edge) => edge.id === id)\n }\n\n /** Edges that start or end at a node. */\n edgesOf(id: string) {\n return this.edges.filter((edge) => edge.source.node === id || edge.target.node === id)\n }\n\n setMeta(meta: FlowTransactionMeta) {\n Object.assign(this.meta, meta)\n return this\n }\n\n step(step: FlowStep) {\n const inverse = this.apply(step)\n if (inverse) {\n this.steps.push(step)\n this.inverse.unshift(inverse)\n }\n return this\n }\n\n addNode(node: FlowNode, index?: number) {\n return this.step({ type: 'addNode', node, index })\n }\n\n /** Removes a node and every edge attached to it. */\n removeNode(id: string) {\n for (const edge of this.edgesOf(id)) this.removeEdge(edge.id)\n return this.step({ type: 'removeNode', id })\n }\n\n updateNode(id: string, patch: Patch<FlowNode>) {\n return this.step({ type: 'updateNode', id, patch })\n }\n\n moveNode(id: string, x: number, y: number) {\n return this.updateNode(id, { x, y })\n }\n\n /** Merges `patch` into the node's `data`. */\n updateData(id: string, patch: Record<string, unknown>) {\n const node = this.getNode(id)\n if (!node) return this\n return this.updateNode(id, { data: { ...node.data, ...patch } })\n }\n\n addEdge(edge: FlowEdge, index?: number) {\n return this.step({ type: 'addEdge', edge, index })\n }\n\n removeEdge(id: string) {\n return this.step({ type: 'removeEdge', id })\n }\n\n updateEdge(id: string, patch: Patch<FlowEdge>) {\n return this.step({ type: 'updateEdge', id, patch })\n }\n\n private writable(kind: 'nodes' | 'edges') {\n if (!this.copied[kind]) {\n this.copied[kind] = true\n if (kind === 'nodes') this.nodes = this.nodes.slice()\n else this.edges = this.edges.slice()\n }\n return kind === 'nodes' ? this.nodes : this.edges\n }\n\n /** Applies a step and returns its inverse, or `null` when it changes nothing. */\n private apply(step: FlowStep): FlowStep | null {\n switch (step.type) {\n case 'addNode': {\n if (this.getNode(step.node.id)) return null\n const list = this.writable('nodes') as FlowNode[]\n const index = clampIndex(step.index, list.length)\n list.splice(index, 0, step.node)\n return { type: 'removeNode', id: step.node.id }\n }\n case 'removeNode': {\n const index = this.nodes.findIndex((node) => node.id === step.id)\n if (index < 0) return null\n const list = this.writable('nodes') as FlowNode[]\n const [node] = list.splice(index, 1)\n return { type: 'addNode', node: node!, index }\n }\n case 'updateNode': {\n const index = this.nodes.findIndex((node) => node.id === step.id)\n if (index < 0) return null\n const node = this.nodes[index]!\n const keys = Object.keys(step.patch) as (keyof FlowNode)[]\n if (keys.every((key) => sameValue(node[key], step.patch[key]))) return null\n const list = this.writable('nodes') as FlowNode[]\n list[index] = patched(node, step.patch)\n return { type: 'updateNode', id: step.id, patch: previous(node, step.patch) }\n }\n case 'addEdge': {\n if (this.getEdge(step.edge.id)) return null\n const list = this.writable('edges') as FlowEdge[]\n list.splice(clampIndex(step.index, list.length), 0, step.edge)\n return { type: 'removeEdge', id: step.edge.id }\n }\n case 'removeEdge': {\n const index = this.edges.findIndex((edge) => edge.id === step.id)\n if (index < 0) return null\n const list = this.writable('edges') as FlowEdge[]\n const [edge] = list.splice(index, 1)\n return { type: 'addEdge', edge: edge!, index }\n }\n case 'updateEdge': {\n const index = this.edges.findIndex((edge) => edge.id === step.id)\n if (index < 0) return null\n const edge = this.edges[index]!\n const keys = Object.keys(step.patch) as (keyof FlowEdge)[]\n if (keys.every((key) => sameValue(edge[key], step.patch[key]))) return null\n const list = this.writable('edges') as FlowEdge[]\n list[index] = patched(edge, step.patch)\n return { type: 'updateEdge', id: step.id, patch: previous(edge, step.patch) }\n }\n }\n }\n}\n\nfunction clampIndex(index: number | undefined, length: number) {\n if (index == null) return length\n return Math.max(0, Math.min(length, index))\n}\n\n/** Applies steps to a document; steps that no longer apply are skipped. */\nexport function applyFlowSteps(doc: FlowDocument, steps: readonly FlowStep[]) {\n const tr = new FlowTransaction(doc)\n for (const step of steps) tr.step(step)\n return tr\n}\n\nconst targetOf = (step: FlowStep) => {\n switch (step.type) {\n case 'addNode':\n return `n:${step.node.id}`\n case 'addEdge':\n return `e:${step.edge.id}`\n case 'removeNode':\n case 'updateNode':\n return `n:${step.id}`\n case 'removeEdge':\n case 'updateEdge':\n return `e:${step.id}`\n }\n}\n\n/**\n * Folds updates of the same node or edge into one, as long as nothing\n * adds or removes it in between. Updates of different targets commute, so\n * the result applies the same as the original list.\n */\nexport function compactFlowSteps(steps: readonly FlowStep[]): FlowStep[] {\n const out: (FlowStep | null)[] = []\n const lastUpdate = new Map<string, number>()\n for (const step of steps) {\n const target = targetOf(step)\n if (step.type === 'updateNode' || step.type === 'updateEdge') {\n const at = lastUpdate.get(target)\n if (at != null) {\n const prev = out[at] as typeof step\n out[at] = { ...prev, patch: { ...prev.patch, ...step.patch } } as FlowStep\n continue\n }\n lastUpdate.set(target, out.length)\n } else {\n lastUpdate.delete(target)\n }\n out.push(step)\n }\n return out.filter((step): step is FlowStep => step != null)\n}\n\nlet warnedUncancellable = false\n/** Says once that cancelling a change kept out of the history does nothing. */\nfunction warnUncancellable() {\n // the app's bundler replaces this, as for Vue's own warnings\n if (process.env.NODE_ENV === 'production' || warnedUncancellable) return\n warnedUncancellable = true\n console.warn(\n \"[@dou-ui/flow] a change kept out of the history (undo, redo or another person's) can't be cancelled; it goes ahead\",\n )\n}\n"],"mappings":";AA8BA,SAAS,QAA0B,MAAS,OAAoB;CAC9D,MAAM,OAAO,EAAE,GAAG,KAAK;CACvB,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GAAG;EACpC,MAAM,QAAS,MAAkC;EACjD,IAAI,UAAU,KAAA,GAAW,OAAO,KAAK;OAChC,KAAK,OAAO;CACnB;CACA,OAAO;AACT;;AAGA,SAAS,SAA2B,MAAS,OAA2B;CACtE,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GAAG,IAAI,OAAQ,KAAiC;CACnF,OAAO;AACT;AAEA,IAAM,aAAa,GAAY,MAAe,MAAM;;;;;AAMpD,IAAa,kBAAb,MAA6B;CASN;CARrB,QAA6B,CAAC;;CAE9B,UAA+B,CAAC;CAChC,OAA4B,CAAC;CAC7B;CACA;CACA,SAAiB;EAAE,OAAO;EAAO,OAAO;CAAM;CAE9C,YAAY,QAA+B;EAAtB,KAAA,SAAA;EACnB,KAAK,QAAQ,OAAO;EACpB,KAAK,QAAQ,OAAO;CACtB;CAEA,IAAI,MAAoB;EACtB,IAAI,CAAC,KAAK,MAAM,QAAQ,OAAO,KAAK;EACpC,OAAO;GAAE,GAAG,KAAK;GAAQ,OAAO,KAAK;GAAO,OAAO,KAAK;EAAM;CAChE;;CAGA,IAAI,UAAU;EACZ,OAAO,KAAK,MAAM,SAAS,KAAK,CAAC,KAAK;CACxC;CAEA,UAA8C;;;;;;;;CAS9C,OAAO,QAAiB;EACtB,IAAI,KAAK,KAAK,YAAY,OAAO,KAAK,UAAU,EAAE,OAAO;OACpD,kBAAkB;EACvB,OAAO;CACT;;CAGA,IAAI,YAAY;EACd,OAAO,KAAK,YAAY;CAC1B;;CAGA,IAAI,eAAe;EACjB,OAAO,KAAK,SAAS;CACvB;CAEA,QAAQ,IAAY;EAClB,OAAO,KAAK,MAAM,MAAM,SAAS,KAAK,OAAO,EAAE;CACjD;CAEA,QAAQ,IAAY;EAClB,OAAO,KAAK,MAAM,MAAM,SAAS,KAAK,OAAO,EAAE;CACjD;;CAGA,QAAQ,IAAY;EAClB,OAAO,KAAK,MAAM,QAAQ,SAAS,KAAK,OAAO,SAAS,MAAM,KAAK,OAAO,SAAS,EAAE;CACvF;CAEA,QAAQ,MAA2B;EACjC,OAAO,OAAO,KAAK,MAAM,IAAI;EAC7B,OAAO;CACT;CAEA,KAAK,MAAgB;EACnB,MAAM,UAAU,KAAK,MAAM,IAAI;EAC/B,IAAI,SAAS;GACX,KAAK,MAAM,KAAK,IAAI;GACpB,KAAK,QAAQ,QAAQ,OAAO;EAC9B;EACA,OAAO;CACT;CAEA,QAAQ,MAAgB,OAAgB;EACtC,OAAO,KAAK,KAAK;GAAE,MAAM;GAAW;GAAM;EAAM,CAAC;CACnD;;CAGA,WAAW,IAAY;EACrB,KAAK,MAAM,QAAQ,KAAK,QAAQ,EAAE,GAAG,KAAK,WAAW,KAAK,EAAE;EAC5D,OAAO,KAAK,KAAK;GAAE,MAAM;GAAc;EAAG,CAAC;CAC7C;CAEA,WAAW,IAAY,OAAwB;EAC7C,OAAO,KAAK,KAAK;GAAE,MAAM;GAAc;GAAI;EAAM,CAAC;CACpD;CAEA,SAAS,IAAY,GAAW,GAAW;EACzC,OAAO,KAAK,WAAW,IAAI;GAAE;GAAG;EAAE,CAAC;CACrC;;CAGA,WAAW,IAAY,OAAgC;EACrD,MAAM,OAAO,KAAK,QAAQ,EAAE;EAC5B,IAAI,CAAC,MAAM,OAAO;EAClB,OAAO,KAAK,WAAW,IAAI,EAAE,MAAM;GAAE,GAAG,KAAK;GAAM,GAAG;EAAM,EAAE,CAAC;CACjE;CAEA,QAAQ,MAAgB,OAAgB;EACtC,OAAO,KAAK,KAAK;GAAE,MAAM;GAAW;GAAM;EAAM,CAAC;CACnD;CAEA,WAAW,IAAY;EACrB,OAAO,KAAK,KAAK;GAAE,MAAM;GAAc;EAAG,CAAC;CAC7C;CAEA,WAAW,IAAY,OAAwB;EAC7C,OAAO,KAAK,KAAK;GAAE,MAAM;GAAc;GAAI;EAAM,CAAC;CACpD;CAEA,SAAiB,MAAyB;EACxC,IAAI,CAAC,KAAK,OAAO,OAAO;GACtB,KAAK,OAAO,QAAQ;GACpB,IAAI,SAAS,SAAS,KAAK,QAAQ,KAAK,MAAM,MAAM;QAC/C,KAAK,QAAQ,KAAK,MAAM,MAAM;EACrC;EACA,OAAO,SAAS,UAAU,KAAK,QAAQ,KAAK;CAC9C;;CAGA,MAAc,MAAiC;EAC7C,QAAQ,KAAK,MAAb;GACE,KAAK,WAAW;IACd,IAAI,KAAK,QAAQ,KAAK,KAAK,EAAE,GAAG,OAAO;IACvC,MAAM,OAAO,KAAK,SAAS,OAAO;IAClC,MAAM,QAAQ,WAAW,KAAK,OAAO,KAAK,MAAM;IAChD,KAAK,OAAO,OAAO,GAAG,KAAK,IAAI;IAC/B,OAAO;KAAE,MAAM;KAAc,IAAI,KAAK,KAAK;IAAG;GAChD;GACA,KAAK,cAAc;IACjB,MAAM,QAAQ,KAAK,MAAM,WAAW,SAAS,KAAK,OAAO,KAAK,EAAE;IAChE,IAAI,QAAQ,GAAG,OAAO;IAEtB,MAAM,CAAC,QADM,KAAK,SAAS,OACZ,CAAA,CAAK,OAAO,OAAO,CAAC;IACnC,OAAO;KAAE,MAAM;KAAiB;KAAO;IAAM;GAC/C;GACA,KAAK,cAAc;IACjB,MAAM,QAAQ,KAAK,MAAM,WAAW,SAAS,KAAK,OAAO,KAAK,EAAE;IAChE,IAAI,QAAQ,GAAG,OAAO;IACtB,MAAM,OAAO,KAAK,MAAM;IAExB,IADa,OAAO,KAAK,KAAK,KAC1B,CAAA,CAAK,OAAO,QAAQ,UAAU,KAAK,MAAM,KAAK,MAAM,IAAI,CAAC,GAAG,OAAO;IACvE,MAAM,OAAO,KAAK,SAAS,OAAO;IAClC,KAAK,SAAS,QAAQ,MAAM,KAAK,KAAK;IACtC,OAAO;KAAE,MAAM;KAAc,IAAI,KAAK;KAAI,OAAO,SAAS,MAAM,KAAK,KAAK;IAAE;GAC9E;GACA,KAAK,WAAW;IACd,IAAI,KAAK,QAAQ,KAAK,KAAK,EAAE,GAAG,OAAO;IACvC,MAAM,OAAO,KAAK,SAAS,OAAO;IAClC,KAAK,OAAO,WAAW,KAAK,OAAO,KAAK,MAAM,GAAG,GAAG,KAAK,IAAI;IAC7D,OAAO;KAAE,MAAM;KAAc,IAAI,KAAK,KAAK;IAAG;GAChD;GACA,KAAK,cAAc;IACjB,MAAM,QAAQ,KAAK,MAAM,WAAW,SAAS,KAAK,OAAO,KAAK,EAAE;IAChE,IAAI,QAAQ,GAAG,OAAO;IAEtB,MAAM,CAAC,QADM,KAAK,SAAS,OACZ,CAAA,CAAK,OAAO,OAAO,CAAC;IACnC,OAAO;KAAE,MAAM;KAAiB;KAAO;IAAM;GAC/C;GACA,KAAK,cAAc;IACjB,MAAM,QAAQ,KAAK,MAAM,WAAW,SAAS,KAAK,OAAO,KAAK,EAAE;IAChE,IAAI,QAAQ,GAAG,OAAO;IACtB,MAAM,OAAO,KAAK,MAAM;IAExB,IADa,OAAO,KAAK,KAAK,KAC1B,CAAA,CAAK,OAAO,QAAQ,UAAU,KAAK,MAAM,KAAK,MAAM,IAAI,CAAC,GAAG,OAAO;IACvE,MAAM,OAAO,KAAK,SAAS,OAAO;IAClC,KAAK,SAAS,QAAQ,MAAM,KAAK,KAAK;IACtC,OAAO;KAAE,MAAM;KAAc,IAAI,KAAK;KAAI,OAAO,SAAS,MAAM,KAAK,KAAK;IAAE;GAC9E;EACF;CACF;AACF;AAEA,SAAS,WAAW,OAA2B,QAAgB;CAC7D,IAAI,SAAS,MAAM,OAAO;CAC1B,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,KAAK,CAAC;AAC5C;;AAGA,SAAgB,eAAe,KAAmB,OAA4B;CAC5E,MAAM,KAAK,IAAI,gBAAgB,GAAG;CAClC,KAAK,MAAM,QAAQ,OAAO,GAAG,KAAK,IAAI;CACtC,OAAO;AACT;AAEA,IAAM,YAAY,SAAmB;CACnC,QAAQ,KAAK,MAAb;EACE,KAAK,WACH,OAAO,KAAK,KAAK,KAAK;EACxB,KAAK,WACH,OAAO,KAAK,KAAK,KAAK;EACxB,KAAK;EACL,KAAK,cACH,OAAO,KAAK,KAAK;EACnB,KAAK;EACL,KAAK,cACH,OAAO,KAAK,KAAK;CACrB;AACF;;;;;;AAOA,SAAgB,iBAAiB,OAAwC;CACvE,MAAM,MAA2B,CAAC;CAClC,MAAM,6BAAa,IAAI,IAAoB;CAC3C,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,SAAS,IAAI;EAC5B,IAAI,KAAK,SAAS,gBAAgB,KAAK,SAAS,cAAc;GAC5D,MAAM,KAAK,WAAW,IAAI,MAAM;GAChC,IAAI,MAAM,MAAM;IACd,MAAM,OAAO,IAAI;IACjB,IAAI,MAAM;KAAE,GAAG;KAAM,OAAO;MAAE,GAAG,KAAK;MAAO,GAAG,KAAK;KAAM;IAAE;IAC7D;GACF;GACA,WAAW,IAAI,QAAQ,IAAI,MAAM;EACnC,OACE,WAAW,OAAO,MAAM;EAE1B,IAAI,KAAK,IAAI;CACf;CACA,OAAO,IAAI,QAAQ,SAA2B,QAAQ,IAAI;AAC5D;AAEA,IAAI,sBAAsB;;AAE1B,SAAS,oBAAoB;CAE3B,IAAA,QAAA,IAAA,aAA6B,gBAAgB,qBAAqB;CAClE,sBAAsB;CACtB,QAAQ,KACN,oHACF;AACF"}
@@ -0,0 +1,220 @@
1
+ import { portsOf } from "./core/node-types.js";
2
+ import { defaultNode } from "./components/default-node2.js";
3
+ import { resolveFlowExtensions } from "./core/extension.js";
4
+ import { flowKit } from "./kit.js";
5
+ // src/document.ts
6
+ var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
7
+ /**
8
+ * What is wrong with the shape itself: the document is not an object, or a
9
+ * list in it is not an array. Nothing else can be checked until it is fixed.
10
+ */
11
+ function shapeProblems(value, lists) {
12
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [{
13
+ code: "shape",
14
+ path: "",
15
+ text: "",
16
+ message: "The document is not an object"
17
+ }];
18
+ return lists.filter((key) => !Array.isArray(value[key])).map((key) => ({
19
+ code: "shape",
20
+ path: key,
21
+ text: "",
22
+ message: `"${key}" is not a list`
23
+ }));
24
+ }
25
+ /**
26
+ * The problems of a flow document, before it is saved or shown: an unknown
27
+ * version, nodes and edges without an id or sharing one, positions that are
28
+ * not numbers, edges whose ends are missing, `parent` pointing nowhere or
29
+ * in a circle, and, given the editor's types, types nobody registered and
30
+ * ports a node does not have.
31
+ */
32
+ function checkDocument(document, options = {}) {
33
+ const shape = shapeProblems(document, ["nodes", "edges"]);
34
+ if (shape.length) return shape;
35
+ const problems = [];
36
+ if (document.version !== 1) problems.push({
37
+ code: "version",
38
+ path: "version",
39
+ text: String(document.version),
40
+ message: `Version ${String(document.version)} is not known`
41
+ });
42
+ const resolved = options.extensions || options.nodeTypes || options.edgeTypes ? resolveFlowExtensions(options.extensions ?? flowKit()) : void 0;
43
+ const nodeTypes = resolved && new Map(resolved.nodeTypes);
44
+ for (const spec of options.nodeTypes ?? []) nodeTypes?.set(spec.type, spec);
45
+ const edgeTypes = resolved && new Set(resolved.edgeTypes.keys());
46
+ for (const spec of options.edgeTypes ?? []) edgeTypes?.add(spec.type);
47
+ const nodes = /* @__PURE__ */ new Map();
48
+ document.nodes.forEach((node, i) => {
49
+ const path = `nodes[${i}]`;
50
+ if (!node.id) {
51
+ problems.push({
52
+ code: "missing-id",
53
+ path,
54
+ text: "",
55
+ message: `${path} has no id`
56
+ });
57
+ return;
58
+ }
59
+ if (nodes.has(node.id)) problems.push({
60
+ code: "duplicate-id",
61
+ nodeId: node.id,
62
+ path,
63
+ text: node.id,
64
+ message: `The node id "${node.id}" is used more than once`
65
+ });
66
+ else nodes.set(node.id, node);
67
+ if (!isNumber(node.x) || !isNumber(node.y)) problems.push({
68
+ code: "bad-position",
69
+ nodeId: node.id,
70
+ path,
71
+ text: node.id,
72
+ message: `The node "${node.id}" has no numeric x and y`
73
+ });
74
+ if (nodeTypes && node.type && node.type !== "default" && !nodeTypes.has(node.type)) problems.push({
75
+ code: "unknown-type",
76
+ nodeId: node.id,
77
+ path,
78
+ text: node.type,
79
+ message: `No node type "${node.type}" is registered`
80
+ });
81
+ });
82
+ document.nodes.forEach((node, i) => {
83
+ if (!node.id || !node.parent || nodes.get(node.id) !== node) return;
84
+ const path = `nodes[${i}].parent`;
85
+ if (!nodes.has(node.parent)) {
86
+ problems.push({
87
+ code: "missing-parent",
88
+ nodeId: node.id,
89
+ path,
90
+ text: node.parent,
91
+ message: `The parent "${node.parent}" of "${node.id}" is not in the document`
92
+ });
93
+ return;
94
+ }
95
+ const seen = /* @__PURE__ */ new Set();
96
+ for (let at = nodes.get(node.parent); at; at = at.parent ? nodes.get(at.parent) : void 0) {
97
+ if (at.id !== node.id) {
98
+ if (seen.has(at.id)) break;
99
+ seen.add(at.id);
100
+ continue;
101
+ }
102
+ problems.push({
103
+ code: "parent-cycle",
104
+ nodeId: node.id,
105
+ path,
106
+ text: node.parent,
107
+ message: `The node "${node.id}" is inside itself through its parents`
108
+ });
109
+ break;
110
+ }
111
+ });
112
+ const edgeIds = /* @__PURE__ */ new Set();
113
+ document.edges.forEach((edge, i) => {
114
+ const path = `edges[${i}]`;
115
+ if (!edge.id) problems.push({
116
+ code: "missing-id",
117
+ path,
118
+ text: "",
119
+ message: `${path} has no id`
120
+ });
121
+ else if (edgeIds.has(edge.id)) problems.push({
122
+ code: "duplicate-id",
123
+ edgeId: edge.id,
124
+ path,
125
+ text: edge.id,
126
+ message: `The edge id "${edge.id}" is used more than once`
127
+ });
128
+ edgeIds.add(edge.id);
129
+ if (edgeTypes && edge.type && !edgeTypes.has(edge.type)) problems.push({
130
+ code: "unknown-type",
131
+ edgeId: edge.id,
132
+ path,
133
+ text: edge.type,
134
+ message: `No edge type "${edge.type}" is registered`
135
+ });
136
+ for (const end of ["source", "target"]) checkEnd(edge, end, `${path}.${end}`, nodes, nodeTypes, problems);
137
+ });
138
+ return problems;
139
+ }
140
+ function checkEnd(edge, end, path, nodes, nodeTypes, problems) {
141
+ const endpoint = edge[end];
142
+ const node = endpoint && nodes.get(endpoint.node);
143
+ if (!node) {
144
+ const id = String(endpoint?.node ?? "");
145
+ problems.push({
146
+ code: "missing-node",
147
+ edgeId: edge.id,
148
+ nodeId: id || void 0,
149
+ path,
150
+ text: id,
151
+ message: `The ${end} "${id}" of the edge "${edge.id}" is not in the document`
152
+ });
153
+ return;
154
+ }
155
+ if (!nodeTypes || endpoint.port === void 0) return;
156
+ const spec = nodeTypes.get(node.type ?? "default") ?? nodeTypes.get("default") ?? defaultNode;
157
+ if (portsOf(spec, node).some((port) => port.id === endpoint.port)) return;
158
+ problems.push({
159
+ code: "missing-port",
160
+ edgeId: edge.id,
161
+ nodeId: node.id,
162
+ path,
163
+ text: endpoint.port,
164
+ message: `The node "${node.id}" has no port "${endpoint.port}"`
165
+ });
166
+ }
167
+ var objects = (value) => (Array.isArray(value) ? value : []).filter((item) => !!item && typeof item === "object");
168
+ /** The saved data as an object of version 1, or an error saying why it can't be. */
169
+ function sourceOf(input) {
170
+ if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error(`[@dou-ui/flow] a flow document must be an object, not ${Array.isArray(input) ? "an array" : JSON.stringify(input) ?? String(input)}`);
171
+ const source = input;
172
+ const version = source.version ?? 1;
173
+ if (version !== 1) throw new Error(`[@dou-ui/flow] unknown document version ${JSON.stringify(version) ?? String(version)} (expected 1); data saved by a newer @dou-ui/flow needs that version to open`);
174
+ return source;
175
+ }
176
+ /**
177
+ * Brings a saved document up to the current version, and fills what an
178
+ * older or hand-written one may lack (`version`, `nodes`, `edges`). Nodes
179
+ * and edges that are not objects are dropped; anything else is kept as it
180
+ * is. Throws for data that is not an object
181
+ * (an array, `null`, a string) and for a version it does not know.
182
+ */
183
+ function migrateDocument(input) {
184
+ const source = sourceOf(input);
185
+ return {
186
+ ...source,
187
+ version: 1,
188
+ nodes: objects(source.nodes),
189
+ edges: objects(source.edges)
190
+ };
191
+ }
192
+ var warned = /* @__PURE__ */ new Set();
193
+ /**
194
+ * The `v-model` as the editor reads it. A document of the wrong shape (not
195
+ * an object, without `nodes` or `edges`, without a known `version`) gets a
196
+ * warning in development and is read with what is missing left empty and
197
+ * the version as 1, so the editor still opens and what it writes back
198
+ * carries `version: 1`.
199
+ */
200
+ function readDocument(value) {
201
+ const plain = !!value && typeof value === "object" && !Array.isArray(value);
202
+ const source = plain ? value : {};
203
+ const nodes = Array.isArray(source.nodes);
204
+ const edges = Array.isArray(source.edges);
205
+ const problem = !plain ? `is ${Array.isArray(value) ? "an array" : JSON.stringify(value) ?? String(value)}` : !nodes ? "has no nodes array" : !edges ? "has no edges array" : source.version === void 0 ? "has no version" : source.version !== 1 ? `has version ${JSON.stringify(source.version)}` : "";
206
+ if (!problem) return value;
207
+ if (process.env.NODE_ENV !== "production" && !warned.has(problem)) {
208
+ warned.add(problem);
209
+ console.warn(`[@dou-ui/flow] the v-model ${problem}; expected { version: 1, nodes: [...], edges: [...] }. What is missing is read as empty and the version as 1; read saved data with migrateDocument().`);
210
+ }
211
+ return {
212
+ ...source,
213
+ version: 1,
214
+ nodes: nodes ? source.nodes : [],
215
+ edges: edges ? source.edges : []
216
+ };
217
+ }
218
+ export { checkDocument, migrateDocument, readDocument };
219
+
220
+ //# sourceMappingURL=document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.js","names":[],"sources":["../src/document.ts"],"sourcesContent":["import { resolveFlowExtensions, type FlowExtension } from './core/extension'\nimport type { EdgeTypeSpec } from './core/edge-types'\nimport type { FlowDocument, FlowEdge, FlowNode } from './core/model'\nimport { portsOf, type NodeTypeSpec } from './core/node-types'\nimport { defaultNode } from './components/default-node'\nimport { flowKit } from './kit'\n\nexport interface DocumentProblem {\n code:\n | 'version'\n | 'shape'\n | 'missing-id'\n | 'duplicate-id'\n | 'bad-position'\n | 'unknown-type'\n | 'missing-node'\n | 'missing-port'\n | 'missing-parent'\n | 'parent-cycle'\n /** The node at fault, or the node an edge's end points at. */\n nodeId?: string\n /** The edge at fault. */\n edgeId?: string\n /**\n * Where the problem is, as a path into the document, e.g. `nodes[2]` or\n * `edges[0].target`; `version` for the version.\n */\n path: string\n /** The id, type or port at fault, for the editor's message. */\n text: string\n /** An English description, for logs. */\n message: string\n}\n\nexport interface CheckDocumentOptions {\n /**\n * The editor's `extensions`, for the node and edge types they add. With\n * none of `extensions`, `nodeTypes` and `edgeTypes`, types and ports are\n * not checked; with `nodeTypes` or `edgeTypes` alone, the default\n * `flowKit()` is assumed, as in the editor.\n */\n extensions?: readonly (FlowExtension | readonly FlowExtension[])[]\n /** The editor's `nodeTypes`. */\n nodeTypes?: readonly NodeTypeSpec<any>[]\n /** The editor's `edgeTypes`. */\n edgeTypes?: readonly EdgeTypeSpec<any>[]\n}\n\nconst isNumber = (value: unknown) => typeof value === 'number' && Number.isFinite(value)\n\n/**\n * What is wrong with the shape itself: the document is not an object, or a\n * list in it is not an array. Nothing else can be checked until it is fixed.\n */\nfunction shapeProblems(value: unknown, lists: string[]): DocumentProblem[] {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n return [{ code: 'shape', path: '', text: '', message: 'The document is not an object' }]\n return lists\n .filter((key) => !Array.isArray((value as Record<string, unknown>)[key]))\n .map((key) => ({ code: 'shape', path: key, text: '', message: `\"${key}\" is not a list` }))\n}\n\n/**\n * The problems of a flow document, before it is saved or shown: an unknown\n * version, nodes and edges without an id or sharing one, positions that are\n * not numbers, edges whose ends are missing, `parent` pointing nowhere or\n * in a circle, and, given the editor's types, types nobody registered and\n * ports a node does not have.\n */\nexport function checkDocument(\n document: FlowDocument,\n options: CheckDocumentOptions = {},\n): DocumentProblem[] {\n const shape = shapeProblems(document, ['nodes', 'edges'])\n if (shape.length) return shape\n const problems: DocumentProblem[] = []\n if (document.version !== 1)\n problems.push({\n code: 'version',\n path: 'version',\n text: String(document.version),\n message: `Version ${String(document.version)} is not known`,\n })\n\n const typed = options.extensions || options.nodeTypes || options.edgeTypes\n const resolved = typed ? resolveFlowExtensions(options.extensions ?? flowKit()) : undefined\n const nodeTypes = resolved && new Map<string, NodeTypeSpec>(resolved.nodeTypes)\n for (const spec of options.nodeTypes ?? []) nodeTypes?.set(spec.type, spec as NodeTypeSpec)\n const edgeTypes = resolved && new Set(resolved.edgeTypes.keys())\n for (const spec of options.edgeTypes ?? []) edgeTypes?.add(spec.type)\n\n const nodes = new Map<string, FlowNode>()\n document.nodes.forEach((node, i) => {\n const path = `nodes[${i}]`\n if (!node.id) {\n problems.push({ code: 'missing-id', path, text: '', message: `${path} has no id` })\n return\n }\n if (nodes.has(node.id))\n problems.push({\n code: 'duplicate-id',\n nodeId: node.id,\n path,\n text: node.id,\n message: `The node id \"${node.id}\" is used more than once`,\n })\n else nodes.set(node.id, node)\n if (!isNumber(node.x) || !isNumber(node.y))\n problems.push({\n code: 'bad-position',\n nodeId: node.id,\n path,\n text: node.id,\n message: `The node \"${node.id}\" has no numeric x and y`,\n })\n // A node without a type is drawn as the default node, registered or not.\n if (nodeTypes && node.type && node.type !== 'default' && !nodeTypes.has(node.type))\n problems.push({\n code: 'unknown-type',\n nodeId: node.id,\n path,\n text: node.type,\n message: `No node type \"${node.type}\" is registered`,\n })\n })\n\n document.nodes.forEach((node, i) => {\n if (!node.id || !node.parent || nodes.get(node.id) !== node) return\n const path = `nodes[${i}].parent`\n if (!nodes.has(node.parent)) {\n problems.push({\n code: 'missing-parent',\n nodeId: node.id,\n path,\n text: node.parent,\n message: `The parent \"${node.parent}\" of \"${node.id}\" is not in the document`,\n })\n return\n }\n // only the members of a circle are reported; one hanging below it is fine itself\n const seen = new Set<string>()\n for (let at = nodes.get(node.parent); at; at = at.parent ? nodes.get(at.parent) : undefined) {\n if (at.id !== node.id) {\n if (seen.has(at.id)) break\n seen.add(at.id)\n continue\n }\n problems.push({\n code: 'parent-cycle',\n nodeId: node.id,\n path,\n text: node.parent,\n message: `The node \"${node.id}\" is inside itself through its parents`,\n })\n break\n }\n })\n\n const edgeIds = new Set<string>()\n document.edges.forEach((edge, i) => {\n const path = `edges[${i}]`\n if (!edge.id)\n problems.push({ code: 'missing-id', path, text: '', message: `${path} has no id` })\n else if (edgeIds.has(edge.id))\n problems.push({\n code: 'duplicate-id',\n edgeId: edge.id,\n path,\n text: edge.id,\n message: `The edge id \"${edge.id}\" is used more than once`,\n })\n edgeIds.add(edge.id)\n if (edgeTypes && edge.type && !edgeTypes.has(edge.type))\n problems.push({\n code: 'unknown-type',\n edgeId: edge.id,\n path,\n text: edge.type,\n message: `No edge type \"${edge.type}\" is registered`,\n })\n for (const end of ['source', 'target'] as const)\n checkEnd(edge, end, `${path}.${end}`, nodes, nodeTypes, problems)\n })\n return problems\n}\n\nfunction checkEnd(\n edge: FlowEdge,\n end: 'source' | 'target',\n path: string,\n nodes: Map<string, FlowNode>,\n nodeTypes: Map<string, NodeTypeSpec> | undefined,\n problems: DocumentProblem[],\n) {\n const endpoint = edge[end]\n const node = endpoint && nodes.get(endpoint.node)\n if (!node) {\n const id = String(endpoint?.node ?? '')\n problems.push({\n code: 'missing-node',\n edgeId: edge.id,\n nodeId: id || undefined,\n path,\n text: id,\n message: `The ${end} \"${id}\" of the edge \"${edge.id}\" is not in the document`,\n })\n return\n }\n if (!nodeTypes || endpoint.port === undefined) return\n // as the editor draws it: an unknown type as the default node\n const spec =\n nodeTypes.get(node.type ?? 'default') ??\n nodeTypes.get('default') ??\n (defaultNode as NodeTypeSpec)\n if (portsOf(spec, node).some((port) => port.id === endpoint.port)) return\n problems.push({\n code: 'missing-port',\n edgeId: edge.id,\n nodeId: node.id,\n path,\n text: endpoint.port,\n message: `The node \"${node.id}\" has no port \"${endpoint.port}\"`,\n })\n}\n\nconst objects = <T>(value: unknown) =>\n (Array.isArray(value) ? value : []).filter(\n (item): item is T => !!item && typeof item === 'object',\n )\n\n/** The saved data as an object of version 1, or an error saying why it can't be. */\nfunction sourceOf(input: unknown): Record<string, unknown> {\n if (!input || typeof input !== 'object' || Array.isArray(input))\n throw new Error(\n `[@dou-ui/flow] a flow document must be an object, not ${Array.isArray(input) ? 'an array' : (JSON.stringify(input) ?? String(input))}`,\n )\n const source = input as Record<string, unknown>\n const version = source.version ?? 1\n if (version !== 1)\n throw new Error(\n `[@dou-ui/flow] unknown document version ${JSON.stringify(version) ?? String(version)} (expected 1); data saved by a newer @dou-ui/flow needs that version to open`,\n )\n return source\n}\n\n/**\n * Brings a saved document up to the current version, and fills what an\n * older or hand-written one may lack (`version`, `nodes`, `edges`). Nodes\n * and edges that are not objects are dropped; anything else is kept as it\n * is. Throws for data that is not an object\n * (an array, `null`, a string) and for a version it does not know.\n */\nexport function migrateDocument(input: unknown): FlowDocument {\n const source = sourceOf(input)\n return {\n ...source,\n version: 1,\n nodes: objects<FlowNode>(source.nodes),\n edges: objects<FlowEdge>(source.edges),\n }\n}\n\nconst warned = new Set<string>()\n\n/**\n * The `v-model` as the editor reads it. A document of the wrong shape (not\n * an object, without `nodes` or `edges`, without a known `version`) gets a\n * warning in development and is read with what is missing left empty and\n * the version as 1, so the editor still opens and what it writes back\n * carries `version: 1`.\n */\nexport function readDocument(value: unknown): FlowDocument {\n const plain = !!value && typeof value === 'object' && !Array.isArray(value)\n const source = (plain ? value : {}) as Partial<FlowDocument>\n const nodes = Array.isArray(source.nodes)\n const edges = Array.isArray(source.edges)\n const problem = !plain\n ? `is ${Array.isArray(value) ? 'an array' : (JSON.stringify(value) ?? String(value))}`\n : !nodes\n ? 'has no nodes array'\n : !edges\n ? 'has no edges array'\n : source.version === undefined\n ? 'has no version'\n : source.version !== 1\n ? `has version ${JSON.stringify(source.version)}`\n : ''\n if (!problem) return value as FlowDocument\n if (process.env.NODE_ENV !== 'production' && !warned.has(problem)) {\n warned.add(problem)\n console.warn(\n `[@dou-ui/flow] the v-model ${problem}; expected { version: 1, nodes: [...], edges: [...] }. What is missing is read as empty and the version as 1; read saved data with migrateDocument().`,\n )\n }\n return {\n ...source,\n version: 1,\n nodes: nodes ? source.nodes! : [],\n edges: edges ? source.edges! : [],\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ // src/export/color.ts
2
+ var normalizer;
3
+ var SRGB = /^(#|rgba?\()/;
4
+ /**
5
+ * A computed color as `rgb(…)`, `rgba(…)` or `#rrggbb`, which every SVG reader
6
+ * understands. A canvas converts most colors as it parses them; colors it
7
+ * keeps in another space (`oklab(…)`, `color(…)`) are painted on a pixel
8
+ * and read back.
9
+ */
10
+ function plainColor(color) {
11
+ const value = color.trim();
12
+ if (!value || value === "none" || SRGB.test(value)) return value;
13
+ if (value === "transparent") return "rgba(0, 0, 0, 0)";
14
+ if (normalizer === void 0) try {
15
+ const sheet = document.createElement("canvas");
16
+ sheet.width = sheet.height = 1;
17
+ normalizer = sheet.getContext("2d", { willReadFrequently: true });
18
+ } catch {
19
+ normalizer = null;
20
+ }
21
+ if (!normalizer) return value;
22
+ normalizer.fillStyle = "#000";
23
+ normalizer.fillStyle = value;
24
+ const parsed = String(normalizer.fillStyle);
25
+ if (SRGB.test(parsed)) return parsed;
26
+ normalizer.clearRect(0, 0, 1, 1);
27
+ normalizer.fillRect(0, 0, 1, 1);
28
+ const [r, g, b, a] = normalizer.getImageData(0, 0, 1, 1).data;
29
+ if (a === 255) return `#${[
30
+ r,
31
+ g,
32
+ b
33
+ ].map((n) => n.toString(16).padStart(2, "0")).join("")}`;
34
+ return `rgba(${r}, ${g}, ${b}, ${Math.round(a / 255 * 1e3) / 1e3})`;
35
+ }
36
+ export { plainColor };
37
+
38
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.js","names":[],"sources":["../../src/export/color.ts"],"sourcesContent":["let normalizer: CanvasRenderingContext2D | null | undefined\n\nconst SRGB = /^(#|rgba?\\()/\n\n/**\n * A computed color as `rgb(…)`, `rgba(…)` or `#rrggbb`, which every SVG reader\n * understands. A canvas converts most colors as it parses them; colors it\n * keeps in another space (`oklab(…)`, `color(…)`) are painted on a pixel\n * and read back.\n */\nexport function plainColor(color: string): string {\n const value = color.trim()\n if (!value || value === 'none' || SRGB.test(value)) return value\n if (value === 'transparent') return 'rgba(0, 0, 0, 0)'\n if (normalizer === undefined)\n try {\n const sheet = document.createElement('canvas')\n sheet.width = sheet.height = 1\n normalizer = sheet.getContext('2d', { willReadFrequently: true })\n } catch {\n normalizer = null\n }\n if (!normalizer) return value\n normalizer.fillStyle = '#000'\n normalizer.fillStyle = value\n const parsed = String(normalizer.fillStyle)\n if (SRGB.test(parsed)) return parsed\n normalizer.clearRect(0, 0, 1, 1)\n normalizer.fillRect(0, 0, 1, 1)\n const [r, g, b, a] = normalizer.getImageData(0, 0, 1, 1).data\n if (a === 255) return `#${[r, g, b].map((n) => n!.toString(16).padStart(2, '0')).join('')}`\n return `rgba(${r}, ${g}, ${b}, ${Math.round((a! / 255) * 1000) / 1000})`\n}\n"],"mappings":";AAAA,IAAI;AAEJ,IAAM,OAAO;;;;;;;AAQb,SAAgB,WAAW,OAAuB;CAChD,MAAM,QAAQ,MAAM,KAAK;CACzB,IAAI,CAAC,SAAS,UAAU,UAAU,KAAK,KAAK,KAAK,GAAG,OAAO;CAC3D,IAAI,UAAU,eAAe,OAAO;CACpC,IAAI,eAAe,KAAA,GACjB,IAAI;EACF,MAAM,QAAQ,SAAS,cAAc,QAAQ;EAC7C,MAAM,QAAQ,MAAM,SAAS;EAC7B,aAAa,MAAM,WAAW,MAAM,EAAE,oBAAoB,KAAK,CAAC;CAClE,QAAQ;EACN,aAAa;CACf;CACF,IAAI,CAAC,YAAY,OAAO;CACxB,WAAW,YAAY;CACvB,WAAW,YAAY;CACvB,MAAM,SAAS,OAAO,WAAW,SAAS;CAC1C,IAAI,KAAK,KAAK,MAAM,GAAG,OAAO;CAC9B,WAAW,UAAU,GAAG,GAAG,GAAG,CAAC;CAC/B,WAAW,SAAS,GAAG,GAAG,GAAG,CAAC;CAC9B,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK,WAAW,aAAa,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC;CACzD,IAAI,MAAM,KAAK,OAAO,IAAI;EAAC;EAAG;EAAG;CAAC,CAAC,CAAC,KAAK,MAAM,EAAG,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;CACxF,OAAO,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,KAAK,MAAO,IAAK,MAAO,GAAI,IAAI,IAAK;AACxE"}