@conuti-das/prince-ui-dmn 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,1423 @@
1
+ import DmnModdleFactory from 'dmn-moddle';
2
+ import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
3
+ import { Select, SelectItem, Button, AnalyticalTable, Modal, TextField, SegmentedControl, Segment } from '@conuti-das/prince-ui';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+
6
+ // src/theme/diagram-theme.ts
7
+ function readToken(name, fallback) {
8
+ if (typeof window === "undefined" || !document?.documentElement) return fallback;
9
+ const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
10
+ return value || fallback;
11
+ }
12
+ function isDarkMode(scheme = "auto") {
13
+ if (scheme === "dark") return true;
14
+ if (scheme === "light") return false;
15
+ if (typeof document !== "undefined") {
16
+ const attr = document.documentElement.getAttribute("data-theme");
17
+ if (attr === "dark" || attr === "cu") return true;
18
+ if (attr === "light") return false;
19
+ }
20
+ if (typeof window !== "undefined" && window.matchMedia) {
21
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
22
+ }
23
+ return false;
24
+ }
25
+ function getDiagramColors(scheme = "auto") {
26
+ const dark = isDarkMode(scheme);
27
+ return {
28
+ defaultFillColor: readToken("--prn-bg-elevated", dark ? "#1c1c1e" : "#ffffff"),
29
+ defaultStrokeColor: readToken("--prn-label", dark ? "#e4e4e6" : "#1d1d1f"),
30
+ defaultLabelColor: readToken("--prn-label", dark ? "#e4e4e6" : "#1d1d1f"),
31
+ canvasBackground: readToken("--prn-bg", dark ? "#000000" : "#f5f5f7"),
32
+ accent: readToken("--prn-accent", "#a0d22b")
33
+ };
34
+ }
35
+ function onThemeChange(onChange) {
36
+ if (typeof window === "undefined" || !window.MutationObserver) return () => {
37
+ };
38
+ const observer = new MutationObserver((mutations) => {
39
+ for (const m of mutations) {
40
+ if (m.type === "attributes" && m.attributeName === "data-theme") {
41
+ onChange();
42
+ return;
43
+ }
44
+ }
45
+ });
46
+ observer.observe(document.documentElement, {
47
+ attributes: true,
48
+ attributeFilter: ["data-theme"]
49
+ });
50
+ let media;
51
+ if (window.matchMedia) {
52
+ media = window.matchMedia("(prefers-color-scheme: dark)");
53
+ media.addEventListener?.("change", onChange);
54
+ }
55
+ return () => {
56
+ observer.disconnect();
57
+ media?.removeEventListener?.("change", onChange);
58
+ };
59
+ }
60
+ var createModdle = DmnModdleFactory;
61
+ var idCounter = 0;
62
+ function makeId(prefix) {
63
+ idCounter += 1;
64
+ return `${prefix}_${Date.now().toString(36)}_${idCounter.toString(36)}`;
65
+ }
66
+ var VALID_HIT_POLICIES = [
67
+ "UNIQUE",
68
+ "FIRST",
69
+ "PRIORITY",
70
+ "ANY",
71
+ "COLLECT",
72
+ "RULE ORDER",
73
+ "OUTPUT ORDER"
74
+ ];
75
+ var VALID_AGGREGATIONS = ["SUM", "MIN", "MAX", "COUNT"];
76
+ function normalizeHitPolicy(value) {
77
+ const v = typeof value === "string" ? value.toUpperCase() : "";
78
+ return VALID_HIT_POLICIES.includes(v) ? v : "UNIQUE";
79
+ }
80
+ function normalizeAggregation(value) {
81
+ const v = typeof value === "string" ? value.toUpperCase() : "";
82
+ return VALID_AGGREGATIONS.includes(v) ? v : void 0;
83
+ }
84
+ function asArray(value) {
85
+ return Array.isArray(value) ? value : [];
86
+ }
87
+ function getText(el) {
88
+ if (!el) return "";
89
+ const t = el.text;
90
+ return typeof t === "string" ? t.trim() : "";
91
+ }
92
+ function findDecisions(definitions) {
93
+ return asArray(definitions.drgElement).filter(
94
+ (e) => e.$type === "dmn:Decision"
95
+ );
96
+ }
97
+ function getDecisionTable(decision) {
98
+ const logic = decision.decisionLogic;
99
+ return logic && logic.$type === "dmn:DecisionTable" ? logic : void 0;
100
+ }
101
+ function tableToModel(decision, table) {
102
+ const inputs = asArray(table.input);
103
+ const outputs = asArray(table.output);
104
+ asArray(table.annotation);
105
+ const rules = asArray(table.rule);
106
+ const inputCols = inputs.map((input) => {
107
+ const expr = input.inputExpression;
108
+ const valuesEl = input.inputValues;
109
+ const inputValues = parseUnaryTestValues(getText(valuesEl));
110
+ return {
111
+ id: input.id ?? makeId("input"),
112
+ label: input.label ?? "",
113
+ expression: getText(expr),
114
+ kind: "input",
115
+ typeRef: expr?.typeRef ?? "string",
116
+ ...inputValues.length > 0 ? { inputValues } : {}
117
+ };
118
+ });
119
+ const outputCols = outputs.map((output) => {
120
+ const valuesEl = output.outputValues;
121
+ const outputValues = parseUnaryTestValues(getText(valuesEl));
122
+ return {
123
+ id: output.id ?? makeId("output"),
124
+ label: output.name ?? output.label ?? "",
125
+ expression: output.name ?? "",
126
+ kind: "output",
127
+ typeRef: output.typeRef ?? "string",
128
+ ...outputValues.length > 0 ? { inputValues: outputValues } : {}
129
+ };
130
+ });
131
+ const columns = [...inputCols, ...outputCols];
132
+ const rows = rules.map((rule) => {
133
+ const inputEntries = asArray(rule.inputEntry);
134
+ const outputEntries = asArray(rule.outputEntry);
135
+ const annotationEntries = asArray(rule.annotationEntry);
136
+ const cells = {};
137
+ inputEntries.forEach((entry, i) => {
138
+ const col = inputCols[i];
139
+ if (col) cells[col.id] = getText(entry);
140
+ });
141
+ outputEntries.forEach((entry, i) => {
142
+ const col = outputCols[i];
143
+ if (col) cells[col.id] = getText(entry);
144
+ });
145
+ const annotation = getText(annotationEntries[0]) || (typeof rule.description === "string" ? rule.description.trim() : "");
146
+ return {
147
+ id: rule.id ?? makeId("rule"),
148
+ cells,
149
+ ...annotation ? { annotation } : {}
150
+ };
151
+ });
152
+ return {
153
+ id: decision.id ?? table.id ?? makeId("decision"),
154
+ name: decision.name ?? "",
155
+ hitPolicy: normalizeHitPolicy(table.hitPolicy),
156
+ ...normalizeAggregation(table.aggregation) ? { aggregation: normalizeAggregation(table.aggregation) } : {},
157
+ columns,
158
+ rows
159
+ };
160
+ }
161
+ function parseUnaryTestValues(text) {
162
+ if (!text) return [];
163
+ return Array.from(text.matchAll(/"([^"]*)"/g), (m) => m[1] ?? "");
164
+ }
165
+ async function listDecisions(xml) {
166
+ const { rootElement } = await createModdle().fromXML(xml);
167
+ return findDecisions(rootElement).filter((d) => getDecisionTable(d)).map((d) => ({ id: d.id ?? "", name: d.name ?? "" }));
168
+ }
169
+ async function parseDmnModel(xml, options = {}) {
170
+ let result;
171
+ try {
172
+ result = await createModdle().fromXML(xml);
173
+ } catch (e) {
174
+ throw new Error(`DMN-Parsing fehlgeschlagen: ${e.message}`);
175
+ }
176
+ const decisions = findDecisions(result.rootElement);
177
+ if (decisions.length === 0) {
178
+ throw new Error("Keine dmn:Decision im Dokument gefunden");
179
+ }
180
+ let decision;
181
+ if (options.decisionId) {
182
+ decision = decisions.find((d) => d.id === options.decisionId);
183
+ } else if (typeof options.decisionIndex === "number") {
184
+ decision = decisions[options.decisionIndex];
185
+ }
186
+ if (!decision) {
187
+ decision = decisions.find((d) => getDecisionTable(d)) ?? decisions[0];
188
+ }
189
+ if (!decision) {
190
+ throw new Error("Decision nicht gefunden");
191
+ }
192
+ const table = getDecisionTable(decision);
193
+ if (!table) {
194
+ throw new Error(
195
+ `Decision "${decision.id ?? ""}" enth\xE4lt keine dmn:DecisionTable`
196
+ );
197
+ }
198
+ return tableToModel(decision, table);
199
+ }
200
+ function buildUnaryTests(moddle, text) {
201
+ return moddle.create("dmn:UnaryTests", { text });
202
+ }
203
+ function buildLiteralExpression(moddle, text, id) {
204
+ return moddle.create("dmn:LiteralExpression", { id, text });
205
+ }
206
+ function applyModelToTable(moddle, decision, table, model) {
207
+ const inputCols = model.columns.filter((c) => c.kind === "input");
208
+ const outputCols = model.columns.filter((c) => c.kind === "output");
209
+ decision.name = model.name;
210
+ table.hitPolicy = model.hitPolicy;
211
+ if (model.hitPolicy === "COLLECT" && model.aggregation) {
212
+ table.aggregation = model.aggregation;
213
+ } else {
214
+ delete table.aggregation;
215
+ }
216
+ table.input = inputCols.map((col) => {
217
+ const inputExpression = buildLiteralExpression(
218
+ moddle,
219
+ col.expression,
220
+ `${col.id}_expr`
221
+ );
222
+ inputExpression.typeRef = col.typeRef;
223
+ const input = moddle.create("dmn:InputClause", {
224
+ id: col.id,
225
+ label: col.label,
226
+ inputExpression
227
+ });
228
+ if (col.inputValues && col.inputValues.length > 0) {
229
+ input.inputValues = buildUnaryTests(
230
+ moddle,
231
+ col.inputValues.map((v) => `"${v}"`).join(",")
232
+ );
233
+ }
234
+ return input;
235
+ });
236
+ table.output = outputCols.map((col) => {
237
+ const output = moddle.create("dmn:OutputClause", {
238
+ id: col.id,
239
+ name: col.expression || col.label,
240
+ typeRef: col.typeRef
241
+ });
242
+ if (col.label && col.label !== col.expression) {
243
+ output.label = col.label;
244
+ }
245
+ if (col.inputValues && col.inputValues.length > 0) {
246
+ output.outputValues = buildUnaryTests(
247
+ moddle,
248
+ col.inputValues.map((v) => `"${v}"`).join(",")
249
+ );
250
+ }
251
+ return output;
252
+ });
253
+ const hasAnnotations = model.rows.some((r) => (r.annotation ?? "").length > 0);
254
+ table.annotation = hasAnnotations ? [moddle.create("dmn:RuleAnnotationClause", { name: "Annotation" })] : [];
255
+ table.rule = model.rows.map((row) => {
256
+ const inputEntry = inputCols.map(
257
+ (col) => buildUnaryTests(moddle, row.cells[col.id] ?? "")
258
+ );
259
+ const outputEntry = outputCols.map(
260
+ (col, i) => buildLiteralExpression(
261
+ moddle,
262
+ row.cells[col.id] ?? "",
263
+ `${row.id}_out_${i}`
264
+ )
265
+ );
266
+ const rule = moddle.create("dmn:DecisionRule", {
267
+ id: row.id,
268
+ inputEntry,
269
+ outputEntry
270
+ });
271
+ if (hasAnnotations) {
272
+ rule.annotationEntry = [
273
+ moddle.create("dmn:RuleAnnotation", { text: row.annotation ?? "" })
274
+ ];
275
+ }
276
+ return rule;
277
+ });
278
+ }
279
+ async function serializeDmnModel(model, originalXml) {
280
+ const moddle = createModdle();
281
+ if (originalXml) {
282
+ const { rootElement } = await moddle.fromXML(originalXml);
283
+ const decisions = findDecisions(rootElement);
284
+ const decision2 = decisions.find((d) => d.id === model.id) ?? decisions.find((d) => getDecisionTable(d)) ?? decisions[0];
285
+ if (!decision2) {
286
+ throw new Error("Keine Decision zum Schreiben gefunden");
287
+ }
288
+ let table2 = getDecisionTable(decision2);
289
+ if (!table2) {
290
+ table2 = moddle.create("dmn:DecisionTable", { id: `${decision2.id}_table` });
291
+ decision2.decisionLogic = table2;
292
+ }
293
+ applyModelToTable(moddle, decision2, table2, model);
294
+ const { xml: xml2 } = await moddle.toXML(rootElement, { format: true });
295
+ return xml2;
296
+ }
297
+ const table = moddle.create("dmn:DecisionTable", {
298
+ id: `${model.id}_table`
299
+ });
300
+ const decision = moddle.create("dmn:Decision", {
301
+ id: model.id,
302
+ name: model.name,
303
+ decisionLogic: table
304
+ });
305
+ applyModelToTable(moddle, decision, table, model);
306
+ const definitions = moddle.create("dmn:Definitions", {
307
+ id: `Definitions_${model.id}`,
308
+ name: model.name,
309
+ namespace: "http://camunda.org/schema/1.0/dmn",
310
+ drgElement: [decision]
311
+ });
312
+ const { xml } = await moddle.toXML(definitions, { format: true });
313
+ return xml;
314
+ }
315
+ function emptyRow(model) {
316
+ const cells = {};
317
+ for (const col of model.columns) cells[col.id] = "";
318
+ return { id: makeId("rule"), cells };
319
+ }
320
+ function addRow(model, at) {
321
+ const row = emptyRow(model);
322
+ const rows = [...model.rows];
323
+ rows.splice(at ?? rows.length, 0, row);
324
+ return { ...model, rows };
325
+ }
326
+ function deleteRow(model, rowId) {
327
+ return { ...model, rows: model.rows.filter((r) => r.id !== rowId) };
328
+ }
329
+ function moveRow(model, rowId, direction) {
330
+ const idx = model.rows.findIndex((r) => r.id === rowId);
331
+ if (idx < 0) return model;
332
+ const target = idx + direction;
333
+ if (target < 0 || target >= model.rows.length) return model;
334
+ const rows = [...model.rows];
335
+ const [moved] = rows.splice(idx, 1);
336
+ rows.splice(target, 0, moved);
337
+ return { ...model, rows };
338
+ }
339
+ function updateCell(model, rowId, colId, value) {
340
+ return {
341
+ ...model,
342
+ rows: model.rows.map(
343
+ (r) => r.id === rowId ? { ...r, cells: { ...r.cells, [colId]: value } } : r
344
+ )
345
+ };
346
+ }
347
+ function updateAnnotation(model, rowId, annotation) {
348
+ return {
349
+ ...model,
350
+ rows: model.rows.map(
351
+ (r) => r.id === rowId ? { ...r, annotation } : r
352
+ )
353
+ };
354
+ }
355
+ function setHitPolicy(model, hitPolicy, aggregation) {
356
+ return {
357
+ ...model,
358
+ hitPolicy,
359
+ ...hitPolicy === "COLLECT" && aggregation ? { aggregation } : { aggregation: void 0 }
360
+ };
361
+ }
362
+ function addColumn(model, kind, partial) {
363
+ const id = makeId(kind);
364
+ const col = {
365
+ id,
366
+ label: partial?.label ?? (kind === "input" ? "Neuer Input" : "Neuer Output"),
367
+ expression: partial?.expression ?? "",
368
+ kind,
369
+ typeRef: partial?.typeRef ?? "string",
370
+ ...partial?.inputValues ? { inputValues: partial.inputValues } : {}
371
+ };
372
+ const columns = [...model.columns];
373
+ if (kind === "input") {
374
+ const lastInput = columns.map((c) => c.kind).lastIndexOf("input");
375
+ columns.splice(lastInput + 1, 0, col);
376
+ } else {
377
+ columns.push(col);
378
+ }
379
+ const rows = model.rows.map((r) => ({
380
+ ...r,
381
+ cells: { ...r.cells, [id]: "" }
382
+ }));
383
+ return { ...model, columns, rows };
384
+ }
385
+ function updateColumn(model, colId, patch) {
386
+ return {
387
+ ...model,
388
+ columns: model.columns.map(
389
+ (c) => c.id === colId ? { ...c, ...patch } : c
390
+ )
391
+ };
392
+ }
393
+ function deleteColumn(model, colId) {
394
+ const columns = model.columns.filter((c) => c.id !== colId);
395
+ const rows = model.rows.map((r) => {
396
+ const { [colId]: _removed, ...cells } = r.cells;
397
+ return { ...r, cells };
398
+ });
399
+ return { ...model, columns, rows };
400
+ }
401
+
402
+ // src/model/feel-linter.ts
403
+ var EMPTY_RE = /^(-|)$/;
404
+ var STRING_RE = /^"[^"]*"$/;
405
+ var STRING_LIST_RE = /^"[^"]*"(\s*,\s*"[^"]*")+$/;
406
+ var NUMBER_RE = /^-?\d+(\.\d+)?$/;
407
+ var NUMBER_RANGE_RE = /^[([]\s*-?\d+(\.\d+)?\s*\.\.\s*-?\d+(\.\d+)?\s*[)\]]$/;
408
+ var COMPARISON_RE = /^(>=|<=|>|<)\s*-?\d+(\.\d+)?$/;
409
+ var BOOLEAN_RE = /^(true|false)$/i;
410
+ var NOT_RE = /^not\s*\(.+\)$/;
411
+ var CONTAINS_RE = /^contains\s*\(.+\)$/;
412
+ var FUNCTION_RE = /^[a-zA-Z_][a-zA-Z0-9_]*\s*\(.*\)$/;
413
+ function parseInputValues(inputValues) {
414
+ const matches = inputValues.matchAll(/"([^"]*)"/g);
415
+ return Array.from(matches, (m) => m[1] ?? "");
416
+ }
417
+ function lintFeel(expr, column) {
418
+ const trimmed = expr.trim();
419
+ const allowed = column?.inputValues && column.inputValues.length > 0 ? column.inputValues.flatMap(parseInputValues) : [];
420
+ if (EMPTY_RE.test(trimmed)) {
421
+ return { valid: true, type: "any" };
422
+ }
423
+ if (STRING_LIST_RE.test(trimmed)) {
424
+ if (allowed.length > 0) {
425
+ const vals = trimmed.split(",").map((v) => v.trim().replace(/^"|"$/g, ""));
426
+ const invalid = vals.filter((v) => !allowed.includes(v));
427
+ if (invalid.length > 0) {
428
+ return {
429
+ valid: false,
430
+ message: `Nicht erlaubt: ${invalid.join(", ")}`,
431
+ type: "string list"
432
+ };
433
+ }
434
+ }
435
+ return { valid: true, type: "string list" };
436
+ }
437
+ if (STRING_RE.test(trimmed)) {
438
+ if (allowed.length > 0) {
439
+ const val = trimmed.replace(/^"|"$/g, "");
440
+ if (!allowed.includes(val)) {
441
+ return {
442
+ valid: false,
443
+ message: `"${val}" ist nicht in den erlaubten Werten`,
444
+ type: "string"
445
+ };
446
+ }
447
+ }
448
+ return { valid: true, type: "string" };
449
+ }
450
+ if (NUMBER_RANGE_RE.test(trimmed)) {
451
+ return { valid: true, type: "number range" };
452
+ }
453
+ if (COMPARISON_RE.test(trimmed)) {
454
+ return { valid: true, type: "number range" };
455
+ }
456
+ if (NUMBER_RE.test(trimmed)) {
457
+ return { valid: true, type: "number" };
458
+ }
459
+ if (BOOLEAN_RE.test(trimmed)) {
460
+ return { valid: true, type: "boolean" };
461
+ }
462
+ if (NOT_RE.test(trimmed) || CONTAINS_RE.test(trimmed) || FUNCTION_RE.test(trimmed)) {
463
+ return { valid: true, type: "expression" };
464
+ }
465
+ return { valid: false, message: `Unbekannter FEEL-Ausdruck: "${trimmed}"` };
466
+ }
467
+ var HIT_POLICIES = [
468
+ "UNIQUE",
469
+ "FIRST",
470
+ "PRIORITY",
471
+ "ANY",
472
+ "COLLECT",
473
+ "RULE ORDER",
474
+ "OUTPUT ORDER"
475
+ ];
476
+ var AGGREGATIONS = ["SUM", "MIN", "MAX", "COUNT"];
477
+ var TYPE_REFS = ["string", "number", "boolean", "date"];
478
+ var FEEL_SNIPPETS = [
479
+ 'not("X")',
480
+ "[1..100]",
481
+ ">= 0",
482
+ '"Ja","Nein"',
483
+ 'date("2026-01-01")'
484
+ ];
485
+ function DmnTableEditor({
486
+ value,
487
+ defaultValue,
488
+ onChange,
489
+ onSave,
490
+ decisionId,
491
+ title,
492
+ subtitle,
493
+ onSwitchToExpert,
494
+ cellPlugins = [],
495
+ actionsSlot,
496
+ readOnly = false,
497
+ className
498
+ }) {
499
+ const isControlled = value !== void 0;
500
+ const sourceXml = isControlled ? value : defaultValue;
501
+ const [model, setModel] = useState(null);
502
+ const [error, setError] = useState(null);
503
+ const [saving, setSaving] = useState(false);
504
+ const [selectedRowId, setSelectedRowId] = useState(null);
505
+ const [editing, setEditing] = useState(null);
506
+ const [feel, setFeel] = useState(null);
507
+ const [columnDialog, setColumnDialog] = useState(null);
508
+ const [addColumnDialog, setAddColumnDialog] = useState(
509
+ null
510
+ );
511
+ const originalXmlRef = useRef(sourceXml);
512
+ useEffect(() => {
513
+ let cancelled = false;
514
+ if (!sourceXml) {
515
+ setModel(null);
516
+ setError(null);
517
+ return;
518
+ }
519
+ originalXmlRef.current = sourceXml;
520
+ parseDmnModel(sourceXml, decisionId ? { decisionId } : {}).then((m) => {
521
+ if (!cancelled) {
522
+ setModel(m);
523
+ setError(null);
524
+ }
525
+ }).catch((e) => {
526
+ if (!cancelled) {
527
+ setError(e.message);
528
+ setModel(null);
529
+ }
530
+ });
531
+ return () => {
532
+ cancelled = true;
533
+ };
534
+ }, [sourceXml, decisionId]);
535
+ const commitModel = useCallback(
536
+ (next) => {
537
+ setModel(next);
538
+ if (onChange) {
539
+ void serializeDmnModel(next, originalXmlRef.current).then((xml) => {
540
+ originalXmlRef.current = xml;
541
+ onChange(xml);
542
+ });
543
+ }
544
+ },
545
+ [onChange]
546
+ );
547
+ const lintMap = useMemo(() => {
548
+ const map = {};
549
+ if (!model) return map;
550
+ for (const row of model.rows) {
551
+ for (const col of model.columns) {
552
+ map[`${row.id}:${col.id}`] = lintFeel(row.cells[col.id] ?? "", col);
553
+ }
554
+ }
555
+ return map;
556
+ }, [model]);
557
+ const errorCount = useMemo(
558
+ () => Object.values(lintMap).filter((r) => !r.valid).length,
559
+ [lintMap]
560
+ );
561
+ const handleSave = useCallback(async () => {
562
+ if (!model || !onSave || readOnly) return;
563
+ setSaving(true);
564
+ try {
565
+ const xml = await serializeDmnModel(model, originalXmlRef.current);
566
+ originalXmlRef.current = xml;
567
+ await onSave(xml);
568
+ } catch (e) {
569
+ setError(e.message);
570
+ } finally {
571
+ setSaving(false);
572
+ }
573
+ }, [model, onSave, readOnly]);
574
+ useEffect(() => {
575
+ const handler = (e) => {
576
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "s") {
577
+ e.preventDefault();
578
+ void handleSave();
579
+ }
580
+ };
581
+ window.addEventListener("keydown", handler);
582
+ return () => window.removeEventListener("keydown", handler);
583
+ }, [handleSave]);
584
+ const setCell = useCallback(
585
+ (rowId, colId, val) => {
586
+ if (!model) return;
587
+ commitModel(updateCell(model, rowId, colId, val));
588
+ },
589
+ [model, commitModel]
590
+ );
591
+ const findPlugin = useCallback(
592
+ (col) => cellPlugins.find((p) => p.matches(col)),
593
+ [cellPlugins]
594
+ );
595
+ const columns = useMemo(() => {
596
+ if (!model) return [];
597
+ const idxCol = {
598
+ id: "__index",
599
+ header: "#",
600
+ width: "44px",
601
+ align: "end",
602
+ sortable: false,
603
+ cellRender: ({ index }) => /* @__PURE__ */ jsx("span", { className: "prn-dmn-index", children: index + 1 })
604
+ };
605
+ const dataCols = model.columns.map((col) => ({
606
+ id: col.id,
607
+ sortable: false,
608
+ header: /* @__PURE__ */ jsx(
609
+ ColumnHeader,
610
+ {
611
+ column: col,
612
+ readOnly,
613
+ onEdit: () => setColumnDialog(col.id),
614
+ onRename: (label) => commitModel(updateColumn(model, col.id, { label }))
615
+ }
616
+ ),
617
+ cellRender: ({ row }) => {
618
+ const cellVal = row.cells[col.id] ?? "";
619
+ const lint = lintMap[`${row.id}:${col.id}`];
620
+ const isEditing = editing?.rowId === row.id && editing?.colId === col.id;
621
+ const plugin = findPlugin(col);
622
+ if (isEditing && !readOnly) {
623
+ if (plugin) {
624
+ return plugin.renderEditor({
625
+ value: cellVal,
626
+ onChange: (next) => setCell(row.id, col.id, next),
627
+ onCommit: () => setEditing(null),
628
+ onCancel: () => setEditing(null),
629
+ column: col
630
+ });
631
+ }
632
+ return /* @__PURE__ */ jsx(
633
+ InlineInput,
634
+ {
635
+ value: cellVal,
636
+ onCommit: (v) => {
637
+ setCell(row.id, col.id, v);
638
+ setEditing(null);
639
+ },
640
+ onCancel: () => setEditing(null)
641
+ }
642
+ );
643
+ }
644
+ return /* @__PURE__ */ jsxs(
645
+ "div",
646
+ {
647
+ className: "prn-dmn-cell",
648
+ "data-invalid": lint && !lint.valid ? "true" : void 0,
649
+ title: lint && !lint.valid ? lint.message : void 0,
650
+ onClick: () => {
651
+ if (!readOnly) setEditing({ rowId: row.id, colId: col.id });
652
+ },
653
+ onDoubleClick: () => {
654
+ if (!readOnly)
655
+ setFeel({
656
+ rowId: row.id,
657
+ colId: col.id,
658
+ column: col,
659
+ value: cellVal
660
+ });
661
+ },
662
+ children: [
663
+ cellVal ? /* @__PURE__ */ jsx("span", { children: cellVal }) : /* @__PURE__ */ jsx("span", { className: "prn-dmn-cell__empty", children: "\u2013" }),
664
+ lint && !lint.valid && /* @__PURE__ */ jsx("span", { className: "prn-dmn-cell__warn", children: "\u26A0" })
665
+ ]
666
+ }
667
+ );
668
+ }
669
+ }));
670
+ const annotationCol = {
671
+ id: "__annotation",
672
+ header: "Annotation",
673
+ sortable: false,
674
+ minWidth: 140,
675
+ cellRender: ({ row }) => {
676
+ const isEditing = editing?.rowId === row.id && editing?.colId === "__annotation";
677
+ if (isEditing && !readOnly) {
678
+ return /* @__PURE__ */ jsx(
679
+ InlineInput,
680
+ {
681
+ value: row.annotation ?? "",
682
+ onCommit: (v) => {
683
+ if (model) commitModel(updateAnnotation(model, row.id, v));
684
+ setEditing(null);
685
+ },
686
+ onCancel: () => setEditing(null)
687
+ }
688
+ );
689
+ }
690
+ return /* @__PURE__ */ jsx(
691
+ "div",
692
+ {
693
+ className: "prn-dmn-cell",
694
+ onClick: () => {
695
+ if (!readOnly)
696
+ setEditing({ rowId: row.id, colId: "__annotation" });
697
+ },
698
+ children: row.annotation ? /* @__PURE__ */ jsx("span", { children: row.annotation }) : /* @__PURE__ */ jsx("span", { className: "prn-dmn-cell__empty", children: "\u2013" })
699
+ }
700
+ );
701
+ }
702
+ };
703
+ return [idxCol, ...dataCols, annotationCol];
704
+ }, [model, lintMap, editing, readOnly, findPlugin, setCell, commitModel]);
705
+ const rowsView = useMemo(
706
+ () => model ? model.rows.map((row, index) => ({ row, index })) : [],
707
+ [model]
708
+ );
709
+ if (error) {
710
+ return /* @__PURE__ */ jsx("div", { className: cls("prn-dmn-table", className), children: /* @__PURE__ */ jsxs("div", { className: "prn-dmn-lint", "data-tone": "negative", children: [
711
+ "Fehler: ",
712
+ error
713
+ ] }) });
714
+ }
715
+ if (!model) {
716
+ return /* @__PURE__ */ jsx("div", { className: cls("prn-dmn-table", className), children: /* @__PURE__ */ jsx("div", { className: "prn-dmn-lint", children: "Kein DMN-Dokument geladen." }) });
717
+ }
718
+ return /* @__PURE__ */ jsxs("div", { className: cls("prn-dmn-table", className), "data-prn-dmn-table": true, children: [
719
+ /* @__PURE__ */ jsxs("div", { className: "prn-dmn-toolbar", children: [
720
+ /* @__PURE__ */ jsxs("div", { className: "prn-dmn-title", children: [
721
+ title && /* @__PURE__ */ jsx("span", { className: "prn-dmn-title__name", children: title }),
722
+ subtitle && /* @__PURE__ */ jsx("span", { className: "prn-dmn-title__meta", children: subtitle }),
723
+ !title && /* @__PURE__ */ jsx(
724
+ InlineEditableText,
725
+ {
726
+ className: "prn-dmn-title__name",
727
+ value: model.name || model.id,
728
+ readOnly,
729
+ ariaLabel: "Decision-Name bearbeiten",
730
+ onCommit: (v) => commitModel({ ...model, name: v })
731
+ }
732
+ )
733
+ ] }),
734
+ /* @__PURE__ */ jsxs("div", { className: "prn-dmn-toolbar-group", children: [
735
+ /* @__PURE__ */ jsx(
736
+ Select,
737
+ {
738
+ "aria-label": "Hit-Policy",
739
+ selectedKey: model.hitPolicy,
740
+ isDisabled: readOnly,
741
+ onSelectionChange: (key) => commitModel(
742
+ setHitPolicy(model, key, model.aggregation)
743
+ ),
744
+ children: HIT_POLICIES.map((p) => /* @__PURE__ */ jsx(SelectItem, { id: p, textValue: p, children: p }, p))
745
+ }
746
+ ),
747
+ model.hitPolicy === "COLLECT" && /* @__PURE__ */ jsx(
748
+ Select,
749
+ {
750
+ "aria-label": "Aggregation",
751
+ selectedKey: model.aggregation ?? "SUM",
752
+ isDisabled: readOnly,
753
+ onSelectionChange: (key) => commitModel(
754
+ setHitPolicy(model, "COLLECT", key)
755
+ ),
756
+ children: AGGREGATIONS.map((a) => /* @__PURE__ */ jsx(SelectItem, { id: a, textValue: a, children: a }, a))
757
+ }
758
+ )
759
+ ] }),
760
+ /* @__PURE__ */ jsx("span", { className: "prn-dmn-spacer" }),
761
+ /* @__PURE__ */ jsxs("div", { className: "prn-dmn-toolbar-group", children: [
762
+ actionsSlot,
763
+ onSwitchToExpert && /* @__PURE__ */ jsx(Button, { variant: "plain", onPress: onSwitchToExpert, children: "Experte" }),
764
+ onSave && /* @__PURE__ */ jsx(
765
+ Button,
766
+ {
767
+ variant: "filled",
768
+ isDisabled: saving || readOnly,
769
+ onPress: () => void handleSave(),
770
+ children: saving ? "Speichern\u2026" : "Speichern"
771
+ }
772
+ )
773
+ ] })
774
+ ] }),
775
+ /* @__PURE__ */ jsx(
776
+ "div",
777
+ {
778
+ className: "prn-dmn-lint",
779
+ "data-tone": errorCount === 0 ? "positive" : "negative",
780
+ role: "status",
781
+ children: errorCount === 0 ? `\u2713 Alle ${model.rows.length} Regeln valide` : `\u26A0 ${errorCount} ung\xFCltige Zelle(n)`
782
+ }
783
+ ),
784
+ !readOnly && /* @__PURE__ */ jsxs("div", { className: "prn-dmn-toolbar prn-dmn-toolbar--sub", children: [
785
+ /* @__PURE__ */ jsxs("span", { className: "prn-dmn-title__meta", children: [
786
+ "Regeln (",
787
+ model.rows.length,
788
+ ")"
789
+ ] }),
790
+ /* @__PURE__ */ jsx("span", { className: "prn-dmn-spacer" }),
791
+ /* @__PURE__ */ jsxs("div", { className: "prn-dmn-toolbar-group", children: [
792
+ /* @__PURE__ */ jsx(Button, { variant: "plain", onPress: () => setAddColumnDialog("input"), children: "+ Input" }),
793
+ /* @__PURE__ */ jsx(Button, { variant: "plain", onPress: () => setAddColumnDialog("output"), children: "+ Output" }),
794
+ /* @__PURE__ */ jsx(Button, { variant: "tinted", onPress: () => commitModel(addRow(model)), children: "+ Zeile" }),
795
+ /* @__PURE__ */ jsx(
796
+ Button,
797
+ {
798
+ variant: "plain",
799
+ isDisabled: !selectedRowId,
800
+ onPress: () => {
801
+ if (selectedRowId) {
802
+ commitModel(moveRow(model, selectedRowId, -1));
803
+ }
804
+ },
805
+ children: "\u2191"
806
+ }
807
+ ),
808
+ /* @__PURE__ */ jsx(
809
+ Button,
810
+ {
811
+ variant: "plain",
812
+ isDisabled: !selectedRowId,
813
+ onPress: () => {
814
+ if (selectedRowId) {
815
+ commitModel(moveRow(model, selectedRowId, 1));
816
+ }
817
+ },
818
+ children: "\u2193"
819
+ }
820
+ ),
821
+ /* @__PURE__ */ jsx(
822
+ Button,
823
+ {
824
+ variant: "plain",
825
+ isDisabled: !selectedRowId,
826
+ onPress: () => {
827
+ if (selectedRowId) {
828
+ commitModel(deleteRow(model, selectedRowId));
829
+ setSelectedRowId(null);
830
+ }
831
+ },
832
+ children: "Zeile l\xF6schen"
833
+ }
834
+ )
835
+ ] })
836
+ ] }),
837
+ /* @__PURE__ */ jsx("div", { className: "prn-dmn-table__body", children: /* @__PURE__ */ jsx(
838
+ AnalyticalTable,
839
+ {
840
+ data: rowsView,
841
+ columns,
842
+ getRowId: (r) => r.row.id,
843
+ selectionMode: readOnly ? "none" : "single",
844
+ selectionBehavior: "rowSelector",
845
+ selectedKeys: new Set(selectedRowId ? [selectedRowId] : []),
846
+ onSelectionChange: (keys) => {
847
+ const arr = Array.from(keys);
848
+ setSelectedRowId(arr[0] ?? null);
849
+ }
850
+ }
851
+ ) }),
852
+ feel && /* @__PURE__ */ jsx(
853
+ Modal,
854
+ {
855
+ isOpen: true,
856
+ title: `FEEL \u2014 ${feel.column.label || feel.column.expression}`,
857
+ onOpenChange: (open) => {
858
+ if (!open) setFeel(null);
859
+ },
860
+ children: /* @__PURE__ */ jsx(
861
+ FeelEditor,
862
+ {
863
+ initial: feel.value,
864
+ column: feel.column,
865
+ onCancel: () => setFeel(null),
866
+ onApply: (v) => {
867
+ setCell(feel.rowId, feel.colId, v);
868
+ setFeel(null);
869
+ }
870
+ }
871
+ )
872
+ }
873
+ ),
874
+ columnDialog && /* @__PURE__ */ jsx(
875
+ ColumnDialog,
876
+ {
877
+ column: model.columns.find((c) => c.id === columnDialog),
878
+ onClose: () => setColumnDialog(null),
879
+ onApply: (patch) => {
880
+ commitModel(updateColumn(model, columnDialog, patch));
881
+ setColumnDialog(null);
882
+ },
883
+ onDelete: () => {
884
+ commitModel(deleteColumn(model, columnDialog));
885
+ setColumnDialog(null);
886
+ }
887
+ }
888
+ ),
889
+ addColumnDialog && /* @__PURE__ */ jsx(
890
+ ColumnDialog,
891
+ {
892
+ kind: addColumnDialog,
893
+ onClose: () => setAddColumnDialog(null),
894
+ onApply: (patch) => {
895
+ commitModel(addColumn(model, addColumnDialog, patch));
896
+ setAddColumnDialog(null);
897
+ }
898
+ }
899
+ )
900
+ ] });
901
+ }
902
+ function ColumnHeader({
903
+ column,
904
+ readOnly,
905
+ onEdit,
906
+ onRename
907
+ }) {
908
+ return /* @__PURE__ */ jsxs("div", { className: "prn-dmn-colhead", "data-kind": column.kind, children: [
909
+ /* @__PURE__ */ jsxs("div", { className: "prn-dmn-colhead__top", children: [
910
+ /* @__PURE__ */ jsxs("span", { className: "prn-dmn-colhead__kind", children: [
911
+ column.kind === "input" ? "Input" : "Output",
912
+ " \xB7 ",
913
+ column.typeRef
914
+ ] }),
915
+ !readOnly && /* @__PURE__ */ jsx(
916
+ Button,
917
+ {
918
+ className: "prn-dmn-colhead__edit",
919
+ variant: "plain",
920
+ onPress: onEdit,
921
+ "aria-label": "Spalte bearbeiten",
922
+ children: "\u2699"
923
+ }
924
+ )
925
+ ] }),
926
+ /* @__PURE__ */ jsx(
927
+ InlineEditableText,
928
+ {
929
+ className: "prn-dmn-colhead__label",
930
+ value: column.label || column.expression,
931
+ readOnly,
932
+ ariaLabel: "Spaltentitel bearbeiten",
933
+ onCommit: onRename
934
+ }
935
+ ),
936
+ column.label && column.expression && column.label !== column.expression && /* @__PURE__ */ jsx("span", { className: "prn-dmn-colhead__expr", children: column.expression })
937
+ ] });
938
+ }
939
+ function InlineInput({
940
+ value,
941
+ onCommit,
942
+ onCancel
943
+ }) {
944
+ const [v, setV] = useState(value);
945
+ const ref = useRef(null);
946
+ const committed = useRef(false);
947
+ useEffect(() => {
948
+ ref.current?.focus();
949
+ ref.current?.select();
950
+ }, []);
951
+ const commit = () => {
952
+ if (committed.current) return;
953
+ committed.current = true;
954
+ onCommit(v);
955
+ };
956
+ return /* @__PURE__ */ jsx(
957
+ "input",
958
+ {
959
+ ref,
960
+ className: "prn-dmn-cell__input",
961
+ value: v,
962
+ onChange: (e) => setV(e.target.value),
963
+ onBlur: commit,
964
+ onClick: (e) => e.stopPropagation(),
965
+ onKeyDown: (e) => {
966
+ if (e.key === "Enter" || e.key === "Tab") {
967
+ e.preventDefault();
968
+ commit();
969
+ } else if (e.key === "Escape") {
970
+ e.preventDefault();
971
+ committed.current = true;
972
+ onCancel();
973
+ }
974
+ }
975
+ }
976
+ );
977
+ }
978
+ function FeelEditor({
979
+ initial,
980
+ column,
981
+ onApply,
982
+ onCancel
983
+ }) {
984
+ const [text, setText] = useState(initial);
985
+ const lint = lintFeel(text, column);
986
+ return /* @__PURE__ */ jsxs("div", { className: "prn-dmn-feel", children: [
987
+ /* @__PURE__ */ jsx(
988
+ "textarea",
989
+ {
990
+ autoFocus: true,
991
+ className: "prn-dmn-feel__editor",
992
+ "data-valid": lint.valid ? "true" : "false",
993
+ value: text,
994
+ onChange: (e) => setText(e.target.value),
995
+ onKeyDown: (e) => {
996
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && lint.valid) {
997
+ onApply(text);
998
+ }
999
+ if (e.key === "Escape") onCancel();
1000
+ }
1001
+ }
1002
+ ),
1003
+ /* @__PURE__ */ jsxs("div", { className: "prn-dmn-feel__status", "data-valid": lint.valid ? "true" : "false", children: [
1004
+ /* @__PURE__ */ jsx("span", { children: lint.valid ? "\u2713 Valide" : "\u2717 Ung\xFCltig" }),
1005
+ lint.message && /* @__PURE__ */ jsx("span", { children: lint.message }),
1006
+ lint.type && /* @__PURE__ */ jsx("span", { className: "prn-dmn-feel__type", children: lint.type })
1007
+ ] }),
1008
+ /* @__PURE__ */ jsx("div", { className: "prn-dmn-feel__snippets", children: FEEL_SNIPPETS.map((s) => /* @__PURE__ */ jsx(Button, { variant: "plain", onPress: () => setText(s), children: s }, s)) }),
1009
+ column.inputValues && column.inputValues.length > 0 && /* @__PURE__ */ jsxs("div", { className: "prn-dmn-feel__hint", children: [
1010
+ "Erlaubte Werte:",
1011
+ " ",
1012
+ /* @__PURE__ */ jsx("code", { children: column.inputValues.map((v) => `"${v}"`).join(", ") })
1013
+ ] }),
1014
+ /* @__PURE__ */ jsxs("div", { className: "prn-dmn-feel__footer", children: [
1015
+ /* @__PURE__ */ jsx(Button, { variant: "plain", onPress: onCancel, children: "Abbrechen" }),
1016
+ /* @__PURE__ */ jsx(Button, { variant: "filled", isDisabled: !lint.valid, onPress: () => onApply(text), children: "\xDCbernehmen" })
1017
+ ] })
1018
+ ] });
1019
+ }
1020
+ function ColumnDialog({
1021
+ column,
1022
+ kind,
1023
+ onApply,
1024
+ onDelete,
1025
+ onClose
1026
+ }) {
1027
+ const [label, setLabel] = useState(column?.label ?? "");
1028
+ const [expression, setExpression] = useState(column?.expression ?? "");
1029
+ const [typeRef, setTypeRef] = useState(column?.typeRef ?? "string");
1030
+ const [inputValues, setInputValues] = useState(
1031
+ (column?.inputValues ?? []).join(", ")
1032
+ );
1033
+ const effectiveKind = column?.kind ?? kind ?? "input";
1034
+ return /* @__PURE__ */ jsx(
1035
+ Modal,
1036
+ {
1037
+ isOpen: true,
1038
+ title: column ? "Spalte bearbeiten" : `Neue ${effectiveKind === "input" ? "Input" : "Output"}-Spalte`,
1039
+ onOpenChange: (open) => {
1040
+ if (!open) onClose();
1041
+ },
1042
+ children: /* @__PURE__ */ jsxs("div", { className: "prn-dmn-feel prn-dmn-feel--dialog", children: [
1043
+ /* @__PURE__ */ jsx(TextField, { label: "Label", value: label, onChange: setLabel }),
1044
+ /* @__PURE__ */ jsx(
1045
+ TextField,
1046
+ {
1047
+ label: effectiveKind === "input" ? "FEEL-Expression" : "Output-Name",
1048
+ value: expression,
1049
+ onChange: setExpression
1050
+ }
1051
+ ),
1052
+ /* @__PURE__ */ jsx(
1053
+ Select,
1054
+ {
1055
+ label: "Typ",
1056
+ selectedKey: typeRef,
1057
+ onSelectionChange: (k) => setTypeRef(String(k)),
1058
+ children: TYPE_REFS.map((t) => /* @__PURE__ */ jsx(SelectItem, { id: t, textValue: t, children: t }, t))
1059
+ }
1060
+ ),
1061
+ /* @__PURE__ */ jsx(
1062
+ TextField,
1063
+ {
1064
+ label: "Erlaubte Werte (kommagetrennt)",
1065
+ value: inputValues,
1066
+ onChange: setInputValues,
1067
+ description: "z. B. Ja, Nein"
1068
+ }
1069
+ ),
1070
+ /* @__PURE__ */ jsxs("div", { className: "prn-dmn-feel__footer", children: [
1071
+ onDelete && /* @__PURE__ */ jsx(Button, { variant: "plain", onPress: onDelete, children: "L\xF6schen" }),
1072
+ /* @__PURE__ */ jsx(Button, { variant: "plain", onPress: onClose, children: "Abbrechen" }),
1073
+ /* @__PURE__ */ jsx(
1074
+ Button,
1075
+ {
1076
+ variant: "filled",
1077
+ onPress: () => onApply({
1078
+ label,
1079
+ expression,
1080
+ typeRef,
1081
+ inputValues: inputValues.split(",").map((v) => v.trim()).filter(Boolean)
1082
+ }),
1083
+ children: column ? "Speichern" : "Anlegen"
1084
+ }
1085
+ )
1086
+ ] })
1087
+ ] })
1088
+ }
1089
+ );
1090
+ }
1091
+ function InlineEditableText({
1092
+ value,
1093
+ onCommit,
1094
+ readOnly,
1095
+ className,
1096
+ ariaLabel
1097
+ }) {
1098
+ const [editing, setEditing] = useState(false);
1099
+ const [draft, setDraft] = useState(value);
1100
+ const ref = useRef(null);
1101
+ useEffect(() => {
1102
+ if (editing) {
1103
+ ref.current?.focus();
1104
+ ref.current?.select();
1105
+ }
1106
+ }, [editing]);
1107
+ if (readOnly) return /* @__PURE__ */ jsx("span", { className, children: value });
1108
+ if (editing) {
1109
+ return /* @__PURE__ */ jsx(
1110
+ "input",
1111
+ {
1112
+ ref,
1113
+ className: cls(className, "prn-dmn-inline-edit"),
1114
+ value: draft,
1115
+ "aria-label": ariaLabel,
1116
+ onChange: (e) => setDraft(e.target.value),
1117
+ onClick: (e) => e.stopPropagation(),
1118
+ onBlur: () => {
1119
+ setEditing(false);
1120
+ if (draft !== value) onCommit(draft);
1121
+ },
1122
+ onKeyDown: (e) => {
1123
+ if (e.key === "Enter") {
1124
+ e.preventDefault();
1125
+ e.target.blur();
1126
+ } else if (e.key === "Escape") {
1127
+ e.preventDefault();
1128
+ setDraft(value);
1129
+ setEditing(false);
1130
+ }
1131
+ }
1132
+ }
1133
+ );
1134
+ }
1135
+ return /* @__PURE__ */ jsx(
1136
+ "button",
1137
+ {
1138
+ type: "button",
1139
+ className: cls(className, "prn-dmn-inline-trigger"),
1140
+ title: "Zum Bearbeiten klicken",
1141
+ onClick: (e) => {
1142
+ e.stopPropagation();
1143
+ setDraft(value);
1144
+ setEditing(true);
1145
+ },
1146
+ children: value
1147
+ }
1148
+ );
1149
+ }
1150
+ function cls(...parts) {
1151
+ return parts.filter(Boolean).join(" ");
1152
+ }
1153
+ function DmnExpertEditor({
1154
+ value,
1155
+ defaultValue,
1156
+ onChange,
1157
+ onSave,
1158
+ colorScheme = "auto",
1159
+ actionsSlot,
1160
+ onSwitchToTable,
1161
+ propertiesPanel = true,
1162
+ className
1163
+ }) {
1164
+ const canvasRef = useRef(null);
1165
+ const panelRef = useRef(null);
1166
+ const modelerRef = useRef(null);
1167
+ const [loading, setLoading] = useState(true);
1168
+ const [error, setError] = useState(null);
1169
+ const [saving, setSaving] = useState(false);
1170
+ const [hasPanel, setHasPanel] = useState(false);
1171
+ const [themeTick, setThemeTick] = useState(0);
1172
+ const xml = value ?? defaultValue ?? "";
1173
+ useEffect(() => onThemeChange(() => setThemeTick((t) => t + 1)), []);
1174
+ useEffect(() => {
1175
+ let cancelled = false;
1176
+ setLoading(true);
1177
+ setError(null);
1178
+ const boot = async () => {
1179
+ const container = canvasRef.current;
1180
+ if (!container) return;
1181
+ if (modelerRef.current) {
1182
+ try {
1183
+ modelerRef.current.destroy();
1184
+ } catch {
1185
+ }
1186
+ modelerRef.current = null;
1187
+ }
1188
+ try {
1189
+ const mod = await import('dmn-js/lib/Modeler');
1190
+ if (cancelled || !canvasRef.current) return;
1191
+ const colors = getDiagramColors(colorScheme);
1192
+ const rendererColors = {
1193
+ defaultFillColor: colors.defaultFillColor,
1194
+ defaultStrokeColor: colors.defaultStrokeColor,
1195
+ defaultLabelColor: colors.defaultLabelColor
1196
+ };
1197
+ const opts = {
1198
+ container,
1199
+ drd: {
1200
+ drawCustom: false,
1201
+ // dmn-js-DRD injiziert seine Renderer-Config aus `config.drdRenderer`
1202
+ // (DrdRenderer.$inject = ['config.drdRenderer', …]). Der frühere Key
1203
+ // `bpmnRenderer` wurde ignoriert → Shapes blieben hartcodiert weiß/
1204
+ // schwarz. Beide Keys setzen wir zur Sicherheit.
1205
+ drdRenderer: rendererColors,
1206
+ bpmnRenderer: rendererColors
1207
+ }
1208
+ };
1209
+ let panelLoaded = false;
1210
+ if (propertiesPanel && panelRef.current) {
1211
+ const panelMods = await loadPropertiesPanel();
1212
+ if (panelMods && !cancelled) {
1213
+ opts.drd = {
1214
+ ...opts.drd,
1215
+ propertiesPanel: { parent: panelRef.current },
1216
+ additionalModules: panelMods.modules
1217
+ };
1218
+ opts.moddleExtensions = panelMods.moddleExtensions;
1219
+ panelLoaded = true;
1220
+ }
1221
+ }
1222
+ const Modeler = mod.default;
1223
+ const modeler = new Modeler(opts);
1224
+ modelerRef.current = modeler;
1225
+ if (!cancelled) setHasPanel(panelLoaded);
1226
+ if (xml.trim()) {
1227
+ await modeler.importXML(xml);
1228
+ }
1229
+ if (onChange) {
1230
+ modeler.on("views.changed", () => emitChange());
1231
+ modeler.on("commandStack.changed", () => emitChange());
1232
+ }
1233
+ } catch (e) {
1234
+ if (!cancelled) setError(e.message);
1235
+ } finally {
1236
+ if (!cancelled) setLoading(false);
1237
+ }
1238
+ };
1239
+ const emitChange = () => {
1240
+ const m = modelerRef.current;
1241
+ if (!m || !onChange) return;
1242
+ m.saveXML({ format: true }).then(({ xml: out }) => onChange(out)).catch(() => {
1243
+ });
1244
+ };
1245
+ void boot();
1246
+ return () => {
1247
+ cancelled = true;
1248
+ };
1249
+ }, [colorScheme, propertiesPanel, themeTick]);
1250
+ useEffect(() => {
1251
+ return () => {
1252
+ if (modelerRef.current) {
1253
+ try {
1254
+ modelerRef.current.destroy();
1255
+ } catch {
1256
+ }
1257
+ modelerRef.current = null;
1258
+ }
1259
+ };
1260
+ }, []);
1261
+ const handleSave = useCallback(async () => {
1262
+ const m = modelerRef.current;
1263
+ if (!m || !onSave) return;
1264
+ setSaving(true);
1265
+ try {
1266
+ const { xml: out } = await m.saveXML({ format: true });
1267
+ await onSave(out);
1268
+ } catch (e) {
1269
+ setError(e.message);
1270
+ } finally {
1271
+ setSaving(false);
1272
+ }
1273
+ }, [onSave]);
1274
+ return /* @__PURE__ */ jsxs("div", { className: cls2("prn-dmn-expert", className), "data-prn-dmn-expert": true, children: [
1275
+ /* @__PURE__ */ jsxs("div", { className: "prn-dmn-toolbar", children: [
1276
+ /* @__PURE__ */ jsx("span", { className: "prn-dmn-title__name", children: "DMN \u2014 Experten-Modus" }),
1277
+ /* @__PURE__ */ jsx("span", { className: "prn-dmn-spacer" }),
1278
+ /* @__PURE__ */ jsxs("div", { className: "prn-dmn-toolbar-group", children: [
1279
+ actionsSlot,
1280
+ onSwitchToTable && /* @__PURE__ */ jsx(Button, { variant: "plain", onPress: onSwitchToTable, children: "Tabelle" }),
1281
+ onSave && /* @__PURE__ */ jsx(
1282
+ Button,
1283
+ {
1284
+ variant: "filled",
1285
+ isDisabled: saving || loading,
1286
+ onPress: () => void handleSave(),
1287
+ children: saving ? "Speichern\u2026" : "Speichern"
1288
+ }
1289
+ )
1290
+ ] })
1291
+ ] }),
1292
+ error && /* @__PURE__ */ jsxs("div", { className: "prn-dmn-lint", "data-tone": "negative", role: "alert", children: [
1293
+ "Fehler: ",
1294
+ error
1295
+ ] }),
1296
+ /* @__PURE__ */ jsxs("div", { className: "prn-dmn-expert__stage", children: [
1297
+ /* @__PURE__ */ jsx("div", { ref: canvasRef, className: "prn-dmn-expert__canvas" }),
1298
+ /* @__PURE__ */ jsx(
1299
+ "div",
1300
+ {
1301
+ ref: panelRef,
1302
+ className: "prn-dmn-expert__panel",
1303
+ "data-visible": hasPanel ? "true" : "false"
1304
+ }
1305
+ ),
1306
+ loading && /* @__PURE__ */ jsx("div", { className: "prn-dmn-expert__loading", role: "status", children: "dmn-js wird geladen\u2026" })
1307
+ ] })
1308
+ ] });
1309
+ }
1310
+ var dynamicImport = (spec) => import(
1311
+ /* @vite-ignore */
1312
+ /* webpackIgnore: true */
1313
+ spec
1314
+ );
1315
+ async function loadPropertiesPanel() {
1316
+ try {
1317
+ const [panel, provider, moddle] = await Promise.all([
1318
+ dynamicImport("@bpmn-io/properties-panel"),
1319
+ dynamicImport("dmn-js-properties-panel"),
1320
+ dynamicImport("camunda-dmn-moddle/resources/camunda.json").catch(
1321
+ () => null
1322
+ )
1323
+ ]);
1324
+ const pnl = panel;
1325
+ const prov = provider;
1326
+ const modules = [
1327
+ pnl.PropertiesPanelModule ?? panel,
1328
+ prov.DmnPropertiesPanelModule ?? prov.default,
1329
+ prov.CamundaPropertiesProviderModule
1330
+ ].filter(Boolean);
1331
+ return {
1332
+ modules,
1333
+ moddleExtensions: moddle ? { camunda: moddle.default ?? moddle } : {}
1334
+ };
1335
+ } catch {
1336
+ return null;
1337
+ }
1338
+ }
1339
+ function cls2(...parts) {
1340
+ return parts.filter(Boolean).join(" ");
1341
+ }
1342
+ function DmnEditor({
1343
+ value,
1344
+ defaultValue,
1345
+ onChange,
1346
+ onSave,
1347
+ defaultMode = "table",
1348
+ mode: controlledMode,
1349
+ onModeChange,
1350
+ colorScheme,
1351
+ title,
1352
+ subtitle,
1353
+ cellPlugins,
1354
+ actionsSlot,
1355
+ decisionId,
1356
+ className
1357
+ }) {
1358
+ const [internalMode, setInternalMode] = useState(defaultMode);
1359
+ const mode = controlledMode ?? internalMode;
1360
+ const [bufferXml, setBufferXml] = useState(
1361
+ value ?? defaultValue
1362
+ );
1363
+ const isControlled = value !== void 0;
1364
+ const currentXml = isControlled ? value : bufferXml;
1365
+ const setMode = (next) => {
1366
+ if (controlledMode === void 0) setInternalMode(next);
1367
+ onModeChange?.(next);
1368
+ };
1369
+ const handleChange = (xml) => {
1370
+ if (!isControlled) setBufferXml(xml);
1371
+ onChange?.(xml);
1372
+ };
1373
+ const switcher = /* @__PURE__ */ jsxs(
1374
+ SegmentedControl,
1375
+ {
1376
+ "aria-label": "Editor-Modus",
1377
+ selectedKeys: /* @__PURE__ */ new Set([mode]),
1378
+ onSelectionChange: (keys) => {
1379
+ const next = Array.from(keys)[0];
1380
+ if (next) setMode(next);
1381
+ },
1382
+ children: [
1383
+ /* @__PURE__ */ jsx(Segment, { id: "table", children: "Tabelle" }),
1384
+ /* @__PURE__ */ jsx(Segment, { id: "expert", children: "Experte" })
1385
+ ]
1386
+ }
1387
+ );
1388
+ return /* @__PURE__ */ jsx("div", { className: cls3("prn-dmn-editor", className), "data-prn-dmn-editor": true, children: mode === "table" ? /* @__PURE__ */ jsx(
1389
+ DmnTableEditor,
1390
+ {
1391
+ value: currentXml,
1392
+ onChange: handleChange,
1393
+ onSave,
1394
+ decisionId,
1395
+ title,
1396
+ subtitle,
1397
+ cellPlugins,
1398
+ actionsSlot: /* @__PURE__ */ jsxs(Fragment, { children: [
1399
+ switcher,
1400
+ actionsSlot
1401
+ ] })
1402
+ }
1403
+ ) : /* @__PURE__ */ jsx(
1404
+ DmnExpertEditor,
1405
+ {
1406
+ value: currentXml,
1407
+ onChange: handleChange,
1408
+ onSave,
1409
+ colorScheme,
1410
+ actionsSlot: /* @__PURE__ */ jsxs(Fragment, { children: [
1411
+ switcher,
1412
+ actionsSlot
1413
+ ] })
1414
+ }
1415
+ ) });
1416
+ }
1417
+ function cls3(...parts) {
1418
+ return parts.filter(Boolean).join(" ");
1419
+ }
1420
+
1421
+ export { DmnEditor, DmnExpertEditor, DmnTableEditor, addColumn, addRow, deleteColumn, deleteRow, emptyRow, getDiagramColors, isDarkMode, lintFeel, listDecisions, makeId, moveRow, onThemeChange, parseDmnModel, parseInputValues, readToken, serializeDmnModel, setHitPolicy, updateAnnotation, updateCell, updateColumn };
1422
+ //# sourceMappingURL=index.js.map
1423
+ //# sourceMappingURL=index.js.map