@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,373 @@
1
+ import { cloneValue, createId, isContainer, isList, isPage, walkNodes } from "../../core/document.js";
2
+ import { formatMessage } from "../../locale.js";
3
+ import { canContainFormNode, formNodePlaces } from "./transaction.js";
4
+ // src/builder/core/commands.ts
5
+ /** The list a node's name is a key of, or `null` for names at the top of the form. */
6
+ function scopeOf(document, parent) {
7
+ const map = formNodePlaces(document);
8
+ let id = parent;
9
+ while (id !== null) {
10
+ const place = map.get(id);
11
+ if (!place) return null;
12
+ if (isList(place.node)) return place.node;
13
+ id = place.parent?.id ?? null;
14
+ }
15
+ return null;
16
+ }
17
+ /** The names already used in a scope: fields and lists, not the fields of nested lists. */
18
+ function namesIn(document, scope) {
19
+ const names = /* @__PURE__ */ new Set();
20
+ const visit = (nodes) => {
21
+ for (const node of nodes) {
22
+ if ("name" in node && node.name) names.add(node.name);
23
+ if (isContainer(node) && !isList(node)) visit(node.children);
24
+ }
25
+ };
26
+ visit(scope ? scope.children : document.nodes);
27
+ return names;
28
+ }
29
+ /** `select3` after `select` and `select2`: the type in camel case and the first free number. */
30
+ function uniqueName(base, taken) {
31
+ const stem = base.replace(/-(\w)/g, (_, letter) => letter.toUpperCase());
32
+ for (let index = 1;; index++) {
33
+ const name = `${stem}${index}`;
34
+ if (!taken.has(name)) return name;
35
+ }
36
+ }
37
+ var newPage = (title) => ({
38
+ id: createId("p"),
39
+ type: "page",
40
+ title,
41
+ children: []
42
+ });
43
+ /** A new node of a type, with an id, a free name and texts to start from. */
44
+ function createNode(type, context) {
45
+ const { messages, taken } = context;
46
+ const label = context.fields.get(type)?.label ?? messages.types[type] ?? type;
47
+ switch (type) {
48
+ case "section": return {
49
+ id: createId("s"),
50
+ type: "section",
51
+ title: label,
52
+ children: []
53
+ };
54
+ case "tabs":
55
+ case "steps": {
56
+ const text = type === "tabs" ? messages.created.page : messages.created.step;
57
+ return {
58
+ id: createId(type === "tabs" ? "t" : "st"),
59
+ type,
60
+ children: [1, 2].map((index) => newPage(formatMessage(text, { index })))
61
+ };
62
+ }
63
+ case "page": return newPage(formatMessage(messages.created.page, { index: 1 }));
64
+ case "list": return {
65
+ id: createId("l"),
66
+ type: "list",
67
+ name: uniqueName("list", taken),
68
+ label,
69
+ children: []
70
+ };
71
+ }
72
+ const spec = context.fields.get(type);
73
+ const node = {
74
+ id: createId(),
75
+ type
76
+ };
77
+ if (spec?.kind === "display") {
78
+ if (type === "text") node.props = { content: messages.created.text };
79
+ if (type === "alert") node.props = { title: messages.created.alert };
80
+ return node;
81
+ }
82
+ node.name = uniqueName(type, taken);
83
+ node.label = label;
84
+ if (spec?.optionsProp) {
85
+ const options = [
86
+ 1,
87
+ 2,
88
+ 3
89
+ ].map((index) => ({
90
+ label: formatMessage(messages.created.option, { index }),
91
+ value: `option${index}`
92
+ }));
93
+ node.props = { [spec.optionsProp]: options };
94
+ }
95
+ return node;
96
+ }
97
+ /**
98
+ * Where a node of `type` goes when added from the palette: inside the
99
+ * last selected container (the shown page of tabs and steps), else after
100
+ * the last selected node, else at the end of the form.
101
+ */
102
+ function insertPlace(document, selection, type, shown) {
103
+ const map = formNodePlaces(document);
104
+ const probe = {
105
+ id: "",
106
+ type,
107
+ children: []
108
+ };
109
+ const last = selection[selection.length - 1];
110
+ let place = last ? map.get(last) : void 0;
111
+ while (place) {
112
+ let target = place.node;
113
+ if (target.type === "tabs" || target.type === "steps") {
114
+ const page = shown(target);
115
+ if (page) target = page;
116
+ }
117
+ if (isContainer(target) && canContainFormNode(target, probe)) return {
118
+ parent: target.id,
119
+ index: target.children.length
120
+ };
121
+ const parent = place.parent;
122
+ if (canContainFormNode(parent, probe)) return {
123
+ parent: parent?.id ?? null,
124
+ index: place.index + 1
125
+ };
126
+ place = parent ? map.get(parent.id) : void 0;
127
+ }
128
+ return {
129
+ parent: null,
130
+ index: document.nodes.length
131
+ };
132
+ }
133
+ /** The selected nodes without the ones inside other selected nodes, in document order. */
134
+ function topmost(document, ids) {
135
+ const map = formNodePlaces(document);
136
+ const chosen = new Set(ids.filter((id) => map.has(id)));
137
+ const result = [];
138
+ for (const node of walkNodes(document.nodes)) {
139
+ if (!chosen.has(node.id)) continue;
140
+ let up = map.get(node.id).parent;
141
+ let inside = false;
142
+ while (up) {
143
+ if (chosen.has(up.id)) {
144
+ inside = true;
145
+ break;
146
+ }
147
+ up = map.get(up.id).parent;
148
+ }
149
+ if (!inside) result.push(node.id);
150
+ }
151
+ return result;
152
+ }
153
+ /** A copy of a node with new ids, and a free name for the copy itself. */
154
+ function copyNode(node, taken) {
155
+ const copy = cloneValue(node);
156
+ for (const each of walkNodes([copy])) each.id = createId(each.id.split("-")[0] || "f");
157
+ if ("name" in copy && copy.name) {
158
+ copy.name = uniqueName(copy.name.replace(/\d+$/, "") || copy.type, taken);
159
+ taken.add(copy.name);
160
+ }
161
+ return copy;
162
+ }
163
+ /** Remove nodes; the node to select afterwards: the next one, else the one before, else the parent. */
164
+ function removeNodes(tr, ids) {
165
+ const ids2 = topmost(tr.doc, ids);
166
+ if (!ids2.length) return {
167
+ removed: 0,
168
+ next: void 0
169
+ };
170
+ const map = formNodePlaces(tr.doc);
171
+ const last = map.get(ids2[ids2.length - 1]);
172
+ const siblings = last.parent ? last.parent.children : tr.doc.nodes;
173
+ const gone = new Set(ids2);
174
+ const after = siblings.slice(last.index + 1).find((node) => !gone.has(node.id));
175
+ let before;
176
+ for (let at = last.index - 1; at >= 0 && !before; at--) if (!gone.has(siblings[at].id)) before = siblings[at];
177
+ let next = after?.id ?? before?.id;
178
+ if (!next) {
179
+ let up = last.parent;
180
+ while (up && gone.has(up.id)) up = map.get(up.id)?.parent ?? null;
181
+ next = up?.id;
182
+ }
183
+ for (const id of ids2) tr.remove(id);
184
+ return {
185
+ removed: ids2.length,
186
+ next
187
+ };
188
+ }
189
+ /** Put a copy of each node right after it; the ids of the copies. */
190
+ function duplicateNodes(tr, ids) {
191
+ const copies = [];
192
+ for (const id of topmost(tr.doc, ids)) {
193
+ const place = formNodePlaces(tr.doc).get(id);
194
+ if (!place) continue;
195
+ const parent = place.parent?.id ?? null;
196
+ const taken = namesIn(tr.doc, scopeOf(tr.doc, parent));
197
+ const copy = copyNode(place.node, taken);
198
+ tr.insert(copy, parent, place.index + 1);
199
+ copies.push(copy.id);
200
+ }
201
+ return copies;
202
+ }
203
+ /**
204
+ * Move each node one place up (`-1`) or down (`1`) among its siblings.
205
+ * Nodes already at the edge stay, and so do the selected ones next to
206
+ * them, so a selection keeps its shape. Returns whether anything moved.
207
+ */
208
+ function moveNodes(tr, ids, delta) {
209
+ const chosen = topmost(tr.doc, ids);
210
+ const byParent = /* @__PURE__ */ new Map();
211
+ const map = formNodePlaces(tr.doc);
212
+ for (const id of chosen) {
213
+ const place = map.get(id);
214
+ const key = place.parent?.id ?? null;
215
+ const list = byParent.get(key) ?? [];
216
+ list.push(place.index);
217
+ byParent.set(key, list);
218
+ }
219
+ let moved = false;
220
+ for (const [parent, indexes] of byParent) {
221
+ const siblings = parent === null ? tr.doc.nodes : map.get(parent).node.children;
222
+ indexes.sort((a, b) => delta < 0 ? a - b : b - a);
223
+ const blocked = /* @__PURE__ */ new Set();
224
+ for (const index of indexes) {
225
+ const target = index + delta;
226
+ if (target < 0 || target >= siblings.length || blocked.has(target)) {
227
+ blocked.add(index);
228
+ continue;
229
+ }
230
+ const id = siblings[index].id;
231
+ tr.move(id, parent, target);
232
+ moved = true;
233
+ }
234
+ }
235
+ return moved;
236
+ }
237
+ /** Put nodes of one container into a new section where the first of them was; its id. */
238
+ function wrapNodes(tr, ids, context) {
239
+ const chosen = topmost(tr.doc, ids);
240
+ if (!chosen.length) return void 0;
241
+ const map = formNodePlaces(tr.doc);
242
+ const first = map.get(chosen[0]);
243
+ const parent = first.parent;
244
+ if (chosen.some((id) => map.get(id).parent !== parent || isPage(map.get(id).node))) return void 0;
245
+ const section = createNode("section", {
246
+ ...context,
247
+ taken: /* @__PURE__ */ new Set()
248
+ });
249
+ const parentId = parent?.id ?? null;
250
+ tr.insert(section, parentId, first.index);
251
+ chosen.forEach((id, index) => tr.move(id, section.id, index));
252
+ return section.id;
253
+ }
254
+ /**
255
+ * Move nodes, in document order, into a container (`null`: the top) so
256
+ * that they start at `index` of its children as they are now (the moved
257
+ * nodes counted). Returns whether anything changed.
258
+ */
259
+ function moveNodesTo(tr, ids, parent, index) {
260
+ const chosen = topmost(tr.doc, ids);
261
+ if (!chosen.length) return false;
262
+ const moving = new Set(chosen);
263
+ const before = formNodePlaces(tr.doc);
264
+ const siblings = (doc) => parent === null ? doc.nodes : (formNodePlaces(doc).get(parent)?.node)?.children ?? [];
265
+ const anchor = siblings(tr.doc).slice(index).find((node) => !moving.has(node.id))?.id;
266
+ for (const id of chosen) {
267
+ const map = formNodePlaces(tr.doc);
268
+ const own = map.get(id);
269
+ const inside = (own.parent?.id ?? null) === parent;
270
+ let target = anchor ? map.get(anchor).index : siblings(tr.doc).length;
271
+ if (inside && own.index < target) target--;
272
+ tr.move(id, parent, target);
273
+ }
274
+ const after = formNodePlaces(tr.doc);
275
+ return chosen.some((id) => {
276
+ const a = before.get(id);
277
+ const b = after.get(id);
278
+ return a.parent?.id !== b.parent?.id || a.index !== b.index;
279
+ });
280
+ }
281
+ /**
282
+ * Every place nodes can be moved to, in reading order: before each child
283
+ * of each container and at its end. Places inside the moved nodes, in
284
+ * pages not shown, or in containers that cannot hold them are left out.
285
+ */
286
+ function dropPlaces(document, ids, shown) {
287
+ const map = formNodePlaces(document);
288
+ const nodes = topmost(document, ids).map((id) => map.get(id)?.node).filter((node) => !!node);
289
+ const moving = new Set(nodes.map((node) => node.id));
290
+ const result = [];
291
+ const visit = (holder, children) => {
292
+ const fits = nodes.every((node) => canContainFormNode(holder, node));
293
+ children.forEach((child, index) => {
294
+ if (fits) result.push({
295
+ parent: holder?.id ?? null,
296
+ index
297
+ });
298
+ if (moving.has(child.id) || !isContainer(child)) return;
299
+ if (child.type === "tabs" || child.type === "steps") {
300
+ const page = shown(child);
301
+ if (page) visit(page, page.children);
302
+ } else visit(child, child.children);
303
+ });
304
+ if (fits) result.push({
305
+ parent: holder?.id ?? null,
306
+ index: children.length
307
+ });
308
+ };
309
+ visit(null, document.nodes);
310
+ return result;
311
+ }
312
+ /**
313
+ * Turn the document into another, e.g. one imported as JSON or one changed
314
+ * by another person, in as few steps as it takes: nodes only the old one
315
+ * has go, nodes only the new one has come in, nodes in both move where the
316
+ * new one has them and take its keys, and the settings follow. Nodes that
317
+ * did not change stay the same objects. As steps of a transaction, so undo
318
+ * brings the old document back.
319
+ */
320
+ function replaceDocument(tr, next) {
321
+ const wanted = /* @__PURE__ */ new Map();
322
+ for (const node of walkNodes(next.nodes)) wanted.set(node.id, node);
323
+ for (const node of walkNodes(tr.doc.nodes)) {
324
+ const target = wanted.get(node.id);
325
+ if (target && target.type === node.type) continue;
326
+ if (formNodePlaces(tr.doc).has(node.id)) tr.remove(node.id);
327
+ }
328
+ const place = (children, parent) => {
329
+ children.forEach((target, index) => {
330
+ const at = formNodePlaces(tr.doc).get(target.id);
331
+ if (!at) {
332
+ const whole = ![...walkNodes([target])].some((node) => node !== target && formNodePlaces(tr.doc).has(node.id));
333
+ const node = whole || !isContainer(target) ? target : {
334
+ ...target,
335
+ children: []
336
+ };
337
+ tr.insert(node, parent, index);
338
+ if (!whole && isContainer(target)) place(target.children, target.id);
339
+ return;
340
+ }
341
+ if ((at.parent?.id ?? null) !== parent || at.index !== index) tr.move(target.id, parent, index);
342
+ const patch = nodePatch(at.node, target);
343
+ if (patch) tr.update(target.id, patch);
344
+ if (isContainer(target)) place(target.children, target.id);
345
+ });
346
+ };
347
+ place(next.nodes, null);
348
+ const before = tr.doc;
349
+ const after = next;
350
+ const patch = {};
351
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
352
+ if (key === "nodes" || key === "version" || sameValue(before[key], after[key])) continue;
353
+ patch[key] = after[key];
354
+ }
355
+ if (Object.keys(patch).length) tr.updateDocument(patch);
356
+ }
357
+ var sameValue = (a, b) => a === b || typeof a === "object" && typeof b === "object" && JSON.stringify(a) === JSON.stringify(b);
358
+ /** The keys of `to` that differ from `from`, besides its place and what it holds; `null` when none. */
359
+ function nodePatch(from, to) {
360
+ const a = from;
361
+ const b = to;
362
+ const patch = {};
363
+ let changed = false;
364
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) {
365
+ if (key === "id" || key === "type" || key === "children" || sameValue(a[key], b[key])) continue;
366
+ patch[key] = b[key];
367
+ changed = true;
368
+ }
369
+ return changed ? patch : null;
370
+ }
371
+ export { copyNode, createNode, dropPlaces, duplicateNodes, insertPlace, moveNodes, moveNodesTo, namesIn, removeNodes, replaceDocument, scopeOf, topmost, uniqueName, wrapNodes };
372
+
373
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","names":[],"sources":["../../../src/builder/core/commands.ts"],"sourcesContent":["import { cloneValue, createId, isContainer, isList, isPage, walkNodes } from '../../core/document'\nimport type {\n FormContainerNode,\n FormDocument,\n FormFieldNode,\n FormListNode,\n FormNode,\n FormPageNode,\n FormSectionNode,\n} from '../../core/types'\nimport type { FormFieldType } from '../../fields/define'\nimport type { FormBuilderMessages } from '../locale'\nimport { formatMessage } from '../../locale'\nimport { canContainFormNode, formNodePlaces } from './transaction'\nimport type { FormBuilderTransaction, FormDocumentPatch } from './transaction'\n\n/** Where a new node goes: into a container (`null`: the top) at an index. */\nexport interface FormBuilderInsertPlace {\n parent: string | null\n index: number\n}\n\n/** The list a node's name is a key of, or `null` for names at the top of the form. */\nexport function scopeOf(document: FormDocument, parent: string | null): FormListNode | null {\n const map = formNodePlaces(document)\n let id = parent\n while (id !== null) {\n const place = map.get(id)\n if (!place) return null\n if (isList(place.node)) return place.node\n id = place.parent?.id ?? null\n }\n return null\n}\n\n/** The names already used in a scope: fields and lists, not the fields of nested lists. */\nexport function namesIn(document: FormDocument, scope: FormListNode | null): Set<string> {\n const names = new Set<string>()\n const visit = (nodes: readonly FormNode[]) => {\n for (const node of nodes) {\n if ('name' in node && node.name) names.add(node.name)\n if (isContainer(node) && !isList(node)) visit(node.children)\n }\n }\n visit(scope ? scope.children : document.nodes)\n return names\n}\n\n/** `select3` after `select` and `select2`: the type in camel case and the first free number. */\nexport function uniqueName(base: string, taken: ReadonlySet<string>) {\n const stem = base.replace(/-(\\w)/g, (_, letter: string) => letter.toUpperCase())\n for (let index = 1; ; index++) {\n const name = `${stem}${index}`\n if (!taken.has(name)) return name\n }\n}\n\nconst newPage = (title: string): FormPageNode => ({\n id: createId('p'),\n type: 'page',\n title,\n children: [],\n})\n\n/** A new node of a type, with an id, a free name and texts to start from. */\nexport function createNode(\n type: string,\n context: {\n fields: Map<string, FormFieldType>\n messages: FormBuilderMessages\n taken: ReadonlySet<string>\n },\n): FormNode {\n const { messages, taken } = context\n const label = context.fields.get(type)?.label ?? messages.types[type] ?? type\n switch (type) {\n case 'section':\n return { id: createId('s'), type: 'section', title: label, children: [] }\n case 'tabs':\n case 'steps': {\n const text = type === 'tabs' ? messages.created.page : messages.created.step\n return {\n id: createId(type === 'tabs' ? 't' : 'st'),\n type,\n children: [1, 2].map((index) => newPage(formatMessage(text, { index }))),\n }\n }\n case 'page':\n return newPage(formatMessage(messages.created.page, { index: 1 }))\n case 'list':\n return {\n id: createId('l'),\n type: 'list',\n name: uniqueName('list', taken),\n label,\n children: [],\n }\n }\n const spec = context.fields.get(type)\n const node: FormFieldNode = { id: createId(), type }\n if (spec?.kind === 'display') {\n if (type === 'text') node.props = { content: messages.created.text }\n if (type === 'alert') node.props = { title: messages.created.alert }\n return node\n }\n node.name = uniqueName(type, taken)\n node.label = label\n if (spec?.optionsProp) {\n const options = [1, 2, 3].map((index) => ({\n label: formatMessage(messages.created.option, { index }),\n value: `option${index}`,\n }))\n node.props = { [spec.optionsProp]: options }\n // A tree's options are nodes: the same shape, read as `label` and `value`.\n }\n return node\n}\n\n/** The page of tabs or steps the builder shows, given which ones are open. */\nexport type ShownPage = (container: FormContainerNode) => FormPageNode | undefined\n\n/**\n * Where a node of `type` goes when added from the palette: inside the\n * last selected container (the shown page of tabs and steps), else after\n * the last selected node, else at the end of the form.\n */\nexport function insertPlace(\n document: FormDocument,\n selection: readonly string[],\n type: string,\n shown: ShownPage,\n): FormBuilderInsertPlace {\n const map = formNodePlaces(document)\n const probe = { id: '', type, children: [] } as unknown as FormNode\n const last = selection[selection.length - 1]\n let place = last ? map.get(last) : undefined\n while (place) {\n let target: FormNode = place.node\n if (target.type === 'tabs' || target.type === 'steps') {\n const page = shown(target as FormContainerNode)\n if (page) target = page\n }\n if (isContainer(target) && canContainFormNode(target, probe))\n return { parent: target.id, index: target.children.length }\n const parent = place.parent\n if (canContainFormNode(parent, probe))\n return { parent: parent?.id ?? null, index: place.index + 1 }\n place = parent ? map.get(parent.id) : undefined\n }\n return { parent: null, index: document.nodes.length }\n}\n\n/** The selected nodes without the ones inside other selected nodes, in document order. */\nexport function topmost(document: FormDocument, ids: readonly string[]): string[] {\n const map = formNodePlaces(document)\n const chosen = new Set(ids.filter((id) => map.has(id)))\n const result: string[] = []\n for (const node of walkNodes(document.nodes)) {\n if (!chosen.has(node.id)) continue\n let up = map.get(node.id)!.parent\n let inside = false\n while (up) {\n if (chosen.has(up.id)) {\n inside = true\n break\n }\n up = map.get(up.id)!.parent\n }\n if (!inside) result.push(node.id)\n }\n return result\n}\n\n/** A copy of a node with new ids, and a free name for the copy itself. */\nexport function copyNode(node: FormNode, taken: Set<string>): FormNode {\n const copy = cloneValue(node)\n for (const each of walkNodes([copy])) each.id = createId(each.id.split('-')[0] || 'f')\n if ('name' in copy && copy.name) {\n const stem = copy.name.replace(/\\d+$/, '')\n copy.name = uniqueName(stem || copy.type, taken)\n taken.add(copy.name)\n }\n return copy\n}\n\n/** What the commands below read besides the document. */\nexport interface CommandContext {\n fields: Map<string, FormFieldType>\n messages: FormBuilderMessages\n}\n\n/** Remove nodes; the node to select afterwards: the next one, else the one before, else the parent. */\nexport function removeNodes(tr: FormBuilderTransaction, ids: readonly string[]) {\n const ids2 = topmost(tr.doc, ids)\n if (!ids2.length) return { removed: 0, next: undefined as string | undefined }\n const map = formNodePlaces(tr.doc)\n const last = map.get(ids2[ids2.length - 1]!)!\n const siblings = last.parent ? last.parent.children : tr.doc.nodes\n const gone = new Set(ids2)\n const after = siblings.slice(last.index + 1).find((node) => !gone.has(node.id))\n let before: FormNode | undefined\n for (let at = last.index - 1; at >= 0 && !before; at--)\n if (!gone.has(siblings[at]!.id)) before = siblings[at]\n let next = after?.id ?? before?.id\n if (!next) {\n let up = last.parent\n while (up && gone.has(up.id)) up = map.get(up.id)?.parent ?? null\n next = up?.id\n }\n for (const id of ids2) tr.remove(id)\n return { removed: ids2.length, next }\n}\n\n/** Put a copy of each node right after it; the ids of the copies. */\nexport function duplicateNodes(tr: FormBuilderTransaction, ids: readonly string[]) {\n const copies: string[] = []\n for (const id of topmost(tr.doc, ids)) {\n const place = formNodePlaces(tr.doc).get(id)\n if (!place) continue\n const parent = place.parent?.id ?? null\n const taken = namesIn(tr.doc, scopeOf(tr.doc, parent))\n const copy = copyNode(place.node, taken)\n tr.insert(copy, parent, place.index + 1)\n copies.push(copy.id)\n }\n return copies\n}\n\n/**\n * Move each node one place up (`-1`) or down (`1`) among its siblings.\n * Nodes already at the edge stay, and so do the selected ones next to\n * them, so a selection keeps its shape. Returns whether anything moved.\n */\nexport function moveNodes(tr: FormBuilderTransaction, ids: readonly string[], delta: -1 | 1) {\n const chosen = topmost(tr.doc, ids)\n const byParent = new Map<string | null, number[]>()\n const map = formNodePlaces(tr.doc)\n for (const id of chosen) {\n const place = map.get(id)!\n const key = place.parent?.id ?? null\n const list = byParent.get(key) ?? []\n list.push(place.index)\n byParent.set(key, list)\n }\n let moved = false\n for (const [parent, indexes] of byParent) {\n const siblings =\n parent === null ? tr.doc.nodes : (map.get(parent)!.node as FormContainerNode).children\n indexes.sort((a, b) => (delta < 0 ? a - b : b - a))\n const blocked = new Set<number>()\n for (const index of indexes) {\n const target = index + delta\n if (target < 0 || target >= siblings.length || blocked.has(target)) {\n blocked.add(index)\n continue\n }\n const id = siblings[index]!.id\n tr.move(id, parent, target)\n moved = true\n }\n }\n return moved\n}\n\n/** Put nodes of one container into a new section where the first of them was; its id. */\nexport function wrapNodes(\n tr: FormBuilderTransaction,\n ids: readonly string[],\n context: CommandContext,\n) {\n const chosen = topmost(tr.doc, ids)\n if (!chosen.length) return undefined\n const map = formNodePlaces(tr.doc)\n const first = map.get(chosen[0]!)!\n const parent = first.parent\n if (chosen.some((id) => map.get(id)!.parent !== parent || isPage(map.get(id)!.node)))\n return undefined\n const section = createNode('section', { ...context, taken: new Set() }) as FormSectionNode\n const parentId = parent?.id ?? null\n tr.insert(section, parentId, first.index)\n chosen.forEach((id, index) => tr.move(id, section.id, index))\n return section.id\n}\n\n/**\n * Move nodes, in document order, into a container (`null`: the top) so\n * that they start at `index` of its children as they are now (the moved\n * nodes counted). Returns whether anything changed.\n */\nexport function moveNodesTo(\n tr: FormBuilderTransaction,\n ids: readonly string[],\n parent: string | null,\n index: number,\n) {\n const chosen = topmost(tr.doc, ids)\n if (!chosen.length) return false\n const moving = new Set(chosen)\n const before = formNodePlaces(tr.doc)\n const siblings = (doc: FormDocument) =>\n parent === null\n ? doc.nodes\n : ((formNodePlaces(doc).get(parent)?.node as FormContainerNode | undefined)?.children ?? [])\n // The first node at or after `index` that stays: the moved nodes go in front of it.\n const anchor = siblings(tr.doc)\n .slice(index)\n .find((node) => !moving.has(node.id))?.id\n for (const id of chosen) {\n const map = formNodePlaces(tr.doc)\n const own = map.get(id)!\n const inside = (own.parent?.id ?? null) === parent\n let target = anchor ? map.get(anchor)!.index : siblings(tr.doc).length\n if (inside && own.index < target) target--\n tr.move(id, parent, target)\n }\n const after = formNodePlaces(tr.doc)\n const moved = chosen.some((id) => {\n const a = before.get(id)!\n const b = after.get(id)!\n return a.parent?.id !== b.parent?.id || a.index !== b.index\n })\n return moved\n}\n\n/**\n * Every place nodes can be moved to, in reading order: before each child\n * of each container and at its end. Places inside the moved nodes, in\n * pages not shown, or in containers that cannot hold them are left out.\n */\nexport function dropPlaces(\n document: FormDocument,\n ids: readonly string[],\n shown: ShownPage,\n): FormBuilderInsertPlace[] {\n const map = formNodePlaces(document)\n const nodes = topmost(document, ids)\n .map((id) => map.get(id)?.node)\n .filter((node): node is FormNode => !!node)\n const moving = new Set(nodes.map((node) => node.id))\n const result: FormBuilderInsertPlace[] = []\n const visit = (holder: FormContainerNode | null, children: readonly FormNode[]) => {\n const fits = nodes.every((node) => canContainFormNode(holder, node))\n children.forEach((child, index) => {\n if (fits) result.push({ parent: holder?.id ?? null, index })\n if (moving.has(child.id) || !isContainer(child)) return\n if (child.type === 'tabs' || child.type === 'steps') {\n const page = shown(child as FormContainerNode)\n if (page) visit(page, page.children)\n } else visit(child as FormContainerNode, child.children)\n })\n if (fits) result.push({ parent: holder?.id ?? null, index: children.length })\n }\n visit(null, document.nodes)\n return result\n}\n\n/**\n * Turn the document into another, e.g. one imported as JSON or one changed\n * by another person, in as few steps as it takes: nodes only the old one\n * has go, nodes only the new one has come in, nodes in both move where the\n * new one has them and take its keys, and the settings follow. Nodes that\n * did not change stay the same objects. As steps of a transaction, so undo\n * brings the old document back.\n */\nexport function replaceDocument(tr: FormBuilderTransaction, next: FormDocument) {\n const wanted = new Map<string, FormNode>()\n for (const node of walkNodes(next.nodes)) wanted.set(node.id, node)\n\n // Nodes the new document has not, or has as another type, go first, with what they hold.\n for (const node of walkNodes(tr.doc.nodes)) {\n const target = wanted.get(node.id)\n if (target && target.type === node.type) continue\n if (formNodePlaces(tr.doc).has(node.id)) tr.remove(node.id)\n }\n\n // Then from the top down, so each node's container is in its place before the node.\n const place = (children: readonly FormNode[], parent: string | null) => {\n children.forEach((target, index) => {\n const at = formNodePlaces(tr.doc).get(target.id)\n if (!at) {\n // A node that is new with all it holds comes in whole; one holding nodes that are here, empty.\n const whole = ![...walkNodes([target])].some(\n (node) => node !== target && formNodePlaces(tr.doc).has(node.id),\n )\n const node = whole || !isContainer(target) ? target : { ...target, children: [] }\n tr.insert(node as FormNode, parent, index)\n if (!whole && isContainer(target)) place(target.children, target.id)\n return\n }\n if ((at.parent?.id ?? null) !== parent || at.index !== index)\n tr.move(target.id, parent, index)\n const patch = nodePatch(at.node, target)\n if (patch) tr.update(target.id, patch)\n if (isContainer(target)) place(target.children, target.id)\n })\n }\n place(next.nodes, null)\n\n const before = tr.doc as unknown as Record<string, unknown>\n const after = next as unknown as Record<string, unknown>\n const patch: Record<string, unknown> = {}\n for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {\n if (key === 'nodes' || key === 'version' || sameValue(before[key], after[key])) continue\n patch[key] = after[key]\n }\n if (Object.keys(patch).length) tr.updateDocument(patch as FormDocumentPatch)\n}\n\nconst sameValue = (a: unknown, b: unknown) =>\n a === b ||\n (typeof a === 'object' && typeof b === 'object' && JSON.stringify(a) === JSON.stringify(b))\n\n/** The keys of `to` that differ from `from`, besides its place and what it holds; `null` when none. */\nfunction nodePatch(from: FormNode, to: FormNode): Record<string, unknown> | null {\n const a = from as unknown as Record<string, unknown>\n const b = to as unknown as Record<string, unknown>\n const patch: Record<string, unknown> = {}\n let changed = false\n for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {\n if (key === 'id' || key === 'type' || key === 'children' || sameValue(a[key], b[key])) continue\n patch[key] = b[key]\n changed = true\n }\n return changed ? patch : null\n}\n"],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import { applyFormSteps, compactFormSteps } from "./transaction.js";
2
+ // src/builder/core/history.ts
3
+ /** Transactions of one group merge when they come within this many ms. */
4
+ var GROUP_WINDOW = 1e3;
5
+ /** Undo and redo of a builder: the steps of each transaction and the selection around it. */
6
+ var FormBuilderHistory = class {
7
+ depth;
8
+ done = [];
9
+ undone = [];
10
+ constructor(depth = 200) {
11
+ this.depth = depth;
12
+ }
13
+ get canUndo() {
14
+ return this.done.length > 0;
15
+ }
16
+ get canRedo() {
17
+ return this.undone.length > 0;
18
+ }
19
+ record(tr, selectionBefore, selectionAfter, now = Date.now()) {
20
+ if (!tr.changed || tr.meta.history === false) return;
21
+ this.undone = [];
22
+ const last = this.done[this.done.length - 1];
23
+ const group = tr.meta.group;
24
+ if (last && group && last.group === group && now - last.time < GROUP_WINDOW) {
25
+ last.steps = compactFormSteps([...last.steps, ...tr.steps]);
26
+ last.inverse = compactFormSteps([...tr.inverse, ...last.inverse]);
27
+ last.time = now;
28
+ last.selectionAfter = selectionAfter;
29
+ return;
30
+ }
31
+ this.done.push({
32
+ steps: tr.steps.slice(),
33
+ inverse: tr.inverse.slice(),
34
+ group,
35
+ time: now,
36
+ selectionBefore,
37
+ selectionAfter
38
+ });
39
+ if (this.done.length > this.depth) this.done.shift();
40
+ }
41
+ /** End the current group, so the next change starts a new undo step. */
42
+ close() {
43
+ const last = this.done[this.done.length - 1];
44
+ if (last) last.group = void 0;
45
+ }
46
+ undo(document) {
47
+ const entry = this.done.pop();
48
+ if (!entry) return null;
49
+ const tr = applyFormSteps(document, entry.inverse);
50
+ this.undone.push(entry);
51
+ return {
52
+ tr,
53
+ selection: entry.selectionBefore
54
+ };
55
+ }
56
+ redo(document) {
57
+ const entry = this.undone.pop();
58
+ if (!entry) return null;
59
+ const tr = applyFormSteps(document, entry.steps);
60
+ this.done.push({
61
+ ...entry,
62
+ group: void 0
63
+ });
64
+ return {
65
+ tr,
66
+ selection: entry.selectionAfter
67
+ };
68
+ }
69
+ clear() {
70
+ this.done = [];
71
+ this.undone = [];
72
+ }
73
+ };
74
+ export { FormBuilderHistory };
75
+
76
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","names":[],"sources":["../../../src/builder/core/history.ts"],"sourcesContent":["import type { FormDocument } from '../../core/types'\nimport { applyFormSteps, compactFormSteps } from './transaction'\nimport type { FormBuilderStep, FormBuilderTransaction } from './transaction'\n\ninterface Entry {\n steps: FormBuilderStep[]\n inverse: FormBuilderStep[]\n group?: string\n time: number\n selectionBefore: string[]\n selectionAfter: string[]\n}\n\n/** Transactions of one group merge when they come within this many ms. */\nconst GROUP_WINDOW = 1000\n\n/** Undo and redo of a builder: the steps of each transaction and the selection around it. */\nexport class FormBuilderHistory {\n private done: Entry[] = []\n private undone: Entry[] = []\n\n constructor(private depth = 200) {}\n\n get canUndo() {\n return this.done.length > 0\n }\n\n get canRedo() {\n return this.undone.length > 0\n }\n\n record(\n tr: FormBuilderTransaction,\n selectionBefore: string[],\n selectionAfter: string[],\n now = Date.now(),\n ) {\n if (!tr.changed || tr.meta.history === false) return\n this.undone = []\n const last = this.done[this.done.length - 1]\n const group = tr.meta.group\n if (last && group && last.group === group && now - last.time < GROUP_WINDOW) {\n last.steps = compactFormSteps([...last.steps, ...tr.steps])\n last.inverse = compactFormSteps([...tr.inverse, ...last.inverse])\n last.time = now\n last.selectionAfter = selectionAfter\n return\n }\n this.done.push({\n steps: tr.steps.slice(),\n inverse: tr.inverse.slice(),\n group,\n time: now,\n selectionBefore,\n selectionAfter,\n })\n if (this.done.length > this.depth) this.done.shift()\n }\n\n /** End the current group, so the next change starts a new undo step. */\n close() {\n const last = this.done[this.done.length - 1]\n if (last) last.group = undefined\n }\n\n undo(document: FormDocument) {\n const entry = this.done.pop()\n if (!entry) return null\n const tr = applyFormSteps(document, entry.inverse)\n this.undone.push(entry)\n return { tr, selection: entry.selectionBefore }\n }\n\n redo(document: FormDocument) {\n const entry = this.undone.pop()\n if (!entry) return null\n const tr = applyFormSteps(document, entry.steps)\n this.done.push({ ...entry, group: undefined })\n return { tr, selection: entry.selectionAfter }\n }\n\n clear() {\n this.done = []\n this.undone = []\n }\n}\n"],"mappings":";;;AAcA,IAAM,eAAe;;AAGrB,IAAa,qBAAb,MAAgC;CAIV;CAHpB,OAAwB,CAAC;CACzB,SAA0B,CAAC;CAE3B,YAAY,QAAgB,KAAK;EAAb,KAAA,QAAA;CAAc;CAElC,IAAI,UAAU;EACZ,OAAO,KAAK,KAAK,SAAS;CAC5B;CAEA,IAAI,UAAU;EACZ,OAAO,KAAK,OAAO,SAAS;CAC9B;CAEA,OACE,IACA,iBACA,gBACA,MAAM,KAAK,IAAI,GACf;EACA,IAAI,CAAC,GAAG,WAAW,GAAG,KAAK,YAAY,OAAO;EAC9C,KAAK,SAAS,CAAC;EACf,MAAM,OAAO,KAAK,KAAK,KAAK,KAAK,SAAS;EAC1C,MAAM,QAAQ,GAAG,KAAK;EACtB,IAAI,QAAQ,SAAS,KAAK,UAAU,SAAS,MAAM,KAAK,OAAO,cAAc;GAC3E,KAAK,QAAQ,iBAAiB,CAAC,GAAG,KAAK,OAAO,GAAG,GAAG,KAAK,CAAC;GAC1D,KAAK,UAAU,iBAAiB,CAAC,GAAG,GAAG,SAAS,GAAG,KAAK,OAAO,CAAC;GAChE,KAAK,OAAO;GACZ,KAAK,iBAAiB;GACtB;EACF;EACA,KAAK,KAAK,KAAK;GACb,OAAO,GAAG,MAAM,MAAM;GACtB,SAAS,GAAG,QAAQ,MAAM;GAC1B;GACA,MAAM;GACN;GACA;EACF,CAAC;EACD,IAAI,KAAK,KAAK,SAAS,KAAK,OAAO,KAAK,KAAK,MAAM;CACrD;;CAGA,QAAQ;EACN,MAAM,OAAO,KAAK,KAAK,KAAK,KAAK,SAAS;EAC1C,IAAI,MAAM,KAAK,QAAQ,KAAA;CACzB;CAEA,KAAK,UAAwB;EAC3B,MAAM,QAAQ,KAAK,KAAK,IAAI;EAC5B,IAAI,CAAC,OAAO,OAAO;EACnB,MAAM,KAAK,eAAe,UAAU,MAAM,OAAO;EACjD,KAAK,OAAO,KAAK,KAAK;EACtB,OAAO;GAAE;GAAI,WAAW,MAAM;EAAgB;CAChD;CAEA,KAAK,UAAwB;EAC3B,MAAM,QAAQ,KAAK,OAAO,IAAI;EAC9B,IAAI,CAAC,OAAO,OAAO;EACnB,MAAM,KAAK,eAAe,UAAU,MAAM,KAAK;EAC/C,KAAK,KAAK,KAAK;GAAE,GAAG;GAAO,OAAO,KAAA;EAAU,CAAC;EAC7C,OAAO;GAAE;GAAI,WAAW,MAAM;EAAe;CAC/C;CAEA,QAAQ;EACN,KAAK,OAAO,CAAC;EACb,KAAK,SAAS,CAAC;CACjB;AACF"}