@bpmnkit/plugins 0.0.8

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 (140) hide show
  1. package/README.md +153 -0
  2. package/dist/ai-bridge/css.d.ts +2 -0
  3. package/dist/ai-bridge/css.js +366 -0
  4. package/dist/ai-bridge/index.d.ts +44 -0
  5. package/dist/ai-bridge/index.js +132 -0
  6. package/dist/ai-bridge/panel.d.ts +27 -0
  7. package/dist/ai-bridge/panel.js +715 -0
  8. package/dist/ascii-view/css.d.ts +4 -0
  9. package/dist/ascii-view/css.js +83 -0
  10. package/dist/ascii-view/index.d.ts +25 -0
  11. package/dist/ascii-view/index.js +92 -0
  12. package/dist/command-palette/css.d.ts +4 -0
  13. package/dist/command-palette/css.js +157 -0
  14. package/dist/command-palette/index.d.ts +53 -0
  15. package/dist/command-palette/index.js +378 -0
  16. package/dist/command-palette-editor/index.d.ts +35 -0
  17. package/dist/command-palette-editor/index.js +60 -0
  18. package/dist/config-panel/css.d.ts +4 -0
  19. package/dist/config-panel/css.js +719 -0
  20. package/dist/config-panel/index.d.ts +36 -0
  21. package/dist/config-panel/index.js +73 -0
  22. package/dist/config-panel/renderer.d.ts +127 -0
  23. package/dist/config-panel/renderer.js +1162 -0
  24. package/dist/config-panel/types.d.ts +118 -0
  25. package/dist/config-panel/types.js +2 -0
  26. package/dist/config-panel-bpmn/index.d.ts +58 -0
  27. package/dist/config-panel-bpmn/index.js +1320 -0
  28. package/dist/config-panel-bpmn/template-engine.d.ts +17 -0
  29. package/dist/config-panel-bpmn/template-engine.js +307 -0
  30. package/dist/config-panel-bpmn/template-to-service-task.d.ts +30 -0
  31. package/dist/config-panel-bpmn/template-to-service-task.js +92 -0
  32. package/dist/config-panel-bpmn/template-types.d.ts +126 -0
  33. package/dist/config-panel-bpmn/template-types.js +6 -0
  34. package/dist/config-panel-bpmn/templates/generated.d.ts +3 -0
  35. package/dist/config-panel-bpmn/templates/generated.js +57865 -0
  36. package/dist/config-panel-bpmn/util.d.ts +46 -0
  37. package/dist/config-panel-bpmn/util.js +195 -0
  38. package/dist/dmn-editor/css.d.ts +3 -0
  39. package/dist/dmn-editor/css.js +667 -0
  40. package/dist/dmn-editor/dmn-editor.d.ts +34 -0
  41. package/dist/dmn-editor/dmn-editor.js +646 -0
  42. package/dist/dmn-editor/drd-canvas.d.ts +85 -0
  43. package/dist/dmn-editor/drd-canvas.js +1087 -0
  44. package/dist/dmn-editor/index.d.ts +3 -0
  45. package/dist/dmn-editor/index.js +2 -0
  46. package/dist/dmn-viewer/css.d.ts +3 -0
  47. package/dist/dmn-viewer/css.js +151 -0
  48. package/dist/dmn-viewer/dmn-viewer.d.ts +30 -0
  49. package/dist/dmn-viewer/dmn-viewer.js +160 -0
  50. package/dist/dmn-viewer/feel.d.ts +3 -0
  51. package/dist/dmn-viewer/feel.js +2 -0
  52. package/dist/dmn-viewer/index.d.ts +14 -0
  53. package/dist/dmn-viewer/index.js +12 -0
  54. package/dist/element-docs/content.d.ts +20 -0
  55. package/dist/element-docs/content.js +1032 -0
  56. package/dist/element-docs/index.d.ts +7 -0
  57. package/dist/element-docs/index.js +643 -0
  58. package/dist/feel-playground/css.d.ts +4 -0
  59. package/dist/feel-playground/css.js +292 -0
  60. package/dist/feel-playground/index.d.ts +4 -0
  61. package/dist/feel-playground/index.js +3 -0
  62. package/dist/feel-playground/playground-plugin.d.ts +14 -0
  63. package/dist/feel-playground/playground-plugin.js +292 -0
  64. package/dist/form-editor/css.d.ts +3 -0
  65. package/dist/form-editor/css.js +703 -0
  66. package/dist/form-editor/form-editor.d.ts +48 -0
  67. package/dist/form-editor/form-editor.js +1039 -0
  68. package/dist/form-editor/index.d.ts +3 -0
  69. package/dist/form-editor/index.js +2 -0
  70. package/dist/form-viewer/css.d.ts +3 -0
  71. package/dist/form-viewer/css.js +303 -0
  72. package/dist/form-viewer/form-viewer.d.ts +29 -0
  73. package/dist/form-viewer/form-viewer.js +376 -0
  74. package/dist/form-viewer/index.d.ts +12 -0
  75. package/dist/form-viewer/index.js +11 -0
  76. package/dist/history/checkpoint.d.ts +10 -0
  77. package/dist/history/checkpoint.js +92 -0
  78. package/dist/history/css.d.ts +2 -0
  79. package/dist/history/css.js +130 -0
  80. package/dist/history/history-panel.d.ts +12 -0
  81. package/dist/history/history-panel.js +142 -0
  82. package/dist/history/index.d.ts +5 -0
  83. package/dist/history/index.js +3 -0
  84. package/dist/main-menu/css.d.ts +4 -0
  85. package/dist/main-menu/css.js +272 -0
  86. package/dist/main-menu/index.d.ts +59 -0
  87. package/dist/main-menu/index.js +285 -0
  88. package/dist/minimap/css.d.ts +10 -0
  89. package/dist/minimap/css.js +54 -0
  90. package/dist/minimap/index.d.ts +38 -0
  91. package/dist/minimap/index.js +69 -0
  92. package/dist/minimap/minimap.d.ts +35 -0
  93. package/dist/minimap/minimap.js +166 -0
  94. package/dist/optimize/index.d.ts +21 -0
  95. package/dist/optimize/index.js +304 -0
  96. package/dist/process-runner/css.d.ts +2 -0
  97. package/dist/process-runner/css.js +467 -0
  98. package/dist/process-runner/index.d.ts +56 -0
  99. package/dist/process-runner/index.js +549 -0
  100. package/dist/storage/auto-save.d.ts +11 -0
  101. package/dist/storage/auto-save.js +44 -0
  102. package/dist/storage/css.d.ts +2 -0
  103. package/dist/storage/css.js +2 -0
  104. package/dist/storage/db.d.ts +99 -0
  105. package/dist/storage/db.js +116 -0
  106. package/dist/storage/dialog.d.ts +15 -0
  107. package/dist/storage/dialog.js +201 -0
  108. package/dist/storage/export.d.ts +3 -0
  109. package/dist/storage/export.js +137 -0
  110. package/dist/storage/index.d.ts +44 -0
  111. package/dist/storage/index.js +201 -0
  112. package/dist/storage/sidebar.d.ts +2 -0
  113. package/dist/storage/sidebar.js +2 -0
  114. package/dist/storage/storage-api.d.ts +76 -0
  115. package/dist/storage/storage-api.js +342 -0
  116. package/dist/storage/types.d.ts +44 -0
  117. package/dist/storage/types.js +2 -0
  118. package/dist/storage-tabs-bridge/index.d.ts +96 -0
  119. package/dist/storage-tabs-bridge/index.js +502 -0
  120. package/dist/tabs/css.d.ts +3 -0
  121. package/dist/tabs/css.js +642 -0
  122. package/dist/tabs/file-resolver.d.ts +71 -0
  123. package/dist/tabs/file-resolver.js +46 -0
  124. package/dist/tabs/index.d.ts +15 -0
  125. package/dist/tabs/index.js +13 -0
  126. package/dist/tabs/tabs-plugin.d.ts +250 -0
  127. package/dist/tabs/tabs-plugin.js +1119 -0
  128. package/dist/token-highlight/css.d.ts +2 -0
  129. package/dist/token-highlight/css.js +81 -0
  130. package/dist/token-highlight/index.d.ts +44 -0
  131. package/dist/token-highlight/index.js +164 -0
  132. package/dist/watermark/css.d.ts +10 -0
  133. package/dist/watermark/css.js +54 -0
  134. package/dist/watermark/index.d.ts +48 -0
  135. package/dist/watermark/index.js +64 -0
  136. package/dist/zoom-controls/css.d.ts +4 -0
  137. package/dist/zoom-controls/css.js +48 -0
  138. package/dist/zoom-controls/index.d.ts +36 -0
  139. package/dist/zoom-controls/index.js +111 -0
  140. package/package.json +121 -0
@@ -0,0 +1,1039 @@
1
+ import { Form } from "@bpmnkit/core";
2
+ import { injectFormEditorStyles } from "./css.js";
3
+ function uid() {
4
+ return Math.random().toString(36).slice(2, 10);
5
+ }
6
+ const FIELD_TYPES = new Set([
7
+ "textfield",
8
+ "textarea",
9
+ "number",
10
+ "select",
11
+ "radio",
12
+ "checkbox",
13
+ "checklist",
14
+ "taglist",
15
+ "filepicker",
16
+ "datetime",
17
+ "expression",
18
+ "table",
19
+ ]);
20
+ const OPTION_TYPES = new Set(["select", "radio", "checklist", "taglist"]);
21
+ const CONTAINER_TYPES = new Set(["group", "dynamiclist"]);
22
+ const TYPE_LABELS = {
23
+ textfield: "Text Field",
24
+ textarea: "Text Area",
25
+ number: "Number",
26
+ select: "Select",
27
+ radio: "Radio",
28
+ checkbox: "Checkbox",
29
+ checklist: "Checklist",
30
+ taglist: "Tag List",
31
+ filepicker: "File Picker",
32
+ datetime: "Date / Time",
33
+ expression: "Expression",
34
+ table: "Table",
35
+ text: "Text",
36
+ html: "HTML",
37
+ image: "Image",
38
+ button: "Button",
39
+ separator: "Separator",
40
+ spacer: "Spacer",
41
+ iframe: "iFrame",
42
+ group: "Group",
43
+ dynamiclist: "Dynamic List",
44
+ };
45
+ const PALETTE_ICONS = {
46
+ textfield: "Aa",
47
+ textarea: "¶",
48
+ number: "123",
49
+ datetime: "⏱",
50
+ filepicker: "↑",
51
+ expression: "ƒ",
52
+ checkbox: "✓",
53
+ radio: "◎",
54
+ select: "▾",
55
+ checklist: "☑",
56
+ taglist: "⊞",
57
+ text: "T",
58
+ html: "<>",
59
+ image: "⊡",
60
+ table: "⊟",
61
+ separator: "—",
62
+ spacer: "░",
63
+ group: "⬚",
64
+ dynamiclist: "≋",
65
+ iframe: "◫",
66
+ button: "▶",
67
+ };
68
+ const PALETTE_GROUPS = [
69
+ {
70
+ label: "Input",
71
+ group: "input",
72
+ types: ["textfield", "textarea", "number", "datetime", "filepicker", "expression"],
73
+ },
74
+ {
75
+ label: "Selection",
76
+ group: "selection",
77
+ types: ["checkbox", "radio", "select", "checklist", "taglist"],
78
+ },
79
+ {
80
+ label: "Presentation",
81
+ group: "presentation",
82
+ types: ["text", "html", "image", "table", "separator", "spacer"],
83
+ },
84
+ {
85
+ label: "Containers",
86
+ group: "container",
87
+ types: ["group", "dynamiclist", "iframe"],
88
+ },
89
+ { label: "Action", group: "action", types: ["button"] },
90
+ ];
91
+ /** Group name for a given type. */
92
+ function typeGroup(type) {
93
+ for (const g of PALETTE_GROUPS) {
94
+ if (g.types.includes(type))
95
+ return g.group;
96
+ }
97
+ return "input";
98
+ }
99
+ /** Icon color CSS class. */
100
+ function groupIconClass(group) {
101
+ return `fe-palette-item-${group}`;
102
+ }
103
+ function makeDefaultComponent(type) {
104
+ const id = uid();
105
+ if (type === "textfield" || type === "textarea" || type === "number") {
106
+ return { id, type, label: "Label", key: `field_${id}` };
107
+ }
108
+ if (type === "select" || type === "radio") {
109
+ return { id, type, label: "Label", key: `field_${id}`, values: [] };
110
+ }
111
+ if (type === "checkbox") {
112
+ return { id, type, label: "Label", key: `field_${id}` };
113
+ }
114
+ if (type === "checklist" || type === "taglist") {
115
+ return { id, type, label: "Label", key: `field_${id}`, values: [] };
116
+ }
117
+ if (type === "filepicker") {
118
+ return { id, type, label: "File", key: `field_${id}` };
119
+ }
120
+ if (type === "datetime") {
121
+ return { id, type, key: `field_${id}` };
122
+ }
123
+ if (type === "button") {
124
+ return { id, type, label: "Submit", action: "submit" };
125
+ }
126
+ if (type === "text") {
127
+ return { id, type, text: "Text content" };
128
+ }
129
+ if (type === "html") {
130
+ return { id, type, content: "<p>HTML content</p>" };
131
+ }
132
+ if (type === "image") {
133
+ return { id, type, source: "" };
134
+ }
135
+ if (type === "separator" || type === "spacer") {
136
+ return { id, type };
137
+ }
138
+ if (type === "iframe") {
139
+ return { id, type, url: "", height: 200 };
140
+ }
141
+ if (type === "expression") {
142
+ return { id, type, key: `expr_${id}`, expression: "" };
143
+ }
144
+ if (type === "table") {
145
+ return { id, type, label: "Table" };
146
+ }
147
+ if (type === "group") {
148
+ return { id, type, label: "Group", components: [] };
149
+ }
150
+ if (type === "dynamiclist") {
151
+ return { id, type, label: "List", components: [], path: `list_${id}` };
152
+ }
153
+ return { id, type };
154
+ }
155
+ /** Build a lightweight non-interactive preview for a component. */
156
+ function buildPreview(comp) {
157
+ const el = document.createElement("div");
158
+ el.className = "fe-preview";
159
+ const labelText = ("label" in comp && typeof comp.label === "string" && comp.label) ||
160
+ TYPE_LABELS[comp.type] ||
161
+ comp.type;
162
+ switch (comp.type) {
163
+ case "textfield":
164
+ case "number":
165
+ case "filepicker":
166
+ case "datetime":
167
+ case "expression": {
168
+ const lbl = document.createElement("div");
169
+ lbl.className = "fe-preview-label";
170
+ lbl.textContent = labelText;
171
+ el.appendChild(lbl);
172
+ const inp = document.createElement("div");
173
+ inp.className = "fe-preview-input";
174
+ el.appendChild(inp);
175
+ break;
176
+ }
177
+ case "textarea": {
178
+ const lbl = document.createElement("div");
179
+ lbl.className = "fe-preview-label";
180
+ lbl.textContent = labelText;
181
+ el.appendChild(lbl);
182
+ const inp = document.createElement("div");
183
+ inp.className = "fe-preview-textarea";
184
+ el.appendChild(inp);
185
+ break;
186
+ }
187
+ case "checkbox": {
188
+ const row = document.createElement("div");
189
+ row.className = "fe-preview-checkbox-row";
190
+ const box = document.createElement("div");
191
+ box.className = "fe-preview-checkbox";
192
+ const span = document.createElement("span");
193
+ span.textContent = labelText;
194
+ row.appendChild(box);
195
+ row.appendChild(span);
196
+ el.appendChild(row);
197
+ break;
198
+ }
199
+ case "radio":
200
+ case "checklist": {
201
+ const lbl = document.createElement("div");
202
+ lbl.className = "fe-preview-label";
203
+ lbl.textContent = labelText;
204
+ el.appendChild(lbl);
205
+ const opts = comp.values?.slice(0, 3) ?? [];
206
+ if (opts.length === 0) {
207
+ const r = document.createElement("div");
208
+ r.className = "fe-preview-option-row";
209
+ const dot = document.createElement("div");
210
+ dot.className = comp.type === "radio" ? "fe-preview-radio" : "fe-preview-checkbox";
211
+ const span = document.createElement("span");
212
+ span.textContent = "Option";
213
+ r.appendChild(dot);
214
+ r.appendChild(span);
215
+ el.appendChild(r);
216
+ }
217
+ else {
218
+ for (const opt of opts) {
219
+ const r = document.createElement("div");
220
+ r.className = "fe-preview-option-row";
221
+ const dot = document.createElement("div");
222
+ dot.className = comp.type === "radio" ? "fe-preview-radio" : "fe-preview-checkbox";
223
+ const span = document.createElement("span");
224
+ span.textContent = opt.label;
225
+ r.appendChild(dot);
226
+ r.appendChild(span);
227
+ el.appendChild(r);
228
+ }
229
+ }
230
+ break;
231
+ }
232
+ case "select":
233
+ case "taglist": {
234
+ const lbl = document.createElement("div");
235
+ lbl.className = "fe-preview-label";
236
+ lbl.textContent = labelText;
237
+ el.appendChild(lbl);
238
+ const sel = document.createElement("div");
239
+ sel.className = "fe-preview-select";
240
+ const placeholder = document.createElement("span");
241
+ placeholder.textContent = "Select…";
242
+ const arrow = document.createElement("span");
243
+ arrow.textContent = "▾";
244
+ sel.appendChild(placeholder);
245
+ sel.appendChild(arrow);
246
+ el.appendChild(sel);
247
+ break;
248
+ }
249
+ case "button": {
250
+ const btn = document.createElement("div");
251
+ btn.className = "fe-preview-button";
252
+ btn.textContent = labelText;
253
+ el.appendChild(btn);
254
+ break;
255
+ }
256
+ case "text": {
257
+ const p = document.createElement("div");
258
+ p.className = "fe-preview-text";
259
+ const txt = comp.text ?? "";
260
+ p.textContent = txt.slice(0, 80);
261
+ el.appendChild(p);
262
+ break;
263
+ }
264
+ case "html":
265
+ case "table":
266
+ case "iframe": {
267
+ const badge = document.createElement("span");
268
+ badge.className = "fe-preview-badge";
269
+ badge.textContent = TYPE_LABELS[comp.type] ?? comp.type;
270
+ el.appendChild(badge);
271
+ break;
272
+ }
273
+ case "image": {
274
+ const img = document.createElement("div");
275
+ img.className = "fe-preview-image";
276
+ img.textContent = "⊡ Image";
277
+ el.appendChild(img);
278
+ break;
279
+ }
280
+ case "separator": {
281
+ const hr = document.createElement("hr");
282
+ hr.className = "fe-preview-separator";
283
+ el.appendChild(hr);
284
+ break;
285
+ }
286
+ case "spacer": {
287
+ const sp = document.createElement("div");
288
+ sp.className = "fe-preview-spacer";
289
+ el.appendChild(sp);
290
+ break;
291
+ }
292
+ case "group":
293
+ case "dynamiclist": {
294
+ const lbl = document.createElement("div");
295
+ lbl.className = "fe-preview-group-label";
296
+ lbl.textContent = labelText;
297
+ el.appendChild(lbl);
298
+ break;
299
+ }
300
+ default: {
301
+ const badge = document.createElement("span");
302
+ badge.className = "fe-preview-badge";
303
+ badge.textContent = comp.type;
304
+ el.appendChild(badge);
305
+ }
306
+ }
307
+ return el;
308
+ }
309
+ /** Native drag-and-drop form component editor — three-panel layout. */
310
+ export class FormEditor {
311
+ _form = null;
312
+ _selectedId = null;
313
+ _dragSource = null;
314
+ _root;
315
+ _canvas;
316
+ _props;
317
+ _handlers = [];
318
+ _readonly;
319
+ _destroyed = false;
320
+ constructor(options) {
321
+ injectFormEditorStyles();
322
+ this._readonly = options.readonly ?? false;
323
+ this._root = document.createElement("div");
324
+ this._root.className = `form-editor ${options.theme ?? "light"}${this._readonly ? " form-editor--readonly" : ""}`;
325
+ if (!this._readonly) {
326
+ const palette = this._buildPalette();
327
+ this._root.appendChild(palette);
328
+ }
329
+ this._canvas = document.createElement("div");
330
+ this._canvas.className = "fe-canvas";
331
+ this._root.appendChild(this._canvas);
332
+ this._props = document.createElement("div");
333
+ this._props.className = "fe-props";
334
+ if (!this._readonly) {
335
+ this._root.appendChild(this._props);
336
+ }
337
+ options.container.appendChild(this._root);
338
+ this._renderCanvas();
339
+ if (!this._readonly) {
340
+ this._renderProps();
341
+ }
342
+ }
343
+ async loadSchema(schema) {
344
+ // Normalize: ensure `id` and `type` are present (real API may omit them)
345
+ const normalized = {
346
+ id: uid(),
347
+ type: "default",
348
+ ...schema,
349
+ };
350
+ if (typeof normalized.id !== "string" || normalized.id.length === 0) {
351
+ normalized.id = uid();
352
+ }
353
+ try {
354
+ this._form = Form.parse(JSON.stringify(normalized));
355
+ }
356
+ catch {
357
+ this._form = null;
358
+ }
359
+ this._selectedId = null;
360
+ this._renderCanvas();
361
+ if (!this._readonly) {
362
+ this._renderProps();
363
+ }
364
+ }
365
+ getSchema() {
366
+ if (!this._form)
367
+ return {};
368
+ return JSON.parse(Form.export(this._form));
369
+ }
370
+ onChange(handler) {
371
+ this._handlers.push(handler);
372
+ return () => {
373
+ const idx = this._handlers.indexOf(handler);
374
+ if (idx !== -1)
375
+ this._handlers.splice(idx, 1);
376
+ };
377
+ }
378
+ setTheme(theme) {
379
+ this._root.className = `form-editor ${theme}`;
380
+ }
381
+ destroy() {
382
+ this._destroyed = true;
383
+ this._handlers.length = 0;
384
+ this._root.remove();
385
+ }
386
+ _emit() {
387
+ if (this._destroyed)
388
+ return;
389
+ for (const h of this._handlers)
390
+ h();
391
+ }
392
+ // ── Palette ──────────────────────────────────────────────────────────────────
393
+ _buildPalette() {
394
+ const palette = document.createElement("div");
395
+ palette.className = "fe-palette";
396
+ const header = document.createElement("div");
397
+ header.className = "fe-palette-header";
398
+ header.textContent = "Components";
399
+ palette.appendChild(header);
400
+ const search = document.createElement("input");
401
+ search.type = "search";
402
+ search.className = "fe-palette-search";
403
+ search.placeholder = "Search…";
404
+ palette.appendChild(search);
405
+ const entries = document.createElement("div");
406
+ entries.className = "fe-palette-entries";
407
+ palette.appendChild(entries);
408
+ const groupEls = [];
409
+ for (const g of PALETTE_GROUPS) {
410
+ const groupEl = document.createElement("div");
411
+ groupEl.className = "fe-palette-group";
412
+ const groupLabel = document.createElement("div");
413
+ groupLabel.className = "fe-palette-group-label";
414
+ groupLabel.textContent = g.label;
415
+ groupEl.appendChild(groupLabel);
416
+ const groupItems = document.createElement("div");
417
+ groupItems.className = "fe-palette-group-items";
418
+ groupEl.appendChild(groupItems);
419
+ const items = [];
420
+ for (const type of g.types) {
421
+ const label = TYPE_LABELS[type] ?? type;
422
+ const btn = document.createElement("button");
423
+ btn.type = "button";
424
+ btn.className = `fe-palette-item ${groupIconClass(g.group)}`;
425
+ btn.draggable = true;
426
+ btn.title = label;
427
+ const icon = document.createElement("div");
428
+ icon.className = "fe-palette-icon";
429
+ icon.textContent = PALETTE_ICONS[type] ?? type.slice(0, 2);
430
+ btn.appendChild(icon);
431
+ const lbl = document.createElement("div");
432
+ lbl.className = "fe-palette-item-label";
433
+ lbl.textContent = label;
434
+ btn.appendChild(lbl);
435
+ btn.addEventListener("click", () => {
436
+ this._addComponent(type, { index: this._getList(null).length, parentId: null });
437
+ });
438
+ btn.addEventListener("dragstart", (e) => {
439
+ this._dragSource = { kind: "palette", fieldType: type };
440
+ if (e.dataTransfer) {
441
+ e.dataTransfer.effectAllowed = "copy";
442
+ e.dataTransfer.setData("text/plain", type);
443
+ }
444
+ this._canvas.classList.add("fe-dragging");
445
+ });
446
+ btn.addEventListener("dragend", () => {
447
+ this._endDrag();
448
+ });
449
+ groupItems.appendChild(btn);
450
+ items.push({ itemEl: btn, type, label: label.toLowerCase() });
451
+ }
452
+ entries.appendChild(groupEl);
453
+ groupEls.push({ groupEl, items });
454
+ }
455
+ search.addEventListener("input", () => {
456
+ const q = search.value.toLowerCase().trim();
457
+ for (const { groupEl, items } of groupEls) {
458
+ let anyVisible = false;
459
+ for (const { itemEl, type, label } of items) {
460
+ const visible = !q || type.includes(q) || label.includes(q);
461
+ itemEl.style.display = visible ? "" : "none";
462
+ if (visible)
463
+ anyVisible = true;
464
+ }
465
+ groupEl.style.display = anyVisible ? "" : "none";
466
+ }
467
+ });
468
+ return palette;
469
+ }
470
+ // ── Canvas ───────────────────────────────────────────────────────────────────
471
+ _renderCanvas() {
472
+ this._canvas.innerHTML = "";
473
+ if (!this._form || this._form.components.length === 0) {
474
+ this._canvas.appendChild(this._buildEmptyState());
475
+ return;
476
+ }
477
+ const inner = document.createElement("div");
478
+ inner.className = "fe-canvas-inner";
479
+ if (!this._readonly) {
480
+ this._appendDropZone(inner, 0, null);
481
+ }
482
+ for (let i = 0; i < this._form.components.length; i++) {
483
+ const comp = this._form.components[i];
484
+ if (!comp)
485
+ continue;
486
+ this._appendCard(inner, comp);
487
+ if (!this._readonly) {
488
+ this._appendDropZone(inner, i + 1, null);
489
+ }
490
+ }
491
+ this._canvas.appendChild(inner);
492
+ }
493
+ _buildEmptyState() {
494
+ const empty = document.createElement("div");
495
+ empty.className = "fe-canvas-empty";
496
+ const icon = document.createElement("div");
497
+ icon.className = "fe-canvas-empty-icon";
498
+ icon.textContent = "⊡";
499
+ empty.appendChild(icon);
500
+ const heading = document.createElement("div");
501
+ heading.className = "fe-canvas-empty-heading";
502
+ heading.textContent = this._readonly ? "No form" : "Build your form";
503
+ empty.appendChild(heading);
504
+ const hint = document.createElement("div");
505
+ hint.className = "fe-canvas-empty-hint";
506
+ hint.textContent = this._readonly
507
+ ? "This task has no form schema."
508
+ : "Drag and drop components here to start designing.";
509
+ empty.appendChild(hint);
510
+ if (this._readonly)
511
+ return empty;
512
+ empty.addEventListener("dragover", (e) => {
513
+ if (!this._dragSource)
514
+ return;
515
+ e.preventDefault();
516
+ if (e.dataTransfer)
517
+ e.dataTransfer.dropEffect = "copy";
518
+ empty.classList.add("fe-canvas-empty-active");
519
+ });
520
+ empty.addEventListener("dragleave", (e) => {
521
+ if (!empty.contains(e.relatedTarget)) {
522
+ empty.classList.remove("fe-canvas-empty-active");
523
+ }
524
+ });
525
+ empty.addEventListener("drop", (e) => {
526
+ e.preventDefault();
527
+ this._handleDrop({ index: 0, parentId: null });
528
+ this._endDrag();
529
+ });
530
+ return empty;
531
+ }
532
+ _appendDropZone(parent, index, parentId) {
533
+ const zone = document.createElement("div");
534
+ zone.className = "fe-drop-zone";
535
+ zone.addEventListener("dragover", (e) => {
536
+ if (!this._dragSource)
537
+ return;
538
+ e.preventDefault();
539
+ e.stopPropagation();
540
+ if (e.dataTransfer) {
541
+ e.dataTransfer.dropEffect = this._dragSource.kind === "palette" ? "copy" : "move";
542
+ }
543
+ zone.classList.add("fe-drop-zone-active");
544
+ });
545
+ zone.addEventListener("dragleave", (e) => {
546
+ if (!zone.contains(e.relatedTarget)) {
547
+ zone.classList.remove("fe-drop-zone-active");
548
+ }
549
+ });
550
+ zone.addEventListener("drop", (e) => {
551
+ e.preventDefault();
552
+ e.stopPropagation();
553
+ zone.classList.remove("fe-drop-zone-active");
554
+ this._handleDrop({ index, parentId });
555
+ this._endDrag();
556
+ });
557
+ parent.appendChild(zone);
558
+ }
559
+ _appendCard(parent, comp) {
560
+ const card = document.createElement("div");
561
+ card.className = `fe-canvas-card${comp.id === this._selectedId ? " selected" : ""}`;
562
+ card.dataset.compId = comp.id;
563
+ if (!this._readonly) {
564
+ card.draggable = true;
565
+ const handle = document.createElement("div");
566
+ handle.className = "fe-card-drag-handle";
567
+ handle.textContent = "⠿";
568
+ card.appendChild(handle);
569
+ }
570
+ const content = document.createElement("div");
571
+ content.className = "fe-card-content";
572
+ content.appendChild(buildPreview(comp));
573
+ // Container children
574
+ if (CONTAINER_TYPES.has(comp.type)) {
575
+ const gc = comp;
576
+ const children = gc.components ?? [];
577
+ const containerCanvas = document.createElement("div");
578
+ containerCanvas.className = "fe-container-canvas";
579
+ if (children.length === 0 && !this._readonly) {
580
+ containerCanvas.appendChild(this._buildContainerEmpty(comp.id));
581
+ }
582
+ else {
583
+ if (!this._readonly) {
584
+ this._appendDropZone(containerCanvas, 0, comp.id);
585
+ }
586
+ for (let i = 0; i < children.length; i++) {
587
+ const child = children[i];
588
+ if (!child)
589
+ continue;
590
+ this._appendCard(containerCanvas, child);
591
+ if (!this._readonly) {
592
+ this._appendDropZone(containerCanvas, i + 1, comp.id);
593
+ }
594
+ }
595
+ }
596
+ content.appendChild(containerCanvas);
597
+ }
598
+ card.appendChild(content);
599
+ if (!this._readonly) {
600
+ const delBtn = document.createElement("button");
601
+ delBtn.type = "button";
602
+ delBtn.className = "fe-card-delete";
603
+ delBtn.textContent = "✕";
604
+ delBtn.addEventListener("click", (e) => {
605
+ e.stopPropagation();
606
+ this._deleteComp(comp.id);
607
+ });
608
+ card.appendChild(delBtn);
609
+ // Select on click
610
+ card.addEventListener("click", (e) => {
611
+ e.stopPropagation();
612
+ this._selectedId = comp.id;
613
+ this._renderCanvas();
614
+ this._renderProps();
615
+ });
616
+ // Drag from canvas
617
+ card.addEventListener("dragstart", (e) => {
618
+ e.stopPropagation();
619
+ this._dragSource = { kind: "canvas", compId: comp.id };
620
+ if (e.dataTransfer) {
621
+ e.dataTransfer.effectAllowed = "move";
622
+ e.dataTransfer.setData("text/plain", comp.id);
623
+ }
624
+ this._canvas.classList.add("fe-dragging");
625
+ requestAnimationFrame(() => {
626
+ card.classList.add("fe-card-dragging");
627
+ });
628
+ });
629
+ card.addEventListener("dragend", () => {
630
+ card.classList.remove("fe-card-dragging");
631
+ this._endDrag();
632
+ });
633
+ }
634
+ parent.appendChild(card);
635
+ }
636
+ _buildContainerEmpty(parentId) {
637
+ const empty = document.createElement("div");
638
+ empty.className = "fe-container-empty";
639
+ empty.textContent = "Drag and drop components here.";
640
+ empty.addEventListener("dragover", (e) => {
641
+ if (!this._dragSource)
642
+ return;
643
+ e.preventDefault();
644
+ e.stopPropagation();
645
+ if (e.dataTransfer) {
646
+ e.dataTransfer.dropEffect = this._dragSource.kind === "palette" ? "copy" : "move";
647
+ }
648
+ empty.classList.add("fe-drop-zone-active");
649
+ });
650
+ empty.addEventListener("dragleave", (e) => {
651
+ if (!empty.contains(e.relatedTarget)) {
652
+ empty.classList.remove("fe-drop-zone-active");
653
+ }
654
+ });
655
+ empty.addEventListener("drop", (e) => {
656
+ e.preventDefault();
657
+ e.stopPropagation();
658
+ empty.classList.remove("fe-drop-zone-active");
659
+ this._handleDrop({ index: 0, parentId });
660
+ this._endDrag();
661
+ });
662
+ return empty;
663
+ }
664
+ // ── Drag & drop logic ────────────────────────────────────────────────────────
665
+ _handleDrop(target) {
666
+ const src = this._dragSource;
667
+ if (!src)
668
+ return;
669
+ if (src.kind === "palette") {
670
+ this._addComponent(src.fieldType, target);
671
+ }
672
+ else {
673
+ this._moveComponent(src.compId, target);
674
+ }
675
+ }
676
+ _addComponent(type, target) {
677
+ if (!this._form)
678
+ return;
679
+ const comp = makeDefaultComponent(type);
680
+ const list = this._getList(target.parentId);
681
+ list.splice(target.index, 0, comp);
682
+ this._selectedId = comp.id;
683
+ this._renderCanvas();
684
+ this._renderProps();
685
+ this._emit();
686
+ }
687
+ _moveComponent(compId, target) {
688
+ const found = this._findComp(compId);
689
+ if (!found)
690
+ return;
691
+ // Prevent dropping a container into itself
692
+ if (target.parentId !== null) {
693
+ const targetParent = this._findComp(target.parentId);
694
+ if (targetParent && this._isAncestorOf(compId, target.parentId))
695
+ return;
696
+ }
697
+ const targetList = this._getList(target.parentId);
698
+ found.list.splice(found.idx, 1);
699
+ let insertIdx = target.index;
700
+ if (found.list === targetList && found.idx < insertIdx) {
701
+ insertIdx--;
702
+ }
703
+ targetList.splice(Math.max(0, insertIdx), 0, found.comp);
704
+ this._renderCanvas();
705
+ this._emit();
706
+ }
707
+ _isAncestorOf(ancestorId, descendantId) {
708
+ const found = this._findComp(ancestorId);
709
+ if (!found || !CONTAINER_TYPES.has(found.comp.type))
710
+ return false;
711
+ const gc = found.comp;
712
+ for (const child of gc.components ?? []) {
713
+ if (child.id === descendantId)
714
+ return true;
715
+ if (this._isAncestorOf(child.id, descendantId))
716
+ return true;
717
+ }
718
+ return false;
719
+ }
720
+ _endDrag() {
721
+ this._dragSource = null;
722
+ this._canvas.classList.remove("fe-dragging");
723
+ for (const el of Array.from(this._canvas.querySelectorAll(".fe-drop-zone-active"))) {
724
+ el.classList.remove("fe-drop-zone-active");
725
+ }
726
+ for (const el of Array.from(this._canvas.querySelectorAll(".fe-canvas-empty-active"))) {
727
+ el.classList.remove("fe-canvas-empty-active");
728
+ }
729
+ }
730
+ _deleteComp(id) {
731
+ const found = this._findComp(id);
732
+ if (!found)
733
+ return;
734
+ found.list.splice(found.idx, 1);
735
+ if (this._selectedId === id) {
736
+ this._selectedId = null;
737
+ this._renderProps();
738
+ }
739
+ this._renderCanvas();
740
+ this._emit();
741
+ }
742
+ // ── Helpers ──────────────────────────────────────────────────────────────────
743
+ _getList(parentId) {
744
+ if (!this._form)
745
+ return [];
746
+ if (parentId === null)
747
+ return this._form.components;
748
+ const found = this._findComp(parentId);
749
+ if (!found || !CONTAINER_TYPES.has(found.comp.type))
750
+ return [];
751
+ const gc = found.comp;
752
+ if (!gc.components)
753
+ gc.components = [];
754
+ return gc.components;
755
+ }
756
+ _findComp(id) {
757
+ if (!this._form)
758
+ return null;
759
+ return this._searchList(this._form.components, id);
760
+ }
761
+ _searchList(list, id) {
762
+ for (let i = 0; i < list.length; i++) {
763
+ const comp = list[i];
764
+ if (!comp)
765
+ continue;
766
+ if (comp.id === id)
767
+ return { comp, list, idx: i };
768
+ if (CONTAINER_TYPES.has(comp.type)) {
769
+ const gc = comp;
770
+ const found = this._searchList(gc.components ?? [], id);
771
+ if (found)
772
+ return found;
773
+ }
774
+ }
775
+ return null;
776
+ }
777
+ // ── Properties panel ─────────────────────────────────────────────────────────
778
+ _renderProps() {
779
+ this._props.innerHTML = "";
780
+ const header = document.createElement("div");
781
+ header.className = "fe-props-header";
782
+ const titleRow = document.createElement("div");
783
+ titleRow.className = "fe-props-header-title";
784
+ const iconEl = document.createElement("div");
785
+ iconEl.className = "fe-props-header-icon";
786
+ const typeEl = document.createElement("div");
787
+ typeEl.className = "fe-props-header-type";
788
+ titleRow.appendChild(iconEl);
789
+ titleRow.appendChild(typeEl);
790
+ header.appendChild(titleRow);
791
+ const hintEl = document.createElement("div");
792
+ hintEl.className = "fe-props-header-hint";
793
+ header.appendChild(hintEl);
794
+ this._props.appendChild(header);
795
+ if (!this._selectedId) {
796
+ iconEl.textContent = "⊞";
797
+ iconEl.style.background = "#e5e7eb";
798
+ iconEl.style.color = "#374151";
799
+ typeEl.textContent = "Form";
800
+ hintEl.textContent = "Select a form field to edit its properties.";
801
+ return;
802
+ }
803
+ const found = this._findComp(this._selectedId);
804
+ if (!found)
805
+ return;
806
+ const { comp } = found;
807
+ const group = typeGroup(comp.type);
808
+ iconEl.textContent = PALETTE_ICONS[comp.type] ?? comp.type.slice(0, 2);
809
+ // Apply icon color via inline style matching palette colors
810
+ const groupColors = {
811
+ input: { bg: "#dbeafe", fg: "#1d4ed8" },
812
+ selection: { bg: "#dcfce7", fg: "#15803d" },
813
+ presentation: { bg: "#fef3c7", fg: "#92400e" },
814
+ container: { bg: "#ede9fe", fg: "#6d28d9" },
815
+ action: { bg: "#fee2e2", fg: "#b91c1c" },
816
+ };
817
+ const colors = groupColors[group] ?? groupColors.input;
818
+ if (colors) {
819
+ iconEl.style.background = colors.bg;
820
+ iconEl.style.color = colors.fg;
821
+ }
822
+ typeEl.textContent = TYPE_LABELS[comp.type] ?? comp.type;
823
+ hintEl.textContent =
824
+ ("label" in comp && typeof comp.label === "string" && comp.label) ||
825
+ ("key" in comp && typeof comp.key === "string"
826
+ ? (comp.key ?? "")
827
+ : "") ||
828
+ "";
829
+ const body = document.createElement("div");
830
+ body.className = "fe-props-body";
831
+ this._buildPropsFor(comp, body);
832
+ this._props.appendChild(body);
833
+ }
834
+ _buildPropsFor(comp, body) {
835
+ // Label
836
+ if ("label" in comp) {
837
+ body.appendChild(this._propRow("Label", () => {
838
+ return this._textInput(String(comp.label ?? ""), (v) => {
839
+ ;
840
+ comp.label = v;
841
+ this._renderCanvas(); // update preview, preserve props focus
842
+ this._emit();
843
+ });
844
+ }));
845
+ }
846
+ // Key (field types)
847
+ if (FIELD_TYPES.has(comp.type) && "key" in comp) {
848
+ body.appendChild(this._propRow("Key", () => {
849
+ return this._textInput(String(comp.key ?? ""), (v) => {
850
+ ;
851
+ comp.key = v;
852
+ this._emit();
853
+ });
854
+ }));
855
+ }
856
+ // Required
857
+ if (FIELD_TYPES.has(comp.type)) {
858
+ const validate = comp.validate ?? {};
859
+ body.appendChild(this._propRow("Required", () => {
860
+ return this._checkboxInput(validate.required ?? false, (v) => {
861
+ ;
862
+ comp.validate = {
863
+ ...validate,
864
+ required: v,
865
+ };
866
+ this._emit();
867
+ });
868
+ }));
869
+ }
870
+ // Text content
871
+ if (comp.type === "text") {
872
+ body.appendChild(this._propRow("Text (Markdown)", () => {
873
+ return this._textareaInput(comp.text, (v) => {
874
+ ;
875
+ comp.text = v;
876
+ this._renderCanvas();
877
+ this._emit();
878
+ });
879
+ }));
880
+ }
881
+ // HTML
882
+ if (comp.type === "html") {
883
+ body.appendChild(this._propRow("HTML Content", () => {
884
+ return this._textareaInput(comp.content ?? "", (v) => {
885
+ ;
886
+ comp.content = v;
887
+ this._emit();
888
+ });
889
+ }));
890
+ }
891
+ // Image source
892
+ if (comp.type === "image") {
893
+ body.appendChild(this._propRow("Source URL", () => {
894
+ return this._textInput(comp.source ?? "", (v) => {
895
+ ;
896
+ comp.source = v;
897
+ this._emit();
898
+ });
899
+ }));
900
+ }
901
+ // iFrame URL
902
+ if (comp.type === "iframe") {
903
+ body.appendChild(this._propRow("URL", () => {
904
+ return this._textInput(comp.url ?? "", (v) => {
905
+ ;
906
+ comp.url = v;
907
+ this._emit();
908
+ });
909
+ }));
910
+ }
911
+ // Expression
912
+ if (comp.type === "expression") {
913
+ body.appendChild(this._propRow("Expression", () => {
914
+ return this._textInput(comp.expression ?? "", (v) => {
915
+ ;
916
+ comp.expression = v;
917
+ this._emit();
918
+ });
919
+ }));
920
+ }
921
+ // Options
922
+ if (OPTION_TYPES.has(comp.type)) {
923
+ const withValues = comp;
924
+ if (!withValues.values)
925
+ withValues.values = [];
926
+ body.appendChild(this._optionsEditor(withValues.values));
927
+ }
928
+ }
929
+ // ── Property input helpers ────────────────────────────────────────────────────
930
+ _propRow(label, buildInput) {
931
+ const row = document.createElement("div");
932
+ row.className = "fe-prop-row";
933
+ const lbl = document.createElement("label");
934
+ lbl.className = "fe-prop-label";
935
+ lbl.textContent = label;
936
+ row.appendChild(lbl);
937
+ row.appendChild(buildInput());
938
+ return row;
939
+ }
940
+ _textInput(value, onChange) {
941
+ const input = document.createElement("input");
942
+ input.type = "text";
943
+ input.className = "fe-prop-input";
944
+ input.value = value;
945
+ input.addEventListener("input", () => onChange(input.value));
946
+ return input;
947
+ }
948
+ _textareaInput(value, onChange) {
949
+ const ta = document.createElement("textarea");
950
+ ta.className = "fe-prop-input";
951
+ ta.value = value;
952
+ ta.rows = 4;
953
+ ta.style.resize = "vertical";
954
+ ta.addEventListener("input", () => onChange(ta.value));
955
+ return ta;
956
+ }
957
+ _checkboxInput(checked, onChange) {
958
+ const label = document.createElement("label");
959
+ label.className = "fe-prop-checkbox";
960
+ const cb = document.createElement("input");
961
+ cb.type = "checkbox";
962
+ cb.checked = checked;
963
+ cb.addEventListener("change", () => onChange(cb.checked));
964
+ label.appendChild(cb);
965
+ label.appendChild(document.createTextNode("Required"));
966
+ return label;
967
+ }
968
+ _optionsEditor(values) {
969
+ const section = document.createElement("div");
970
+ const titleRow = document.createElement("div");
971
+ titleRow.style.display = "flex";
972
+ titleRow.style.alignItems = "center";
973
+ titleRow.style.marginBottom = "6px";
974
+ const lbl = document.createElement("span");
975
+ lbl.className = "fe-prop-label";
976
+ lbl.style.flex = "1";
977
+ lbl.style.marginBottom = "0";
978
+ lbl.textContent = "Options";
979
+ titleRow.appendChild(lbl);
980
+ const addOptBtn = document.createElement("button");
981
+ addOptBtn.type = "button";
982
+ addOptBtn.className = "fe-btn";
983
+ addOptBtn.textContent = "+ Add";
984
+ addOptBtn.addEventListener("click", () => {
985
+ values.push({ label: "Option", value: `option_${uid()}` });
986
+ rebuildList();
987
+ this._emit();
988
+ });
989
+ titleRow.appendChild(addOptBtn);
990
+ section.appendChild(titleRow);
991
+ const list = document.createElement("div");
992
+ list.className = "fe-options-list";
993
+ section.appendChild(list);
994
+ const rebuildList = () => {
995
+ list.innerHTML = "";
996
+ for (let i = 0; i < values.length; i++) {
997
+ const opt = values[i];
998
+ if (!opt)
999
+ continue;
1000
+ const row = document.createElement("div");
1001
+ row.className = "fe-option-row";
1002
+ const labelIn = document.createElement("input");
1003
+ labelIn.type = "text";
1004
+ labelIn.className = "fe-prop-input";
1005
+ labelIn.placeholder = "Label";
1006
+ labelIn.value = opt.label;
1007
+ labelIn.addEventListener("input", () => {
1008
+ opt.label = labelIn.value;
1009
+ this._emit();
1010
+ });
1011
+ row.appendChild(labelIn);
1012
+ const valueIn = document.createElement("input");
1013
+ valueIn.type = "text";
1014
+ valueIn.className = "fe-prop-input";
1015
+ valueIn.placeholder = "Value";
1016
+ valueIn.value = opt.value;
1017
+ valueIn.addEventListener("input", () => {
1018
+ opt.value = valueIn.value;
1019
+ this._emit();
1020
+ });
1021
+ row.appendChild(valueIn);
1022
+ const delBtn = document.createElement("button");
1023
+ delBtn.type = "button";
1024
+ delBtn.className = "fe-btn fe-btn-icon fe-btn-danger";
1025
+ delBtn.textContent = "×";
1026
+ delBtn.addEventListener("click", () => {
1027
+ values.splice(i, 1);
1028
+ rebuildList();
1029
+ this._emit();
1030
+ });
1031
+ row.appendChild(delBtn);
1032
+ list.appendChild(row);
1033
+ }
1034
+ };
1035
+ rebuildList();
1036
+ return section;
1037
+ }
1038
+ }
1039
+ //# sourceMappingURL=form-editor.js.map