@conuti-das/prince-ui-forms 1.0.1

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.
package/dist/index.js ADDED
@@ -0,0 +1,1378 @@
1
+ import { useState, useCallback, useRef, useEffect } from 'react';
2
+ import { Form, Button, DescriptionList, Field, Separator, TextField, CheckboxGroup, Checkbox, RadioGroup, Radio, Select, SelectItem, DatePicker, NumberField, ComboBox, ComboBoxItem, TagGroup, Tag, SegmentedControl, Segment, Card, EmptyState, Notice } from '@conuti-das/prince-ui';
3
+ import { parseDate } from '@internationalized/date';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+
6
+ // src/model/task-form.ts
7
+ function mapValidationConstraints(constraints) {
8
+ if (!constraints || constraints.length === 0) return void 0;
9
+ const validate = {};
10
+ for (const c of constraints) {
11
+ const name = c.name?.toLowerCase();
12
+ const cfg = c.configuration;
13
+ switch (name) {
14
+ case "required":
15
+ validate.required = true;
16
+ break;
17
+ case "minlength":
18
+ if (cfg != null && cfg !== "") validate.minLength = Number(cfg);
19
+ break;
20
+ case "maxlength":
21
+ if (cfg != null && cfg !== "") validate.maxLength = Number(cfg);
22
+ break;
23
+ case "min":
24
+ if (cfg != null && cfg !== "") validate.min = Number(cfg);
25
+ break;
26
+ case "max":
27
+ if (cfg != null && cfg !== "") validate.max = Number(cfg);
28
+ break;
29
+ }
30
+ }
31
+ return Object.keys(validate).length > 0 ? validate : void 0;
32
+ }
33
+ function mapValues(values) {
34
+ if (!values) return void 0;
35
+ const out = Object.entries(values).map(([value, label]) => ({
36
+ value,
37
+ label: label ?? value
38
+ }));
39
+ return out.length > 0 ? out : void 0;
40
+ }
41
+ function camundaTypeName(type) {
42
+ if (typeof type === "string") return type;
43
+ return type?.name ?? "string";
44
+ }
45
+ function camundaTypeToFieldType(field) {
46
+ const name = camundaTypeName(field.type);
47
+ switch (name) {
48
+ case "enum":
49
+ return "select";
50
+ case "boolean":
51
+ return "checkbox";
52
+ case "date":
53
+ return "datetime";
54
+ case "long":
55
+ case "integer":
56
+ case "double":
57
+ case "short":
58
+ return "number";
59
+ case "string":
60
+ default:
61
+ return "textfield";
62
+ }
63
+ }
64
+ function camundaSubmitType(name) {
65
+ switch (name) {
66
+ case "boolean":
67
+ return "Boolean";
68
+ case "long":
69
+ case "integer":
70
+ case "short":
71
+ return "Integer";
72
+ case "double":
73
+ return "Double";
74
+ case "date":
75
+ return "Date";
76
+ case "enum":
77
+ case "string":
78
+ default:
79
+ return "String";
80
+ }
81
+ }
82
+ function taskFormToSchema(fields) {
83
+ const components = (fields ?? []).map((f) => {
84
+ const type = camundaTypeToFieldType(f);
85
+ const field = {
86
+ id: f.id,
87
+ key: f.id,
88
+ type,
89
+ label: f.label ?? f.id
90
+ };
91
+ if (f.defaultValue !== void 0) field.defaultValue = f.defaultValue;
92
+ field._camundaType = camundaSubmitType(camundaTypeName(f.type));
93
+ const validate = mapValidationConstraints(f.validationConstraints);
94
+ if (validate) field.validate = validate;
95
+ const values = mapValues(f.values);
96
+ if (values) field.values = values;
97
+ return field;
98
+ });
99
+ return {
100
+ type: "default",
101
+ components,
102
+ schemaVersion: 1,
103
+ exporter: { name: "@conuti-das/prince-ui-forms", version: "0.1.0" }
104
+ };
105
+ }
106
+
107
+ // src/model/conditional.ts
108
+ function evalConditional(hideExpr, data) {
109
+ if (!hideExpr) return false;
110
+ let expr = hideExpr.trim();
111
+ if (expr.startsWith("=")) expr = expr.slice(1).trim();
112
+ if (!expr) return false;
113
+ try {
114
+ return Boolean(evalExpr(expr, data));
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
119
+ function evalExpr(expr, data) {
120
+ const orParts = splitTopLevel(expr, " or ");
121
+ if (orParts.length > 1) {
122
+ return orParts.some((p) => Boolean(evalExpr(p.trim(), data)));
123
+ }
124
+ const andParts = splitTopLevel(expr, " and ");
125
+ if (andParts.length > 1) {
126
+ return andParts.every((p) => Boolean(evalExpr(p.trim(), data)));
127
+ }
128
+ return evalComparison(expr.trim(), data);
129
+ }
130
+ function splitTopLevel(expr, sep) {
131
+ const out = [];
132
+ let buf = "";
133
+ let quote = null;
134
+ for (let i = 0; i < expr.length; i++) {
135
+ const ch = expr[i];
136
+ if (quote) {
137
+ buf += ch;
138
+ if (ch === quote) quote = null;
139
+ continue;
140
+ }
141
+ if (ch === '"' || ch === "'") {
142
+ quote = ch;
143
+ buf += ch;
144
+ continue;
145
+ }
146
+ if (expr.startsWith(sep, i)) {
147
+ out.push(buf);
148
+ buf = "";
149
+ i += sep.length - 1;
150
+ continue;
151
+ }
152
+ buf += ch;
153
+ }
154
+ out.push(buf);
155
+ return out;
156
+ }
157
+ var COMPARATORS = ["!=", ">=", "<=", "==", ">", "<", "="];
158
+ function evalComparison(expr, data) {
159
+ if (expr.toLowerCase().startsWith("not(") && expr.endsWith(")")) {
160
+ return !evalExpr(expr.slice(4, -1).trim(), data);
161
+ }
162
+ if (expr.startsWith("!")) {
163
+ return !evalExpr(expr.slice(1).trim(), data);
164
+ }
165
+ for (const op of COMPARATORS) {
166
+ const idx = findOperator(expr, op);
167
+ if (idx >= 0) {
168
+ const left = resolveValue(expr.slice(0, idx).trim(), data);
169
+ const right = resolveValue(expr.slice(idx + op.length).trim(), data);
170
+ return compare(left, right, op);
171
+ }
172
+ }
173
+ return Boolean(resolveValue(expr, data));
174
+ }
175
+ function findOperator(expr, op) {
176
+ let quote = null;
177
+ for (let i = 0; i <= expr.length - op.length; i++) {
178
+ const ch = expr[i];
179
+ if (quote) {
180
+ if (ch === quote) quote = null;
181
+ continue;
182
+ }
183
+ if (ch === '"' || ch === "'") {
184
+ quote = ch;
185
+ continue;
186
+ }
187
+ if (expr.startsWith(op, i)) {
188
+ if (op === "=") {
189
+ const prev = expr[i - 1];
190
+ const next = expr[i + 1];
191
+ if (prev === "!" || prev === ">" || prev === "<" || prev === "=") continue;
192
+ if (next === "=") continue;
193
+ }
194
+ return i;
195
+ }
196
+ }
197
+ return -1;
198
+ }
199
+ function compare(left, right, op) {
200
+ switch (op) {
201
+ case "==":
202
+ case "=":
203
+ return looseEq(left, right);
204
+ case "!=":
205
+ return !looseEq(left, right);
206
+ case ">":
207
+ return Number(left) > Number(right);
208
+ case ">=":
209
+ return Number(left) >= Number(right);
210
+ case "<":
211
+ return Number(left) < Number(right);
212
+ case "<=":
213
+ return Number(left) <= Number(right);
214
+ default:
215
+ return false;
216
+ }
217
+ }
218
+ function looseEq(a, b) {
219
+ if (a === b) return true;
220
+ if (a == null || b == null) return a == null && b == null;
221
+ return String(a) === String(b);
222
+ }
223
+ function resolveValue(token, data) {
224
+ const t = token.trim();
225
+ if (t === "") return void 0;
226
+ if (t === "true") return true;
227
+ if (t === "false") return false;
228
+ if (t === "null") return null;
229
+ if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
230
+ return t.slice(1, -1);
231
+ }
232
+ if (/^-?\d+(\.\d+)?$/.test(t)) return Number(t);
233
+ return getPath(data, t);
234
+ }
235
+ function getPath(obj, path) {
236
+ if (!path) return void 0;
237
+ const parts = path.split(".");
238
+ let cur = obj;
239
+ for (const part of parts) {
240
+ if (cur == null || typeof cur !== "object") return void 0;
241
+ cur = cur[part];
242
+ }
243
+ return cur;
244
+ }
245
+ function setPath(obj, path, value) {
246
+ const parts = path.split(".");
247
+ let cur = obj;
248
+ for (let i = 0; i < parts.length - 1; i++) {
249
+ const part = parts[i];
250
+ if (typeof cur[part] !== "object" || cur[part] === null) {
251
+ cur[part] = {};
252
+ }
253
+ cur = cur[part];
254
+ }
255
+ cur[parts[parts.length - 1]] = value;
256
+ }
257
+
258
+ // src/model/schema.ts
259
+ var PRESENTATIONAL_TYPES = /* @__PURE__ */ new Set([
260
+ "text",
261
+ "spacer",
262
+ "separator",
263
+ "button",
264
+ "group"
265
+ ]);
266
+ function isDataField(field) {
267
+ return !PRESENTATIONAL_TYPES.has(field.type) && typeof field.key === "string" && field.key !== "";
268
+ }
269
+ function flattenFields(schema) {
270
+ const out = [];
271
+ const walk = (fields) => {
272
+ if (!fields) return;
273
+ for (const f of fields) {
274
+ out.push(f);
275
+ if (f.type === "group" && Array.isArray(f.components)) {
276
+ walk(f.components);
277
+ }
278
+ }
279
+ };
280
+ walk(schema.components);
281
+ return out;
282
+ }
283
+ function dataFields(schema) {
284
+ return flattenFields(schema).filter(isDataField);
285
+ }
286
+
287
+ // src/model/camunda.ts
288
+ function camundaTypeOf(value) {
289
+ if (typeof value === "boolean") return "Boolean";
290
+ if (typeof value === "number") {
291
+ return Number.isInteger(value) ? "Integer" : "Double";
292
+ }
293
+ if (value instanceof Date) return "Date";
294
+ if (value !== null && typeof value === "object") return "Json";
295
+ return "String";
296
+ }
297
+ function camundaTypeForField(field, value) {
298
+ if (field) {
299
+ const declared = field._camundaType;
300
+ if (typeof declared === "string" && declared) return declared;
301
+ switch (field.type) {
302
+ case "number":
303
+ return camundaTypeOf(typeof value === "number" ? value : Number(value));
304
+ case "checkbox":
305
+ return "Boolean";
306
+ case "datetime":
307
+ return "Date";
308
+ }
309
+ }
310
+ return camundaTypeOf(value);
311
+ }
312
+ function formDataToCamundaVariables(data, schema) {
313
+ const out = {};
314
+ if (schema) {
315
+ for (const field of dataFields(schema)) {
316
+ if (evalConditional(field.conditional?.hide, data)) continue;
317
+ const key = field.key;
318
+ const value = getPath(data, key);
319
+ if (value === void 0) continue;
320
+ out[key] = { value, type: camundaTypeForField(field, value) };
321
+ }
322
+ return out;
323
+ }
324
+ for (const [key, value] of Object.entries(data ?? {})) {
325
+ if (value === void 0) continue;
326
+ out[key] = { value, type: camundaTypeOf(value) };
327
+ }
328
+ return out;
329
+ }
330
+
331
+ // src/model/validation.ts
332
+ var VALIDATION_MESSAGES = {
333
+ required: "Dieses Feld ist erforderlich.",
334
+ min: (min) => `Wert muss mindestens ${min} sein.`,
335
+ max: (max) => `Wert darf h\xF6chstens ${max} sein.`,
336
+ minLength: (n) => `Mindestens ${n} Zeichen erforderlich.`,
337
+ maxLength: (n) => `H\xF6chstens ${n} Zeichen erlaubt.`,
338
+ pattern: "Eingabe entspricht nicht dem erwarteten Format."
339
+ };
340
+ function isEmpty(value) {
341
+ return value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
342
+ }
343
+ function validateField(field, value) {
344
+ const errors = [];
345
+ const v = field.validate;
346
+ if (!v) return errors;
347
+ if (v.required && isEmpty(value)) {
348
+ errors.push(VALIDATION_MESSAGES.required);
349
+ return errors;
350
+ }
351
+ if (isEmpty(value)) return errors;
352
+ if (typeof v.min === "number" && Number(value) < v.min) {
353
+ errors.push(VALIDATION_MESSAGES.min(v.min));
354
+ }
355
+ if (typeof v.max === "number" && Number(value) > v.max) {
356
+ errors.push(VALIDATION_MESSAGES.max(v.max));
357
+ }
358
+ const str = typeof value === "string" ? value : String(value);
359
+ if (typeof v.minLength === "number" && str.length < v.minLength) {
360
+ errors.push(VALIDATION_MESSAGES.minLength(v.minLength));
361
+ }
362
+ if (typeof v.maxLength === "number" && str.length > v.maxLength) {
363
+ errors.push(VALIDATION_MESSAGES.maxLength(v.maxLength));
364
+ }
365
+ if (v.pattern) {
366
+ try {
367
+ if (!new RegExp(v.pattern).test(str)) {
368
+ errors.push(VALIDATION_MESSAGES.pattern);
369
+ }
370
+ } catch {
371
+ }
372
+ }
373
+ return errors;
374
+ }
375
+ function validateForm(schema, data) {
376
+ const errors = {};
377
+ for (const field of dataFields(schema)) {
378
+ if (evalConditional(field.conditional?.hide, data)) continue;
379
+ const key = field.key;
380
+ const fieldErrors = validateField(field, getPath(data, key));
381
+ if (fieldErrors.length > 0) errors[key] = fieldErrors;
382
+ }
383
+ return errors;
384
+ }
385
+ function buildInitialData(schema, seed) {
386
+ const out = seed ? structuredClone(seed) : {};
387
+ for (const field of dataFields(schema)) {
388
+ const key = field.key;
389
+ if (getPath(out, key) !== void 0) continue;
390
+ if (field.defaultValue !== void 0) {
391
+ setPath(out, key, field.defaultValue);
392
+ continue;
393
+ }
394
+ switch (field.type) {
395
+ case "checkbox":
396
+ setPath(out, key, false);
397
+ break;
398
+ case "checklist":
399
+ case "taglist":
400
+ setPath(out, key, []);
401
+ break;
402
+ }
403
+ }
404
+ return out;
405
+ }
406
+ function FormRenderer({
407
+ schema,
408
+ data,
409
+ defaultData,
410
+ onChange,
411
+ onSubmit,
412
+ readOnly = false,
413
+ submitVariableFormat = "camunda",
414
+ submitLabel = "Absenden",
415
+ actionsSlot,
416
+ className
417
+ }) {
418
+ const isControlled = data !== void 0;
419
+ const [internal, setInternal] = useState(
420
+ () => buildInitialData(schema, isControlled ? data : defaultData)
421
+ );
422
+ const values = isControlled ? data : internal;
423
+ const [errors, setErrors] = useState({});
424
+ const [submitted, setSubmitted] = useState(false);
425
+ const setValue = useCallback(
426
+ (key, value) => {
427
+ const next = structuredClone(values);
428
+ setPath(next, key, value);
429
+ if (!isControlled) setInternal(next);
430
+ onChange?.(next);
431
+ if (submitted) setErrors(validateForm(schema, next));
432
+ },
433
+ [values, isControlled, onChange, submitted, schema]
434
+ );
435
+ const handleSubmit = useCallback(
436
+ (e) => {
437
+ e.preventDefault();
438
+ const errs = validateForm(schema, values);
439
+ setErrors(errs);
440
+ setSubmitted(true);
441
+ const variables = submitVariableFormat === "camunda" ? formDataToCamundaVariables(values, schema) : values;
442
+ onSubmit?.({ data: values, errors: errs, variables });
443
+ },
444
+ [schema, values, submitVariableFormat, onSubmit]
445
+ );
446
+ const isHidden = useCallback(
447
+ (field) => evalConditional(field.conditional?.hide, values),
448
+ [values]
449
+ );
450
+ if (readOnly) {
451
+ return /* @__PURE__ */ jsx(
452
+ ReadOnlyForm,
453
+ {
454
+ schema,
455
+ values,
456
+ isHidden,
457
+ className
458
+ }
459
+ );
460
+ }
461
+ return /* @__PURE__ */ jsxs(
462
+ Form,
463
+ {
464
+ className: cxForm(className),
465
+ onSubmit: handleSubmit,
466
+ validationBehavior: "aria",
467
+ "data-prn-form-renderer": "",
468
+ children: [
469
+ /* @__PURE__ */ jsx("div", { className: "prn-form-fields", children: schema.components.map((field, i) => /* @__PURE__ */ jsx(
470
+ FieldRenderer,
471
+ {
472
+ field,
473
+ values,
474
+ errors,
475
+ isHidden,
476
+ setValue
477
+ },
478
+ field.id ?? field.key ?? `${field.type}-${i}`
479
+ )) }),
480
+ (submitLabel !== null || actionsSlot) && /* @__PURE__ */ jsxs("div", { className: "prn-form-actions", children: [
481
+ actionsSlot,
482
+ submitLabel !== null && /* @__PURE__ */ jsx(Button, { type: "submit", variant: "filled", children: submitLabel })
483
+ ] })
484
+ ]
485
+ }
486
+ );
487
+ }
488
+ function cxForm(className) {
489
+ return className ? `prn-form-renderer ${className}` : "prn-form-renderer";
490
+ }
491
+ function ReadOnlyForm({
492
+ schema,
493
+ values,
494
+ isHidden,
495
+ className
496
+ }) {
497
+ const fields = dataFields(schema).filter((f) => !isHidden(f));
498
+ return /* @__PURE__ */ jsx("div", { className: cxForm(className), "data-prn-form-readonly": "", children: /* @__PURE__ */ jsx(DescriptionList, { layout: "stacked", children: fields.map((field) => {
499
+ const key = field.key;
500
+ return /* @__PURE__ */ jsx(
501
+ Field,
502
+ {
503
+ label: field.label ?? key,
504
+ value: formatReadOnly(field, getPath(values, key))
505
+ },
506
+ field.id ?? key
507
+ );
508
+ }) }) });
509
+ }
510
+ function formatReadOnly(field, value) {
511
+ if (value === void 0 || value === null || value === "") return "\u2014";
512
+ if (typeof value === "boolean") return value ? "Ja" : "Nein";
513
+ if (Array.isArray(value)) {
514
+ const labels = value.map((v) => optionLabel(field, v));
515
+ return labels.length ? labels.join(", ") : "\u2014";
516
+ }
517
+ if (field.values) return optionLabel(field, value);
518
+ return String(value);
519
+ }
520
+ function optionLabel(field, value) {
521
+ const opt = field.values?.find((o) => o.value === String(value));
522
+ return opt?.label ?? String(value);
523
+ }
524
+ function FieldRenderer({ field, values, errors, isHidden, setValue }) {
525
+ if (isHidden(field)) return null;
526
+ switch (field.type) {
527
+ case "group":
528
+ return /* @__PURE__ */ jsxs("fieldset", { className: "prn-form-group", children: [
529
+ field.label && /* @__PURE__ */ jsx("legend", { className: "prn-form-group-legend", children: field.label }),
530
+ (field.components ?? []).map((child, i) => /* @__PURE__ */ jsx(
531
+ FieldRenderer,
532
+ {
533
+ field: child,
534
+ values,
535
+ errors,
536
+ isHidden,
537
+ setValue
538
+ },
539
+ child.id ?? child.key ?? `${child.type}-${i}`
540
+ ))
541
+ ] });
542
+ case "text":
543
+ return /* @__PURE__ */ jsx("div", { className: "prn-form-text", children: field.text ?? field.label });
544
+ case "separator":
545
+ return /* @__PURE__ */ jsx(Separator, { className: "prn-form-separator" });
546
+ case "spacer":
547
+ return /* @__PURE__ */ jsx("div", { className: "prn-form-spacer", "aria-hidden": true });
548
+ case "button":
549
+ return null;
550
+ // Submit-Button wird zentral gerendert.
551
+ default:
552
+ return /* @__PURE__ */ jsx(
553
+ DataFieldRenderer,
554
+ {
555
+ field,
556
+ values,
557
+ errors,
558
+ setValue
559
+ }
560
+ );
561
+ }
562
+ }
563
+ function DataFieldRenderer({
564
+ field,
565
+ values,
566
+ errors,
567
+ setValue
568
+ }) {
569
+ if (!isDataField(field)) return null;
570
+ const key = field.key;
571
+ const value = getPath(values, key);
572
+ const error = errors[key]?.[0];
573
+ const label = field.label ?? key;
574
+ const required = field.validate?.required;
575
+ const options = field.values ?? [];
576
+ const commonField = {
577
+ label,
578
+ description: field.description,
579
+ errorMessage: error,
580
+ isInvalid: Boolean(error),
581
+ isRequired: required
582
+ };
583
+ switch (field.type) {
584
+ case "textarea":
585
+ return /* @__PURE__ */ jsx(
586
+ TextField,
587
+ {
588
+ ...commonField,
589
+ value: value == null ? "" : String(value),
590
+ onChange: (v) => setValue(key, v)
591
+ }
592
+ );
593
+ case "number":
594
+ return /* @__PURE__ */ jsx(
595
+ NumberField,
596
+ {
597
+ ...commonField,
598
+ value: typeof value === "number" ? value : Number.NaN,
599
+ minValue: field.validate?.min,
600
+ maxValue: field.validate?.max,
601
+ onChange: (v) => setValue(key, Number.isNaN(v) ? void 0 : v)
602
+ }
603
+ );
604
+ case "checkbox":
605
+ return /* @__PURE__ */ jsxs(
606
+ Checkbox,
607
+ {
608
+ isSelected: Boolean(value),
609
+ isInvalid: Boolean(error),
610
+ onChange: (v) => setValue(key, v),
611
+ children: [
612
+ label,
613
+ required ? " *" : ""
614
+ ]
615
+ }
616
+ );
617
+ case "datetime": {
618
+ let dateValue = null;
619
+ const raw = value == null ? "" : String(value).slice(0, 10);
620
+ if (raw.trim()) {
621
+ try {
622
+ dateValue = parseDate(raw);
623
+ } catch {
624
+ dateValue = null;
625
+ }
626
+ }
627
+ return /* @__PURE__ */ jsx(
628
+ DatePicker,
629
+ {
630
+ ...commonField,
631
+ value: dateValue,
632
+ onChange: (v) => setValue(key, v ? v.toString() : void 0)
633
+ }
634
+ );
635
+ }
636
+ case "select":
637
+ return /* @__PURE__ */ jsxs("div", { className: "prn-form-select-wrap", children: [
638
+ /* @__PURE__ */ jsx(
639
+ Select,
640
+ {
641
+ label,
642
+ isInvalid: Boolean(error),
643
+ isRequired: required,
644
+ selectedKey: value == null ? null : String(value),
645
+ onSelectionChange: (k) => setValue(key, k == null ? void 0 : String(k)),
646
+ children: options.map((o) => /* @__PURE__ */ jsx(SelectItem, { id: o.value, textValue: o.label, children: o.label }, o.value))
647
+ }
648
+ ),
649
+ field.description && /* @__PURE__ */ jsx("div", { className: "prn-form-hint", children: field.description }),
650
+ error && /* @__PURE__ */ jsx("div", { className: "prn-form-error", role: "alert", children: error })
651
+ ] });
652
+ case "radio":
653
+ return /* @__PURE__ */ jsx(
654
+ RadioGroup,
655
+ {
656
+ ...commonField,
657
+ value: value == null ? null : String(value),
658
+ onChange: (v) => setValue(key, v),
659
+ children: options.map((o) => /* @__PURE__ */ jsx(Radio, { value: o.value, children: o.label }, o.value))
660
+ }
661
+ );
662
+ case "checklist":
663
+ return /* @__PURE__ */ jsx(
664
+ CheckboxGroup,
665
+ {
666
+ ...commonField,
667
+ value: Array.isArray(value) ? value.map(String) : [],
668
+ onChange: (v) => setValue(key, v),
669
+ children: options.map((o) => /* @__PURE__ */ jsx(Checkbox, { value: o.value, children: o.label }, o.value))
670
+ }
671
+ );
672
+ case "taglist":
673
+ return /* @__PURE__ */ jsx(
674
+ TaglistField,
675
+ {
676
+ label,
677
+ options,
678
+ selected: Array.isArray(value) ? value.map(String) : [],
679
+ onChange: (v) => setValue(key, v),
680
+ error
681
+ }
682
+ );
683
+ case "textfield":
684
+ default:
685
+ return /* @__PURE__ */ jsx(
686
+ TextField,
687
+ {
688
+ ...commonField,
689
+ value: value == null ? "" : String(value),
690
+ onChange: (v) => setValue(key, v)
691
+ }
692
+ );
693
+ }
694
+ }
695
+ function TaglistField({
696
+ label,
697
+ options,
698
+ selected,
699
+ onChange,
700
+ error
701
+ }) {
702
+ const available = options.filter((o) => !selected.includes(o.value));
703
+ const labelFor = (v) => options.find((o) => o.value === v)?.label ?? v;
704
+ return /* @__PURE__ */ jsxs("div", { className: "prn-form-taglist", children: [
705
+ /* @__PURE__ */ jsx(
706
+ ComboBox,
707
+ {
708
+ label,
709
+ errorMessage: error,
710
+ isInvalid: Boolean(error),
711
+ selectedKey: null,
712
+ onSelectionChange: (k) => {
713
+ if (k != null) onChange([...selected, String(k)]);
714
+ },
715
+ children: available.map((o) => /* @__PURE__ */ jsx(ComboBoxItem, { id: o.value, textValue: o.label, children: o.label }, o.value))
716
+ }
717
+ ),
718
+ selected.length > 0 && /* @__PURE__ */ jsx(
719
+ TagGroup,
720
+ {
721
+ "aria-label": `${String(label)} Auswahl`,
722
+ onRemove: (keys) => onChange(selected.filter((s) => !keys.has(s))),
723
+ children: selected.map((v) => /* @__PURE__ */ jsx(Tag, { id: v, children: labelFor(v) }, v))
724
+ }
725
+ )
726
+ ] });
727
+ }
728
+
729
+ // src/builder/builder-model.ts
730
+ var BUILDER_FIELD_TYPES = [
731
+ "textfield",
732
+ "textarea",
733
+ "number",
734
+ "checkbox",
735
+ "checklist",
736
+ "radio",
737
+ "select",
738
+ "datetime",
739
+ "taglist",
740
+ "text",
741
+ "separator",
742
+ "spacer"
743
+ ];
744
+ var FIELD_TYPE_LABELS = {
745
+ textfield: "Textfeld",
746
+ textarea: "Textbereich",
747
+ number: "Zahl",
748
+ checkbox: "Checkbox",
749
+ checklist: "Checkliste",
750
+ radio: "Optionsfeld",
751
+ select: "Auswahl",
752
+ datetime: "Datum",
753
+ taglist: "Tag-Liste",
754
+ text: "Text",
755
+ separator: "Trennlinie",
756
+ spacer: "Abstand"
757
+ };
758
+ var PRESENTATIONAL = /* @__PURE__ */ new Set([
759
+ "text",
760
+ "separator",
761
+ "spacer"
762
+ ]);
763
+ var OPTION_TYPES = /* @__PURE__ */ new Set([
764
+ "select",
765
+ "radio",
766
+ "checklist",
767
+ "taglist"
768
+ ]);
769
+ function isPresentational(type) {
770
+ return PRESENTATIONAL.has(type);
771
+ }
772
+ function hasOptions(type) {
773
+ return OPTION_TYPES.has(type);
774
+ }
775
+ var idCounter = 0;
776
+ function generateFieldId() {
777
+ idCounter += 1;
778
+ const rand = Math.random().toString(36).slice(2, 8);
779
+ return `Field_${rand}${idCounter}`;
780
+ }
781
+ function emptySchema() {
782
+ return {
783
+ type: "default",
784
+ components: [],
785
+ schemaVersion: 1,
786
+ exporter: { name: "@conuti-das/prince-ui-forms", version: "0.1.0" }
787
+ };
788
+ }
789
+ function createField(type) {
790
+ const id = generateFieldId();
791
+ if (isPresentational(type)) {
792
+ if (type === "text") return { id, type, text: "Statischer Text" };
793
+ return { id, type };
794
+ }
795
+ const field = {
796
+ id,
797
+ type,
798
+ key: id,
799
+ label: FIELD_TYPE_LABELS[type]
800
+ };
801
+ if (hasOptions(type)) {
802
+ field.values = [
803
+ { value: "option1", label: "Option 1" },
804
+ { value: "option2", label: "Option 2" }
805
+ ];
806
+ }
807
+ return field;
808
+ }
809
+ function insertField(schema, field, index) {
810
+ const components = [...schema.components];
811
+ const at = index == null ? components.length : Math.max(0, Math.min(index, components.length));
812
+ components.splice(at, 0, field);
813
+ return { ...schema, components };
814
+ }
815
+ function removeFieldAt(schema, index) {
816
+ const components = schema.components.filter((_, i) => i !== index);
817
+ return { ...schema, components };
818
+ }
819
+ function moveField(schema, from, to) {
820
+ const components = [...schema.components];
821
+ if (from < 0 || from >= components.length) return schema;
822
+ const clampedTo = Math.max(0, Math.min(to, components.length - 1));
823
+ const [moved] = components.splice(from, 1);
824
+ if (moved === void 0) return schema;
825
+ components.splice(clampedTo, 0, moved);
826
+ return { ...schema, components };
827
+ }
828
+ function updateFieldAt(schema, index, patch) {
829
+ const components = schema.components.map(
830
+ (f, i) => i === index ? { ...f, ...patch } : f
831
+ );
832
+ return { ...schema, components };
833
+ }
834
+ var DND_MIME = "application/x-prn-field-type";
835
+ function FormBuilder({
836
+ value,
837
+ defaultValue,
838
+ onChange,
839
+ onSave,
840
+ mode,
841
+ onModeChange,
842
+ fieldTypes = BUILDER_FIELD_TYPES,
843
+ className
844
+ }) {
845
+ const isControlled = value !== void 0;
846
+ const [internal, setInternal] = useState(
847
+ () => value ?? defaultValue ?? emptySchema()
848
+ );
849
+ const schema = isControlled ? value : internal;
850
+ const [modeInternal, setModeInternal] = useState(mode ?? "design");
851
+ const activeMode = mode ?? modeInternal;
852
+ const [selectedIndex, setSelectedIndex] = useState(null);
853
+ const [dropIndex, setDropIndex] = useState(null);
854
+ const commit = useCallback(
855
+ (next) => {
856
+ if (!isControlled) setInternal(next);
857
+ onChange?.(next);
858
+ },
859
+ [isControlled, onChange]
860
+ );
861
+ const setMode = useCallback(
862
+ (m) => {
863
+ if (mode === void 0) setModeInternal(m);
864
+ onModeChange?.(m);
865
+ },
866
+ [mode, onModeChange]
867
+ );
868
+ const dragFrom = useRef(null);
869
+ const onPaletteDragStart = (e, type) => {
870
+ e.dataTransfer.setData(DND_MIME, type);
871
+ e.dataTransfer.effectAllowed = "copy";
872
+ dragFrom.current = null;
873
+ };
874
+ const onCanvasItemDragStart = (e, index) => {
875
+ e.dataTransfer.setData(DND_MIME, "__move__");
876
+ e.dataTransfer.effectAllowed = "move";
877
+ dragFrom.current = index;
878
+ };
879
+ const onDropAt = (e, index) => {
880
+ e.preventDefault();
881
+ const data = e.dataTransfer.getData(DND_MIME);
882
+ setDropIndex(null);
883
+ if (dragFrom.current != null) {
884
+ const next = moveField(schema, dragFrom.current, index);
885
+ commit(next);
886
+ setSelectedIndex(index);
887
+ dragFrom.current = null;
888
+ return;
889
+ }
890
+ if (data && data !== "__move__") {
891
+ const field = createField(data);
892
+ commit(insertField(schema, field, index));
893
+ setSelectedIndex(index);
894
+ }
895
+ };
896
+ const onDropEnd = (e) => {
897
+ e.preventDefault();
898
+ const data = e.dataTransfer.getData(DND_MIME);
899
+ setDropIndex(null);
900
+ if (dragFrom.current != null) {
901
+ commit(moveField(schema, dragFrom.current, schema.components.length - 1));
902
+ dragFrom.current = null;
903
+ return;
904
+ }
905
+ if (data && data !== "__move__") {
906
+ const field = createField(data);
907
+ commit(insertField(schema, field));
908
+ setSelectedIndex(schema.components.length);
909
+ }
910
+ };
911
+ const selectedField = selectedIndex != null ? schema.components[selectedIndex] : void 0;
912
+ const patchSelected = useCallback(
913
+ (patch) => {
914
+ if (selectedIndex == null) return;
915
+ commit(updateFieldAt(schema, selectedIndex, patch));
916
+ },
917
+ [selectedIndex, schema, commit]
918
+ );
919
+ const deleteSelected = useCallback(() => {
920
+ if (selectedIndex == null) return;
921
+ commit(removeFieldAt(schema, selectedIndex));
922
+ setSelectedIndex(null);
923
+ }, [selectedIndex, schema, commit]);
924
+ return /* @__PURE__ */ jsxs("div", { className: cx("prn-form-builder", className), "data-mode": activeMode, children: [
925
+ /* @__PURE__ */ jsxs("div", { className: "prn-fb-toolbar", children: [
926
+ /* @__PURE__ */ jsxs(
927
+ SegmentedControl,
928
+ {
929
+ "aria-label": "Builder-Modus",
930
+ selectedKeys: /* @__PURE__ */ new Set([activeMode]),
931
+ onSelectionChange: (keys) => {
932
+ const next = [...keys][0];
933
+ if (next) setMode(next);
934
+ },
935
+ children: [
936
+ /* @__PURE__ */ jsx(Segment, { id: "design", children: "Designer" }),
937
+ /* @__PURE__ */ jsx(Segment, { id: "expert", children: "Experte (form-js)" })
938
+ ]
939
+ }
940
+ ),
941
+ onSave && /* @__PURE__ */ jsx(Button, { variant: "filled", onPress: () => onSave(schema), children: "Speichern" })
942
+ ] }),
943
+ activeMode === "expert" ? /* @__PURE__ */ jsx(ExpertEditor, { schema, onChange: commit }) : /* @__PURE__ */ jsxs("div", { className: "prn-fb-grid", children: [
944
+ /* @__PURE__ */ jsx(Card, { title: "Felder", className: "prn-fb-palette", children: /* @__PURE__ */ jsx("div", { className: "prn-fb-palette-list", children: fieldTypes.map((type) => /* @__PURE__ */ jsxs(
945
+ "button",
946
+ {
947
+ type: "button",
948
+ className: "prn-fb-palette-item",
949
+ draggable: true,
950
+ onDragStart: (e) => onPaletteDragStart(e, type),
951
+ onClick: () => {
952
+ commit(insertField(schema, createField(type)));
953
+ setSelectedIndex(schema.components.length);
954
+ },
955
+ children: [
956
+ /* @__PURE__ */ jsx("span", { className: "prn-fb-palette-glyph", "aria-hidden": true, children: GLYPH[type] ?? FALLBACK_GLYPH }),
957
+ FIELD_TYPE_LABELS[type]
958
+ ]
959
+ },
960
+ type
961
+ )) }) }),
962
+ /* @__PURE__ */ jsx(Card, { title: "Formular", className: "prn-fb-canvas", children: /* @__PURE__ */ jsx(
963
+ "div",
964
+ {
965
+ className: "prn-fb-canvas-drop",
966
+ onDragOver: (e) => {
967
+ e.preventDefault();
968
+ setDropIndex(schema.components.length);
969
+ },
970
+ onDrop: onDropEnd,
971
+ children: schema.components.length === 0 ? /* @__PURE__ */ jsx(
972
+ EmptyState,
973
+ {
974
+ title: "Leeres Formular",
975
+ description: "Felder aus der Palette hierher ziehen oder anklicken."
976
+ }
977
+ ) : /* @__PURE__ */ jsx("ul", { className: "prn-fb-canvas-list", role: "list", children: schema.components.map((field, index) => /* @__PURE__ */ jsxs(
978
+ "li",
979
+ {
980
+ className: "prn-fb-canvas-item",
981
+ "data-selected": selectedIndex === index || void 0,
982
+ "data-dropbefore": dropIndex === index || void 0,
983
+ draggable: true,
984
+ onDragStart: (e) => onCanvasItemDragStart(e, index),
985
+ onDragOver: (e) => {
986
+ e.preventDefault();
987
+ e.stopPropagation();
988
+ setDropIndex(index);
989
+ },
990
+ onDrop: (e) => {
991
+ e.stopPropagation();
992
+ onDropAt(e, index);
993
+ },
994
+ onClick: () => setSelectedIndex(index),
995
+ children: [
996
+ /* @__PURE__ */ jsx("span", { className: "prn-fb-item-type", children: FIELD_TYPE_LABELS[field.type] ?? field.type }),
997
+ /* @__PURE__ */ jsx("span", { className: "prn-fb-item-label", children: isPresentational(field.type) ? field.text ?? "\u2014" : field.label ?? field.key ?? "\u2014" }),
998
+ /* @__PURE__ */ jsx(
999
+ Button,
1000
+ {
1001
+ variant: "plain",
1002
+ className: "prn-fb-item-remove",
1003
+ "aria-label": "Feld entfernen",
1004
+ onPress: () => {
1005
+ commit(removeFieldAt(schema, index));
1006
+ if (selectedIndex === index) setSelectedIndex(null);
1007
+ },
1008
+ children: "\u2715"
1009
+ }
1010
+ )
1011
+ ]
1012
+ },
1013
+ field.id ?? index
1014
+ )) })
1015
+ }
1016
+ ) }),
1017
+ /* @__PURE__ */ jsx(Card, { title: "Eigenschaften", className: "prn-fb-props", children: selectedField ? /* @__PURE__ */ jsx(
1018
+ PropertiesPanel,
1019
+ {
1020
+ field: selectedField,
1021
+ onPatch: patchSelected,
1022
+ onDelete: deleteSelected
1023
+ }
1024
+ ) : /* @__PURE__ */ jsx("p", { className: "prn-fb-props-empty", children: "Kein Feld ausgew\xE4hlt." }) }),
1025
+ /* @__PURE__ */ jsx(Card, { title: "Vorschau", className: "prn-fb-preview", children: schema.components.length === 0 ? /* @__PURE__ */ jsx("p", { className: "prn-fb-props-empty", children: "Noch keine Felder." }) : /* @__PURE__ */ jsx(FormRenderer, { schema, submitLabel: null }) })
1026
+ ] })
1027
+ ] });
1028
+ }
1029
+ var ic = (children) => /* @__PURE__ */ jsx(
1030
+ "svg",
1031
+ {
1032
+ viewBox: "0 0 16 16",
1033
+ width: "15",
1034
+ height: "15",
1035
+ fill: "none",
1036
+ stroke: "currentColor",
1037
+ strokeWidth: "1.4",
1038
+ strokeLinecap: "round",
1039
+ strokeLinejoin: "round",
1040
+ children
1041
+ }
1042
+ );
1043
+ var FALLBACK_GLYPH = ic(/* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "10", height: "10", rx: "2.5" }));
1044
+ var GLYPH = {
1045
+ textfield: ic(
1046
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1047
+ /* @__PURE__ */ jsx("rect", { x: "2", y: "5.5", width: "12", height: "5", rx: "1.5" }),
1048
+ /* @__PURE__ */ jsx("path", { d: "M4.5 8h3" })
1049
+ ] })
1050
+ ),
1051
+ textarea: ic(
1052
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1053
+ /* @__PURE__ */ jsx("rect", { x: "2", y: "3", width: "12", height: "10", rx: "1.5" }),
1054
+ /* @__PURE__ */ jsx("path", { d: "M4.5 6h7M4.5 8.5h7M4.5 11h4" })
1055
+ ] })
1056
+ ),
1057
+ number: ic(/* @__PURE__ */ jsx("path", { d: "M6 2.5 4.5 13.5M11.5 2.5 10 13.5M2.5 5.5h11M2 10.5h11" })),
1058
+ checkbox: ic(
1059
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1060
+ /* @__PURE__ */ jsx("rect", { x: "2.5", y: "2.5", width: "11", height: "11", rx: "2.5" }),
1061
+ /* @__PURE__ */ jsx("path", { d: "M5.5 8.2l1.8 1.8 3.2-3.6" })
1062
+ ] })
1063
+ ),
1064
+ checklist: ic(
1065
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1066
+ /* @__PURE__ */ jsx("path", { d: "M6 4.5h7.5M6 8h7.5M6 11.5h7.5" }),
1067
+ /* @__PURE__ */ jsx("path", { d: "M2.3 4.2l.8.9 1.5-1.7M2.3 7.7l.8.9 1.5-1.7M2.3 11.2l.8.9 1.5-1.7" })
1068
+ ] })
1069
+ ),
1070
+ radio: ic(
1071
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1072
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "5.5" }),
1073
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "2.2", fill: "currentColor", stroke: "none" })
1074
+ ] })
1075
+ ),
1076
+ select: ic(
1077
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1078
+ /* @__PURE__ */ jsx("rect", { x: "2", y: "4", width: "12", height: "8", rx: "1.5" }),
1079
+ /* @__PURE__ */ jsx("path", { d: "M6 7.5 8 9.5 10 7.5" })
1080
+ ] })
1081
+ ),
1082
+ datetime: ic(
1083
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1084
+ /* @__PURE__ */ jsx("rect", { x: "2.25", y: "3.25", width: "11.5", height: "10.5", rx: "2.5" }),
1085
+ /* @__PURE__ */ jsx("path", { d: "M2.25 6.5h11.5M5 2v2.2M11 2v2.2" })
1086
+ ] })
1087
+ ),
1088
+ taglist: ic(
1089
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1090
+ /* @__PURE__ */ jsx("path", { d: "M2.5 7.6V3.6a1 1 0 0 1 1-1h4l6 6-5 5-6-6z" }),
1091
+ /* @__PURE__ */ jsx("circle", { cx: "5.4", cy: "5.5", r: "1", fill: "currentColor", stroke: "none" })
1092
+ ] })
1093
+ ),
1094
+ text: ic(/* @__PURE__ */ jsx("path", { d: "M4 4h8M8 4v8M6.5 12h3" })),
1095
+ separator: ic(/* @__PURE__ */ jsx("path", { d: "M2.5 8h11" })),
1096
+ spacer: ic(
1097
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1098
+ /* @__PURE__ */ jsx("path", { d: "M8 3v10" }),
1099
+ /* @__PURE__ */ jsx("path", { d: "M5.5 5 8 2.5 10.5 5M5.5 11 8 13.5 10.5 11" })
1100
+ ] })
1101
+ )
1102
+ };
1103
+ function PropertiesPanel({
1104
+ field,
1105
+ onPatch,
1106
+ onDelete
1107
+ }) {
1108
+ const presentational = isPresentational(field.type);
1109
+ const optioned = hasOptions(field.type);
1110
+ return /* @__PURE__ */ jsxs("div", { className: "prn-fb-props-form", children: [
1111
+ presentational ? field.type === "text" && /* @__PURE__ */ jsx(
1112
+ TextField,
1113
+ {
1114
+ label: "Text",
1115
+ value: field.text ?? "",
1116
+ onChange: (v) => onPatch({ text: v })
1117
+ }
1118
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1119
+ /* @__PURE__ */ jsx(
1120
+ TextField,
1121
+ {
1122
+ label: "Label",
1123
+ value: field.label ?? "",
1124
+ onChange: (v) => onPatch({ label: v })
1125
+ }
1126
+ ),
1127
+ /* @__PURE__ */ jsx(
1128
+ TextField,
1129
+ {
1130
+ label: "Key (Datenbindung)",
1131
+ value: field.key ?? "",
1132
+ onChange: (v) => onPatch({ key: v })
1133
+ }
1134
+ ),
1135
+ /* @__PURE__ */ jsx(
1136
+ TextField,
1137
+ {
1138
+ label: "Beschreibung",
1139
+ value: field.description ?? "",
1140
+ onChange: (v) => onPatch({ description: v })
1141
+ }
1142
+ ),
1143
+ /* @__PURE__ */ jsx(
1144
+ Checkbox,
1145
+ {
1146
+ isSelected: Boolean(field.validate?.required),
1147
+ onChange: (v) => onPatch({ validate: { ...field.validate, required: v } }),
1148
+ children: "Pflichtfeld"
1149
+ }
1150
+ ),
1151
+ field.type === "number" && /* @__PURE__ */ jsxs("div", { className: "prn-fb-props-row", children: [
1152
+ /* @__PURE__ */ jsx(
1153
+ NumberField,
1154
+ {
1155
+ label: "Min",
1156
+ value: field.validate?.min ?? Number.NaN,
1157
+ onChange: (v) => onPatch({
1158
+ validate: { ...field.validate, min: Number.isNaN(v) ? void 0 : v }
1159
+ })
1160
+ }
1161
+ ),
1162
+ /* @__PURE__ */ jsx(
1163
+ NumberField,
1164
+ {
1165
+ label: "Max",
1166
+ value: field.validate?.max ?? Number.NaN,
1167
+ onChange: (v) => onPatch({
1168
+ validate: { ...field.validate, max: Number.isNaN(v) ? void 0 : v }
1169
+ })
1170
+ }
1171
+ )
1172
+ ] }),
1173
+ (field.type === "textfield" || field.type === "textarea") && /* @__PURE__ */ jsxs("div", { className: "prn-fb-props-row", children: [
1174
+ /* @__PURE__ */ jsx(
1175
+ NumberField,
1176
+ {
1177
+ label: "Min. L\xE4nge",
1178
+ value: field.validate?.minLength ?? Number.NaN,
1179
+ onChange: (v) => onPatch({
1180
+ validate: {
1181
+ ...field.validate,
1182
+ minLength: Number.isNaN(v) ? void 0 : v
1183
+ }
1184
+ })
1185
+ }
1186
+ ),
1187
+ /* @__PURE__ */ jsx(
1188
+ NumberField,
1189
+ {
1190
+ label: "Max. L\xE4nge",
1191
+ value: field.validate?.maxLength ?? Number.NaN,
1192
+ onChange: (v) => onPatch({
1193
+ validate: {
1194
+ ...field.validate,
1195
+ maxLength: Number.isNaN(v) ? void 0 : v
1196
+ }
1197
+ })
1198
+ }
1199
+ )
1200
+ ] }),
1201
+ field.type === "textfield" && /* @__PURE__ */ jsx(
1202
+ TextField,
1203
+ {
1204
+ label: "Pattern (RegEx)",
1205
+ value: field.validate?.pattern ?? "",
1206
+ onChange: (v) => onPatch({ validate: { ...field.validate, pattern: v || void 0 } })
1207
+ }
1208
+ ),
1209
+ /* @__PURE__ */ jsx(
1210
+ TextField,
1211
+ {
1212
+ label: "Sichtbar wenn versteckt (conditional.hide)",
1213
+ description: 'FEEL-nah, z. B. role = "user"',
1214
+ value: field.conditional?.hide ?? "",
1215
+ onChange: (v) => onPatch({ conditional: { hide: v || void 0 } })
1216
+ }
1217
+ ),
1218
+ optioned && /* @__PURE__ */ jsx(
1219
+ OptionsEditor,
1220
+ {
1221
+ options: field.values ?? [],
1222
+ onChange: (values) => onPatch({ values })
1223
+ }
1224
+ )
1225
+ ] }),
1226
+ /* @__PURE__ */ jsx(Button, { variant: "tinted", onPress: onDelete, className: "prn-fb-delete", children: "Feld l\xF6schen" })
1227
+ ] });
1228
+ }
1229
+ function OptionsEditor({
1230
+ options,
1231
+ onChange
1232
+ }) {
1233
+ const update = (i, patch) => onChange(options.map((o, idx) => idx === i ? { ...o, ...patch } : o));
1234
+ return /* @__PURE__ */ jsxs("div", { className: "prn-fb-options", children: [
1235
+ /* @__PURE__ */ jsx("div", { className: "prn-fb-options-head", children: "Optionen" }),
1236
+ options.map((o, i) => /* @__PURE__ */ jsxs("div", { className: "prn-fb-props-row", children: [
1237
+ /* @__PURE__ */ jsx(
1238
+ TextField,
1239
+ {
1240
+ label: "Label",
1241
+ value: o.label,
1242
+ onChange: (v) => update(i, { label: v })
1243
+ }
1244
+ ),
1245
+ /* @__PURE__ */ jsx(
1246
+ TextField,
1247
+ {
1248
+ label: "Wert",
1249
+ value: o.value,
1250
+ onChange: (v) => update(i, { value: v })
1251
+ }
1252
+ ),
1253
+ /* @__PURE__ */ jsx(
1254
+ Button,
1255
+ {
1256
+ variant: "plain",
1257
+ "aria-label": "Option entfernen",
1258
+ onPress: () => onChange(options.filter((_, idx) => idx !== i)),
1259
+ children: "\u2715"
1260
+ }
1261
+ )
1262
+ ] }, i)),
1263
+ /* @__PURE__ */ jsx(
1264
+ Button,
1265
+ {
1266
+ variant: "tinted",
1267
+ onPress: () => onChange([
1268
+ ...options,
1269
+ { label: `Option ${options.length + 1}`, value: `option${options.length + 1}` }
1270
+ ]),
1271
+ children: "Option hinzuf\xFCgen"
1272
+ }
1273
+ )
1274
+ ] });
1275
+ }
1276
+ function ExpertEditor({
1277
+ schema,
1278
+ onChange
1279
+ }) {
1280
+ const containerRef = useRef(null);
1281
+ const editorRef = useRef(null);
1282
+ const [status, setStatus] = useState(
1283
+ "loading"
1284
+ );
1285
+ const [errorMsg, setErrorMsg] = useState(null);
1286
+ const initialSchema = useRef(schema);
1287
+ useEffect(() => {
1288
+ let cancelled = false;
1289
+ const el = containerRef.current;
1290
+ if (!el) return;
1291
+ setStatus("loading");
1292
+ setErrorMsg(null);
1293
+ (async () => {
1294
+ try {
1295
+ await Promise.all([
1296
+ // @ts-ignore CSS-Asset ohne Typdeklaration; vom Bundler aufgelöst.
1297
+ import(
1298
+ /* @vite-ignore */
1299
+ '@bpmn-io/form-js-editor/dist/assets/form-js-editor.css'
1300
+ )
1301
+ ]).catch(() => {
1302
+ });
1303
+ const mod = await import(
1304
+ /* @vite-ignore */
1305
+ '@bpmn-io/form-js-editor'
1306
+ );
1307
+ if (cancelled) return;
1308
+ const version = mod.schemaVersion ?? 18;
1309
+ const baseSchema = {
1310
+ schemaVersion: version,
1311
+ ...initialSchema.current
1312
+ };
1313
+ let editor;
1314
+ try {
1315
+ editor = await mod.createFormEditor({ container: el, schema: baseSchema });
1316
+ } catch {
1317
+ editor = await mod.createFormEditor({
1318
+ container: el,
1319
+ schema: { schemaVersion: version, type: "default", components: [] }
1320
+ });
1321
+ }
1322
+ if (cancelled) {
1323
+ editor.destroy();
1324
+ return;
1325
+ }
1326
+ editorRef.current = editor;
1327
+ editor.on("changed", () => {
1328
+ try {
1329
+ onChange(editor.getSchema());
1330
+ } catch {
1331
+ }
1332
+ });
1333
+ if (!cancelled) setStatus("ready");
1334
+ window.setTimeout(() => {
1335
+ if (cancelled) return;
1336
+ const fjs = el.querySelector(".fjs-container");
1337
+ if (fjs && fjs.children.length === 0) setStatus("empty");
1338
+ }, 800);
1339
+ } catch (e) {
1340
+ if (!cancelled) {
1341
+ setErrorMsg(e?.message ?? String(e));
1342
+ setStatus("error");
1343
+ }
1344
+ }
1345
+ })();
1346
+ return () => {
1347
+ cancelled = true;
1348
+ const editor = editorRef.current;
1349
+ try {
1350
+ editor?.destroy?.();
1351
+ } catch {
1352
+ }
1353
+ editorRef.current = null;
1354
+ };
1355
+ }, []);
1356
+ return /* @__PURE__ */ jsxs("div", { className: "prn-fb-expert", children: [
1357
+ status === "error" && /* @__PURE__ */ jsxs(Notice, { tone: "negative", title: "form-js nicht verf\xFCgbar", children: [
1358
+ "Der Experten-Editor (`@bpmn-io/form-js-editor`) konnte nicht geladen werden. Stellen Sie sicher, dass das optionale Peer-Paket installiert ist.",
1359
+ errorMsg ? ` (${errorMsg})` : ""
1360
+ ] }),
1361
+ status === "empty" && /* @__PURE__ */ jsx(Notice, { tone: "info", title: "Experten-Editor konnte nicht gerendert werden", children: 'Der form-js-Editor wurde geladen, aber in dieser Umgebung nicht dargestellt (bekanntes Bundler-/Preact-Problem). Nutzen Sie bitte den \u201EDesigner"-Modus \u2014 er deckt dieselben Funktionen ab.' }),
1362
+ /* @__PURE__ */ jsx(
1363
+ "div",
1364
+ {
1365
+ ref: containerRef,
1366
+ className: "prn-fb-expert-host",
1367
+ "data-status": status
1368
+ }
1369
+ )
1370
+ ] });
1371
+ }
1372
+ function cx(...parts) {
1373
+ return parts.filter(Boolean).join(" ");
1374
+ }
1375
+
1376
+ export { BUILDER_FIELD_TYPES, FIELD_TYPE_LABELS, FormBuilder, FormRenderer, PRESENTATIONAL_TYPES, VALIDATION_MESSAGES, camundaSubmitType, camundaTypeName, camundaTypeOf, camundaTypeToFieldType, createField, dataFields, emptySchema, evalConditional, flattenFields, formDataToCamundaVariables, getPath, insertField, isDataField, moveField, removeFieldAt, setPath, taskFormToSchema, updateFieldAt, validateField, validateForm };
1377
+ //# sourceMappingURL=index.js.map
1378
+ //# sourceMappingURL=index.js.map