@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,695 @@
1
+ import { getPath, isContainer, isList, isPage, setPath } from "../core/document.js";
2
+ import { startValue } from "../core/values.js";
3
+ import { formatMessage, useFormRendererMessages } from "../locale.js";
4
+ import { rendererKey, rootScope, scopeKey } from "../renderer/context.js";
5
+ import { FormNodeView, nodeKey, spanStyle } from "../renderer/node-view.js";
6
+ import { formNodePlaces } from "./core/transaction.js";
7
+ import { nodeTitle } from "./settings.js";
8
+ import { canvasOrder, useBuilderState } from "./context.js";
9
+ import { menuItems, nodeMenu } from "./menu.js";
10
+ import { createDragController } from "./drag.js";
11
+ import { DouButton, DouContextMenu, DouDropdown, DouForm, DouTooltip, holdPointer } from "dou-ui";
12
+ import { computed, defineComponent, h, inject, onBeforeUnmount, onMounted, provide, reactive, shallowRef, watch } from "vue";
13
+ import { ArrowDownIcon, ArrowUpIcon, CopyIcon, CornerLeftUpIcon, MoreHorizontalIcon, PlusIcon, SigmaIcon, TrashIcon, WorkflowIcon } from "@dou-ui/icons";
14
+ // src/builder/canvas.ts
15
+ /**
16
+ * The key a node's value has on the canvas: made from its id, not its
17
+ * name, so a name being typed, left empty or used twice does not bind
18
+ * the field to another field's value.
19
+ */
20
+ var previewKey = (node) => `n_${node.id.replace(/\W/g, "_")}`;
21
+ /**
22
+ * A field as the canvas draws it: always shown, enabled and editable in
23
+ * look, with no options to load, and its value under `previewKey`. Kept
24
+ * per node, so an untouched field is not drawn again.
25
+ */
26
+ var designed = /* @__PURE__ */ new WeakMap();
27
+ function designNode(node) {
28
+ let result = designed.get(node);
29
+ if (!result) {
30
+ const rest = { ...node };
31
+ for (const key of [
32
+ "visibleWhen",
33
+ "disabledWhen",
34
+ "formula",
35
+ "source",
36
+ "requiredWhen"
37
+ ]) delete rest[key];
38
+ if ("name" in node) rest.name = previewKey(node);
39
+ result = node.requiredWhen ? {
40
+ ...rest,
41
+ required: true
42
+ } : rest;
43
+ designed.set(node, result);
44
+ }
45
+ return result;
46
+ }
47
+ /**
48
+ * The values the canvas shows: each field's default, and one item in
49
+ * every list. Only fields whose node changed are written again.
50
+ */
51
+ function useScratch(state) {
52
+ const model = reactive({});
53
+ const written = /* @__PURE__ */ new WeakMap();
54
+ function sync(nodes, prefix, types) {
55
+ for (const node of nodes) if (isList(node)) {
56
+ const path = prefix + previewKey(node);
57
+ const items = getPath(model, path);
58
+ if (!Array.isArray(items) || !items.length || typeof items[0] !== "object") setPath(model, path, [{}]);
59
+ sync(node.children, `${path}.0.`, types);
60
+ } else if (isContainer(node)) sync(node.children, prefix, types);
61
+ else if ("name" in node) {
62
+ const path = prefix + previewKey(node);
63
+ if (written.get(node) === path) continue;
64
+ const type = types.get(node.type);
65
+ if (!type || type.kind === "display") continue;
66
+ written.set(node, path);
67
+ setPath(model, path, startValue(type, node));
68
+ }
69
+ }
70
+ watch(() => [state.document, state.fields], ([document, types]) => sync(document.nodes, "", types), { immediate: true });
71
+ return model;
72
+ }
73
+ var noop = () => {};
74
+ var canvasView = (state) => ({
75
+ name: "canvas",
76
+ order: () => canvasOrder(state)
77
+ });
78
+ /** How many fields the canvas draws right away when it opens. */
79
+ var EAGER_FIELDS = 16;
80
+ var lazyKey = Symbol("DouFormBuilderCanvasLazy");
81
+ /** The canvas: the form as the renderer draws it, each node selectable. */
82
+ var FormBuilderCanvas = defineComponent({
83
+ name: "DouFormBuilderCanvas",
84
+ setup() {
85
+ const state = useBuilderState();
86
+ const scratch = useScratch(state);
87
+ const rendererMessages = useFormRendererMessages();
88
+ const document = computed(() => state.document);
89
+ const layout = computed(() => document.value.layout ?? "vertical");
90
+ const columns = computed(() => Math.max(1, Math.round(document.value.columns ?? 12)));
91
+ provide(rendererKey, {
92
+ model: () => scratch,
93
+ fields: () => state.fields,
94
+ labelWidth: () => layout.value === "horizontal" ? document.value.labelWidth ?? 96 : void 0,
95
+ changed: noop,
96
+ functions: () => void 0,
97
+ validators: () => void 0,
98
+ readonly: () => false,
99
+ blockedFormulas: () => /* @__PURE__ */ new Set(),
100
+ settled: () => scratch,
101
+ controlsReadonly: () => false,
102
+ load: () => Promise.resolve([]),
103
+ sourceVersion: () => 0,
104
+ messages: () => rendererMessages.value,
105
+ validate: async () => ({
106
+ valid: true,
107
+ errors: {}
108
+ }),
109
+ scrollToField: noop,
110
+ addRevealer: () => noop,
111
+ trackError: () => noop,
112
+ errorPaths: () => [],
113
+ footer: () => null,
114
+ footerOwner: () => void 0,
115
+ resets: () => 0
116
+ });
117
+ provide(scopeKey, rootScope);
118
+ const view = canvasView(state);
119
+ const scroller = shallowRef(null);
120
+ let budget = EAGER_FIELDS;
121
+ const waiting = /* @__PURE__ */ new Map();
122
+ let observer;
123
+ const lazy = {
124
+ eager: () => budget-- > 0,
125
+ pressed: false,
126
+ observe(el, show) {
127
+ if (typeof IntersectionObserver === "undefined") return show();
128
+ observer ??= new IntersectionObserver((entries) => {
129
+ for (const entry of entries) {
130
+ if (!entry.isIntersecting) continue;
131
+ observer.unobserve(entry.target);
132
+ waiting.get(entry.target)?.();
133
+ waiting.delete(entry.target);
134
+ }
135
+ }, {
136
+ root: scroller.value,
137
+ rootMargin: "800px 0px"
138
+ });
139
+ waiting.set(el, show);
140
+ observer.observe(el);
141
+ return () => {
142
+ observer?.unobserve(el);
143
+ waiting.delete(el);
144
+ };
145
+ }
146
+ };
147
+ provide(lazyKey, lazy);
148
+ onMounted(() => budget = Infinity);
149
+ onBeforeUnmount(() => observer?.disconnect());
150
+ watch(() => state.selection[state.selection.length - 1], (id) => {
151
+ if (lazy.pressed) return void (lazy.pressed = false);
152
+ if (!id) return;
153
+ requestAnimationFrame(() => {
154
+ (globalThis.document?.getElementById(state.domId(id, "canvas")))?.scrollIntoView?.({
155
+ block: "nearest",
156
+ inline: "nearest"
157
+ });
158
+ });
159
+ });
160
+ const layer = {
161
+ indicator: null,
162
+ holder: null,
163
+ ghost: null
164
+ };
165
+ const dragger = createDragController(state, () => scroller.value, layer);
166
+ state.dragger.value = dragger;
167
+ onBeforeUnmount(() => {
168
+ dragger.stop();
169
+ if (state.dragger.value === dragger) state.dragger.value = null;
170
+ });
171
+ const active = computed(() => {
172
+ const last = state.selection[state.selection.length - 1];
173
+ return last ? state.domId(last, "canvas") : void 0;
174
+ });
175
+ const contextMenu = shallowRef(null);
176
+ const openMenu = (x, y) => {
177
+ if (nodeMenu(state, state.selection).length) contextMenu.value?.open(x, y);
178
+ };
179
+ state.openMenu.value = openMenu;
180
+ onBeforeUnmount(() => {
181
+ if (state.openMenu.value === openMenu) state.openMenu.value = null;
182
+ });
183
+ function onTreeKeydown(event) {
184
+ if (event.key === "ContextMenu" || event.shiftKey && event.key === "F10") {
185
+ event.preventDefault();
186
+ const id = state.selection[state.selection.length - 1];
187
+ const rect = id ? globalThis.document?.getElementById(state.domId(id, "canvas"))?.getBoundingClientRect() : void 0;
188
+ if (rect) openMenu(rect.left + 16, rect.top + 16);
189
+ return;
190
+ }
191
+ state.keydown(event, view);
192
+ }
193
+ return () => {
194
+ const doc = document.value;
195
+ return h("div", {
196
+ ref: scroller,
197
+ class: "dou-form-builder__canvas",
198
+ onClick: (event) => {
199
+ if (dragger.swallowClick()) return;
200
+ if (!event.target.closest(".dou-form-builder__node")) state.select(null);
201
+ }
202
+ }, [
203
+ h("div", {
204
+ class: "dou-form-builder__drop-layer",
205
+ "aria-hidden": true
206
+ }, [
207
+ h("div", {
208
+ ref: (el) => layer.holder = el,
209
+ class: "dou-form-builder__drop-holder",
210
+ hidden: true
211
+ }),
212
+ h("div", {
213
+ ref: (el) => layer.indicator = el,
214
+ class: "dou-form-builder__drop-line",
215
+ hidden: true
216
+ }),
217
+ h("div", {
218
+ ref: (el) => layer.ghost = el,
219
+ class: "dou-form-builder__drag-ghost",
220
+ hidden: true
221
+ })
222
+ ]),
223
+ h(DouContextMenu, { ref: (instance) => contextMenu.value = instance }, {
224
+ default: () => h("span", { class: "dou-form-builder__menu-anchor" }),
225
+ menu: () => menuItems(nodeMenu(state, state.selection))
226
+ }),
227
+ h("div", { class: "dou-form-builder__paper" }, [h(DouForm, {
228
+ class: [
229
+ "dou-form-renderer",
230
+ `dou-form-renderer--${layout.value}`,
231
+ (doc.requiredMark ?? true) === true && "dou-form-renderer--marked"
232
+ ],
233
+ model: scratch,
234
+ layout: layout.value,
235
+ labelAlign: doc.labelAlign,
236
+ size: doc.size,
237
+ requiredMark: doc.requiredMark ?? true,
238
+ scrollToError: false,
239
+ onSubmit: noop
240
+ }, () => h("div", {
241
+ ref: (el) => state.canvas.value = el,
242
+ class: ["dou-form-renderer__grid", "dou-form-builder__tree"],
243
+ "data-holder": "",
244
+ style: { "--dou-form-columns": columns.value },
245
+ role: "tree",
246
+ tabindex: 0,
247
+ "aria-label": state.messages.canvas.label,
248
+ "aria-multiselectable": true,
249
+ "aria-activedescendant": active.value,
250
+ onKeydown: onTreeKeydown
251
+ }, doc.nodes.length ? designNodes(doc.nodes, columns.value, 0, 1) : [emptyHint(state)]))])
252
+ ]);
253
+ };
254
+ }
255
+ });
256
+ function emptyHint(state) {
257
+ const messages = state.messages.canvas;
258
+ return h("div", { class: "dou-form-builder__empty" }, state.readonly ? messages.readonlyEmpty : messages.empty);
259
+ }
260
+ function designNodes(nodes, columns, inset, level) {
261
+ return nodes.map((node, index) => h(DesignNode, {
262
+ key: nodeKey(node),
263
+ node,
264
+ columns,
265
+ inset,
266
+ level,
267
+ position: index + 1,
268
+ size: nodes.length
269
+ }));
270
+ }
271
+ /** A grid of child nodes, or a hint where there are none. */
272
+ function childGrid(state, holder, nodes, columns, inset, level) {
273
+ return h("div", {
274
+ class: "dou-form-renderer__grid",
275
+ style: { "--dou-form-columns": columns },
276
+ role: "group",
277
+ "data-holder": holder
278
+ }, nodes.length ? designNodes(nodes, columns, inset, level) : [emptyHint(state)]);
279
+ }
280
+ var DesignNode = defineComponent({
281
+ name: "DouFormBuilderNode",
282
+ props: {
283
+ node: {
284
+ type: Object,
285
+ required: true
286
+ },
287
+ columns: {
288
+ type: Number,
289
+ required: true
290
+ },
291
+ inset: {
292
+ type: Number,
293
+ default: 0
294
+ },
295
+ level: {
296
+ type: Number,
297
+ required: true
298
+ },
299
+ position: {
300
+ type: Number,
301
+ required: true
302
+ },
303
+ size: {
304
+ type: Number,
305
+ required: true
306
+ }
307
+ },
308
+ setup(props) {
309
+ const state = useBuilderState();
310
+ const renderer = inject(rendererKey);
311
+ const lazy = inject(lazyKey);
312
+ const selected = computed(() => state.selected.value.has(props.node.id));
313
+ const primary = computed(() => state.focused.value?.id === props.node.id);
314
+ if (isList(props.node)) {
315
+ const outer = inject(scopeKey, rootScope);
316
+ const prefix = () => `${outer.prefix()}${previewKey(props.node)}.0.`;
317
+ provide(scopeKey, {
318
+ prefix,
319
+ item: () => null
320
+ });
321
+ }
322
+ const shown = shallowRef(isContainer(props.node) || lazy.eager());
323
+ const el = shallowRef(null);
324
+ let stop;
325
+ onMounted(() => {
326
+ if (!shown.value && el.value) stop = lazy.observe(el.value, () => shown.value = true);
327
+ });
328
+ onBeforeUnmount(() => stop?.());
329
+ const moreOpen = shallowRef(false);
330
+ function press(event, id = props.node.id) {
331
+ event.stopPropagation();
332
+ if (state.dragger.value?.swallowClick()) return;
333
+ lazy.pressed = true;
334
+ state.pick(id, event, canvasView(state));
335
+ state.canvas.value?.focus({ preventScroll: true });
336
+ }
337
+ return () => {
338
+ const node = props.node;
339
+ const title = nodeTitle(node, state.fields, state.messages);
340
+ const logic = logicOf(state, node);
341
+ const marks = state.extensions().marks.flatMap((marksOf) => marksOf(state, node));
342
+ let body;
343
+ if (isList(node)) body = listBody(state, node, props, renderer.messages());
344
+ else if (node.type === "tabs" || node.type === "steps") body = pagesBody(state, node, props, press);
345
+ else if (isContainer(node)) body = sectionBody(state, node, props);
346
+ else if (!shown.value) body = h("div", {
347
+ class: "dou-form-builder__placeholder",
348
+ "data-type": node.type
349
+ }, [h("span", { class: "dou-form-builder__placeholder-label" }), h("span", { class: "dou-form-builder__placeholder-box" })]);
350
+ else body = h("div", {
351
+ class: "dou-form-builder__content",
352
+ inert: true
353
+ }, h(FormNodeView, {
354
+ node: designNode(node),
355
+ columns: props.columns,
356
+ inset: props.inset
357
+ }));
358
+ return h("div", {
359
+ class: [
360
+ "dou-form-renderer__cell",
361
+ "dou-form-builder__node",
362
+ isContainer(node) && "is-container",
363
+ selected.value && "is-selected",
364
+ marks.length > 0 && "is-marked"
365
+ ],
366
+ ref: el,
367
+ style: [spanStyle(node.span, props.columns), marks.length ? { "--dou-form-builder-mark": marks[0].color ?? "currentColor" } : null],
368
+ id: state.domId(node.id, "canvas"),
369
+ role: "treeitem",
370
+ "aria-level": props.level,
371
+ "aria-posinset": props.position,
372
+ "aria-setsize": props.size,
373
+ "aria-selected": selected.value,
374
+ "aria-expanded": isContainer(node) ? true : void 0,
375
+ "aria-label": title,
376
+ "aria-description": logic?.text,
377
+ "data-node": node.id,
378
+ onClick: press,
379
+ onContextmenu: (event) => {
380
+ event.stopPropagation();
381
+ event.preventDefault();
382
+ if (event.pointerType === "touch") return;
383
+ if (!selected.value) state.select(node.id);
384
+ state.openMenu.value?.(event.clientX, event.clientY);
385
+ },
386
+ onPointerdown: (event) => {
387
+ event.stopPropagation();
388
+ if (event.target.closest(".dou-form-builder__actions, .dou-form-builder__resize, .dou-form-builder__strip")) return;
389
+ state.dragger.value?.pressNode(event, node.id);
390
+ }
391
+ }, [
392
+ body,
393
+ logic && !primary.value ? logicMark(logic) : null,
394
+ marks.length ? markTags(marks) : null,
395
+ primary.value && !state.readonly ? actionBar(state, node, title, moreOpen) : null,
396
+ primary.value && !state.readonly && !isPage(node) ? resizeHandle(state, node, el) : null
397
+ ]);
398
+ };
399
+ }
400
+ });
401
+ /** The labels of a node's marks, over its top edge, each in its color. */
402
+ function markTags(marks) {
403
+ return h("div", {
404
+ class: "dou-form-builder__marks",
405
+ "aria-hidden": "true"
406
+ }, marks.map((mark) => h("span", {
407
+ key: mark.key,
408
+ class: "dou-form-builder__mark",
409
+ style: mark.color ? { "--dou-form-builder-mark": mark.color } : void 0
410
+ }, mark.label)));
411
+ }
412
+ var LOGIC_KEYS = [
413
+ "visibleWhen",
414
+ "disabledWhen",
415
+ "requiredWhen",
416
+ "formula"
417
+ ];
418
+ /** The conditions and the formula a node has, as lines for a tooltip; none without them. */
419
+ function logicOf(state, node) {
420
+ const settings = state.messages.settings;
421
+ const lines = [];
422
+ let conditions = false;
423
+ let formula = false;
424
+ for (const key of LOGIC_KEYS) {
425
+ const text = node[key];
426
+ if (!text?.trim()) continue;
427
+ if (key === "formula") formula = true;
428
+ else conditions = true;
429
+ lines.push(formatMessage(state.messages.condition.summary, {
430
+ label: settings[key] ?? key,
431
+ text
432
+ }));
433
+ }
434
+ return lines.length ? {
435
+ lines,
436
+ text: lines.join("; "),
437
+ conditions,
438
+ formula
439
+ } : void 0;
440
+ }
441
+ /** A mark in the node's corner when it has conditions or a formula, telling them on hover. */
442
+ function logicMark(logic) {
443
+ return h(DouTooltip, { placement: "top-end" }, {
444
+ default: () => h("span", {
445
+ class: "dou-form-builder__logic-mark",
446
+ "aria-hidden": "true"
447
+ }, [logic.conditions ? h(WorkflowIcon) : null, logic.formula ? h(SigmaIcon) : null]),
448
+ content: () => h("span", { class: "dou-form-builder__logic-lines" }, logic.lines.map((line) => h("span", { key: line }, line)))
449
+ });
450
+ }
451
+ function sectionBody(state, node, props) {
452
+ const columns = node.columns ?? props.columns;
453
+ const bordered = !isPage(node) && node.bordered;
454
+ return h("div", { class: ["dou-form-renderer__section", bordered && "is-bordered"] }, [node.title || node.description ? h("div", {
455
+ class: "dou-form-renderer__section-header",
456
+ inert: true
457
+ }, [node.title && h("div", { class: "dou-form-renderer__section-title" }, node.title), node.description && h("div", { class: "dou-form-renderer__section-description" }, node.description)]) : null, childGrid(state, node.id, node.children, columns, props.inset + (bordered ? 1 : 0), props.level + 1)]);
458
+ }
459
+ function listBody(state, node, props, messages) {
460
+ const columns = node.columns ?? props.columns;
461
+ return h("div", { class: "dou-form-renderer__list" }, [
462
+ node.label || node.help ? h("div", {
463
+ class: "dou-form-renderer__section-header",
464
+ inert: true
465
+ }, [node.label && h("div", { class: ["dou-form-renderer__section-title", node.required && "is-required"] }, node.label), node.help && h("div", { class: "dou-form-renderer__section-description" }, node.help)]) : null,
466
+ h("div", { class: "dou-form-renderer__list-item dou-form-builder__item" }, [h("div", {
467
+ class: "dou-form-renderer__list-item-header",
468
+ inert: true
469
+ }, h("span", { class: "dou-form-renderer__list-item-title" }, formatMessage(messages.item, { index: 1 }))), childGrid(state, node.id, node.children, columns, props.inset + 1, props.level + 1)]),
470
+ h("div", {
471
+ class: "dou-form-builder__list-add",
472
+ inert: true
473
+ }, h(DouButton, {
474
+ variant: "outline",
475
+ tabindex: -1
476
+ }, {
477
+ leading: () => h(PlusIcon),
478
+ default: () => node.addText ?? messages.add
479
+ }))
480
+ ]);
481
+ }
482
+ function pagesBody(state, node, props, press) {
483
+ const pages = node.children;
484
+ const shown = state.shownPage(node);
485
+ const steps = node.type === "steps";
486
+ const level = props.level + 1;
487
+ return h("div", { class: ["dou-form-builder__pages", steps && "is-steps"] }, [h("div", {
488
+ class: "dou-form-builder__strip",
489
+ role: "group"
490
+ }, [...pages.map((page, index) => h("div", {
491
+ key: page.id,
492
+ class: [
493
+ "dou-form-builder__page-tab",
494
+ page === shown && "is-shown",
495
+ state.selected.value.has(page.id) && "is-selected"
496
+ ],
497
+ id: state.domId(page.id, "canvas"),
498
+ role: "treeitem",
499
+ "aria-level": level,
500
+ "aria-posinset": index + 1,
501
+ "aria-setsize": pages.length,
502
+ "aria-selected": state.selected.value.has(page.id),
503
+ "aria-expanded": page === shown,
504
+ "data-node": page.id,
505
+ onClick: (event) => press(event, page.id)
506
+ }, [steps ? h("span", { class: "dou-form-builder__step-number" }, String(index + 1)) : null, h("span", { class: "dou-form-builder__page-title" }, page.title || "\xA0")])), state.readonly ? null : h("button", {
507
+ type: "button",
508
+ class: "dou-form-builder__page-add",
509
+ tabindex: -1,
510
+ "aria-label": state.messages.actions.addPage,
511
+ title: state.messages.actions.addPage,
512
+ onClick: (event) => {
513
+ event.stopPropagation();
514
+ state.addPage(node.id);
515
+ }
516
+ }, h(PlusIcon))]), shown ? h("div", {
517
+ class: ["dou-form-builder__page", state.selected.value.has(shown.id) && "is-selected"],
518
+ onClick: (event) => press(event, shown.id)
519
+ }, [shown.description ? h("div", {
520
+ class: "dou-form-renderer__page-description",
521
+ inert: true
522
+ }, shown.description) : null, childGrid(state, shown.id, shown.children, shown.columns ?? props.columns, props.inset, level + 1)]) : null]);
523
+ }
524
+ /** One button of the selected node's action bar, its name and key in a tooltip. */
525
+ var actionButton = (label, shortcut, icon, onClick, disabled = false) => h(DouTooltip, {
526
+ content: label,
527
+ shortcut
528
+ }, { default: () => h("button", {
529
+ type: "button",
530
+ class: "dou-form-builder__action",
531
+ tabindex: -1,
532
+ disabled,
533
+ "aria-label": label,
534
+ onClick
535
+ }, icon) });
536
+ /** The buttons of the selected node; its type and name show in the inspector's header. */
537
+ function actionBar(state, node, title, moreOpen) {
538
+ const messages = state.messages;
539
+ const place = formNodePlaces(state.document).get(node.id);
540
+ const siblings = place?.parent ? place.parent.children.length : state.document.nodes.length;
541
+ const after = () => state.canvas.value?.focus({ preventScroll: true });
542
+ const ids = state.selected.value.has(node.id) ? state.selection : [node.id];
543
+ const actions = [
544
+ place?.parent ? {
545
+ label: messages.actions.parent,
546
+ shortcut: "escape",
547
+ icon: CornerLeftUpIcon,
548
+ run: () => state.selectParent()
549
+ } : null,
550
+ {
551
+ label: messages.actions.up,
552
+ shortcut: "mod+arrowup",
553
+ icon: ArrowUpIcon,
554
+ run: () => state.moveBy(-1, [node.id]),
555
+ disabled: !place?.index
556
+ },
557
+ {
558
+ label: messages.actions.down,
559
+ shortcut: "mod+arrowdown",
560
+ icon: ArrowDownIcon,
561
+ run: () => state.moveBy(1, [node.id]),
562
+ disabled: !place || place.index >= siblings - 1
563
+ },
564
+ {
565
+ label: messages.actions.duplicate,
566
+ shortcut: "mod+d",
567
+ icon: CopyIcon,
568
+ run: () => state.duplicate([node.id])
569
+ },
570
+ {
571
+ label: messages.actions.remove,
572
+ shortcut: "delete",
573
+ icon: TrashIcon,
574
+ run: () => state.remove([node.id]),
575
+ danger: true
576
+ }
577
+ ].filter((action) => action !== null);
578
+ let menu = null;
579
+ const more = h(DouDropdown, {
580
+ ref: (instance) => menu = instance,
581
+ placement: "bottom-end",
582
+ "onUpdate:open": (open) => {
583
+ moreOpen.value = open;
584
+ if (!open) requestAnimationFrame(after);
585
+ }
586
+ }, {
587
+ default: () => h("button", {
588
+ type: "button",
589
+ class: "dou-form-builder__action",
590
+ tabindex: -1,
591
+ "aria-label": messages.actions.more,
592
+ onClick: (event) => event.stopPropagation(),
593
+ onKeydown: (event) => {
594
+ const target = event.currentTarget;
595
+ if (event.key !== "Escape" || target.getAttribute("aria-expanded") !== "true") return;
596
+ event.stopPropagation();
597
+ menu?.close();
598
+ }
599
+ }, h(MoreHorizontalIcon)),
600
+ menu: () => menuItems(nodeMenu(state, ids))
601
+ });
602
+ const extra = state.extensions().menus.length > 0;
603
+ return h("div", {
604
+ class: "dou-form-builder__actions",
605
+ role: "toolbar",
606
+ "aria-label": title,
607
+ "data-extra": extra || void 0
608
+ }, [...actions.map((action) => actionButton(action.label, action.shortcut, h(action.icon), (event) => {
609
+ event.stopPropagation();
610
+ action.run();
611
+ after();
612
+ }, action.disabled)), h(DouTooltip, {
613
+ content: messages.actions.more,
614
+ disabled: moreOpen.value
615
+ }, { default: () => h("span", { class: "dou-form-builder__actions-more" }, more) })]);
616
+ }
617
+ /** The form widths the breakpoints of a responsive width start at, as in the renderer's styles. */
618
+ var breakpointWidths = [
619
+ ["xl", 960],
620
+ ["lg", 800],
621
+ ["md", 640],
622
+ ["sm", 480]
623
+ ];
624
+ /** The key of a responsive width that applies at the form's width now. */
625
+ function activeSpanKey(span, cell) {
626
+ const width = cell.closest(".dou-form-renderer")?.getBoundingClientRect().width ?? 0;
627
+ for (const [key, min] of breakpointWidths) if (width >= min && span[key] !== void 0) return key;
628
+ return "base";
629
+ }
630
+ /** The handle on the selected node's end edge: drag it to change the width, snapping to columns. */
631
+ function resizeHandle(state, node, cell) {
632
+ return h("div", {
633
+ class: "dou-form-builder__resize",
634
+ title: state.messages.drag.resize,
635
+ "aria-hidden": true,
636
+ onClick: (event) => event.stopPropagation(),
637
+ onPointerdown: (event) => startResize(event, state, node.id, cell.value)
638
+ });
639
+ }
640
+ function startResize(event, state, id, cell) {
641
+ if (event.button !== 0 || !cell?.parentElement) return;
642
+ event.preventDefault();
643
+ event.stopPropagation();
644
+ const handle = event.currentTarget;
645
+ const grid = cell.parentElement;
646
+ const style = getComputedStyle(grid);
647
+ const columns = Math.max(1, Math.round(Number(style.getPropertyValue("--dou-form-columns")) || 12));
648
+ const gap = Number.parseFloat(style.columnGap) || 0;
649
+ const unit = (grid.getBoundingClientRect().width + gap) / columns;
650
+ const left = cell.getBoundingClientRect().left;
651
+ const hold = holdPointer(event, handle);
652
+ hold.grab();
653
+ const span = (formNodePlaces(state.document).get(id)?.node)?.span;
654
+ const key = span !== void 0 && typeof span === "object" ? activeSpanKey(span, cell) : null;
655
+ const current = () => {
656
+ const now = formNodePlaces(state.document).get(id)?.node.span;
657
+ if (now === void 0) return columns;
658
+ if (typeof now === "number") return now;
659
+ return now[key ?? "base"] ?? now.base ?? columns;
660
+ };
661
+ let last = current();
662
+ handle.dataset.value = `${last} / ${columns}`;
663
+ cell.classList.add("is-resizing");
664
+ const onMove = (e) => {
665
+ if (e.pointerId !== event.pointerId) return;
666
+ const next = Math.min(columns, Math.max(1, Math.round((e.clientX - left + gap) / unit)));
667
+ if (next === last) return;
668
+ last = next;
669
+ handle.dataset.value = `${next} / ${columns}`;
670
+ const now = formNodePlaces(state.document).get(id)?.node.span;
671
+ const value = key && now !== void 0 && typeof now === "object" ? {
672
+ ...now,
673
+ [key]: next
674
+ } : next === columns ? void 0 : next;
675
+ const tr = state.transaction().update(id, { span: value });
676
+ tr.setMeta("group", `resize:${id}`);
677
+ tr.setMeta("origin", "resize");
678
+ state.dispatch(tr);
679
+ };
680
+ const onUp = (e) => {
681
+ if (e.pointerId !== event.pointerId) return;
682
+ window.removeEventListener("pointermove", onMove);
683
+ window.removeEventListener("pointerup", onUp);
684
+ window.removeEventListener("pointercancel", onUp);
685
+ hold.release();
686
+ delete handle.dataset.value;
687
+ cell.classList.remove("is-resizing");
688
+ };
689
+ window.addEventListener("pointermove", onMove);
690
+ window.addEventListener("pointerup", onUp);
691
+ window.addEventListener("pointercancel", onUp);
692
+ }
693
+ export { FormBuilderCanvas };
694
+
695
+ //# sourceMappingURL=canvas.js.map