@dou-ui/form-builder 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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/builder/canvas.js +695 -0
  4. package/dist/builder/canvas.js.map +1 -0
  5. package/dist/builder/completion.js +49 -0
  6. package/dist/builder/completion.js.map +1 -0
  7. package/dist/builder/condition-editor.js +6 -0
  8. package/dist/builder/condition-editor.js.map +1 -0
  9. package/dist/builder/condition-editor.vue_vue_type_script_setup_true_lang.js +410 -0
  10. package/dist/builder/condition-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
  11. package/dist/builder/condition.js +159 -0
  12. package/dist/builder/condition.js.map +1 -0
  13. package/dist/builder/context.js +477 -0
  14. package/dist/builder/context.js.map +1 -0
  15. package/dist/builder/core/commands.js +373 -0
  16. package/dist/builder/core/commands.js.map +1 -0
  17. package/dist/builder/core/history.js +76 -0
  18. package/dist/builder/core/history.js.map +1 -0
  19. package/dist/builder/core/transaction.js +298 -0
  20. package/dist/builder/core/transaction.js.map +1 -0
  21. package/dist/builder/drag.js +466 -0
  22. package/dist/builder/drag.js.map +1 -0
  23. package/dist/builder/expression-box.js +19 -0
  24. package/dist/builder/expression-box.js.map +1 -0
  25. package/dist/builder/expression-input.js +6 -0
  26. package/dist/builder/expression-input.js.map +1 -0
  27. package/dist/builder/expression-input.vue_vue_type_script_setup_true_lang.js +117 -0
  28. package/dist/builder/expression-input.vue_vue_type_script_setup_true_lang.js.map +1 -0
  29. package/dist/builder/extension.js +47 -0
  30. package/dist/builder/extension.js.map +1 -0
  31. package/dist/builder/form-builder.js +6 -0
  32. package/dist/builder/form-builder.js.map +1 -0
  33. package/dist/builder/form-builder.vue_vue_type_script_setup_true_lang.js +450 -0
  34. package/dist/builder/form-builder.vue_vue_type_script_setup_true_lang.js.map +1 -0
  35. package/dist/builder/index.js +22 -0
  36. package/dist/builder/index.js.map +1 -0
  37. package/dist/builder/inspector.js +6 -0
  38. package/dist/builder/inspector.js.map +1 -0
  39. package/dist/builder/inspector.vue_vue_type_script_setup_true_lang.js +442 -0
  40. package/dist/builder/inspector.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/builder/json-dialog.js +6 -0
  42. package/dist/builder/json-dialog.js.map +1 -0
  43. package/dist/builder/json-dialog.vue_vue_type_script_setup_true_lang.js +281 -0
  44. package/dist/builder/json-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  45. package/dist/builder/locale.js +1271 -0
  46. package/dist/builder/locale.js.map +1 -0
  47. package/dist/builder/logic-fields.js +18 -0
  48. package/dist/builder/logic-fields.js.map +1 -0
  49. package/dist/builder/logic.js +332 -0
  50. package/dist/builder/logic.js.map +1 -0
  51. package/dist/builder/menu.js +92 -0
  52. package/dist/builder/menu.js.map +1 -0
  53. package/dist/builder/outline-row.js +6 -0
  54. package/dist/builder/outline-row.js.map +1 -0
  55. package/dist/builder/outline-row.vue_vue_type_script_setup_true_lang.js +88 -0
  56. package/dist/builder/outline-row.vue_vue_type_script_setup_true_lang.js.map +1 -0
  57. package/dist/builder/outline.js +6 -0
  58. package/dist/builder/outline.js.map +1 -0
  59. package/dist/builder/outline.vue_vue_type_script_setup_true_lang.js +111 -0
  60. package/dist/builder/outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
  61. package/dist/builder/palette.js +6 -0
  62. package/dist/builder/palette.js.map +1 -0
  63. package/dist/builder/palette.vue_vue_type_script_setup_true_lang.js +96 -0
  64. package/dist/builder/palette.vue_vue_type_script_setup_true_lang.js.map +1 -0
  65. package/dist/builder/preview.js +6 -0
  66. package/dist/builder/preview.js.map +1 -0
  67. package/dist/builder/preview.vue_vue_type_script_setup_true_lang.js +111 -0
  68. package/dist/builder/preview.vue_vue_type_script_setup_true_lang.js.map +1 -0
  69. package/dist/builder/settings-binding.js +57 -0
  70. package/dist/builder/settings-binding.js.map +1 -0
  71. package/dist/builder/settings.js +487 -0
  72. package/dist/builder/settings.js.map +1 -0
  73. package/dist/builder/span-field.js +104 -0
  74. package/dist/builder/span-field.js.map +1 -0
  75. package/dist/code/expression-input.js +6 -0
  76. package/dist/code/expression-input.js.map +1 -0
  77. package/dist/code/expression-input.vue_vue_type_script_setup_true_lang.js +88 -0
  78. package/dist/code/expression-input.vue_vue_type_script_setup_true_lang.js.map +1 -0
  79. package/dist/code/extension.js +202 -0
  80. package/dist/code/extension.js.map +1 -0
  81. package/dist/code/index.js +19 -0
  82. package/dist/code/index.js.map +1 -0
  83. package/dist/code/language.js +43 -0
  84. package/dist/code/language.js.map +1 -0
  85. package/dist/collaboration/index.js +382 -0
  86. package/dist/collaboration/index.js.map +1 -0
  87. package/dist/collaboration/order.js +88 -0
  88. package/dist/collaboration/order.js.map +1 -0
  89. package/dist/core/check.js +314 -0
  90. package/dist/core/check.js.map +1 -0
  91. package/dist/core/document.js +112 -0
  92. package/dist/core/document.js.map +1 -0
  93. package/dist/core/expression.js +854 -0
  94. package/dist/core/expression.js.map +1 -0
  95. package/dist/core/formulas.js +242 -0
  96. package/dist/core/formulas.js.map +1 -0
  97. package/dist/core/rules.js +83 -0
  98. package/dist/core/rules.js.map +1 -0
  99. package/dist/core/values.js +80 -0
  100. package/dist/core/values.js.map +1 -0
  101. package/dist/fields/builtin.js +167 -0
  102. package/dist/fields/builtin.js.map +1 -0
  103. package/dist/fields/components.js +155 -0
  104. package/dist/fields/components.js.map +1 -0
  105. package/dist/fields/define.js +44 -0
  106. package/dist/fields/define.js.map +1 -0
  107. package/dist/fields/format.js +125 -0
  108. package/dist/fields/format.js.map +1 -0
  109. package/dist/fields/options.js +54 -0
  110. package/dist/fields/options.js.map +1 -0
  111. package/dist/index.js +20 -0
  112. package/dist/locale.js +161 -0
  113. package/dist/locale.js.map +1 -0
  114. package/dist/renderer/containers.js +430 -0
  115. package/dist/renderer/containers.js.map +1 -0
  116. package/dist/renderer/context.js +21 -0
  117. package/dist/renderer/context.js.map +1 -0
  118. package/dist/renderer/form-renderer.js +6 -0
  119. package/dist/renderer/form-renderer.js.map +1 -0
  120. package/dist/renderer/form-renderer.vue_vue_type_script_setup_true_lang.js +272 -0
  121. package/dist/renderer/form-renderer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  122. package/dist/renderer/index.js +19 -0
  123. package/dist/renderer/index.js.map +1 -0
  124. package/dist/renderer/node-view.js +385 -0
  125. package/dist/renderer/node-view.js.map +1 -0
  126. package/dist/style.css +1 -0
  127. package/dist/style.d.ts +1 -0
  128. package/dist/style.js +27 -0
  129. package/dist/types/builder/canvas.d.ts +5 -0
  130. package/dist/types/builder/completion.d.ts +35 -0
  131. package/dist/types/builder/condition-editor.vue.d.ts +17 -0
  132. package/dist/types/builder/condition.d.ts +37 -0
  133. package/dist/types/builder/context.d.ts +149 -0
  134. package/dist/types/builder/core/commands.d.ts +74 -0
  135. package/dist/types/builder/core/history.d.ts +23 -0
  136. package/dist/types/builder/core/transaction.d.ts +108 -0
  137. package/dist/types/builder/drag.d.ts +52 -0
  138. package/dist/types/builder/expression-box.d.ts +8 -0
  139. package/dist/types/builder/expression-input.vue.d.ts +29 -0
  140. package/dist/types/builder/extension.d.ts +149 -0
  141. package/dist/types/builder/form-builder.vue.d.ts +24 -0
  142. package/dist/types/builder/index.d.ts +76 -0
  143. package/dist/types/builder/inspector.vue.d.ts +3 -0
  144. package/dist/types/builder/json-dialog.vue.d.ts +10 -0
  145. package/dist/types/builder/locale.d.ts +195 -0
  146. package/dist/types/builder/logic-fields.d.ts +2 -0
  147. package/dist/types/builder/logic.d.ts +57 -0
  148. package/dist/types/builder/menu.d.ts +23 -0
  149. package/dist/types/builder/outline-row.vue.d.ts +19 -0
  150. package/dist/types/builder/outline.vue.d.ts +3 -0
  151. package/dist/types/builder/palette.vue.d.ts +3 -0
  152. package/dist/types/builder/preview.vue.d.ts +3 -0
  153. package/dist/types/builder/settings-binding.d.ts +23 -0
  154. package/dist/types/builder/settings.d.ts +49 -0
  155. package/dist/types/builder/span-field.d.ts +4 -0
  156. package/dist/types/builder/types.d.ts +59 -0
  157. package/dist/types/code/expression-input.vue.d.ts +23 -0
  158. package/dist/types/code/extension.d.ts +45 -0
  159. package/dist/types/code/index.d.ts +12 -0
  160. package/dist/types/code/language.d.ts +6 -0
  161. package/dist/types/collaboration/index.d.ts +64 -0
  162. package/dist/types/collaboration/order.d.ts +20 -0
  163. package/dist/types/core/check.d.ts +68 -0
  164. package/dist/types/core/document.d.ts +36 -0
  165. package/dist/types/core/expression.d.ts +175 -0
  166. package/dist/types/core/formulas.d.ts +44 -0
  167. package/dist/types/core/rules.d.ts +23 -0
  168. package/dist/types/core/types.d.ts +211 -0
  169. package/dist/types/core/values.d.ts +22 -0
  170. package/dist/types/fields/builtin.d.ts +9 -0
  171. package/dist/types/fields/components.d.ts +177 -0
  172. package/dist/types/fields/define.d.ts +93 -0
  173. package/dist/types/fields/format.d.ts +76 -0
  174. package/dist/types/fields/options.d.ts +11 -0
  175. package/dist/types/index.d.ts +3 -0
  176. package/dist/types/locale.d.ts +45 -0
  177. package/dist/types/renderer/containers.d.ts +20 -0
  178. package/dist/types/renderer/context.d.ts +186 -0
  179. package/dist/types/renderer/form-renderer.vue.d.ts +25 -0
  180. package/dist/types/renderer/index.d.ts +81 -0
  181. package/dist/types/renderer/node-view.d.ts +56 -0
  182. package/package.json +88 -4
@@ -0,0 +1,298 @@
1
+ import { isContainer, isPage, walkNodes } from "../../core/document.js";
2
+ // src/builder/core/transaction.ts
3
+ var placeCache = /* @__PURE__ */ new WeakMap();
4
+ /**
5
+ * Where every node of a document sits, by id. Worked out once per
6
+ * document object, which a transaction replaces on every change.
7
+ */
8
+ function formNodePlaces(document) {
9
+ let map = placeCache.get(document);
10
+ if (map) return map;
11
+ map = /* @__PURE__ */ new Map();
12
+ const visit = (nodes, parent, depth) => {
13
+ nodes.forEach((node, index) => {
14
+ map.set(node.id, {
15
+ node,
16
+ parent,
17
+ index,
18
+ depth
19
+ });
20
+ if (isContainer(node)) visit(node.children, node, depth + 1);
21
+ });
22
+ };
23
+ visit(document.nodes, null, 1);
24
+ placeCache.set(document, map);
25
+ return map;
26
+ }
27
+ var PROTECTED = /* @__PURE__ */ new Set([
28
+ "id",
29
+ "type",
30
+ "children"
31
+ ]);
32
+ /** The node with `patch` applied; `undefined` removes a key. */
33
+ function patched(node, patch) {
34
+ const next = { ...node };
35
+ for (const [key, value] of Object.entries(patch)) {
36
+ if (PROTECTED.has(key)) throw new Error(`[@dou-ui/form-builder] "${key}" of a node cannot be updated: insert, remove or move nodes instead`);
37
+ if (value === void 0) delete next[key];
38
+ else next[key] = value;
39
+ }
40
+ return next;
41
+ }
42
+ /** The values `patch` replaces, as a patch that puts them back. */
43
+ function previous(node, patch) {
44
+ const record = node;
45
+ const back = {};
46
+ for (const key of Object.keys(patch)) back[key] = record[key];
47
+ return back;
48
+ }
49
+ /**
50
+ * Replace the list of children of `parent` (or the document's nodes),
51
+ * copying only the containers on the way from the top to it.
52
+ */
53
+ function withChildren(document, parent, change) {
54
+ if (parent === null) return {
55
+ ...document,
56
+ nodes: change(document.nodes.slice())
57
+ };
58
+ let place = formNodePlaces(document).get(parent);
59
+ if (!place) throw new Error(`[@dou-ui/form-builder] there is no node "${parent}" in the form`);
60
+ if (!isContainer(place.node)) throw new Error(`[@dou-ui/form-builder] the node "${parent}" holds no other nodes`);
61
+ let replaced = {
62
+ ...place.node,
63
+ children: change(place.node.children.slice())
64
+ };
65
+ while (place) {
66
+ const container = place.parent;
67
+ if (!container) {
68
+ const nodes = document.nodes.slice();
69
+ nodes[place.index] = replaced;
70
+ return {
71
+ ...document,
72
+ nodes
73
+ };
74
+ }
75
+ const children = container.children.slice();
76
+ children[place.index] = replaced;
77
+ replaced = {
78
+ ...container,
79
+ children
80
+ };
81
+ place = formNodePlaces(document).get(container.id);
82
+ }
83
+ return document;
84
+ }
85
+ function replaceNode(document, id, node) {
86
+ const place = formNodePlaces(document).get(id);
87
+ return withChildren(document, place.parent?.id ?? null, (children) => {
88
+ children[place.index] = node;
89
+ return children;
90
+ });
91
+ }
92
+ /**
93
+ * Whether a container (`null`: the top of the form) can hold `node`:
94
+ * pages go in tabs and steps, and only pages do.
95
+ */
96
+ function canContainFormNode(parent, node) {
97
+ if (parent?.type === "tabs" || parent?.type === "steps") return isPage(node);
98
+ if (isPage(node)) return false;
99
+ return parent === null || isContainer(parent);
100
+ }
101
+ /** Throw unless `parent` can hold `node`. */
102
+ function checkPlace(map, parent, node) {
103
+ const holder = parent === null ? null : map.get(parent)?.node;
104
+ if (holder === void 0 || holder && !isContainer(holder)) return;
105
+ if (canContainFormNode(holder, node)) return;
106
+ const where = holder ? `"${holder.id}" (${holder.type})` : "the top of the form";
107
+ throw new Error(isPage(node) ? `[@dou-ui/form-builder] the page "${node.id}" can only go in tabs or steps, not in ${where}` : `[@dou-ui/form-builder] ${where} holds only pages, not "${node.id}" (${node.type})`);
108
+ }
109
+ /** Apply one step; the step that undoes it, and the new document. */
110
+ function applyStep(document, step) {
111
+ const map = formNodePlaces(document);
112
+ switch (step.type) {
113
+ case "insert":
114
+ if (map.has(step.node.id)) throw new Error(`[@dou-ui/form-builder] the form already has a node "${step.node.id}"`);
115
+ for (const node of walkNodes([step.node])) if (node !== step.node && map.has(node.id)) throw new Error(`[@dou-ui/form-builder] the form already has a node "${node.id}"`);
116
+ checkPlace(map, step.parent, step.node);
117
+ return [withChildren(document, step.parent, (children) => {
118
+ children.splice(clampIndex(step.index, children.length), 0, step.node);
119
+ return children;
120
+ }), {
121
+ type: "remove",
122
+ id: step.node.id
123
+ }];
124
+ case "remove": {
125
+ const place = map.get(step.id);
126
+ if (!place) throw new Error(`[@dou-ui/form-builder] there is no node "${step.id}" in the form`);
127
+ const parent = place.parent?.id ?? null;
128
+ return [withChildren(document, parent, (children) => {
129
+ children.splice(place.index, 1);
130
+ return children;
131
+ }), {
132
+ type: "insert",
133
+ node: place.node,
134
+ parent,
135
+ index: place.index
136
+ }];
137
+ }
138
+ case "update": {
139
+ const place = map.get(step.id);
140
+ if (!place) throw new Error(`[@dou-ui/form-builder] there is no node "${step.id}" in the form`);
141
+ const back = previous(place.node, step.patch);
142
+ return [replaceNode(document, step.id, patched(place.node, step.patch)), {
143
+ type: "update",
144
+ id: step.id,
145
+ patch: back
146
+ }];
147
+ }
148
+ case "move": {
149
+ const place = map.get(step.id);
150
+ if (!place) throw new Error(`[@dou-ui/form-builder] there is no node "${step.id}" in the form`);
151
+ if (step.parent !== null) {
152
+ let up = map.get(step.parent);
153
+ while (up) {
154
+ if (up.node.id === step.id) throw new Error(`[@dou-ui/form-builder] the node "${step.id}" cannot move inside itself`);
155
+ up = up.parent ? map.get(up.parent.id) : void 0;
156
+ }
157
+ }
158
+ checkPlace(map, step.parent, place.node);
159
+ const from = place.parent?.id ?? null;
160
+ return [withChildren(withChildren(document, from, (children) => {
161
+ children.splice(place.index, 1);
162
+ return children;
163
+ }), step.parent, (children) => {
164
+ children.splice(clampIndex(step.index, children.length), 0, place.node);
165
+ return children;
166
+ }), {
167
+ type: "move",
168
+ id: step.id,
169
+ parent: from,
170
+ index: place.index
171
+ }];
172
+ }
173
+ case "updateDocument": {
174
+ const record = document;
175
+ const next = { ...document };
176
+ const back = {};
177
+ for (const [key, value] of Object.entries(step.patch)) {
178
+ if (key === "nodes" || key === "version") continue;
179
+ back[key] = record[key];
180
+ if (value === void 0) delete next[key];
181
+ else next[key] = value;
182
+ }
183
+ return [next, {
184
+ type: "updateDocument",
185
+ patch: back
186
+ }];
187
+ }
188
+ }
189
+ }
190
+ var clampIndex = (index, length) => Math.min(Math.max(0, Math.round(index)), length);
191
+ /**
192
+ * Changes to a form document, made step by step. Each step returns a new
193
+ * document that shares every node it did not touch with the one before,
194
+ * so views of untouched nodes do not render again. The builder records
195
+ * the steps for undo; extensions and code dispatch them through
196
+ * `useFormBuilder().dispatch`.
197
+ */
198
+ var FormBuilderTransaction = class {
199
+ before;
200
+ /** The document after the steps so far. */
201
+ doc;
202
+ steps = [];
203
+ /** The steps that undo the ones so far, last one first. */
204
+ inverse = [];
205
+ meta = {};
206
+ constructor(before) {
207
+ this.before = before;
208
+ this.doc = before;
209
+ }
210
+ /** Whether any step was made. */
211
+ get changed() {
212
+ return this.steps.length > 0;
213
+ }
214
+ /** Apply a step as it is. */
215
+ step(step) {
216
+ const [doc, inverse] = applyStep(this.doc, step);
217
+ this.doc = doc;
218
+ this.steps.push(step);
219
+ this.inverse.unshift(inverse);
220
+ return this;
221
+ }
222
+ /** Put a node, and what it holds, into a container (`null`: the top), at `index` (the end by default). */
223
+ insert(node, parent = null, index = Infinity) {
224
+ return this.step({
225
+ type: "insert",
226
+ node,
227
+ parent,
228
+ index
229
+ });
230
+ }
231
+ /** Take a node, and what it holds, out of the document. */
232
+ remove(id) {
233
+ return this.step({
234
+ type: "remove",
235
+ id
236
+ });
237
+ }
238
+ /** Set keys of a node; `undefined` removes a key. */
239
+ update(id, patch) {
240
+ return this.step({
241
+ type: "update",
242
+ id,
243
+ patch
244
+ });
245
+ }
246
+ /** Move a node into a container (`null`: the top), at `index` of the list without it. */
247
+ move(id, parent, index) {
248
+ return this.step({
249
+ type: "move",
250
+ id,
251
+ parent,
252
+ index
253
+ });
254
+ }
255
+ /** Change the settings of the document, e.g. `{ layout: 'horizontal' }`. */
256
+ updateDocument(patch) {
257
+ return this.step({
258
+ type: "updateDocument",
259
+ patch
260
+ });
261
+ }
262
+ setMeta(key, value) {
263
+ this.meta[key] = value;
264
+ return this;
265
+ }
266
+ };
267
+ /** Run steps on a document, e.g. ones from undo or from another person. */
268
+ function applyFormSteps(document, steps) {
269
+ const tr = new FormBuilderTransaction(document);
270
+ for (const step of steps) tr.step(step);
271
+ return tr;
272
+ }
273
+ /** Merge updates of the same node that follow each other, for steps kept by undo. */
274
+ function compactFormSteps(steps) {
275
+ const result = [];
276
+ for (const step of steps) {
277
+ const last = result[result.length - 1];
278
+ if (last?.type === "update" && step.type === "update" && last.id === step.id) result[result.length - 1] = {
279
+ ...last,
280
+ patch: {
281
+ ...last.patch,
282
+ ...step.patch
283
+ }
284
+ };
285
+ else if (last?.type === "updateDocument" && step.type === "updateDocument") result[result.length - 1] = {
286
+ ...last,
287
+ patch: {
288
+ ...last.patch,
289
+ ...step.patch
290
+ }
291
+ };
292
+ else result.push(step);
293
+ }
294
+ return result;
295
+ }
296
+ export { FormBuilderTransaction, applyFormSteps, canContainFormNode, compactFormSteps, formNodePlaces };
297
+
298
+ //# sourceMappingURL=transaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction.js","names":[],"sources":["../../../src/builder/core/transaction.ts"],"sourcesContent":["import { isContainer, isPage, walkNodes } from '../../core/document'\nimport type { FormContainerNode, FormDocument, FormNode } from '../../core/types'\n\n/**\n * The settings of a document besides its nodes, e.g. `layout` or\n * `columns`. `undefined` in a patch removes the setting.\n */\nexport type FormDocumentPatch = {\n [K in Exclude<keyof FormDocument, 'nodes' | 'version'>]?: FormDocument[K] | undefined\n}\n\n/**\n * Keys of a node to set; `undefined` removes the key. `id`, `type` and\n * `children` cannot be set: insert, remove and move nodes instead.\n */\nexport type FormNodePatch = Record<string, unknown>\n\n/**\n * One change to a document. `parent` is the id of a container, or `null`\n * for the document's own nodes; `index` counts in that list, after the\n * node moved out of its old place.\n */\nexport type FormBuilderStep =\n | { type: 'insert'; node: FormNode; parent: string | null; index: number }\n | { type: 'remove'; id: string }\n | { type: 'update'; id: string; patch: FormNodePatch }\n | { type: 'move'; id: string; parent: string | null; index: number }\n | { type: 'updateDocument'; patch: FormDocumentPatch }\n\nexport interface FormBuilderTransactionMeta {\n /** `false` keeps the change out of undo, e.g. a change from another person. */\n history?: boolean\n /**\n * Changes of the same group within a second become one undo step, e.g.\n * the keys typed into one setting.\n */\n group?: string\n /** Where the change comes from, e.g. `inspector`, `keyboard` or an extension's name. */\n origin?: string\n [key: string]: unknown\n}\n\n/** Where a node sits: its container (`null` at the top) and position. */\nexport interface FormNodePlace {\n node: FormNode\n parent: FormContainerNode | null\n index: number\n /** How deep it sits, from 1 for the document's own nodes. */\n depth: number\n}\n\nconst placeCache = new WeakMap<FormDocument, Map<string, FormNodePlace>>()\n\n/**\n * Where every node of a document sits, by id. Worked out once per\n * document object, which a transaction replaces on every change.\n */\nexport function formNodePlaces(document: FormDocument): Map<string, FormNodePlace> {\n let map = placeCache.get(document)\n if (map) return map\n map = new Map()\n const visit = (nodes: readonly FormNode[], parent: FormContainerNode | null, depth: number) => {\n nodes.forEach((node, index) => {\n map!.set(node.id, { node, parent, index, depth })\n if (isContainer(node)) visit(node.children, node, depth + 1)\n })\n }\n visit(document.nodes, null, 1)\n placeCache.set(document, map)\n return map\n}\n\nconst PROTECTED = new Set(['id', 'type', 'children'])\n\n/** The node with `patch` applied; `undefined` removes a key. */\nfunction patched(node: FormNode, patch: FormNodePatch): FormNode {\n const next: Record<string, unknown> = { ...node }\n for (const [key, value] of Object.entries(patch)) {\n if (PROTECTED.has(key))\n throw new Error(\n `[@dou-ui/form-builder] \"${key}\" of a node cannot be updated: insert, remove or move nodes instead`,\n )\n if (value === undefined) delete next[key]\n else next[key] = value\n }\n return next as unknown as FormNode\n}\n\n/** The values `patch` replaces, as a patch that puts them back. */\nfunction previous(node: FormNode, patch: FormNodePatch): FormNodePatch {\n const record = node as unknown as Record<string, unknown>\n const back: FormNodePatch = {}\n for (const key of Object.keys(patch)) back[key] = record[key]\n return back\n}\n\n/**\n * Replace the list of children of `parent` (or the document's nodes),\n * copying only the containers on the way from the top to it.\n */\nfunction withChildren(\n document: FormDocument,\n parent: string | null,\n change: (children: FormNode[]) => FormNode[],\n): FormDocument {\n if (parent === null) return { ...document, nodes: change(document.nodes.slice()) }\n let place = formNodePlaces(document).get(parent)\n if (!place) throw new Error(`[@dou-ui/form-builder] there is no node \"${parent}\" in the form`)\n if (!isContainer(place.node))\n throw new Error(`[@dou-ui/form-builder] the node \"${parent}\" holds no other nodes`)\n let replaced: FormNode = {\n ...place.node,\n children: change(place.node.children.slice()),\n } as FormContainerNode\n // Walk up, putting each new node in a copy of its parent.\n while (place) {\n const container = place.parent\n if (!container) {\n const nodes = document.nodes.slice()\n nodes[place.index] = replaced\n return { ...document, nodes }\n }\n const children = container.children.slice()\n children[place.index] = replaced\n replaced = { ...container, children } as FormContainerNode\n place = formNodePlaces(document).get(container.id)\n }\n return document\n}\n\nfunction replaceNode(document: FormDocument, id: string, node: FormNode): FormDocument {\n const place = formNodePlaces(document).get(id)!\n return withChildren(document, place.parent?.id ?? null, (children) => {\n children[place.index] = node\n return children\n })\n}\n\n/**\n * Whether a container (`null`: the top of the form) can hold `node`:\n * pages go in tabs and steps, and only pages do.\n */\nexport function canContainFormNode(parent: FormNode | null, node: FormNode): boolean {\n const holdsPages = parent?.type === 'tabs' || parent?.type === 'steps'\n if (holdsPages) return isPage(node)\n if (isPage(node)) return false\n return parent === null || isContainer(parent)\n}\n\n/** Throw unless `parent` can hold `node`. */\nfunction checkPlace(map: Map<string, FormNodePlace>, parent: string | null, node: FormNode) {\n const holder = parent === null ? null : map.get(parent)?.node\n // A missing parent, or one that holds nothing, is reported when its children are changed.\n if (holder === undefined || (holder && !isContainer(holder))) return\n if (canContainFormNode(holder, node)) return\n const where = holder ? `\"${holder.id}\" (${holder.type})` : 'the top of the form'\n throw new Error(\n isPage(node)\n ? `[@dou-ui/form-builder] the page \"${node.id}\" can only go in tabs or steps, not in ${where}`\n : `[@dou-ui/form-builder] ${where} holds only pages, not \"${node.id}\" (${node.type})`,\n )\n}\n\n/** Apply one step; the step that undoes it, and the new document. */\nfunction applyStep(\n document: FormDocument,\n step: FormBuilderStep,\n): [document: FormDocument, inverse: FormBuilderStep] {\n const map = formNodePlaces(document)\n switch (step.type) {\n case 'insert': {\n if (map.has(step.node.id))\n throw new Error(`[@dou-ui/form-builder] the form already has a node \"${step.node.id}\"`)\n for (const node of walkNodes([step.node]))\n if (node !== step.node && map.has(node.id))\n throw new Error(`[@dou-ui/form-builder] the form already has a node \"${node.id}\"`)\n checkPlace(map, step.parent, step.node)\n const next = withChildren(document, step.parent, (children) => {\n children.splice(clampIndex(step.index, children.length), 0, step.node)\n return children\n })\n return [next, { type: 'remove', id: step.node.id }]\n }\n case 'remove': {\n const place = map.get(step.id)\n if (!place)\n throw new Error(`[@dou-ui/form-builder] there is no node \"${step.id}\" in the form`)\n const parent = place.parent?.id ?? null\n const next = withChildren(document, parent, (children) => {\n children.splice(place.index, 1)\n return children\n })\n return [next, { type: 'insert', node: place.node, parent, index: place.index }]\n }\n case 'update': {\n const place = map.get(step.id)\n if (!place)\n throw new Error(`[@dou-ui/form-builder] there is no node \"${step.id}\" in the form`)\n const back = previous(place.node, step.patch)\n return [\n replaceNode(document, step.id, patched(place.node, step.patch)),\n { type: 'update', id: step.id, patch: back },\n ]\n }\n case 'move': {\n const place = map.get(step.id)\n if (!place)\n throw new Error(`[@dou-ui/form-builder] there is no node \"${step.id}\" in the form`)\n if (step.parent !== null) {\n // A node cannot go inside itself.\n let up: FormNodePlace | undefined = map.get(step.parent)\n while (up) {\n if (up.node.id === step.id)\n throw new Error(\n `[@dou-ui/form-builder] the node \"${step.id}\" cannot move inside itself`,\n )\n up = up.parent ? map.get(up.parent.id) : undefined\n }\n }\n checkPlace(map, step.parent, place.node)\n const from = place.parent?.id ?? null\n const removed = withChildren(document, from, (children) => {\n children.splice(place.index, 1)\n return children\n })\n const next = withChildren(removed, step.parent, (children) => {\n children.splice(clampIndex(step.index, children.length), 0, place.node)\n return children\n })\n return [next, { type: 'move', id: step.id, parent: from, index: place.index }]\n }\n case 'updateDocument': {\n const record = document as unknown as Record<string, unknown>\n const next: Record<string, unknown> = { ...document }\n const back: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(step.patch)) {\n if (key === 'nodes' || key === 'version') continue\n back[key] = record[key]\n if (value === undefined) delete next[key]\n else next[key] = value\n }\n return [next as unknown as FormDocument, { type: 'updateDocument', patch: back }]\n }\n }\n}\n\nconst clampIndex = (index: number, length: number) =>\n Math.min(Math.max(0, Math.round(index)), length)\n\n/**\n * Changes to a form document, made step by step. Each step returns a new\n * document that shares every node it did not touch with the one before,\n * so views of untouched nodes do not render again. The builder records\n * the steps for undo; extensions and code dispatch them through\n * `useFormBuilder().dispatch`.\n */\nexport class FormBuilderTransaction {\n /** The document after the steps so far. */\n doc: FormDocument\n readonly steps: FormBuilderStep[] = []\n /** The steps that undo the ones so far, last one first. */\n readonly inverse: FormBuilderStep[] = []\n readonly meta: FormBuilderTransactionMeta = {}\n\n constructor(\n /** The document the transaction starts from. */\n readonly before: FormDocument,\n ) {\n this.doc = before\n }\n\n /** Whether any step was made. */\n get changed() {\n return this.steps.length > 0\n }\n\n /** Apply a step as it is. */\n step(step: FormBuilderStep) {\n const [doc, inverse] = applyStep(this.doc, step)\n this.doc = doc\n this.steps.push(step)\n this.inverse.unshift(inverse)\n return this\n }\n\n /** Put a node, and what it holds, into a container (`null`: the top), at `index` (the end by default). */\n insert(node: FormNode, parent: string | null = null, index = Infinity) {\n return this.step({ type: 'insert', node, parent, index })\n }\n\n /** Take a node, and what it holds, out of the document. */\n remove(id: string) {\n return this.step({ type: 'remove', id })\n }\n\n /** Set keys of a node; `undefined` removes a key. */\n update(id: string, patch: FormNodePatch) {\n return this.step({ type: 'update', id, patch })\n }\n\n /** Move a node into a container (`null`: the top), at `index` of the list without it. */\n move(id: string, parent: string | null, index: number) {\n return this.step({ type: 'move', id, parent, index })\n }\n\n /** Change the settings of the document, e.g. `{ layout: 'horizontal' }`. */\n updateDocument(patch: FormDocumentPatch) {\n return this.step({ type: 'updateDocument', patch })\n }\n\n setMeta(key: string, value: unknown) {\n this.meta[key] = value\n return this\n }\n}\n\n/** Run steps on a document, e.g. ones from undo or from another person. */\nexport function applyFormSteps(document: FormDocument, steps: readonly FormBuilderStep[]) {\n const tr = new FormBuilderTransaction(document)\n for (const step of steps) tr.step(step)\n return tr\n}\n\n/** Merge updates of the same node that follow each other, for steps kept by undo. */\nexport function compactFormSteps(steps: readonly FormBuilderStep[]): FormBuilderStep[] {\n const result: FormBuilderStep[] = []\n for (const step of steps) {\n const last = result[result.length - 1]\n if (last?.type === 'update' && step.type === 'update' && last.id === step.id)\n result[result.length - 1] = { ...last, patch: { ...last.patch, ...step.patch } }\n else if (last?.type === 'updateDocument' && step.type === 'updateDocument')\n result[result.length - 1] = { ...last, patch: { ...last.patch, ...step.patch } }\n else result.push(step)\n }\n return result\n}\n"],"mappings":"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"}