@omega-flow/editor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3061 @@
1
+ // src/components/WorkflowEditor.tsx
2
+ import { ReactFlowProvider } from "@xyflow/react";
3
+
4
+ // src/context/WorkflowEditorContext.tsx
5
+ import {
6
+ createContext as createContext2,
7
+ useContext as useContext2,
8
+ useReducer,
9
+ useCallback as useCallback2,
10
+ useMemo as useMemo2,
11
+ useEffect as useEffect4
12
+ } from "react";
13
+ import {
14
+ applyNodeChanges,
15
+ applyEdgeChanges
16
+ } from "@xyflow/react";
17
+
18
+ // src/nodes/views/BaseNodeView.tsx
19
+ import { Handle, Position } from "@xyflow/react";
20
+ import { jsx, jsxs } from "react/jsx-runtime";
21
+ var baseStyle = {
22
+ padding: "var(--of-spacing-4, 10px) 15px",
23
+ borderRadius: "var(--of-radius-lg, 8px)",
24
+ border: "2px solid",
25
+ backgroundColor: "var(--of-node-bg, #fff)",
26
+ minWidth: "150px",
27
+ fontSize: "var(--of-font-size-sm, 12px)",
28
+ fontFamily: "var(--of-font-family-base, system-ui, sans-serif)"
29
+ };
30
+ var headerStyle = {
31
+ display: "flex",
32
+ alignItems: "center",
33
+ gap: "var(--of-spacing-2, 6px)",
34
+ fontWeight: "var(--of-font-weight-semibold, 600)",
35
+ marginBottom: "var(--of-spacing-1, 4px)",
36
+ color: "var(--of-color-text-primary, #111827)"
37
+ };
38
+ var contentStyle = {
39
+ color: "var(--of-node-content-color, #666)",
40
+ fontSize: "var(--of-font-size-xs, 11px)"
41
+ };
42
+ var handleStyle = {
43
+ width: "10px",
44
+ height: "10px",
45
+ borderRadius: "50%"
46
+ };
47
+ function BaseNodeView({
48
+ label,
49
+ color = "#666",
50
+ icon,
51
+ sourceHandles = [],
52
+ targetHandles = [],
53
+ selected,
54
+ children
55
+ }) {
56
+ return /* @__PURE__ */ jsxs(
57
+ "div",
58
+ {
59
+ style: {
60
+ ...baseStyle,
61
+ borderColor: color,
62
+ boxShadow: selected ? `0 0 0 2px ${color}40` : "var(--of-node-shadow, 0 2px 4px rgba(0,0,0,0.1))"
63
+ },
64
+ children: [
65
+ targetHandles.map((handle, index) => /* @__PURE__ */ jsx(
66
+ Handle,
67
+ {
68
+ type: "target",
69
+ position: Position.Top,
70
+ id: handle.id,
71
+ style: {
72
+ ...handleStyle,
73
+ backgroundColor: color,
74
+ left: `${(index + 1) / (targetHandles.length + 1) * 100}%`
75
+ }
76
+ },
77
+ handle.id
78
+ )),
79
+ /* @__PURE__ */ jsxs("div", { style: headerStyle, children: [
80
+ icon && /* @__PURE__ */ jsx("span", { style: { color }, children: icon }),
81
+ /* @__PURE__ */ jsx("span", { children: label })
82
+ ] }),
83
+ children && /* @__PURE__ */ jsx("div", { style: contentStyle, children }),
84
+ sourceHandles.map((handle, index) => /* @__PURE__ */ jsx(
85
+ Handle,
86
+ {
87
+ type: "source",
88
+ position: Position.Bottom,
89
+ id: handle.id,
90
+ style: {
91
+ ...handleStyle,
92
+ backgroundColor: color,
93
+ left: `${(index + 1) / (sourceHandles.length + 1) * 100}%`
94
+ }
95
+ },
96
+ handle.id
97
+ ))
98
+ ]
99
+ }
100
+ );
101
+ }
102
+
103
+ // src/i18n/TranslationContext.tsx
104
+ import { createContext, useContext, useMemo } from "react";
105
+
106
+ // src/i18n/defaults.ts
107
+ var defaultTranslations = {
108
+ // ── Control Panel ──────────────────────────────────────────
109
+ "panels.control.title": "Workflow",
110
+ "panels.control.nameLabel": "Name",
111
+ "panels.control.namePlaceholder": "Workflow name",
112
+ "panels.control.saving": "Saving...",
113
+ "panels.control.savedSuccessfully": "Saved successfully",
114
+ "panels.control.failedToSave": "Failed to save",
115
+ "panels.control.unsavedChanges": "Unsaved changes",
116
+ // ── Detail Panel ───────────────────────────────────────────
117
+ "panels.detail.title": "Properties",
118
+ "panels.detail.emptyMessage": "Select a node to edit its properties",
119
+ "panels.detail.delete": "Delete",
120
+ "panels.detail.deleteTitle": "Delete node",
121
+ // ── Nodes Panel ────────────────────────────────────────────
122
+ "panels.nodes.title": "Nodes",
123
+ // ── Options Panel ──────────────────────────────────────────
124
+ "panels.options.title": "Options",
125
+ "panels.options.frequencyGroup": "Frequency",
126
+ "panels.options.frequencyTypeLabel": "Type",
127
+ "panels.options.frequencyTypeHint": "How often this workflow can run for a subject",
128
+ "panels.options.frequencyOneTime": "One time",
129
+ "panels.options.frequencyEveryRematch": "Every rematch",
130
+ "panels.options.intervalLabel": "Interval",
131
+ "panels.options.intervalHint": "Minimum time between runs",
132
+ // ── Node Views (canvas) ────────────────────────────────────
133
+ "nodes.trigger.label": "Trigger",
134
+ "nodes.trigger.noEvent": "No event set",
135
+ "nodes.action.label": "Action",
136
+ "nodes.action.noAction": "No action set",
137
+ "nodes.condition.label": "Condition",
138
+ "nodes.condition.noRules": "No rules",
139
+ "nodes.condition.ruleCount": "{{count}} rules",
140
+ "nodes.condition.ruleCountSingular": "1 rule",
141
+ "nodes.condition.handleTrue": "True",
142
+ "nodes.condition.handleFalse": "False",
143
+ "nodes.wait.label": "Wait",
144
+ "nodes.wait.noDuration": "No duration set",
145
+ "nodes.triggerOrTimeout.label": "Trigger or Timeout",
146
+ "nodes.triggerOrTimeout.notConfigured": "Not configured",
147
+ "nodes.triggerOrTimeout.eventFallback": "event",
148
+ "nodes.triggerOrTimeout.orDuration": "or {{duration}}",
149
+ "nodes.triggerOrTimeout.handleTrigger": "Trigger",
150
+ "nodes.triggerOrTimeout.handleTimeout": "Timeout",
151
+ "nodes.exit.label": "Exit",
152
+ "nodes.exit.endWorkflow": "End workflow",
153
+ // ── Node Details (property editors) ────────────────────────
154
+ "nodeDetails.trigger.group": "Trigger Configuration",
155
+ "nodeDetails.trigger.eventLabel": "Event Type",
156
+ "nodeDetails.trigger.eventPlaceholder": "e.g., user.signup, order.placed",
157
+ "nodeDetails.trigger.eventHint": "The event type that will start this workflow",
158
+ "nodeDetails.action.group": "Action Configuration",
159
+ "nodeDetails.action.nameLabel": "Action Name",
160
+ "nodeDetails.action.namePlaceholder": "e.g., sendEmail, createTask",
161
+ "nodeDetails.action.nameHint": "The action to perform when this node is reached",
162
+ "nodeDetails.action.paramsLabel": "Parameters",
163
+ "nodeDetails.action.paramsHint": "JSON object with action parameters",
164
+ "nodeDetails.condition.group": "Condition Configuration",
165
+ "nodeDetails.condition.conditionsLabel": "Conditions",
166
+ "nodeDetails.condition.conditionsHint": "JSON rules engine format. Use 'all' or 'any' arrays with conditions like: { fact: 'amount', operator: 'greaterThan', value: 100 }",
167
+ "nodeDetails.wait.group": "Wait Configuration",
168
+ "nodeDetails.wait.durationLabel": "Duration",
169
+ "nodeDetails.wait.durationHint": "How long to pause before continuing to the next node",
170
+ "nodeDetails.triggerOrTimeout.group": "Trigger or Timeout Configuration",
171
+ "nodeDetails.triggerOrTimeout.eventLabel": "Event Type",
172
+ "nodeDetails.triggerOrTimeout.eventPlaceholder": "e.g., payment.received",
173
+ "nodeDetails.triggerOrTimeout.eventHint": "The event type to wait for",
174
+ "nodeDetails.triggerOrTimeout.durationLabel": "Timeout Duration",
175
+ "nodeDetails.triggerOrTimeout.durationHint": "Max time to wait before timing out",
176
+ "nodeDetails.exit.group": "Exit Node",
177
+ "nodeDetails.exit.message": "This node ends the workflow. No configuration needed.",
178
+ // ── Default node type definitions (labels & descriptions) ──
179
+ "nodeTypes.trigger.label": "Trigger",
180
+ "nodeTypes.trigger.description": "Starts the workflow when a specific event occurs",
181
+ "nodeTypes.action.label": "Action",
182
+ "nodeTypes.action.description": "Performs an action and continues to the next node",
183
+ "nodeTypes.condition.label": "Condition",
184
+ "nodeTypes.condition.description": "Branches the workflow based on conditions",
185
+ "nodeTypes.wait.label": "Wait",
186
+ "nodeTypes.wait.description": "Pauses the workflow for a specified duration",
187
+ "nodeTypes.triggerOrTimeout.label": "Trigger or Timeout",
188
+ "nodeTypes.triggerOrTimeout.description": "Waits for an event or times out after a duration",
189
+ "nodeTypes.exit.label": "Exit",
190
+ "nodeTypes.exit.description": "Ends the workflow",
191
+ // ── Primitives / fields ────────────────────────────────────
192
+ "fields.json.invalidJson": "Invalid JSON",
193
+ "fields.duration.days": "d",
194
+ "fields.duration.hours": "h",
195
+ "fields.duration.minutes": "m",
196
+ // ── Condition Builder ────────────────────────────────────
197
+ "conditionBuilder.dialogTitle": "Condition Builder",
198
+ "conditionBuilder.topLevelLabel": "Match ANY of the following groups:",
199
+ "conditionBuilder.matchLabel": "Match",
200
+ "conditionBuilder.addGroup": "Add group",
201
+ "conditionBuilder.addCondition": "Add condition",
202
+ "conditionBuilder.removeGroup": "Remove",
203
+ "conditionBuilder.removeCondition": "Remove condition",
204
+ "conditionBuilder.selectProperty": "Select property...",
205
+ "conditionBuilder.selectOperator": "Select operator...",
206
+ "conditionBuilder.factPlaceholder": "fact name",
207
+ "conditionBuilder.valuePlaceholder": "value",
208
+ "conditionBuilder.emptyGroup": "No conditions yet. Add one below.",
209
+ "conditionBuilder.noConditions": "No conditions configured",
210
+ "conditionBuilder.ruleCountSingular": "1 condition",
211
+ "conditionBuilder.ruleCount": "{{count}} conditions",
212
+ "conditionBuilder.editButton": "Edit",
213
+ "conditionBuilder.apply": "Apply",
214
+ "conditionBuilder.cancel": "Cancel"
215
+ };
216
+
217
+ // src/i18n/TranslationContext.tsx
218
+ import { jsx as jsx2 } from "react/jsx-runtime";
219
+ function createTranslationFunction(dictionary) {
220
+ return (key, params) => {
221
+ const template = dictionary[key] ?? key;
222
+ if (!params) return template;
223
+ return template.replace(
224
+ /\{\{(\w+)\}\}/g,
225
+ (_, k) => params[k] ?? `{{${k}}}`
226
+ );
227
+ };
228
+ }
229
+ var defaultT = createTranslationFunction(defaultTranslations);
230
+ var TranslationContext = createContext(defaultT);
231
+ function TranslationProvider({
232
+ children,
233
+ translationFn,
234
+ translations
235
+ }) {
236
+ const t = useMemo(() => {
237
+ if (translationFn) return translationFn;
238
+ if (translations) {
239
+ return createTranslationFunction({
240
+ ...defaultTranslations,
241
+ ...translations
242
+ });
243
+ }
244
+ return defaultT;
245
+ }, [translationFn, translations]);
246
+ return /* @__PURE__ */ jsx2(TranslationContext.Provider, { value: t, children });
247
+ }
248
+ function useTranslation() {
249
+ return useContext(TranslationContext);
250
+ }
251
+
252
+ // src/nodes/views/TriggerNodeView.tsx
253
+ import { jsx as jsx3 } from "react/jsx-runtime";
254
+ var TRIGGER_COLOR = "var(--of-node-trigger-color, #4CAF50)";
255
+ function TriggerNodeView({ id, data, selected }) {
256
+ const t = useTranslation();
257
+ const nodeData = data;
258
+ const params = nodeData.params;
259
+ const eventName = params?.event;
260
+ return /* @__PURE__ */ jsx3(
261
+ BaseNodeView,
262
+ {
263
+ id,
264
+ data: nodeData,
265
+ selected,
266
+ label: t("nodes.trigger.label"),
267
+ color: TRIGGER_COLOR,
268
+ icon: "\u25B6",
269
+ sourceHandles: [{ id: "output" }],
270
+ targetHandles: [],
271
+ children: eventName ? eventName : /* @__PURE__ */ jsx3("em", { children: t("nodes.trigger.noEvent") })
272
+ }
273
+ );
274
+ }
275
+
276
+ // src/nodes/views/ActionNodeView.tsx
277
+ import { jsx as jsx4 } from "react/jsx-runtime";
278
+ var ACTION_COLOR = "var(--of-node-action-color, #2196F3)";
279
+ function ActionNodeView({ id, data, selected }) {
280
+ const t = useTranslation();
281
+ const nodeData = data;
282
+ const actionName = nodeData.action;
283
+ return /* @__PURE__ */ jsx4(
284
+ BaseNodeView,
285
+ {
286
+ id,
287
+ data: nodeData,
288
+ selected,
289
+ label: t("nodes.action.label"),
290
+ color: ACTION_COLOR,
291
+ icon: "\u26A1",
292
+ sourceHandles: [{ id: "output" }],
293
+ targetHandles: [{ id: "input" }],
294
+ children: actionName ? actionName : /* @__PURE__ */ jsx4("em", { children: t("nodes.action.noAction") })
295
+ }
296
+ );
297
+ }
298
+
299
+ // src/nodes/views/ConditionNodeView.tsx
300
+ import { jsx as jsx5 } from "react/jsx-runtime";
301
+ var CONDITION_COLOR = "var(--of-node-condition-color, #FF9800)";
302
+ function ConditionNodeView({ id, data, selected }) {
303
+ const t = useTranslation();
304
+ const nodeData = data;
305
+ const conditions = nodeData.conditions;
306
+ const ruleCount = (conditions?.all?.length ?? 0) + (conditions?.any?.length ?? 0);
307
+ const ruleLabel = ruleCount === 0 ? null : ruleCount === 1 ? t("nodes.condition.ruleCountSingular") : t("nodes.condition.ruleCount", { count: String(ruleCount) });
308
+ return /* @__PURE__ */ jsx5(
309
+ BaseNodeView,
310
+ {
311
+ id,
312
+ data: nodeData,
313
+ selected,
314
+ label: t("nodes.condition.label"),
315
+ color: CONDITION_COLOR,
316
+ icon: "?",
317
+ sourceHandles: [
318
+ { id: "true", label: t("nodes.condition.handleTrue") },
319
+ { id: "false", label: t("nodes.condition.handleFalse") }
320
+ ],
321
+ targetHandles: [{ id: "input" }],
322
+ children: ruleLabel ?? /* @__PURE__ */ jsx5("em", { children: t("nodes.condition.noRules") })
323
+ }
324
+ );
325
+ }
326
+
327
+ // src/nodes/views/ExitNodeView.tsx
328
+ import { jsx as jsx6 } from "react/jsx-runtime";
329
+ var EXIT_COLOR = "var(--of-node-exit-color, #F44336)";
330
+ function ExitNodeView({ id, data, selected }) {
331
+ const t = useTranslation();
332
+ return /* @__PURE__ */ jsx6(
333
+ BaseNodeView,
334
+ {
335
+ id,
336
+ data,
337
+ selected,
338
+ label: t("nodes.exit.label"),
339
+ color: EXIT_COLOR,
340
+ icon: "\u23F9",
341
+ sourceHandles: [],
342
+ targetHandles: [{ id: "input" }],
343
+ children: t("nodes.exit.endWorkflow")
344
+ }
345
+ );
346
+ }
347
+
348
+ // src/utils/duration.ts
349
+ var MS_PER_MINUTE = 6e4;
350
+ var MS_PER_HOUR = 36e5;
351
+ var MS_PER_DAY = 864e5;
352
+ function msToParts(ms) {
353
+ const days = Math.floor(ms / MS_PER_DAY);
354
+ const remainingAfterDays = ms % MS_PER_DAY;
355
+ const hours = Math.floor(remainingAfterDays / MS_PER_HOUR);
356
+ const remainingAfterHours = remainingAfterDays % MS_PER_HOUR;
357
+ const minutes = Math.floor(remainingAfterHours / MS_PER_MINUTE);
358
+ return { days, hours, minutes };
359
+ }
360
+ function partsToMs(parts) {
361
+ return parts.days * MS_PER_DAY + parts.hours * MS_PER_HOUR + parts.minutes * MS_PER_MINUTE;
362
+ }
363
+ function formatDuration(ms) {
364
+ if (ms === 0) return "0m";
365
+ const { days, hours, minutes } = msToParts(ms);
366
+ const parts = [];
367
+ if (days > 0) parts.push(`${days}d`);
368
+ if (hours > 0) parts.push(`${hours}h`);
369
+ if (minutes > 0) parts.push(`${minutes}m`);
370
+ return parts.length > 0 ? parts.join(" ") : "0m";
371
+ }
372
+
373
+ // src/nodes/views/WaitNodeView.tsx
374
+ import { jsx as jsx7 } from "react/jsx-runtime";
375
+ var WAIT_COLOR = "var(--of-node-wait-color, #9C27B0)";
376
+ function WaitNodeView({ id, data, selected }) {
377
+ const t = useTranslation();
378
+ const nodeData = data;
379
+ const params = nodeData.params;
380
+ const duration = params?.duration;
381
+ return /* @__PURE__ */ jsx7(
382
+ BaseNodeView,
383
+ {
384
+ id,
385
+ data: nodeData,
386
+ selected,
387
+ label: t("nodes.wait.label"),
388
+ color: WAIT_COLOR,
389
+ icon: "\u23F1",
390
+ sourceHandles: [{ id: "output" }],
391
+ targetHandles: [{ id: "input" }],
392
+ children: duration != null ? formatDuration(duration) : /* @__PURE__ */ jsx7("em", { children: t("nodes.wait.noDuration") })
393
+ }
394
+ );
395
+ }
396
+
397
+ // src/nodes/views/TriggerOrTimeoutNodeView.tsx
398
+ import { jsx as jsx8 } from "react/jsx-runtime";
399
+ var TRIGGER_OR_TIMEOUT_COLOR = "var(--of-node-trigger-timeout-color, #607D8B)";
400
+ function TriggerOrTimeoutNodeView({ id, data, selected }) {
401
+ const t = useTranslation();
402
+ const nodeData = data;
403
+ const params = nodeData.params;
404
+ const eventName = params?.event;
405
+ const duration = params?.duration;
406
+ const description = [
407
+ eventName || t("nodes.triggerOrTimeout.eventFallback"),
408
+ duration != null ? t("nodes.triggerOrTimeout.orDuration", { duration: formatDuration(duration) }) : ""
409
+ ].filter(Boolean).join(" ");
410
+ return /* @__PURE__ */ jsx8(
411
+ BaseNodeView,
412
+ {
413
+ id,
414
+ data: nodeData,
415
+ selected,
416
+ label: t("nodes.triggerOrTimeout.label"),
417
+ color: TRIGGER_OR_TIMEOUT_COLOR,
418
+ icon: "\u23F0",
419
+ sourceHandles: [
420
+ { id: "trigger", label: t("nodes.triggerOrTimeout.handleTrigger") },
421
+ { id: "timeout", label: t("nodes.triggerOrTimeout.handleTimeout") }
422
+ ],
423
+ targetHandles: [{ id: "input" }],
424
+ children: description || /* @__PURE__ */ jsx8("em", { children: t("nodes.triggerOrTimeout.notConfigured") })
425
+ }
426
+ );
427
+ }
428
+
429
+ // src/primitives/Field.tsx
430
+ import { jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
431
+ var fieldStyle = {
432
+ display: "flex",
433
+ flexDirection: "column",
434
+ gap: "var(--of-spacing-1, 4px)",
435
+ marginBottom: "var(--of-spacing-5, 12px)"
436
+ };
437
+ var labelStyle = {
438
+ fontSize: "var(--of-field-label-size, 12px)",
439
+ fontWeight: "var(--of-font-weight-medium, 500)",
440
+ color: "var(--of-field-label-color, #374151)"
441
+ };
442
+ var errorStyle = {
443
+ fontSize: "var(--of-font-size-xs, 11px)",
444
+ color: "var(--of-field-error-color, #DC2626)"
445
+ };
446
+ var hintStyle = {
447
+ fontSize: "var(--of-font-size-xs, 11px)",
448
+ color: "var(--of-field-hint-color, #6B7280)"
449
+ };
450
+ function Field({ label, children, error, hint }) {
451
+ return /* @__PURE__ */ jsxs2("div", { style: fieldStyle, children: [
452
+ /* @__PURE__ */ jsx9("label", { style: labelStyle, children: label }),
453
+ children,
454
+ error && /* @__PURE__ */ jsx9("span", { style: errorStyle, children: error }),
455
+ hint && !error && /* @__PURE__ */ jsx9("span", { style: hintStyle, children: hint })
456
+ ] });
457
+ }
458
+
459
+ // src/primitives/TextField.tsx
460
+ import { jsx as jsx10 } from "react/jsx-runtime";
461
+ var inputStyle = {
462
+ padding: "var(--of-field-padding, 8px 10px)",
463
+ borderRadius: "var(--of-field-radius, 6px)",
464
+ border: "1px solid var(--of-field-border, #D1D5DB)",
465
+ fontSize: "var(--of-field-font-size, 13px)",
466
+ outline: "none",
467
+ transition: "border-color var(--of-transition-fast, 0.15s)",
468
+ color: "var(--of-color-text-primary, #111827)"
469
+ };
470
+ function TextField({
471
+ label,
472
+ value,
473
+ onChange,
474
+ placeholder,
475
+ disabled,
476
+ error,
477
+ hint
478
+ }) {
479
+ return /* @__PURE__ */ jsx10(Field, { label, error, hint, children: /* @__PURE__ */ jsx10(
480
+ "input",
481
+ {
482
+ type: "text",
483
+ value,
484
+ onChange: (e) => onChange(e.target.value),
485
+ placeholder,
486
+ disabled,
487
+ style: {
488
+ ...inputStyle,
489
+ borderColor: error ? "var(--of-field-border-error, #DC2626)" : "var(--of-field-border, #D1D5DB)",
490
+ backgroundColor: disabled ? "var(--of-color-bg-disabled, #F3F4F6)" : "var(--of-field-bg, #fff)"
491
+ },
492
+ onFocus: (e) => {
493
+ if (!error)
494
+ e.target.style.borderColor = "var(--of-field-border-focus, #3B82F6)";
495
+ },
496
+ onBlur: (e) => {
497
+ if (!error)
498
+ e.target.style.borderColor = "var(--of-field-border, #D1D5DB)";
499
+ }
500
+ }
501
+ ) });
502
+ }
503
+
504
+ // src/primitives/NumberField.tsx
505
+ import { jsx as jsx11 } from "react/jsx-runtime";
506
+ var inputStyle2 = {
507
+ padding: "var(--of-field-padding, 8px 10px)",
508
+ borderRadius: "var(--of-field-radius, 6px)",
509
+ border: "1px solid var(--of-field-border, #D1D5DB)",
510
+ fontSize: "var(--of-field-font-size, 13px)",
511
+ outline: "none",
512
+ transition: "border-color var(--of-transition-fast, 0.15s)",
513
+ color: "var(--of-color-text-primary, #111827)"
514
+ };
515
+ function NumberField({
516
+ label,
517
+ value,
518
+ onChange,
519
+ min,
520
+ max,
521
+ step,
522
+ disabled,
523
+ error,
524
+ hint
525
+ }) {
526
+ return /* @__PURE__ */ jsx11(Field, { label, error, hint, children: /* @__PURE__ */ jsx11(
527
+ "input",
528
+ {
529
+ type: "number",
530
+ value,
531
+ onChange: (e) => onChange(parseFloat(e.target.value) || 0),
532
+ min,
533
+ max,
534
+ step,
535
+ disabled,
536
+ style: {
537
+ ...inputStyle2,
538
+ borderColor: error ? "var(--of-field-border-error, #DC2626)" : "var(--of-field-border, #D1D5DB)",
539
+ backgroundColor: disabled ? "var(--of-color-bg-disabled, #F3F4F6)" : "var(--of-field-bg, #fff)"
540
+ },
541
+ onFocus: (e) => {
542
+ if (!error)
543
+ e.target.style.borderColor = "var(--of-field-border-focus, #3B82F6)";
544
+ },
545
+ onBlur: (e) => {
546
+ if (!error)
547
+ e.target.style.borderColor = "var(--of-field-border, #D1D5DB)";
548
+ }
549
+ }
550
+ ) });
551
+ }
552
+
553
+ // src/primitives/SelectField.tsx
554
+ import { jsx as jsx12, jsxs as jsxs3 } from "react/jsx-runtime";
555
+ var selectStyle = {
556
+ padding: "var(--of-field-padding, 8px 10px)",
557
+ borderRadius: "var(--of-field-radius, 6px)",
558
+ border: "1px solid var(--of-field-border, #D1D5DB)",
559
+ fontSize: "var(--of-field-font-size, 13px)",
560
+ outline: "none",
561
+ transition: "border-color var(--of-transition-fast, 0.15s)",
562
+ backgroundColor: "var(--of-field-bg, #fff)",
563
+ color: "var(--of-color-text-primary, #111827)",
564
+ cursor: "pointer"
565
+ };
566
+ function SelectField({
567
+ label,
568
+ value,
569
+ options,
570
+ onChange,
571
+ placeholder,
572
+ disabled,
573
+ error,
574
+ hint
575
+ }) {
576
+ return /* @__PURE__ */ jsx12(Field, { label, error, hint, children: /* @__PURE__ */ jsxs3(
577
+ "select",
578
+ {
579
+ value,
580
+ onChange: (e) => onChange(e.target.value),
581
+ disabled,
582
+ style: {
583
+ ...selectStyle,
584
+ borderColor: error ? "var(--of-field-border-error, #DC2626)" : "var(--of-field-border, #D1D5DB)",
585
+ backgroundColor: disabled ? "var(--of-color-bg-disabled, #F3F4F6)" : "var(--of-field-bg, #fff)"
586
+ },
587
+ onFocus: (e) => {
588
+ if (!error)
589
+ e.target.style.borderColor = "var(--of-field-border-focus, #3B82F6)";
590
+ },
591
+ onBlur: (e) => {
592
+ if (!error)
593
+ e.target.style.borderColor = "var(--of-field-border, #D1D5DB)";
594
+ },
595
+ children: [
596
+ placeholder && /* @__PURE__ */ jsx12("option", { value: "", disabled: true, children: placeholder }),
597
+ options.map((option) => /* @__PURE__ */ jsx12("option", { value: option.value, children: option.label }, option.value))
598
+ ]
599
+ }
600
+ ) });
601
+ }
602
+
603
+ // src/primitives/CheckboxField.tsx
604
+ import { jsx as jsx13, jsxs as jsxs4 } from "react/jsx-runtime";
605
+ var containerStyle = {
606
+ display: "flex",
607
+ alignItems: "center",
608
+ gap: "var(--of-spacing-3, 8px)",
609
+ marginBottom: "var(--of-spacing-5, 12px)",
610
+ cursor: "pointer"
611
+ };
612
+ var checkboxStyle = {
613
+ width: "16px",
614
+ height: "16px",
615
+ cursor: "pointer"
616
+ };
617
+ var labelStyle2 = {
618
+ fontSize: "var(--of-field-font-size, 13px)",
619
+ color: "var(--of-color-text-secondary, #374151)",
620
+ cursor: "pointer"
621
+ };
622
+ function CheckboxField({
623
+ label,
624
+ checked,
625
+ onChange,
626
+ disabled
627
+ }) {
628
+ return /* @__PURE__ */ jsxs4(
629
+ "label",
630
+ {
631
+ style: {
632
+ ...containerStyle,
633
+ cursor: disabled ? "not-allowed" : "pointer",
634
+ opacity: disabled ? 0.6 : 1
635
+ },
636
+ children: [
637
+ /* @__PURE__ */ jsx13(
638
+ "input",
639
+ {
640
+ type: "checkbox",
641
+ checked,
642
+ onChange: (e) => onChange(e.target.checked),
643
+ disabled,
644
+ style: checkboxStyle
645
+ }
646
+ ),
647
+ /* @__PURE__ */ jsx13("span", { style: labelStyle2, children: label })
648
+ ]
649
+ }
650
+ );
651
+ }
652
+
653
+ // src/primitives/TextAreaField.tsx
654
+ import { jsx as jsx14 } from "react/jsx-runtime";
655
+ var textareaStyle = {
656
+ padding: "var(--of-field-padding, 8px 10px)",
657
+ borderRadius: "var(--of-field-radius, 6px)",
658
+ border: "1px solid var(--of-field-border, #D1D5DB)",
659
+ fontSize: "var(--of-field-font-size, 13px)",
660
+ color: "var(--of-color-text-primary, #111827)",
661
+ outline: "none",
662
+ transition: "border-color var(--of-transition-fast, 0.15s)",
663
+ resize: "vertical",
664
+ fontFamily: "inherit"
665
+ };
666
+ function TextAreaField({
667
+ label,
668
+ value,
669
+ onChange,
670
+ rows = 4,
671
+ placeholder,
672
+ disabled,
673
+ error,
674
+ hint
675
+ }) {
676
+ return /* @__PURE__ */ jsx14(Field, { label, error, hint, children: /* @__PURE__ */ jsx14(
677
+ "textarea",
678
+ {
679
+ value,
680
+ onChange: (e) => onChange(e.target.value),
681
+ rows,
682
+ placeholder,
683
+ disabled,
684
+ style: {
685
+ ...textareaStyle,
686
+ borderColor: error ? "var(--of-field-border-error, #DC2626)" : "var(--of-field-border, #D1D5DB)",
687
+ backgroundColor: disabled ? "var(--of-color-bg-disabled, #F3F4F6)" : "var(--of-field-bg, #fff)"
688
+ },
689
+ onFocus: (e) => {
690
+ if (!error)
691
+ e.target.style.borderColor = "var(--of-field-border-focus, #3B82F6)";
692
+ },
693
+ onBlur: (e) => {
694
+ if (!error)
695
+ e.target.style.borderColor = "var(--of-field-border, #D1D5DB)";
696
+ }
697
+ }
698
+ ) });
699
+ }
700
+
701
+ // src/primitives/DurationField.tsx
702
+ import { useState, useEffect } from "react";
703
+ import { jsx as jsx15, jsxs as jsxs5 } from "react/jsx-runtime";
704
+ var containerStyle2 = {
705
+ display: "flex",
706
+ gap: "var(--of-spacing-3, 8px)"
707
+ };
708
+ var unitGroupStyle = {
709
+ display: "flex",
710
+ alignItems: "center",
711
+ gap: "var(--of-spacing-2, 4px)"
712
+ };
713
+ var inputStyle3 = {
714
+ width: "60px",
715
+ padding: "var(--of-field-padding, 8px 10px)",
716
+ borderRadius: "var(--of-field-radius, 6px)",
717
+ border: "1px solid var(--of-field-border, #D1D5DB)",
718
+ fontSize: "var(--of-field-font-size, 13px)",
719
+ color: "var(--of-color-text-primary, #111827)",
720
+ outline: "none",
721
+ textAlign: "center"
722
+ };
723
+ var labelStyle3 = {
724
+ fontSize: "var(--of-field-font-size, 13px)",
725
+ color: "var(--of-color-text-secondary, #6B7280)"
726
+ };
727
+ function DurationField({
728
+ label,
729
+ value,
730
+ onChange,
731
+ disabled,
732
+ error,
733
+ hint
734
+ }) {
735
+ const t = useTranslation();
736
+ const initial = msToParts(value);
737
+ const [parts, setParts] = useState(initial);
738
+ useEffect(() => {
739
+ const converted = msToParts(value);
740
+ setParts(converted);
741
+ }, [value]);
742
+ const handlePartChange = (part, newValue) => {
743
+ const sanitizedValue = Math.max(0, Math.floor(newValue) || 0);
744
+ const newParts = { ...parts, [part]: sanitizedValue };
745
+ setParts(newParts);
746
+ onChange(partsToMs(newParts));
747
+ };
748
+ const getInputStyle = () => ({
749
+ ...inputStyle3,
750
+ borderColor: error ? "var(--of-field-border-error, #DC2626)" : "var(--of-field-border, #D1D5DB)",
751
+ backgroundColor: disabled ? "var(--of-color-bg-disabled, #F3F4F6)" : "var(--of-field-bg, #fff)"
752
+ });
753
+ return /* @__PURE__ */ jsx15(Field, { label, error, hint, children: /* @__PURE__ */ jsxs5("div", { style: containerStyle2, children: [
754
+ /* @__PURE__ */ jsxs5("div", { style: unitGroupStyle, children: [
755
+ /* @__PURE__ */ jsx15(
756
+ "input",
757
+ {
758
+ type: "number",
759
+ value: parts.days,
760
+ onChange: (e) => handlePartChange("days", parseFloat(e.target.value)),
761
+ min: 0,
762
+ disabled,
763
+ style: getInputStyle()
764
+ }
765
+ ),
766
+ /* @__PURE__ */ jsx15("span", { style: labelStyle3, children: t("fields.duration.days") })
767
+ ] }),
768
+ /* @__PURE__ */ jsxs5("div", { style: unitGroupStyle, children: [
769
+ /* @__PURE__ */ jsx15(
770
+ "input",
771
+ {
772
+ type: "number",
773
+ value: parts.hours,
774
+ onChange: (e) => handlePartChange("hours", parseFloat(e.target.value)),
775
+ min: 0,
776
+ max: 23,
777
+ disabled,
778
+ style: getInputStyle()
779
+ }
780
+ ),
781
+ /* @__PURE__ */ jsx15("span", { style: labelStyle3, children: t("fields.duration.hours") })
782
+ ] }),
783
+ /* @__PURE__ */ jsxs5("div", { style: unitGroupStyle, children: [
784
+ /* @__PURE__ */ jsx15(
785
+ "input",
786
+ {
787
+ type: "number",
788
+ value: parts.minutes,
789
+ onChange: (e) => handlePartChange("minutes", parseFloat(e.target.value)),
790
+ min: 0,
791
+ max: 59,
792
+ disabled,
793
+ style: getInputStyle()
794
+ }
795
+ ),
796
+ /* @__PURE__ */ jsx15("span", { style: labelStyle3, children: t("fields.duration.minutes") })
797
+ ] })
798
+ ] }) });
799
+ }
800
+
801
+ // src/primitives/JsonField.tsx
802
+ import { useState as useState2, useEffect as useEffect2 } from "react";
803
+ import { jsx as jsx16 } from "react/jsx-runtime";
804
+ var textareaStyle2 = {
805
+ padding: "var(--of-field-padding, 8px 10px)",
806
+ borderRadius: "var(--of-field-radius, 6px)",
807
+ border: "1px solid var(--of-field-border, #D1D5DB)",
808
+ fontSize: "var(--of-font-size-sm, 12px)",
809
+ fontFamily: "var(--of-font-family-mono, monospace)",
810
+ color: "var(--of-color-text-primary, #111827)",
811
+ outline: "none",
812
+ transition: "border-color var(--of-transition-fast, 0.15s)",
813
+ resize: "vertical"
814
+ };
815
+ function JsonField({
816
+ label,
817
+ value,
818
+ onChange,
819
+ rows = 6,
820
+ disabled,
821
+ error: externalError,
822
+ hint
823
+ }) {
824
+ const t = useTranslation();
825
+ const [text, setText] = useState2(() => JSON.stringify(value, null, 2));
826
+ const [parseError, setParseError] = useState2(null);
827
+ useEffect2(() => {
828
+ setText(JSON.stringify(value, null, 2));
829
+ setParseError(null);
830
+ }, [value]);
831
+ const handleChange = (newText) => {
832
+ setText(newText);
833
+ try {
834
+ const parsed = JSON.parse(newText);
835
+ setParseError(null);
836
+ onChange(parsed);
837
+ } catch (e) {
838
+ setParseError(t("fields.json.invalidJson"));
839
+ }
840
+ };
841
+ const error = externalError || parseError;
842
+ return /* @__PURE__ */ jsx16(Field, { label, error: error ?? void 0, hint, children: /* @__PURE__ */ jsx16(
843
+ "textarea",
844
+ {
845
+ value: text,
846
+ onChange: (e) => handleChange(e.target.value),
847
+ rows,
848
+ disabled,
849
+ style: {
850
+ ...textareaStyle2,
851
+ borderColor: error ? "var(--of-field-border-error, #DC2626)" : "var(--of-field-border, #D1D5DB)",
852
+ backgroundColor: disabled ? "var(--of-color-bg-disabled, #F3F4F6)" : "var(--of-field-bg, #fff)"
853
+ },
854
+ onFocus: (e) => {
855
+ if (!error)
856
+ e.target.style.borderColor = "var(--of-field-border-focus, #3B82F6)";
857
+ },
858
+ onBlur: (e) => {
859
+ if (!error)
860
+ e.target.style.borderColor = "var(--of-field-border, #D1D5DB)";
861
+ }
862
+ }
863
+ ) });
864
+ }
865
+
866
+ // src/primitives/FieldGroup.tsx
867
+ import { useState as useState3 } from "react";
868
+ import { jsx as jsx17, jsxs as jsxs6 } from "react/jsx-runtime";
869
+ var groupStyle = {
870
+ marginBottom: "var(--of-spacing-6, 16px)"
871
+ };
872
+ var headerStyle2 = {
873
+ display: "flex",
874
+ alignItems: "center",
875
+ gap: "var(--of-spacing-2, 6px)",
876
+ marginBottom: "var(--of-spacing-5, 12px)",
877
+ fontSize: "var(--of-field-font-size, 13px)",
878
+ fontWeight: "var(--of-font-weight-semibold, 600)",
879
+ color: "var(--of-color-text-primary, #111827)"
880
+ };
881
+ var toggleStyle = {
882
+ cursor: "pointer",
883
+ userSelect: "none",
884
+ display: "flex",
885
+ alignItems: "center",
886
+ gap: "var(--of-spacing-2, 6px)"
887
+ };
888
+ var contentStyle2 = {
889
+ paddingLeft: "var(--of-spacing-1, 4px)"
890
+ };
891
+ function FieldGroup({
892
+ label,
893
+ children,
894
+ collapsible = false,
895
+ defaultCollapsed = false
896
+ }) {
897
+ const [collapsed, setCollapsed] = useState3(defaultCollapsed);
898
+ if (!label) {
899
+ return /* @__PURE__ */ jsx17("div", { style: groupStyle, children });
900
+ }
901
+ if (collapsible) {
902
+ return /* @__PURE__ */ jsxs6("div", { style: groupStyle, children: [
903
+ /* @__PURE__ */ jsxs6(
904
+ "div",
905
+ {
906
+ style: { ...headerStyle2, ...toggleStyle },
907
+ onClick: () => setCollapsed(!collapsed),
908
+ children: [
909
+ /* @__PURE__ */ jsx17("span", { style: { transform: collapsed ? "rotate(-90deg)" : "rotate(0)", transition: "transform var(--of-transition-fast, 0.15s)" }, children: "\u25BC" }),
910
+ label
911
+ ]
912
+ }
913
+ ),
914
+ !collapsed && /* @__PURE__ */ jsx17("div", { style: contentStyle2, children })
915
+ ] });
916
+ }
917
+ return /* @__PURE__ */ jsxs6("div", { style: groupStyle, children: [
918
+ /* @__PURE__ */ jsx17("div", { style: headerStyle2, children: label }),
919
+ /* @__PURE__ */ jsx17("div", { style: contentStyle2, children })
920
+ ] });
921
+ }
922
+
923
+ // src/primitives/condition-builder/ConditionBuilder.tsx
924
+ import React5 from "react";
925
+
926
+ // src/primitives/condition-builder/types.ts
927
+ function isPropertyGroup(item) {
928
+ return "children" in item && Array.isArray(item.children);
929
+ }
930
+
931
+ // src/primitives/condition-builder/ConditionRow.tsx
932
+ import { jsx as jsx18, jsxs as jsxs7 } from "react/jsx-runtime";
933
+ var rowStyle = {
934
+ display: "flex",
935
+ alignItems: "center",
936
+ gap: "var(--of-spacing-2, 6px)",
937
+ marginBottom: "var(--of-spacing-2, 6px)"
938
+ };
939
+ var selectStyle2 = {
940
+ padding: "var(--of-spacing-2, 6px) var(--of-spacing-3, 8px)",
941
+ borderRadius: "var(--of-field-radius, 6px)",
942
+ border: "1px solid var(--of-field-border, #D1D5DB)",
943
+ fontSize: "var(--of-font-size-sm, 12px)",
944
+ outline: "none",
945
+ backgroundColor: "var(--of-field-bg, #fff)",
946
+ color: "var(--of-color-text-primary, #111827)",
947
+ minWidth: 0
948
+ };
949
+ var factSelectStyle = {
950
+ ...selectStyle2,
951
+ flex: "1 1 30%"
952
+ };
953
+ var operatorSelectStyle = {
954
+ ...selectStyle2,
955
+ flex: "1 1 30%"
956
+ };
957
+ var inputStyle4 = {
958
+ padding: "var(--of-spacing-2, 6px) var(--of-spacing-3, 8px)",
959
+ borderRadius: "var(--of-field-radius, 6px)",
960
+ border: "1px solid var(--of-field-border, #D1D5DB)",
961
+ fontSize: "var(--of-font-size-sm, 12px)",
962
+ outline: "none",
963
+ backgroundColor: "var(--of-field-bg, #fff)",
964
+ color: "var(--of-color-text-primary, #111827)",
965
+ flex: "1 1 25%",
966
+ minWidth: 0
967
+ };
968
+ var removeButtonStyle = {
969
+ padding: "var(--of-spacing-1, 4px) var(--of-spacing-2, 6px)",
970
+ border: "none",
971
+ background: "none",
972
+ color: "var(--of-color-text-muted, #9CA3AF)",
973
+ cursor: "pointer",
974
+ fontSize: "var(--of-font-size-lg, 14px)",
975
+ lineHeight: 1,
976
+ borderRadius: "var(--of-radius-sm, 4px)",
977
+ flexShrink: 0
978
+ };
979
+ function parseValue(raw) {
980
+ if (raw === "true") return true;
981
+ if (raw === "false") return false;
982
+ const num = Number(raw);
983
+ if (raw !== "" && !isNaN(num)) return num;
984
+ return raw;
985
+ }
986
+ function formatValue(value) {
987
+ if (value === null || value === void 0) return "";
988
+ return String(value);
989
+ }
990
+ function ConditionRow({
991
+ rule,
992
+ properties,
993
+ operators,
994
+ onChange,
995
+ onRemove
996
+ }) {
997
+ const t = useTranslation();
998
+ const hasProperties = properties.length > 0;
999
+ return /* @__PURE__ */ jsxs7("div", { style: rowStyle, children: [
1000
+ hasProperties ? /* @__PURE__ */ jsxs7(
1001
+ "select",
1002
+ {
1003
+ value: rule.fact,
1004
+ onChange: (e) => onChange({ ...rule, fact: e.target.value }),
1005
+ style: factSelectStyle,
1006
+ children: [
1007
+ /* @__PURE__ */ jsx18("option", { value: "", children: t("conditionBuilder.selectProperty") }),
1008
+ properties.map(
1009
+ (item, idx) => isPropertyGroup(item) ? /* @__PURE__ */ jsx18("optgroup", { label: item.label, children: item.children.map((child) => /* @__PURE__ */ jsx18("option", { value: child.value, children: child.label }, child.value)) }, idx) : /* @__PURE__ */ jsx18("option", { value: item.value, children: item.label }, item.value)
1010
+ )
1011
+ ]
1012
+ }
1013
+ ) : /* @__PURE__ */ jsx18(
1014
+ "input",
1015
+ {
1016
+ type: "text",
1017
+ value: rule.fact,
1018
+ onChange: (e) => onChange({ ...rule, fact: e.target.value }),
1019
+ placeholder: t("conditionBuilder.factPlaceholder"),
1020
+ style: { ...inputStyle4, flex: "1 1 30%" }
1021
+ }
1022
+ ),
1023
+ /* @__PURE__ */ jsxs7(
1024
+ "select",
1025
+ {
1026
+ value: rule.operator,
1027
+ onChange: (e) => onChange({ ...rule, operator: e.target.value }),
1028
+ style: operatorSelectStyle,
1029
+ children: [
1030
+ /* @__PURE__ */ jsx18("option", { value: "", children: t("conditionBuilder.selectOperator") }),
1031
+ operators.map((op) => /* @__PURE__ */ jsx18("option", { value: op.value, children: op.label }, op.value))
1032
+ ]
1033
+ }
1034
+ ),
1035
+ /* @__PURE__ */ jsx18(
1036
+ "input",
1037
+ {
1038
+ type: "text",
1039
+ value: formatValue(rule.value),
1040
+ onChange: (e) => onChange({ ...rule, value: parseValue(e.target.value) }),
1041
+ placeholder: t("conditionBuilder.valuePlaceholder"),
1042
+ style: inputStyle4
1043
+ }
1044
+ ),
1045
+ /* @__PURE__ */ jsx18(
1046
+ "button",
1047
+ {
1048
+ type: "button",
1049
+ onClick: onRemove,
1050
+ style: removeButtonStyle,
1051
+ title: t("conditionBuilder.removeCondition"),
1052
+ onMouseEnter: (e) => e.currentTarget.style.color = "var(--of-color-status-error, #DC2626)",
1053
+ onMouseLeave: (e) => e.currentTarget.style.color = "var(--of-color-text-muted, #9CA3AF)",
1054
+ children: "\u2715"
1055
+ }
1056
+ )
1057
+ ] });
1058
+ }
1059
+
1060
+ // src/primitives/condition-builder/ConditionGroup.tsx
1061
+ import { jsx as jsx19, jsxs as jsxs8 } from "react/jsx-runtime";
1062
+ var groupStyle2 = {
1063
+ border: "1px solid var(--of-color-border-secondary, #E5E7EB)",
1064
+ borderRadius: "var(--of-radius-md, 6px)",
1065
+ padding: "var(--of-spacing-4, 10px)",
1066
+ backgroundColor: "var(--of-color-bg-secondary, #F9FAFB)"
1067
+ };
1068
+ var groupHeaderStyle = {
1069
+ display: "flex",
1070
+ alignItems: "center",
1071
+ justifyContent: "space-between",
1072
+ marginBottom: "var(--of-spacing-3, 8px)"
1073
+ };
1074
+ var operatorToggleStyle = {
1075
+ display: "flex",
1076
+ alignItems: "center",
1077
+ gap: "var(--of-spacing-2, 6px)",
1078
+ fontSize: "var(--of-font-size-sm, 12px)",
1079
+ color: "var(--of-color-text-secondary, #374151)"
1080
+ };
1081
+ var toggleButtonBase = {
1082
+ padding: "var(--of-spacing-1, 4px) var(--of-spacing-3, 8px)",
1083
+ border: "1px solid var(--of-color-border-primary, #D1D5DB)",
1084
+ borderRadius: "var(--of-radius-sm, 4px)",
1085
+ fontSize: "var(--of-font-size-xs, 11px)",
1086
+ fontWeight: "var(--of-font-weight-semibold, 600)",
1087
+ cursor: "pointer",
1088
+ transition: "all var(--of-transition-fast, 0.15s)"
1089
+ };
1090
+ var removeGroupButtonStyle = {
1091
+ padding: "var(--of-spacing-1, 4px) var(--of-spacing-2, 6px)",
1092
+ border: "none",
1093
+ background: "none",
1094
+ color: "var(--of-color-text-muted, #9CA3AF)",
1095
+ cursor: "pointer",
1096
+ fontSize: "var(--of-font-size-sm, 12px)",
1097
+ borderRadius: "var(--of-radius-sm, 4px)"
1098
+ };
1099
+ var addConditionButtonStyle = {
1100
+ padding: "var(--of-spacing-1, 4px) var(--of-spacing-3, 8px)",
1101
+ border: "1px dashed var(--of-color-border-primary, #D1D5DB)",
1102
+ background: "none",
1103
+ color: "var(--of-color-text-tertiary, #6B7280)",
1104
+ cursor: "pointer",
1105
+ fontSize: "var(--of-font-size-xs, 11px)",
1106
+ borderRadius: "var(--of-radius-sm, 4px)",
1107
+ width: "100%",
1108
+ marginTop: "var(--of-spacing-2, 6px)"
1109
+ };
1110
+ var emptyStyle = {
1111
+ fontSize: "var(--of-font-size-xs, 11px)",
1112
+ color: "var(--of-color-text-muted, #9CA3AF)",
1113
+ textAlign: "center",
1114
+ padding: "var(--of-spacing-3, 8px) 0"
1115
+ };
1116
+ function createEmptyRule() {
1117
+ return { fact: "", operator: "equal", value: "" };
1118
+ }
1119
+ function ConditionGroupView({
1120
+ group,
1121
+ properties,
1122
+ operators,
1123
+ onChange,
1124
+ onRemove,
1125
+ canRemove
1126
+ }) {
1127
+ const t = useTranslation();
1128
+ const handleOperatorChange = (operator) => {
1129
+ onChange({ ...group, operator });
1130
+ };
1131
+ const handleRuleChange = (index, rule) => {
1132
+ const conditions = [...group.conditions];
1133
+ conditions[index] = rule;
1134
+ onChange({ ...group, conditions });
1135
+ };
1136
+ const handleRuleRemove = (index) => {
1137
+ const conditions = group.conditions.filter((_, i) => i !== index);
1138
+ onChange({ ...group, conditions });
1139
+ };
1140
+ const handleAddCondition = () => {
1141
+ onChange({
1142
+ ...group,
1143
+ conditions: [...group.conditions, createEmptyRule()]
1144
+ });
1145
+ };
1146
+ const isAnd = group.operator === "all";
1147
+ return /* @__PURE__ */ jsxs8("div", { style: groupStyle2, children: [
1148
+ /* @__PURE__ */ jsxs8("div", { style: groupHeaderStyle, children: [
1149
+ /* @__PURE__ */ jsxs8("div", { style: operatorToggleStyle, children: [
1150
+ /* @__PURE__ */ jsx19("span", { children: t("conditionBuilder.matchLabel") }),
1151
+ /* @__PURE__ */ jsx19(
1152
+ "button",
1153
+ {
1154
+ type: "button",
1155
+ onClick: () => handleOperatorChange("all"),
1156
+ style: {
1157
+ ...toggleButtonBase,
1158
+ backgroundColor: isAnd ? "var(--of-color-interactive-primary, #3B82F6)" : "var(--of-field-bg, #fff)",
1159
+ color: isAnd ? "var(--of-color-text-inverse, #fff)" : "var(--of-color-text-secondary, #374151)",
1160
+ borderColor: isAnd ? "var(--of-color-interactive-primary, #3B82F6)" : "var(--of-color-border-primary, #D1D5DB)"
1161
+ },
1162
+ children: "AND"
1163
+ }
1164
+ ),
1165
+ /* @__PURE__ */ jsx19(
1166
+ "button",
1167
+ {
1168
+ type: "button",
1169
+ onClick: () => handleOperatorChange("any"),
1170
+ style: {
1171
+ ...toggleButtonBase,
1172
+ backgroundColor: !isAnd ? "var(--of-color-interactive-primary, #3B82F6)" : "var(--of-field-bg, #fff)",
1173
+ color: !isAnd ? "var(--of-color-text-inverse, #fff)" : "var(--of-color-text-secondary, #374151)",
1174
+ borderColor: !isAnd ? "var(--of-color-interactive-primary, #3B82F6)" : "var(--of-color-border-primary, #D1D5DB)"
1175
+ },
1176
+ children: "OR"
1177
+ }
1178
+ )
1179
+ ] }),
1180
+ canRemove && /* @__PURE__ */ jsx19(
1181
+ "button",
1182
+ {
1183
+ type: "button",
1184
+ onClick: onRemove,
1185
+ style: removeGroupButtonStyle,
1186
+ title: t("conditionBuilder.removeGroup"),
1187
+ onMouseEnter: (e) => e.currentTarget.style.color = "var(--of-color-status-error, #DC2626)",
1188
+ onMouseLeave: (e) => e.currentTarget.style.color = "var(--of-color-text-muted, #9CA3AF)",
1189
+ children: t("conditionBuilder.removeGroup")
1190
+ }
1191
+ )
1192
+ ] }),
1193
+ group.conditions.length === 0 ? /* @__PURE__ */ jsx19("div", { style: emptyStyle, children: t("conditionBuilder.emptyGroup") }) : group.conditions.map((rule, index) => /* @__PURE__ */ jsx19(
1194
+ ConditionRow,
1195
+ {
1196
+ rule,
1197
+ properties,
1198
+ operators,
1199
+ onChange: (updated) => handleRuleChange(index, updated),
1200
+ onRemove: () => handleRuleRemove(index)
1201
+ },
1202
+ index
1203
+ )),
1204
+ /* @__PURE__ */ jsxs8(
1205
+ "button",
1206
+ {
1207
+ type: "button",
1208
+ onClick: handleAddCondition,
1209
+ style: addConditionButtonStyle,
1210
+ onMouseEnter: (e) => {
1211
+ e.currentTarget.style.borderColor = "var(--of-color-interactive-primary, #3B82F6)";
1212
+ e.currentTarget.style.color = "var(--of-color-interactive-primary, #3B82F6)";
1213
+ },
1214
+ onMouseLeave: (e) => {
1215
+ e.currentTarget.style.borderColor = "var(--of-color-border-primary, #D1D5DB)";
1216
+ e.currentTarget.style.color = "var(--of-color-text-tertiary, #6B7280)";
1217
+ },
1218
+ children: [
1219
+ "+ ",
1220
+ t("conditionBuilder.addCondition")
1221
+ ]
1222
+ }
1223
+ )
1224
+ ] });
1225
+ }
1226
+
1227
+ // src/primitives/condition-builder/operators.ts
1228
+ var defaultOperators = [
1229
+ { value: "equal", label: "equals" },
1230
+ { value: "notEqual", label: "does not equal" },
1231
+ { value: "greaterThan", label: "greater than" },
1232
+ { value: "greaterThanInclusive", label: "greater than or equal" },
1233
+ { value: "lessThan", label: "less than" },
1234
+ { value: "lessThanInclusive", label: "less than or equal" },
1235
+ { value: "in", label: "is in" },
1236
+ { value: "notIn", label: "is not in" },
1237
+ { value: "contains", label: "contains" },
1238
+ { value: "doesNotContain", label: "does not contain" }
1239
+ ];
1240
+
1241
+ // src/primitives/condition-builder/ConditionBuilder.tsx
1242
+ import { jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
1243
+ var containerStyle3 = {
1244
+ display: "flex",
1245
+ flexDirection: "column",
1246
+ gap: "var(--of-spacing-3, 8px)"
1247
+ };
1248
+ var topLabelStyle = {
1249
+ fontSize: "var(--of-font-size-sm, 12px)",
1250
+ fontWeight: "var(--of-font-weight-medium, 500)",
1251
+ color: "var(--of-color-text-secondary, #374151)",
1252
+ marginBottom: "var(--of-spacing-1, 4px)"
1253
+ };
1254
+ var orDividerStyle = {
1255
+ display: "flex",
1256
+ alignItems: "center",
1257
+ gap: "var(--of-spacing-3, 8px)",
1258
+ margin: "var(--of-spacing-1, 4px) 0"
1259
+ };
1260
+ var orLineStyle = {
1261
+ flex: 1,
1262
+ height: "1px",
1263
+ backgroundColor: "var(--of-color-border-secondary, #E5E7EB)"
1264
+ };
1265
+ var orTextStyle = {
1266
+ fontSize: "var(--of-font-size-xs, 11px)",
1267
+ fontWeight: "var(--of-font-weight-semibold, 600)",
1268
+ color: "var(--of-color-text-muted, #9CA3AF)",
1269
+ textTransform: "uppercase",
1270
+ letterSpacing: "0.5px"
1271
+ };
1272
+ var addGroupButtonStyle = {
1273
+ padding: "var(--of-spacing-3, 8px) var(--of-spacing-4, 10px)",
1274
+ border: "1px dashed var(--of-color-border-primary, #D1D5DB)",
1275
+ background: "none",
1276
+ color: "var(--of-color-text-tertiary, #6B7280)",
1277
+ cursor: "pointer",
1278
+ fontSize: "var(--of-font-size-sm, 12px)",
1279
+ borderRadius: "var(--of-radius-md, 6px)",
1280
+ marginTop: "var(--of-spacing-2, 6px)"
1281
+ };
1282
+ function normalize(value) {
1283
+ if (!value || !Array.isArray(value.groups) || value.groups.length === 0) {
1284
+ return { groups: [{ operator: "all", conditions: [] }] };
1285
+ }
1286
+ return value;
1287
+ }
1288
+ function ConditionBuilder({
1289
+ value,
1290
+ onChange,
1291
+ properties = [],
1292
+ operators = defaultOperators
1293
+ }) {
1294
+ const t = useTranslation();
1295
+ const current = normalize(value);
1296
+ const emit = (groups) => {
1297
+ onChange({ groups: groups.length > 0 ? groups : [{ operator: "all", conditions: [] }] });
1298
+ };
1299
+ const handleGroupChange = (index, group) => {
1300
+ const groups = [...current.groups];
1301
+ groups[index] = group;
1302
+ emit(groups);
1303
+ };
1304
+ const handleGroupRemove = (index) => {
1305
+ emit(current.groups.filter((_, i) => i !== index));
1306
+ };
1307
+ const handleAddGroup = () => {
1308
+ emit([
1309
+ ...current.groups,
1310
+ { operator: "all", conditions: [{ fact: "", operator: "equal", value: "" }] }
1311
+ ]);
1312
+ };
1313
+ return /* @__PURE__ */ jsxs9("div", { style: containerStyle3, children: [
1314
+ /* @__PURE__ */ jsx20("div", { style: topLabelStyle, children: t("conditionBuilder.topLevelLabel") }),
1315
+ current.groups.map((group, index) => /* @__PURE__ */ jsxs9(React5.Fragment, { children: [
1316
+ index > 0 && /* @__PURE__ */ jsxs9("div", { style: orDividerStyle, children: [
1317
+ /* @__PURE__ */ jsx20("div", { style: orLineStyle }),
1318
+ /* @__PURE__ */ jsx20("span", { style: orTextStyle, children: "OR" }),
1319
+ /* @__PURE__ */ jsx20("div", { style: orLineStyle })
1320
+ ] }),
1321
+ /* @__PURE__ */ jsx20(
1322
+ ConditionGroupView,
1323
+ {
1324
+ group,
1325
+ properties,
1326
+ operators,
1327
+ onChange: (updated) => handleGroupChange(index, updated),
1328
+ onRemove: () => handleGroupRemove(index),
1329
+ canRemove: current.groups.length > 1
1330
+ }
1331
+ )
1332
+ ] }, index)),
1333
+ /* @__PURE__ */ jsxs9(
1334
+ "button",
1335
+ {
1336
+ type: "button",
1337
+ onClick: handleAddGroup,
1338
+ style: addGroupButtonStyle,
1339
+ onMouseEnter: (e) => {
1340
+ e.currentTarget.style.borderColor = "var(--of-color-interactive-primary, #3B82F6)";
1341
+ e.currentTarget.style.color = "var(--of-color-interactive-primary, #3B82F6)";
1342
+ },
1343
+ onMouseLeave: (e) => {
1344
+ e.currentTarget.style.borderColor = "var(--of-color-border-primary, #D1D5DB)";
1345
+ e.currentTarget.style.color = "var(--of-color-text-tertiary, #6B7280)";
1346
+ },
1347
+ children: [
1348
+ "+ ",
1349
+ t("conditionBuilder.addGroup")
1350
+ ]
1351
+ }
1352
+ )
1353
+ ] });
1354
+ }
1355
+
1356
+ // src/primitives/condition-builder/ConditionBuilderDialog.tsx
1357
+ import { useState as useState4, useCallback, useEffect as useEffect3, useRef } from "react";
1358
+ import { createPortal } from "react-dom";
1359
+ import { jsx as jsx21, jsxs as jsxs10 } from "react/jsx-runtime";
1360
+ var backdropStyle = {
1361
+ position: "fixed",
1362
+ inset: 0,
1363
+ backgroundColor: "rgba(0, 0, 0, 0.4)",
1364
+ display: "flex",
1365
+ alignItems: "center",
1366
+ justifyContent: "center",
1367
+ zIndex: 1e4,
1368
+ animation: "of-dialog-fade-in 0.15s ease-out"
1369
+ };
1370
+ var dialogStyle = {
1371
+ backgroundColor: "var(--of-panel-bg, #fff)",
1372
+ borderRadius: "var(--of-radius-lg, 8px)",
1373
+ boxShadow: "0 20px 60px rgba(0, 0, 0, 0.2)",
1374
+ width: "min(680px, calc(100vw - 48px))",
1375
+ maxHeight: "calc(100vh - 48px)",
1376
+ display: "flex",
1377
+ flexDirection: "column",
1378
+ overflow: "hidden",
1379
+ animation: "of-dialog-slide-in 0.15s ease-out"
1380
+ };
1381
+ var headerStyle3 = {
1382
+ display: "flex",
1383
+ alignItems: "center",
1384
+ justifyContent: "space-between",
1385
+ padding: "var(--of-spacing-6, 16px) var(--of-spacing-7, 20px)",
1386
+ borderBottom: "1px solid var(--of-color-border-secondary, #E5E7EB)",
1387
+ flexShrink: 0
1388
+ };
1389
+ var titleStyle = {
1390
+ fontSize: "var(--of-font-size-lg, 14px)",
1391
+ fontWeight: "var(--of-font-weight-semibold, 600)",
1392
+ color: "var(--of-color-text-primary, #111827)"
1393
+ };
1394
+ var closeButtonStyle = {
1395
+ padding: "var(--of-spacing-1, 4px) var(--of-spacing-2, 6px)",
1396
+ border: "none",
1397
+ background: "none",
1398
+ color: "var(--of-color-text-muted, #9CA3AF)",
1399
+ cursor: "pointer",
1400
+ fontSize: "18px",
1401
+ lineHeight: 1,
1402
+ borderRadius: "var(--of-radius-sm, 4px)"
1403
+ };
1404
+ var bodyStyle = {
1405
+ padding: "var(--of-spacing-7, 20px)",
1406
+ overflowY: "auto",
1407
+ flex: 1
1408
+ };
1409
+ var footerStyle = {
1410
+ display: "flex",
1411
+ justifyContent: "flex-end",
1412
+ gap: "var(--of-spacing-3, 8px)",
1413
+ padding: "var(--of-spacing-6, 16px) var(--of-spacing-7, 20px)",
1414
+ borderTop: "1px solid var(--of-color-border-secondary, #E5E7EB)",
1415
+ flexShrink: 0
1416
+ };
1417
+ var cancelButtonStyle = {
1418
+ padding: "var(--of-button-padding, 8px 16px)",
1419
+ border: "1px solid var(--of-color-border-primary, #D1D5DB)",
1420
+ borderRadius: "var(--of-button-radius, 6px)",
1421
+ backgroundColor: "var(--of-field-bg, #fff)",
1422
+ color: "var(--of-color-text-secondary, #374151)",
1423
+ cursor: "pointer",
1424
+ fontSize: "var(--of-font-size-sm, 12px)",
1425
+ fontWeight: "var(--of-font-weight-medium, 500)"
1426
+ };
1427
+ var applyButtonStyle = {
1428
+ padding: "var(--of-button-padding, 8px 16px)",
1429
+ border: "none",
1430
+ borderRadius: "var(--of-button-radius, 6px)",
1431
+ backgroundColor: "var(--of-button-primary-bg, #3B82F6)",
1432
+ color: "var(--of-button-primary-color, #fff)",
1433
+ cursor: "pointer",
1434
+ fontSize: "var(--of-font-size-sm, 12px)",
1435
+ fontWeight: "var(--of-font-weight-medium, 500)"
1436
+ };
1437
+ var stylesInjected = false;
1438
+ function injectStyles() {
1439
+ if (stylesInjected || typeof document === "undefined") return;
1440
+ stylesInjected = true;
1441
+ const style = document.createElement("style");
1442
+ style.textContent = `
1443
+ @keyframes of-dialog-fade-in {
1444
+ from { opacity: 0; }
1445
+ to { opacity: 1; }
1446
+ }
1447
+ @keyframes of-dialog-slide-in {
1448
+ from { opacity: 0; transform: translateY(-8px) scale(0.98); }
1449
+ to { opacity: 1; transform: translateY(0) scale(1); }
1450
+ }
1451
+ `;
1452
+ document.head.appendChild(style);
1453
+ }
1454
+ function ConditionBuilderDialog({
1455
+ open,
1456
+ onClose,
1457
+ value,
1458
+ onChange,
1459
+ properties,
1460
+ operators
1461
+ }) {
1462
+ const t = useTranslation();
1463
+ const [draft, setDraft] = useState4(value);
1464
+ const backdropRef = useRef(null);
1465
+ useEffect3(() => {
1466
+ if (open) {
1467
+ setDraft(value);
1468
+ injectStyles();
1469
+ }
1470
+ }, [open, value]);
1471
+ useEffect3(() => {
1472
+ if (!open) return;
1473
+ const handleKeyDown = (e) => {
1474
+ if (e.key === "Escape") onClose();
1475
+ };
1476
+ document.addEventListener("keydown", handleKeyDown);
1477
+ return () => document.removeEventListener("keydown", handleKeyDown);
1478
+ }, [open, onClose]);
1479
+ const handleDraftChange = useCallback((next) => {
1480
+ setDraft(next);
1481
+ }, []);
1482
+ const handleApply = () => {
1483
+ onChange(draft);
1484
+ onClose();
1485
+ };
1486
+ const handleBackdropClick = (e) => {
1487
+ if (e.target === backdropRef.current) {
1488
+ onClose();
1489
+ }
1490
+ };
1491
+ if (!open) return null;
1492
+ return createPortal(
1493
+ /* @__PURE__ */ jsx21(
1494
+ "div",
1495
+ {
1496
+ ref: backdropRef,
1497
+ style: backdropStyle,
1498
+ onClick: handleBackdropClick,
1499
+ children: /* @__PURE__ */ jsxs10("div", { style: dialogStyle, role: "dialog", "aria-modal": "true", children: [
1500
+ /* @__PURE__ */ jsxs10("div", { style: headerStyle3, children: [
1501
+ /* @__PURE__ */ jsx21("div", { style: titleStyle, children: t("conditionBuilder.dialogTitle") }),
1502
+ /* @__PURE__ */ jsx21(
1503
+ "button",
1504
+ {
1505
+ type: "button",
1506
+ onClick: onClose,
1507
+ style: closeButtonStyle,
1508
+ onMouseEnter: (e) => e.currentTarget.style.color = "var(--of-color-text-primary, #111827)",
1509
+ onMouseLeave: (e) => e.currentTarget.style.color = "var(--of-color-text-muted, #9CA3AF)",
1510
+ children: "\u2715"
1511
+ }
1512
+ )
1513
+ ] }),
1514
+ /* @__PURE__ */ jsx21("div", { style: bodyStyle, children: /* @__PURE__ */ jsx21(
1515
+ ConditionBuilder,
1516
+ {
1517
+ value: draft,
1518
+ onChange: handleDraftChange,
1519
+ properties,
1520
+ operators
1521
+ }
1522
+ ) }),
1523
+ /* @__PURE__ */ jsxs10("div", { style: footerStyle, children: [
1524
+ /* @__PURE__ */ jsx21(
1525
+ "button",
1526
+ {
1527
+ type: "button",
1528
+ onClick: onClose,
1529
+ style: cancelButtonStyle,
1530
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--of-color-bg-tertiary, #F3F4F6)",
1531
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "var(--of-field-bg, #fff)",
1532
+ children: t("conditionBuilder.cancel")
1533
+ }
1534
+ ),
1535
+ /* @__PURE__ */ jsx21(
1536
+ "button",
1537
+ {
1538
+ type: "button",
1539
+ onClick: handleApply,
1540
+ style: applyButtonStyle,
1541
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--of-button-primary-bg-hover, #2563EB)",
1542
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "var(--of-button-primary-bg, #3B82F6)",
1543
+ children: t("conditionBuilder.apply")
1544
+ }
1545
+ )
1546
+ ] })
1547
+ ] })
1548
+ }
1549
+ ),
1550
+ document.body
1551
+ );
1552
+ }
1553
+
1554
+ // src/nodes/details/TriggerNodeDetail.tsx
1555
+ import { jsx as jsx22 } from "react/jsx-runtime";
1556
+ function TriggerNodeDetail({ node, onChange }) {
1557
+ const t = useTranslation();
1558
+ const data = node.data;
1559
+ const handleEventChange = (event) => {
1560
+ onChange({
1561
+ ...data,
1562
+ params: { ...data.params, event }
1563
+ });
1564
+ };
1565
+ return /* @__PURE__ */ jsx22(FieldGroup, { label: t("nodeDetails.trigger.group"), children: /* @__PURE__ */ jsx22(
1566
+ TextField,
1567
+ {
1568
+ label: t("nodeDetails.trigger.eventLabel"),
1569
+ value: data.params?.event ?? "",
1570
+ onChange: handleEventChange,
1571
+ placeholder: t("nodeDetails.trigger.eventPlaceholder"),
1572
+ hint: t("nodeDetails.trigger.eventHint")
1573
+ }
1574
+ ) });
1575
+ }
1576
+
1577
+ // src/nodes/details/ActionNodeDetail.tsx
1578
+ import { jsx as jsx23, jsxs as jsxs11 } from "react/jsx-runtime";
1579
+ function ActionNodeDetail({ node, onChange }) {
1580
+ const t = useTranslation();
1581
+ const data = node.data;
1582
+ const handleActionChange = (action) => {
1583
+ onChange({
1584
+ ...data,
1585
+ action
1586
+ });
1587
+ };
1588
+ const handleParamsChange = (params) => {
1589
+ onChange({
1590
+ ...data,
1591
+ params
1592
+ });
1593
+ };
1594
+ return /* @__PURE__ */ jsxs11(FieldGroup, { label: t("nodeDetails.action.group"), children: [
1595
+ /* @__PURE__ */ jsx23(
1596
+ TextField,
1597
+ {
1598
+ label: t("nodeDetails.action.nameLabel"),
1599
+ value: data.action ?? "",
1600
+ onChange: handleActionChange,
1601
+ placeholder: t("nodeDetails.action.namePlaceholder"),
1602
+ hint: t("nodeDetails.action.nameHint")
1603
+ }
1604
+ ),
1605
+ /* @__PURE__ */ jsx23(
1606
+ JsonField,
1607
+ {
1608
+ label: t("nodeDetails.action.paramsLabel"),
1609
+ value: data.params ?? {},
1610
+ onChange: handleParamsChange,
1611
+ hint: t("nodeDetails.action.paramsHint"),
1612
+ rows: 4
1613
+ }
1614
+ )
1615
+ ] });
1616
+ }
1617
+
1618
+ // src/nodes/details/ConditionNodeDetail.tsx
1619
+ import { useState as useState5 } from "react";
1620
+ import { jsx as jsx24, jsxs as jsxs12 } from "react/jsx-runtime";
1621
+ var EMPTY_CONDITIONS = {
1622
+ groups: [{ operator: "all", conditions: [] }]
1623
+ };
1624
+ function countRules(conditions) {
1625
+ if (!conditions || !Array.isArray(conditions.groups)) return 0;
1626
+ return conditions.groups.reduce(
1627
+ (sum, group) => sum + (group.conditions?.length ?? 0),
1628
+ 0
1629
+ );
1630
+ }
1631
+ var openButtonStyle = {
1632
+ width: "100%",
1633
+ padding: "var(--of-spacing-3, 8px) var(--of-spacing-4, 10px)",
1634
+ border: "1px solid var(--of-color-border-primary, #D1D5DB)",
1635
+ borderRadius: "var(--of-field-radius, 6px)",
1636
+ backgroundColor: "var(--of-field-bg, #fff)",
1637
+ color: "var(--of-color-text-primary, #111827)",
1638
+ cursor: "pointer",
1639
+ fontSize: "var(--of-field-font-size, 13px)",
1640
+ textAlign: "left",
1641
+ display: "flex",
1642
+ alignItems: "center",
1643
+ justifyContent: "space-between"
1644
+ };
1645
+ var ruleCountStyle = {
1646
+ fontSize: "var(--of-font-size-xs, 11px)",
1647
+ color: "var(--of-color-text-muted, #9CA3AF)"
1648
+ };
1649
+ function ConditionNodeDetail({
1650
+ node,
1651
+ onChange,
1652
+ conditionProperties,
1653
+ conditionOperators
1654
+ }) {
1655
+ const t = useTranslation();
1656
+ const data = node.data;
1657
+ const [dialogOpen, setDialogOpen] = useState5(false);
1658
+ const conditions = data.conditions ?? EMPTY_CONDITIONS;
1659
+ const ruleCount = countRules(conditions);
1660
+ const handleConditionsChange = (updated) => {
1661
+ onChange({
1662
+ ...data,
1663
+ conditions: updated
1664
+ });
1665
+ };
1666
+ const ruleLabel = ruleCount === 0 ? t("conditionBuilder.noConditions") : ruleCount === 1 ? t("conditionBuilder.ruleCountSingular") : t("conditionBuilder.ruleCount", { count: String(ruleCount) });
1667
+ return /* @__PURE__ */ jsxs12(FieldGroup, { label: t("nodeDetails.condition.group"), children: [
1668
+ /* @__PURE__ */ jsxs12("div", { style: { marginBottom: "var(--of-spacing-5, 12px)" }, children: [
1669
+ /* @__PURE__ */ jsx24(
1670
+ "label",
1671
+ {
1672
+ style: {
1673
+ display: "block",
1674
+ fontSize: "var(--of-field-label-size, 12px)",
1675
+ fontWeight: "var(--of-font-weight-medium, 500)",
1676
+ color: "var(--of-field-label-color, #374151)",
1677
+ marginBottom: "var(--of-spacing-1, 4px)"
1678
+ },
1679
+ children: t("nodeDetails.condition.conditionsLabel")
1680
+ }
1681
+ ),
1682
+ /* @__PURE__ */ jsxs12(
1683
+ "button",
1684
+ {
1685
+ type: "button",
1686
+ onClick: () => setDialogOpen(true),
1687
+ style: openButtonStyle,
1688
+ onMouseEnter: (e) => e.currentTarget.style.borderColor = "var(--of-field-border-focus, #3B82F6)",
1689
+ onMouseLeave: (e) => e.currentTarget.style.borderColor = "var(--of-color-border-primary, #D1D5DB)",
1690
+ children: [
1691
+ /* @__PURE__ */ jsx24("span", { children: ruleLabel }),
1692
+ /* @__PURE__ */ jsx24("span", { style: ruleCountStyle, children: t("conditionBuilder.editButton") })
1693
+ ]
1694
+ }
1695
+ )
1696
+ ] }),
1697
+ /* @__PURE__ */ jsx24(
1698
+ ConditionBuilderDialog,
1699
+ {
1700
+ open: dialogOpen,
1701
+ onClose: () => setDialogOpen(false),
1702
+ value: conditions,
1703
+ onChange: handleConditionsChange,
1704
+ properties: conditionProperties,
1705
+ operators: conditionOperators
1706
+ }
1707
+ )
1708
+ ] });
1709
+ }
1710
+
1711
+ // src/nodes/details/ExitNodeDetail.tsx
1712
+ import { jsx as jsx25 } from "react/jsx-runtime";
1713
+ function ExitNodeDetail(_props) {
1714
+ const t = useTranslation();
1715
+ return /* @__PURE__ */ jsx25(FieldGroup, { label: t("nodeDetails.exit.group"), children: /* @__PURE__ */ jsx25("p", { style: { fontSize: "13px", color: "#6B7280", margin: 0 }, children: t("nodeDetails.exit.message") }) });
1716
+ }
1717
+
1718
+ // src/nodes/details/WaitNodeDetail.tsx
1719
+ import { jsx as jsx26 } from "react/jsx-runtime";
1720
+ function WaitNodeDetail({ node, onChange }) {
1721
+ const t = useTranslation();
1722
+ const data = node.data;
1723
+ const handleDurationChange = (duration) => {
1724
+ onChange({
1725
+ ...data,
1726
+ params: { ...data.params, duration }
1727
+ });
1728
+ };
1729
+ return /* @__PURE__ */ jsx26(FieldGroup, { label: t("nodeDetails.wait.group"), children: /* @__PURE__ */ jsx26(
1730
+ DurationField,
1731
+ {
1732
+ label: t("nodeDetails.wait.durationLabel"),
1733
+ value: data.params?.duration ?? 6e4,
1734
+ onChange: handleDurationChange,
1735
+ hint: t("nodeDetails.wait.durationHint")
1736
+ }
1737
+ ) });
1738
+ }
1739
+
1740
+ // src/nodes/details/TriggerOrTimeoutNodeDetail.tsx
1741
+ import { jsx as jsx27, jsxs as jsxs13 } from "react/jsx-runtime";
1742
+ function TriggerOrTimeoutNodeDetail({ node, onChange }) {
1743
+ const t = useTranslation();
1744
+ const data = node.data;
1745
+ const handleEventChange = (event) => {
1746
+ onChange({
1747
+ ...data,
1748
+ params: { ...data.params, event }
1749
+ });
1750
+ };
1751
+ const handleDurationChange = (duration) => {
1752
+ onChange({
1753
+ ...data,
1754
+ params: { ...data.params, duration }
1755
+ });
1756
+ };
1757
+ return /* @__PURE__ */ jsxs13(FieldGroup, { label: t("nodeDetails.triggerOrTimeout.group"), children: [
1758
+ /* @__PURE__ */ jsx27(
1759
+ TextField,
1760
+ {
1761
+ label: t("nodeDetails.triggerOrTimeout.eventLabel"),
1762
+ value: data.params?.event ?? "",
1763
+ onChange: handleEventChange,
1764
+ placeholder: t("nodeDetails.triggerOrTimeout.eventPlaceholder"),
1765
+ hint: t("nodeDetails.triggerOrTimeout.eventHint")
1766
+ }
1767
+ ),
1768
+ /* @__PURE__ */ jsx27(
1769
+ DurationField,
1770
+ {
1771
+ label: t("nodeDetails.triggerOrTimeout.durationLabel"),
1772
+ value: data.params?.duration ?? 6e4,
1773
+ onChange: handleDurationChange,
1774
+ hint: t("nodeDetails.triggerOrTimeout.durationHint")
1775
+ }
1776
+ )
1777
+ ] });
1778
+ }
1779
+
1780
+ // src/nodes/icons/index.tsx
1781
+ import { jsx as jsx28, jsxs as jsxs14 } from "react/jsx-runtime";
1782
+ function TriggerIcon({ size = 24 }) {
1783
+ return /* @__PURE__ */ jsx28(
1784
+ "svg",
1785
+ {
1786
+ width: size,
1787
+ height: size,
1788
+ viewBox: "0 0 24 24",
1789
+ fill: "none",
1790
+ stroke: "#4CAF50",
1791
+ strokeWidth: "2",
1792
+ strokeLinecap: "round",
1793
+ strokeLinejoin: "round",
1794
+ children: /* @__PURE__ */ jsx28("polygon", { points: "13 2 3 14 12 14 11 22 21 10 12 10 13 2" })
1795
+ }
1796
+ );
1797
+ }
1798
+ function ActionIcon({ size = 24 }) {
1799
+ return /* @__PURE__ */ jsxs14(
1800
+ "svg",
1801
+ {
1802
+ width: size,
1803
+ height: size,
1804
+ viewBox: "0 0 24 24",
1805
+ fill: "none",
1806
+ stroke: "#2196F3",
1807
+ strokeWidth: "2",
1808
+ strokeLinecap: "round",
1809
+ strokeLinejoin: "round",
1810
+ children: [
1811
+ /* @__PURE__ */ jsx28("circle", { cx: "12", cy: "12", r: "10" }),
1812
+ /* @__PURE__ */ jsx28("polygon", { points: "10 8 16 12 10 16 10 8", fill: "#2196F3" })
1813
+ ]
1814
+ }
1815
+ );
1816
+ }
1817
+ function ConditionIcon({ size = 24 }) {
1818
+ return /* @__PURE__ */ jsxs14(
1819
+ "svg",
1820
+ {
1821
+ width: size,
1822
+ height: size,
1823
+ viewBox: "0 0 24 24",
1824
+ fill: "none",
1825
+ stroke: "#FF9800",
1826
+ strokeWidth: "2",
1827
+ strokeLinecap: "round",
1828
+ strokeLinejoin: "round",
1829
+ children: [
1830
+ /* @__PURE__ */ jsx28("path", { d: "M12 3L21 12L12 21L3 12L12 3Z" }),
1831
+ /* @__PURE__ */ jsx28("path", { d: "M9 12L11 14L15 10" })
1832
+ ]
1833
+ }
1834
+ );
1835
+ }
1836
+ function WaitIcon({ size = 24 }) {
1837
+ return /* @__PURE__ */ jsxs14(
1838
+ "svg",
1839
+ {
1840
+ width: size,
1841
+ height: size,
1842
+ viewBox: "0 0 24 24",
1843
+ fill: "none",
1844
+ stroke: "#9C27B0",
1845
+ strokeWidth: "2",
1846
+ strokeLinecap: "round",
1847
+ strokeLinejoin: "round",
1848
+ children: [
1849
+ /* @__PURE__ */ jsx28("circle", { cx: "12", cy: "12", r: "10" }),
1850
+ /* @__PURE__ */ jsx28("polyline", { points: "12 6 12 12 16 14" })
1851
+ ]
1852
+ }
1853
+ );
1854
+ }
1855
+ function TriggerOrTimeoutIcon({ size = 24 }) {
1856
+ return /* @__PURE__ */ jsxs14(
1857
+ "svg",
1858
+ {
1859
+ width: size,
1860
+ height: size,
1861
+ viewBox: "0 0 24 24",
1862
+ fill: "none",
1863
+ stroke: "#607D8B",
1864
+ strokeWidth: "2",
1865
+ strokeLinecap: "round",
1866
+ strokeLinejoin: "round",
1867
+ children: [
1868
+ /* @__PURE__ */ jsx28("circle", { cx: "12", cy: "12", r: "10" }),
1869
+ /* @__PURE__ */ jsx28("polyline", { points: "12 6 12 12 16 14" }),
1870
+ /* @__PURE__ */ jsx28("path", { d: "M17 2L19 6L15 6L17 2", fill: "#607D8B" })
1871
+ ]
1872
+ }
1873
+ );
1874
+ }
1875
+ function ExitIcon({ size = 24 }) {
1876
+ return /* @__PURE__ */ jsxs14(
1877
+ "svg",
1878
+ {
1879
+ width: size,
1880
+ height: size,
1881
+ viewBox: "0 0 24 24",
1882
+ fill: "none",
1883
+ stroke: "#F44336",
1884
+ strokeWidth: "2",
1885
+ strokeLinecap: "round",
1886
+ strokeLinejoin: "round",
1887
+ children: [
1888
+ /* @__PURE__ */ jsx28("circle", { cx: "12", cy: "12", r: "10" }),
1889
+ /* @__PURE__ */ jsx28("rect", { x: "8", y: "8", width: "8", height: "8", fill: "#F44336" })
1890
+ ]
1891
+ }
1892
+ );
1893
+ }
1894
+
1895
+ // src/nodes/index.ts
1896
+ var defaultNodeTypes = [
1897
+ {
1898
+ type: "Trigger",
1899
+ label: "Trigger",
1900
+ description: "Starts the workflow when a specific event occurs",
1901
+ Icon: TriggerIcon,
1902
+ defaultData: { params: { event: "" } },
1903
+ ViewComponent: TriggerNodeView,
1904
+ DetailComponent: TriggerNodeDetail
1905
+ },
1906
+ {
1907
+ type: "Action",
1908
+ label: "Action",
1909
+ description: "Performs an action and continues to the next node",
1910
+ Icon: ActionIcon,
1911
+ defaultData: { action: "", params: {} },
1912
+ ViewComponent: ActionNodeView,
1913
+ DetailComponent: ActionNodeDetail
1914
+ },
1915
+ {
1916
+ type: "Condition",
1917
+ label: "Condition",
1918
+ description: "Branches the workflow based on conditions",
1919
+ Icon: ConditionIcon,
1920
+ defaultData: { conditions: { all: [] } },
1921
+ ViewComponent: ConditionNodeView,
1922
+ DetailComponent: ConditionNodeDetail
1923
+ },
1924
+ {
1925
+ type: "Wait",
1926
+ label: "Wait",
1927
+ description: "Pauses the workflow for a specified duration",
1928
+ Icon: WaitIcon,
1929
+ defaultData: { params: { duration: 6e4 } },
1930
+ ViewComponent: WaitNodeView,
1931
+ DetailComponent: WaitNodeDetail
1932
+ },
1933
+ {
1934
+ type: "TriggerOrTimeout",
1935
+ label: "Trigger or Timeout",
1936
+ description: "Waits for an event or times out after a duration",
1937
+ Icon: TriggerOrTimeoutIcon,
1938
+ defaultData: { params: { event: "", duration: 6e4 } },
1939
+ ViewComponent: TriggerOrTimeoutNodeView,
1940
+ DetailComponent: TriggerOrTimeoutNodeDetail
1941
+ },
1942
+ {
1943
+ type: "Exit",
1944
+ label: "Exit",
1945
+ description: "Ends the workflow",
1946
+ Icon: ExitIcon,
1947
+ defaultData: {},
1948
+ ViewComponent: ExitNodeView,
1949
+ DetailComponent: ExitNodeDetail
1950
+ }
1951
+ ];
1952
+ function mergeNodeTypes(base, overrides) {
1953
+ const map = new Map(base.map((t) => [t.type, t]));
1954
+ for (const override of overrides) {
1955
+ map.set(override.type, override);
1956
+ }
1957
+ return [...map.values()];
1958
+ }
1959
+
1960
+ // src/context/WorkflowEditorContext.tsx
1961
+ import { jsx as jsx29 } from "react/jsx-runtime";
1962
+ function findSelectedNodeId(nodes) {
1963
+ return nodes.find((n) => n.selected)?.id ?? null;
1964
+ }
1965
+ function createInitialState(workflow, nodeTypes) {
1966
+ const nodeTypesMap = /* @__PURE__ */ new Map();
1967
+ const typesToUse = nodeTypes ?? defaultNodeTypes;
1968
+ typesToUse.forEach((def) => nodeTypesMap.set(def.type, def));
1969
+ if (workflow) {
1970
+ return {
1971
+ workflow,
1972
+ nodes: workflow.flow.nodes,
1973
+ edges: workflow.flow.edges,
1974
+ options: workflow.options,
1975
+ name: workflow.name,
1976
+ selectedNodeId: findSelectedNodeId(workflow.flow.nodes),
1977
+ isDirty: false,
1978
+ nodeTypes: nodeTypesMap
1979
+ };
1980
+ }
1981
+ return {
1982
+ workflow: null,
1983
+ nodes: [],
1984
+ edges: [],
1985
+ options: { frequency: { type: "one_time" } },
1986
+ name: "",
1987
+ selectedNodeId: null,
1988
+ isDirty: false,
1989
+ nodeTypes: nodeTypesMap
1990
+ };
1991
+ }
1992
+ function reducer(state, action) {
1993
+ switch (action.type) {
1994
+ case "LOAD_WORKFLOW": {
1995
+ const workflow = action.payload;
1996
+ return {
1997
+ ...state,
1998
+ workflow,
1999
+ nodes: workflow.flow.nodes,
2000
+ edges: workflow.flow.edges,
2001
+ options: workflow.options,
2002
+ name: workflow.name,
2003
+ selectedNodeId: findSelectedNodeId(workflow.flow.nodes),
2004
+ isDirty: false
2005
+ };
2006
+ }
2007
+ case "RESET_WORKFLOW": {
2008
+ if (state.workflow) {
2009
+ return {
2010
+ ...state,
2011
+ nodes: state.workflow.flow.nodes,
2012
+ edges: state.workflow.flow.edges,
2013
+ options: state.workflow.options,
2014
+ name: state.workflow.name,
2015
+ selectedNodeId: findSelectedNodeId(state.workflow.flow.nodes),
2016
+ isDirty: false
2017
+ };
2018
+ }
2019
+ return {
2020
+ ...state,
2021
+ nodes: [],
2022
+ edges: [],
2023
+ options: { frequency: { type: "one_time" } },
2024
+ name: "",
2025
+ selectedNodeId: null,
2026
+ isDirty: false
2027
+ };
2028
+ }
2029
+ case "SET_NODES":
2030
+ return { ...state, nodes: action.payload, isDirty: true };
2031
+ case "SET_EDGES":
2032
+ return { ...state, edges: action.payload, isDirty: true };
2033
+ case "ADD_NODE":
2034
+ return {
2035
+ ...state,
2036
+ nodes: [...state.nodes, action.payload],
2037
+ isDirty: true
2038
+ };
2039
+ case "UPDATE_NODE": {
2040
+ const { nodeId, data } = action.payload;
2041
+ return {
2042
+ ...state,
2043
+ nodes: state.nodes.map(
2044
+ (node) => node.id === nodeId ? { ...node, data: { ...node.data, ...data } } : node
2045
+ ),
2046
+ isDirty: true
2047
+ };
2048
+ }
2049
+ case "UPDATE_NODE_POSITION": {
2050
+ const { nodeId, position } = action.payload;
2051
+ return {
2052
+ ...state,
2053
+ nodes: state.nodes.map(
2054
+ (node) => node.id === nodeId ? { ...node, position } : node
2055
+ ),
2056
+ isDirty: true
2057
+ };
2058
+ }
2059
+ case "REMOVE_NODE": {
2060
+ const nodeId = action.payload;
2061
+ return {
2062
+ ...state,
2063
+ nodes: state.nodes.filter((node) => node.id !== nodeId),
2064
+ edges: state.edges.filter(
2065
+ (edge) => edge.source !== nodeId && edge.target !== nodeId
2066
+ ),
2067
+ selectedNodeId: state.selectedNodeId === nodeId ? null : state.selectedNodeId,
2068
+ isDirty: true
2069
+ };
2070
+ }
2071
+ case "SELECT_NODE":
2072
+ return { ...state, selectedNodeId: action.payload };
2073
+ case "ADD_EDGE":
2074
+ return {
2075
+ ...state,
2076
+ edges: [...state.edges, action.payload],
2077
+ isDirty: true
2078
+ };
2079
+ case "REMOVE_EDGE":
2080
+ return {
2081
+ ...state,
2082
+ edges: state.edges.filter((edge) => edge.id !== action.payload),
2083
+ isDirty: true
2084
+ };
2085
+ case "SET_NAME":
2086
+ return { ...state, name: action.payload, isDirty: true };
2087
+ case "SET_OPTIONS":
2088
+ return { ...state, options: action.payload, isDirty: true };
2089
+ case "REGISTER_NODE_TYPE": {
2090
+ const newNodeTypes = new Map(state.nodeTypes);
2091
+ newNodeTypes.set(action.payload.type, action.payload);
2092
+ return { ...state, nodeTypes: newNodeTypes };
2093
+ }
2094
+ case "MARK_CLEAN":
2095
+ return { ...state, isDirty: false };
2096
+ case "APPLY_NODE_CHANGES": {
2097
+ const newNodes = applyNodeChanges(action.payload, state.nodes);
2098
+ let newSelectedId = state.selectedNodeId;
2099
+ for (const change of action.payload) {
2100
+ if (change.type === "select" && change.selected) {
2101
+ newSelectedId = change.id;
2102
+ }
2103
+ }
2104
+ for (const change of action.payload) {
2105
+ if (change.type === "select" && !change.selected && change.id === newSelectedId) {
2106
+ newSelectedId = null;
2107
+ } else if (change.type === "remove" && change.id === newSelectedId) {
2108
+ newSelectedId = null;
2109
+ }
2110
+ }
2111
+ const hasDirtyChange = action.payload.some(
2112
+ (c) => c.type === "position" || c.type === "dimensions" || c.type === "remove"
2113
+ );
2114
+ return {
2115
+ ...state,
2116
+ nodes: newNodes,
2117
+ selectedNodeId: newSelectedId,
2118
+ isDirty: state.isDirty || hasDirtyChange
2119
+ };
2120
+ }
2121
+ case "APPLY_EDGE_CHANGES": {
2122
+ const newEdges = applyEdgeChanges(action.payload, state.edges);
2123
+ const hasDirtyChange = action.payload.some((c) => c.type === "remove");
2124
+ return {
2125
+ ...state,
2126
+ edges: newEdges,
2127
+ isDirty: state.isDirty || hasDirtyChange
2128
+ };
2129
+ }
2130
+ default:
2131
+ return state;
2132
+ }
2133
+ }
2134
+ var WorkflowEditorContext = createContext2(null);
2135
+ var nodeIdCounter = 0;
2136
+ function generateNodeId(type) {
2137
+ nodeIdCounter++;
2138
+ return `${type.toLowerCase()}-${Date.now()}-${nodeIdCounter}`;
2139
+ }
2140
+ function generateEdgeId(source, target) {
2141
+ return `edge-${source}-${target}-${Date.now()}`;
2142
+ }
2143
+ function WorkflowEditorProvider({
2144
+ children,
2145
+ workflow,
2146
+ nodeTypes,
2147
+ onWorkflowChange,
2148
+ onDirtyChange
2149
+ }) {
2150
+ const [state, dispatch] = useReducer(
2151
+ reducer,
2152
+ { workflow, nodeTypes },
2153
+ ({ workflow: workflow2, nodeTypes: nodeTypes2 }) => createInitialState(workflow2, nodeTypes2)
2154
+ );
2155
+ useEffect4(() => {
2156
+ if (workflow) {
2157
+ dispatch({ type: "LOAD_WORKFLOW", payload: workflow });
2158
+ }
2159
+ }, [workflow]);
2160
+ useEffect4(() => {
2161
+ onDirtyChange?.(state.isDirty);
2162
+ }, [state.isDirty, onDirtyChange]);
2163
+ useEffect4(() => {
2164
+ if (state.isDirty && onWorkflowChange) {
2165
+ const currentWorkflow = getWorkflowImpl();
2166
+ onWorkflowChange(currentWorkflow);
2167
+ }
2168
+ }, [state.nodes, state.edges, state.options, state.name]);
2169
+ const getWorkflowImpl = useCallback2(() => {
2170
+ return {
2171
+ id: state.workflow?.id ?? "",
2172
+ name: state.name,
2173
+ flow: {
2174
+ nodes: state.nodes,
2175
+ edges: state.edges
2176
+ },
2177
+ options: state.options
2178
+ };
2179
+ }, [state.workflow?.id, state.name, state.nodes, state.edges, state.options]);
2180
+ const loadWorkflow = useCallback2((workflow2) => {
2181
+ dispatch({ type: "LOAD_WORKFLOW", payload: workflow2 });
2182
+ }, []);
2183
+ const resetWorkflow = useCallback2(() => {
2184
+ dispatch({ type: "RESET_WORKFLOW" });
2185
+ }, []);
2186
+ const addNode = useCallback2(
2187
+ (type, position) => {
2188
+ const nodeTypeDef = state.nodeTypes.get(type);
2189
+ if (!nodeTypeDef) {
2190
+ console.warn(`Unknown node type: ${type}`);
2191
+ return;
2192
+ }
2193
+ const newNode = {
2194
+ id: generateNodeId(type),
2195
+ type,
2196
+ position,
2197
+ data: { ...nodeTypeDef.defaultData }
2198
+ };
2199
+ dispatch({ type: "ADD_NODE", payload: newNode });
2200
+ },
2201
+ [state.nodeTypes]
2202
+ );
2203
+ const updateNode = useCallback2(
2204
+ (nodeId, data) => {
2205
+ dispatch({ type: "UPDATE_NODE", payload: { nodeId, data } });
2206
+ },
2207
+ []
2208
+ );
2209
+ const updateNodePosition = useCallback2(
2210
+ (nodeId, position) => {
2211
+ dispatch({ type: "UPDATE_NODE_POSITION", payload: { nodeId, position } });
2212
+ },
2213
+ []
2214
+ );
2215
+ const removeNode = useCallback2((nodeId) => {
2216
+ dispatch({ type: "REMOVE_NODE", payload: nodeId });
2217
+ }, []);
2218
+ const selectNode = useCallback2((nodeId) => {
2219
+ dispatch({ type: "SELECT_NODE", payload: nodeId });
2220
+ }, []);
2221
+ const addEdge = useCallback2(
2222
+ (connection) => {
2223
+ const newEdge = {
2224
+ id: generateEdgeId(connection.source, connection.target),
2225
+ source: connection.source,
2226
+ target: connection.target,
2227
+ sourceHandle: connection.sourceHandle,
2228
+ targetHandle: connection.targetHandle
2229
+ };
2230
+ dispatch({ type: "ADD_EDGE", payload: newEdge });
2231
+ },
2232
+ []
2233
+ );
2234
+ const removeEdge = useCallback2((edgeId) => {
2235
+ dispatch({ type: "REMOVE_EDGE", payload: edgeId });
2236
+ }, []);
2237
+ const setName = useCallback2((name) => {
2238
+ dispatch({ type: "SET_NAME", payload: name });
2239
+ }, []);
2240
+ const setOptions = useCallback2((options) => {
2241
+ dispatch({ type: "SET_OPTIONS", payload: options });
2242
+ }, []);
2243
+ const registerNodeType = useCallback2((definition) => {
2244
+ dispatch({ type: "REGISTER_NODE_TYPE", payload: definition });
2245
+ }, []);
2246
+ const getWorkflow = useCallback2(() => getWorkflowImpl(), [getWorkflowImpl]);
2247
+ const markClean = useCallback2(() => {
2248
+ dispatch({ type: "MARK_CLEAN" });
2249
+ }, []);
2250
+ const onNodesChange = useCallback2((changes) => {
2251
+ dispatch({ type: "APPLY_NODE_CHANGES", payload: changes });
2252
+ }, []);
2253
+ const onEdgesChange = useCallback2((changes) => {
2254
+ dispatch({ type: "APPLY_EDGE_CHANGES", payload: changes });
2255
+ }, []);
2256
+ const onConnect = useCallback2(
2257
+ (connection) => {
2258
+ const conn = connection;
2259
+ if (conn.source && conn.target) {
2260
+ addEdge({
2261
+ source: conn.source,
2262
+ target: conn.target,
2263
+ sourceHandle: conn.sourceHandle ?? void 0,
2264
+ targetHandle: conn.targetHandle ?? void 0
2265
+ });
2266
+ }
2267
+ },
2268
+ [addEdge]
2269
+ );
2270
+ const value = useMemo2(
2271
+ () => ({
2272
+ // State
2273
+ workflow: state.workflow,
2274
+ nodes: state.nodes,
2275
+ edges: state.edges,
2276
+ options: state.options,
2277
+ name: state.name,
2278
+ selectedNodeId: state.selectedNodeId,
2279
+ isDirty: state.isDirty,
2280
+ nodeTypes: state.nodeTypes,
2281
+ // Actions
2282
+ loadWorkflow,
2283
+ resetWorkflow,
2284
+ addNode,
2285
+ updateNode,
2286
+ updateNodePosition,
2287
+ removeNode,
2288
+ selectNode,
2289
+ addEdge,
2290
+ removeEdge,
2291
+ setName,
2292
+ setOptions,
2293
+ registerNodeType,
2294
+ getWorkflow,
2295
+ markClean,
2296
+ onNodesChange,
2297
+ onEdgesChange,
2298
+ onConnect
2299
+ }),
2300
+ [
2301
+ state,
2302
+ loadWorkflow,
2303
+ resetWorkflow,
2304
+ addNode,
2305
+ updateNode,
2306
+ updateNodePosition,
2307
+ removeNode,
2308
+ selectNode,
2309
+ addEdge,
2310
+ removeEdge,
2311
+ setName,
2312
+ setOptions,
2313
+ registerNodeType,
2314
+ getWorkflow,
2315
+ markClean,
2316
+ onNodesChange,
2317
+ onEdgesChange,
2318
+ onConnect
2319
+ ]
2320
+ );
2321
+ return /* @__PURE__ */ jsx29(WorkflowEditorContext.Provider, { value, children });
2322
+ }
2323
+ function useWorkflowEditorContext() {
2324
+ const context = useContext2(WorkflowEditorContext);
2325
+ if (!context) {
2326
+ throw new Error(
2327
+ "useWorkflowEditorContext must be used within a WorkflowEditorProvider"
2328
+ );
2329
+ }
2330
+ return context;
2331
+ }
2332
+
2333
+ // src/components/WorkflowEditor.tsx
2334
+ import { jsx as jsx30 } from "react/jsx-runtime";
2335
+ function WorkflowEditor({
2336
+ children,
2337
+ workflow,
2338
+ nodeTypes,
2339
+ onWorkflowChange,
2340
+ onDirtyChange,
2341
+ translationFn,
2342
+ translations
2343
+ }) {
2344
+ return /* @__PURE__ */ jsx30(ReactFlowProvider, { children: /* @__PURE__ */ jsx30(
2345
+ TranslationProvider,
2346
+ {
2347
+ translationFn,
2348
+ translations,
2349
+ children: /* @__PURE__ */ jsx30(
2350
+ WorkflowEditorProvider,
2351
+ {
2352
+ workflow,
2353
+ nodeTypes,
2354
+ onWorkflowChange,
2355
+ onDirtyChange,
2356
+ children
2357
+ }
2358
+ )
2359
+ }
2360
+ ) });
2361
+ }
2362
+
2363
+ // src/hooks/useNodeRegistry.ts
2364
+ import { useMemo as useMemo3 } from "react";
2365
+ function useNodeRegistry() {
2366
+ const context = useWorkflowEditorContext();
2367
+ const reactFlowNodeTypes = useMemo3(() => {
2368
+ const types = {};
2369
+ context.nodeTypes.forEach((def, type) => {
2370
+ types[type] = def.ViewComponent;
2371
+ });
2372
+ return types;
2373
+ }, [context.nodeTypes]);
2374
+ const nodeTypesList = useMemo3(
2375
+ () => Array.from(context.nodeTypes.values()),
2376
+ [context.nodeTypes]
2377
+ );
2378
+ return {
2379
+ /** Map of node type definitions */
2380
+ nodeTypes: context.nodeTypes,
2381
+ /** Array of node type definitions */
2382
+ nodeTypesList,
2383
+ /** ReactFlow-compatible nodeTypes object */
2384
+ reactFlowNodeTypes,
2385
+ /** Register a new node type */
2386
+ registerNodeType: context.registerNodeType,
2387
+ /** Get a specific node type definition */
2388
+ getNodeType: (type) => context.nodeTypes.get(type)
2389
+ };
2390
+ }
2391
+
2392
+ // src/hooks/useDragAndDrop.ts
2393
+ import { useCallback as useCallback3 } from "react";
2394
+ import { useReactFlow } from "@xyflow/react";
2395
+ function useDragAndDrop() {
2396
+ const { addNode } = useWorkflowEditorContext();
2397
+ const reactFlowInstance = useReactFlow();
2398
+ const onDragStart = useCallback3(
2399
+ (event, nodeType) => {
2400
+ event.dataTransfer.setData("application/reactflow", nodeType);
2401
+ event.dataTransfer.effectAllowed = "move";
2402
+ },
2403
+ []
2404
+ );
2405
+ const onDragOver = useCallback3((event) => {
2406
+ event.preventDefault();
2407
+ event.dataTransfer.dropEffect = "move";
2408
+ }, []);
2409
+ const onDrop = useCallback3(
2410
+ (event) => {
2411
+ event.preventDefault();
2412
+ const type = event.dataTransfer.getData("application/reactflow");
2413
+ if (!type) return;
2414
+ const position = reactFlowInstance.screenToFlowPosition({
2415
+ x: event.clientX,
2416
+ y: event.clientY
2417
+ });
2418
+ addNode(type, position);
2419
+ },
2420
+ [addNode, reactFlowInstance]
2421
+ );
2422
+ return {
2423
+ onDragStart,
2424
+ onDragOver,
2425
+ onDrop
2426
+ };
2427
+ }
2428
+
2429
+ // src/components/NodesPanel.tsx
2430
+ import { jsx as jsx31, jsxs as jsxs15 } from "react/jsx-runtime";
2431
+ var panelStyle = {
2432
+ backgroundColor: "var(--of-panel-bg, #fff)",
2433
+ borderRadius: "var(--of-panel-radius, 8px)",
2434
+ padding: "var(--of-panel-padding, 12px)",
2435
+ boxShadow: "var(--of-panel-shadow, 0 2px 8px rgba(0,0,0,0.1))",
2436
+ minWidth: "180px"
2437
+ };
2438
+ var titleStyle2 = {
2439
+ fontSize: "var(--of-panel-title-size, 12px)",
2440
+ fontWeight: "var(--of-font-weight-semibold, 600)",
2441
+ color: "var(--of-panel-title-color, #374151)",
2442
+ marginBottom: "var(--of-spacing-5, 12px)",
2443
+ textTransform: "uppercase",
2444
+ letterSpacing: "0.5px"
2445
+ };
2446
+ var itemStyle = {
2447
+ display: "flex",
2448
+ alignItems: "center",
2449
+ gap: "var(--of-spacing-3, 8px)",
2450
+ padding: "var(--of-spacing-3, 8px) var(--of-spacing-4, 10px)",
2451
+ borderRadius: "var(--of-radius-md, 6px)",
2452
+ cursor: "grab",
2453
+ marginBottom: "var(--of-spacing-1, 4px)",
2454
+ border: "1px solid var(--of-color-border-secondary, #E5E7EB)",
2455
+ backgroundColor: "var(--of-color-bg-primary, #fff)",
2456
+ transition: "all var(--of-transition-fast, 0.15s)"
2457
+ };
2458
+ var itemIconStyle = {
2459
+ width: "24px",
2460
+ height: "24px",
2461
+ display: "flex",
2462
+ alignItems: "center",
2463
+ justifyContent: "center",
2464
+ flexShrink: 0
2465
+ };
2466
+ var itemTextStyle = {
2467
+ flex: 1
2468
+ };
2469
+ var itemLabelStyle = {
2470
+ fontSize: "var(--of-field-font-size, 13px)",
2471
+ fontWeight: "var(--of-font-weight-medium, 500)",
2472
+ color: "var(--of-color-text-primary, #111827)"
2473
+ };
2474
+ var itemDescStyle = {
2475
+ fontSize: "var(--of-font-size-xs, 11px)",
2476
+ color: "var(--of-color-text-tertiary, #6B7280)",
2477
+ marginTop: "2px"
2478
+ };
2479
+ function NodeItem({
2480
+ nodeType,
2481
+ onDragStart,
2482
+ showDescription = true
2483
+ }) {
2484
+ const IconComponent = nodeType.Icon;
2485
+ return /* @__PURE__ */ jsxs15(
2486
+ "div",
2487
+ {
2488
+ style: itemStyle,
2489
+ draggable: true,
2490
+ onDragStart: (e) => onDragStart(e, nodeType.type),
2491
+ onMouseEnter: (e) => {
2492
+ e.currentTarget.style.borderColor = "var(--of-color-border-focus, #3B82F6)";
2493
+ e.currentTarget.style.backgroundColor = "var(--of-color-bg-secondary, #F9FAFB)";
2494
+ },
2495
+ onMouseLeave: (e) => {
2496
+ e.currentTarget.style.borderColor = "var(--of-color-border-secondary, #E5E7EB)";
2497
+ e.currentTarget.style.backgroundColor = "var(--of-color-bg-primary, #fff)";
2498
+ },
2499
+ children: [
2500
+ /* @__PURE__ */ jsx31("div", { style: itemIconStyle, children: IconComponent ? /* @__PURE__ */ jsx31(IconComponent, { size: 24 }) : null }),
2501
+ /* @__PURE__ */ jsxs15("div", { style: itemTextStyle, children: [
2502
+ /* @__PURE__ */ jsx31("div", { style: itemLabelStyle, children: nodeType.label }),
2503
+ showDescription && nodeType.description && /* @__PURE__ */ jsx31("div", { style: itemDescStyle, children: nodeType.description })
2504
+ ] })
2505
+ ]
2506
+ }
2507
+ );
2508
+ }
2509
+ function NodesPanel({
2510
+ className,
2511
+ showDescriptions = true,
2512
+ filter,
2513
+ renderItem
2514
+ }) {
2515
+ const { nodeTypesList } = useNodeRegistry();
2516
+ const { onDragStart } = useDragAndDrop();
2517
+ const t = useTranslation();
2518
+ const filteredNodeTypes = filter ? nodeTypesList.filter(filter) : nodeTypesList;
2519
+ return /* @__PURE__ */ jsxs15("div", { style: panelStyle, className, children: [
2520
+ /* @__PURE__ */ jsx31("div", { style: titleStyle2, children: t("panels.nodes.title") }),
2521
+ filteredNodeTypes.map(
2522
+ (nodeType) => renderItem ? /* @__PURE__ */ jsx31(
2523
+ "div",
2524
+ {
2525
+ draggable: true,
2526
+ onDragStart: (e) => onDragStart(e, nodeType.type),
2527
+ children: renderItem(nodeType)
2528
+ },
2529
+ nodeType.type
2530
+ ) : /* @__PURE__ */ jsx31(
2531
+ NodeItem,
2532
+ {
2533
+ nodeType,
2534
+ onDragStart,
2535
+ showDescription: showDescriptions
2536
+ },
2537
+ nodeType.type
2538
+ )
2539
+ )
2540
+ ] });
2541
+ }
2542
+
2543
+ // src/hooks/useSelectedNode.ts
2544
+ function useSelectedNode() {
2545
+ const context = useWorkflowEditorContext();
2546
+ const selectedNode = context.selectedNodeId ? context.nodes.find((n) => n.id === context.selectedNodeId) ?? null : null;
2547
+ const nodeType = selectedNode?.type ? context.nodeTypes.get(selectedNode.type) : void 0;
2548
+ return {
2549
+ /** The currently selected node, or null if none */
2550
+ selectedNode,
2551
+ /** The ID of the selected node */
2552
+ selectedNodeId: context.selectedNodeId,
2553
+ /** The node type definition for the selected node */
2554
+ nodeType,
2555
+ /** Select a node by ID */
2556
+ selectNode: context.selectNode,
2557
+ /** Update the selected node's data */
2558
+ updateSelectedNode: (data) => {
2559
+ if (context.selectedNodeId) {
2560
+ context.updateNode(context.selectedNodeId, data);
2561
+ }
2562
+ },
2563
+ /** Remove the selected node */
2564
+ removeSelectedNode: () => {
2565
+ if (context.selectedNodeId) {
2566
+ context.removeNode(context.selectedNodeId);
2567
+ }
2568
+ }
2569
+ };
2570
+ }
2571
+
2572
+ // src/components/DetailPanel.tsx
2573
+ import { jsx as jsx32, jsxs as jsxs16 } from "react/jsx-runtime";
2574
+ var panelStyle2 = {
2575
+ backgroundColor: "var(--of-panel-bg, #fff)",
2576
+ borderRadius: "var(--of-panel-radius, 8px)",
2577
+ padding: "var(--of-panel-padding, 12px)",
2578
+ boxShadow: "var(--of-panel-shadow, 0 2px 8px rgba(0,0,0,0.1))",
2579
+ minWidth: "280px",
2580
+ maxWidth: "320px"
2581
+ };
2582
+ var titleStyle3 = {
2583
+ fontSize: "var(--of-panel-title-size, 12px)",
2584
+ fontWeight: "var(--of-font-weight-semibold, 600)",
2585
+ color: "var(--of-panel-title-color, #374151)",
2586
+ marginBottom: "var(--of-spacing-5, 12px)",
2587
+ textTransform: "uppercase",
2588
+ letterSpacing: "0.5px"
2589
+ };
2590
+ var emptyStyle2 = {
2591
+ fontSize: "var(--of-field-font-size, 13px)",
2592
+ color: "var(--of-color-text-tertiary, #6B7280)",
2593
+ textAlign: "center",
2594
+ padding: "var(--of-spacing-7, 20px) 0"
2595
+ };
2596
+ var headerStyle4 = {
2597
+ display: "flex",
2598
+ alignItems: "center",
2599
+ gap: "var(--of-spacing-3, 8px)",
2600
+ marginBottom: "var(--of-spacing-6, 16px)",
2601
+ paddingBottom: "var(--of-spacing-5, 12px)",
2602
+ borderBottom: "1px solid var(--of-color-border-secondary, #E5E7EB)"
2603
+ };
2604
+ var nodeTypeStyle = {
2605
+ fontSize: "var(--of-font-size-lg, 14px)",
2606
+ fontWeight: "var(--of-font-weight-semibold, 600)",
2607
+ color: "var(--of-color-text-primary, #111827)"
2608
+ };
2609
+ var nodeIdStyle = {
2610
+ fontSize: "var(--of-font-size-xs, 11px)",
2611
+ color: "var(--of-color-text-muted, #9CA3AF)",
2612
+ fontFamily: "var(--of-font-family-mono, monospace)"
2613
+ };
2614
+ var deleteButtonStyle = {
2615
+ marginLeft: "auto",
2616
+ padding: "var(--of-spacing-1, 4px) var(--of-spacing-3, 8px)",
2617
+ fontSize: "var(--of-font-size-xs, 11px)",
2618
+ color: "var(--of-button-danger-color, #DC2626)",
2619
+ backgroundColor: "var(--of-button-danger-bg, #FEE2E2)",
2620
+ border: "none",
2621
+ borderRadius: "var(--of-radius-sm, 4px)",
2622
+ cursor: "pointer"
2623
+ };
2624
+ function DetailPanel({
2625
+ className,
2626
+ emptyMessage,
2627
+ showNodeType = true,
2628
+ showNodeId = false
2629
+ }) {
2630
+ const { selectedNode, nodeType, updateSelectedNode, removeSelectedNode } = useSelectedNode();
2631
+ const t = useTranslation();
2632
+ if (!selectedNode || !nodeType) {
2633
+ return /* @__PURE__ */ jsxs16("div", { style: panelStyle2, className, children: [
2634
+ /* @__PURE__ */ jsx32("div", { style: titleStyle3, children: t("panels.detail.title") }),
2635
+ /* @__PURE__ */ jsx32("div", { style: emptyStyle2, children: emptyMessage ?? t("panels.detail.emptyMessage") })
2636
+ ] });
2637
+ }
2638
+ const DetailComponent = nodeType.DetailComponent;
2639
+ return /* @__PURE__ */ jsxs16("div", { style: panelStyle2, className, children: [
2640
+ /* @__PURE__ */ jsx32("div", { style: titleStyle3, children: t("panels.detail.title") }),
2641
+ /* @__PURE__ */ jsxs16("div", { style: headerStyle4, children: [
2642
+ /* @__PURE__ */ jsxs16("div", { children: [
2643
+ showNodeType && /* @__PURE__ */ jsx32("div", { style: nodeTypeStyle, children: nodeType.label }),
2644
+ showNodeId && /* @__PURE__ */ jsx32("div", { style: nodeIdStyle, children: selectedNode.id })
2645
+ ] }),
2646
+ /* @__PURE__ */ jsx32(
2647
+ "button",
2648
+ {
2649
+ style: deleteButtonStyle,
2650
+ onClick: removeSelectedNode,
2651
+ title: t("panels.detail.deleteTitle"),
2652
+ children: t("panels.detail.delete")
2653
+ }
2654
+ )
2655
+ ] }),
2656
+ /* @__PURE__ */ jsx32(
2657
+ DetailComponent,
2658
+ {
2659
+ node: selectedNode,
2660
+ onChange: (data) => updateSelectedNode(data)
2661
+ }
2662
+ )
2663
+ ] });
2664
+ }
2665
+
2666
+ // src/components/OptionsPanel.tsx
2667
+ import { jsx as jsx33, jsxs as jsxs17 } from "react/jsx-runtime";
2668
+ var panelStyle3 = {
2669
+ backgroundColor: "var(--of-panel-bg, #fff)",
2670
+ borderRadius: "var(--of-panel-radius, 8px)",
2671
+ padding: "var(--of-panel-padding, 12px)",
2672
+ boxShadow: "var(--of-panel-shadow, 0 2px 8px rgba(0,0,0,0.1))",
2673
+ minWidth: "220px"
2674
+ };
2675
+ var titleStyle4 = {
2676
+ fontSize: "var(--of-panel-title-size, 12px)",
2677
+ fontWeight: "var(--of-font-weight-semibold, 600)",
2678
+ color: "var(--of-panel-title-color, #374151)",
2679
+ marginBottom: "var(--of-spacing-5, 12px)",
2680
+ textTransform: "uppercase",
2681
+ letterSpacing: "0.5px"
2682
+ };
2683
+ function OptionsPanel({
2684
+ className,
2685
+ showFrequency = true,
2686
+ customOptions
2687
+ }) {
2688
+ const { options, setOptions } = useWorkflowEditorContext();
2689
+ const t = useTranslation();
2690
+ const frequencyOptions = [
2691
+ { value: "one_time", label: t("panels.options.frequencyOneTime") },
2692
+ { value: "every_rematch", label: t("panels.options.frequencyEveryRematch") }
2693
+ ];
2694
+ const handleFrequencyTypeChange = (type) => {
2695
+ const newFrequency = {
2696
+ type
2697
+ };
2698
+ if (type === "every_rematch") {
2699
+ newFrequency.interval = options.frequency?.interval ?? 3600;
2700
+ }
2701
+ setOptions({ ...options, frequency: newFrequency });
2702
+ };
2703
+ const handleIntervalChange = (ms) => {
2704
+ if (options.frequency) {
2705
+ const intervalInSeconds = Math.round(ms / 1e3);
2706
+ setOptions({
2707
+ ...options,
2708
+ frequency: { ...options.frequency, interval: intervalInSeconds }
2709
+ });
2710
+ }
2711
+ };
2712
+ return /* @__PURE__ */ jsxs17("div", { style: panelStyle3, className, children: [
2713
+ /* @__PURE__ */ jsx33("div", { style: titleStyle4, children: t("panels.options.title") }),
2714
+ showFrequency && /* @__PURE__ */ jsxs17(FieldGroup, { label: t("panels.options.frequencyGroup"), children: [
2715
+ /* @__PURE__ */ jsx33(
2716
+ SelectField,
2717
+ {
2718
+ label: t("panels.options.frequencyTypeLabel"),
2719
+ value: options.frequency?.type ?? "one_time",
2720
+ options: frequencyOptions,
2721
+ onChange: handleFrequencyTypeChange,
2722
+ hint: t("panels.options.frequencyTypeHint")
2723
+ }
2724
+ ),
2725
+ options.frequency?.type === "every_rematch" && /* @__PURE__ */ jsx33(
2726
+ DurationField,
2727
+ {
2728
+ label: t("panels.options.intervalLabel"),
2729
+ value: (options.frequency?.interval ?? 3600) * 1e3,
2730
+ onChange: handleIntervalChange,
2731
+ hint: t("panels.options.intervalHint")
2732
+ }
2733
+ )
2734
+ ] }),
2735
+ customOptions
2736
+ ] });
2737
+ }
2738
+
2739
+ // src/components/ControlPanel.tsx
2740
+ import { useState as useState6 } from "react";
2741
+ import { jsx as jsx34, jsxs as jsxs18 } from "react/jsx-runtime";
2742
+ var panelStyle4 = {
2743
+ backgroundColor: "var(--of-panel-bg, #fff)",
2744
+ borderRadius: "var(--of-panel-radius, 8px)",
2745
+ padding: "var(--of-panel-padding, 12px)",
2746
+ boxShadow: "var(--of-panel-shadow, 0 2px 8px rgba(0,0,0,0.1))",
2747
+ minWidth: "220px"
2748
+ };
2749
+ var titleStyle5 = {
2750
+ fontSize: "var(--of-panel-title-size, 12px)",
2751
+ fontWeight: "var(--of-font-weight-semibold, 600)",
2752
+ color: "var(--of-panel-title-color, #374151)",
2753
+ marginBottom: "var(--of-spacing-5, 12px)",
2754
+ textTransform: "uppercase",
2755
+ letterSpacing: "0.5px"
2756
+ };
2757
+ var actionsStyle = {
2758
+ display: "flex",
2759
+ gap: "var(--of-spacing-3, 8px)",
2760
+ marginTop: "var(--of-spacing-5, 12px)"
2761
+ };
2762
+ var buttonStyle = {
2763
+ padding: "var(--of-button-padding, 8px 16px)",
2764
+ fontSize: "var(--of-field-font-size, 13px)",
2765
+ fontWeight: "var(--of-font-weight-medium, 500)",
2766
+ borderRadius: "var(--of-button-radius, 6px)",
2767
+ cursor: "pointer",
2768
+ border: "none",
2769
+ transition: "all var(--of-transition-fast, 0.15s)"
2770
+ };
2771
+ var saveButtonStyle = {
2772
+ ...buttonStyle,
2773
+ backgroundColor: "var(--of-button-primary-bg, #3B82F6)",
2774
+ color: "var(--of-button-primary-color, #fff)"
2775
+ };
2776
+ var saveButtonDisabledStyle = {
2777
+ ...saveButtonStyle,
2778
+ backgroundColor: "var(--of-button-primary-bg-disabled, #93C5FD)",
2779
+ cursor: "not-allowed"
2780
+ };
2781
+ var statusStyle = {
2782
+ fontSize: "var(--of-font-size-xs, 11px)",
2783
+ color: "var(--of-color-text-tertiary, #6B7280)",
2784
+ marginTop: "var(--of-spacing-3, 8px)"
2785
+ };
2786
+ function ControlPanel({
2787
+ className,
2788
+ showName = true,
2789
+ showSaveButton = true,
2790
+ saveButtonLabel,
2791
+ onSave,
2792
+ renderActions
2793
+ }) {
2794
+ const { name, setName, isDirty, getWorkflow, markClean } = useWorkflowEditorContext();
2795
+ const t = useTranslation();
2796
+ const [isSaving, setIsSaving] = useState6(false);
2797
+ const [saveStatus, setSaveStatus] = useState6(null);
2798
+ const handleSave = async () => {
2799
+ if (!onSave || isSaving) return;
2800
+ setIsSaving(true);
2801
+ setSaveStatus(null);
2802
+ try {
2803
+ await onSave();
2804
+ markClean();
2805
+ setSaveStatus("saved");
2806
+ setTimeout(() => setSaveStatus(null), 2e3);
2807
+ } catch (error) {
2808
+ console.error("Failed to save workflow:", error);
2809
+ setSaveStatus("error");
2810
+ } finally {
2811
+ setIsSaving(false);
2812
+ }
2813
+ };
2814
+ return /* @__PURE__ */ jsxs18("div", { style: panelStyle4, className, children: [
2815
+ /* @__PURE__ */ jsx34("div", { style: titleStyle5, children: t("panels.control.title") }),
2816
+ showName && /* @__PURE__ */ jsx34(
2817
+ TextField,
2818
+ {
2819
+ label: t("panels.control.nameLabel"),
2820
+ value: name,
2821
+ onChange: setName,
2822
+ placeholder: t("panels.control.namePlaceholder")
2823
+ }
2824
+ ),
2825
+ /* @__PURE__ */ jsxs18("div", { style: actionsStyle, children: [
2826
+ showSaveButton && onSave && /* @__PURE__ */ jsx34(
2827
+ "button",
2828
+ {
2829
+ style: isSaving || !isDirty ? saveButtonDisabledStyle : saveButtonStyle,
2830
+ onClick: handleSave,
2831
+ disabled: isSaving || !isDirty,
2832
+ children: isSaving ? t("panels.control.saving") : saveButtonLabel ?? "Save"
2833
+ }
2834
+ ),
2835
+ renderActions?.({ isDirty, workflow: getWorkflow() })
2836
+ ] }),
2837
+ saveStatus && /* @__PURE__ */ jsx34(
2838
+ "div",
2839
+ {
2840
+ style: {
2841
+ ...statusStyle,
2842
+ color: saveStatus === "saved" ? "var(--of-color-status-success, #059669)" : "var(--of-color-status-error, #DC2626)"
2843
+ },
2844
+ children: saveStatus === "saved" ? t("panels.control.savedSuccessfully") : t("panels.control.failedToSave")
2845
+ }
2846
+ ),
2847
+ isDirty && !saveStatus && /* @__PURE__ */ jsx34("div", { style: statusStyle, children: t("panels.control.unsavedChanges") })
2848
+ ] });
2849
+ }
2850
+
2851
+ // src/hooks/useWorkflowEditor.ts
2852
+ function useWorkflowEditor() {
2853
+ const context = useWorkflowEditorContext();
2854
+ return {
2855
+ // State
2856
+ workflow: context.getWorkflow(),
2857
+ nodes: context.nodes,
2858
+ edges: context.edges,
2859
+ options: context.options,
2860
+ name: context.name,
2861
+ isDirty: context.isDirty,
2862
+ selectedNode: context.selectedNodeId ? context.nodes.find((n) => n.id === context.selectedNodeId) ?? null : null,
2863
+ selectedNodeId: context.selectedNodeId,
2864
+ // Actions
2865
+ loadWorkflow: context.loadWorkflow,
2866
+ resetWorkflow: context.resetWorkflow,
2867
+ addNode: context.addNode,
2868
+ updateNode: context.updateNode,
2869
+ removeNode: context.removeNode,
2870
+ selectNode: context.selectNode,
2871
+ addEdge: context.addEdge,
2872
+ removeEdge: context.removeEdge,
2873
+ setName: context.setName,
2874
+ setOptions: context.setOptions,
2875
+ getWorkflow: context.getWorkflow,
2876
+ markClean: context.markClean
2877
+ };
2878
+ }
2879
+
2880
+ // src/hooks/useNodes.ts
2881
+ function useNodes() {
2882
+ const context = useWorkflowEditorContext();
2883
+ return {
2884
+ nodes: context.nodes,
2885
+ onNodesChange: context.onNodesChange,
2886
+ addNode: context.addNode,
2887
+ updateNode: context.updateNode,
2888
+ removeNode: context.removeNode
2889
+ };
2890
+ }
2891
+
2892
+ // src/hooks/useEdges.ts
2893
+ function useEdges() {
2894
+ const context = useWorkflowEditorContext();
2895
+ return {
2896
+ edges: context.edges,
2897
+ onEdgesChange: context.onEdgesChange,
2898
+ onConnect: context.onConnect,
2899
+ addEdge: context.addEdge,
2900
+ removeEdge: context.removeEdge
2901
+ };
2902
+ }
2903
+
2904
+ // src/styles/index.ts
2905
+ function cssVar(name, fallback) {
2906
+ return fallback ? `var(${name}, ${fallback})` : `var(${name})`;
2907
+ }
2908
+ var themeVars = {
2909
+ // Colors
2910
+ color: {
2911
+ bgPrimary: "var(--of-color-bg-primary, #fff)",
2912
+ bgSecondary: "var(--of-color-bg-secondary, #F9FAFB)",
2913
+ bgTertiary: "var(--of-color-bg-tertiary, #F3F4F6)",
2914
+ bgDisabled: "var(--of-color-bg-disabled, #F3F4F6)",
2915
+ textPrimary: "var(--of-color-text-primary, #111827)",
2916
+ textSecondary: "var(--of-color-text-secondary, #374151)",
2917
+ textTertiary: "var(--of-color-text-tertiary, #6B7280)",
2918
+ textMuted: "var(--of-color-text-muted, #9CA3AF)",
2919
+ textInverse: "var(--of-color-text-inverse, #fff)",
2920
+ borderPrimary: "var(--of-color-border-primary, #D1D5DB)",
2921
+ borderSecondary: "var(--of-color-border-secondary, #E5E7EB)",
2922
+ borderFocus: "var(--of-color-border-focus, #3B82F6)",
2923
+ interactivePrimary: "var(--of-color-interactive-primary, #3B82F6)",
2924
+ interactivePrimaryHover: "var(--of-color-interactive-primary-hover, #2563EB)",
2925
+ interactivePrimaryDisabled: "var(--of-color-interactive-primary-disabled, #93C5FD)",
2926
+ statusSuccess: "var(--of-color-status-success, #059669)",
2927
+ statusError: "var(--of-color-status-error, #DC2626)",
2928
+ statusErrorBg: "var(--of-color-status-error-bg, #FEE2E2)",
2929
+ statusWarning: "var(--of-color-status-warning, #FF9800)"
2930
+ },
2931
+ // Node colors
2932
+ node: {
2933
+ trigger: "var(--of-node-trigger-color, #4CAF50)",
2934
+ action: "var(--of-node-action-color, #2196F3)",
2935
+ condition: "var(--of-node-condition-color, #FF9800)",
2936
+ exit: "var(--of-node-exit-color, #F44336)",
2937
+ wait: "var(--of-node-wait-color, #9C27B0)",
2938
+ triggerTimeout: "var(--of-node-trigger-timeout-color, #607D8B)",
2939
+ bg: "var(--of-node-bg, #fff)",
2940
+ contentColor: "var(--of-node-content-color, #666)",
2941
+ shadow: "var(--of-node-shadow, 0 2px 4px rgba(0, 0, 0, 0.1))"
2942
+ },
2943
+ // Spacing
2944
+ spacing: {
2945
+ 1: "var(--of-spacing-1, 4px)",
2946
+ 2: "var(--of-spacing-2, 6px)",
2947
+ 3: "var(--of-spacing-3, 8px)",
2948
+ 4: "var(--of-spacing-4, 10px)",
2949
+ 5: "var(--of-spacing-5, 12px)",
2950
+ 6: "var(--of-spacing-6, 16px)",
2951
+ 7: "var(--of-spacing-7, 20px)"
2952
+ },
2953
+ // Typography
2954
+ font: {
2955
+ sizeXs: "var(--of-font-size-xs, 11px)",
2956
+ sizeSm: "var(--of-font-size-sm, 12px)",
2957
+ sizeMd: "var(--of-font-size-md, 13px)",
2958
+ sizeLg: "var(--of-font-size-lg, 14px)",
2959
+ weightNormal: "var(--of-font-weight-normal, 400)",
2960
+ weightMedium: "var(--of-font-weight-medium, 500)",
2961
+ weightSemibold: "var(--of-font-weight-semibold, 600)",
2962
+ familyBase: "var(--of-font-family-base, system-ui, sans-serif)",
2963
+ familyMono: "var(--of-font-family-mono, monospace)"
2964
+ },
2965
+ // Border radius
2966
+ radius: {
2967
+ sm: "var(--of-radius-sm, 4px)",
2968
+ md: "var(--of-radius-md, 6px)",
2969
+ lg: "var(--of-radius-lg, 8px)"
2970
+ },
2971
+ // Shadows
2972
+ shadow: {
2973
+ panel: "var(--of-shadow-panel, 0 2px 8px rgba(0, 0, 0, 0.1))",
2974
+ node: "var(--of-shadow-node, 0 2px 4px rgba(0, 0, 0, 0.1))"
2975
+ },
2976
+ // Transitions
2977
+ transition: {
2978
+ fast: "var(--of-transition-fast, 0.15s)",
2979
+ normal: "var(--of-transition-normal, 0.2s)"
2980
+ },
2981
+ // Component-specific
2982
+ panel: {
2983
+ bg: "var(--of-panel-bg, #fff)",
2984
+ shadow: "var(--of-panel-shadow, 0 2px 8px rgba(0, 0, 0, 0.1))",
2985
+ radius: "var(--of-panel-radius, 8px)",
2986
+ padding: "var(--of-panel-padding, 12px)",
2987
+ titleColor: "var(--of-panel-title-color, #374151)",
2988
+ titleSize: "var(--of-panel-title-size, 12px)"
2989
+ },
2990
+ field: {
2991
+ bg: "var(--of-field-bg, #fff)",
2992
+ border: "var(--of-field-border, #D1D5DB)",
2993
+ borderFocus: "var(--of-field-border-focus, #3B82F6)",
2994
+ borderError: "var(--of-field-border-error, #DC2626)",
2995
+ radius: "var(--of-field-radius, 6px)",
2996
+ padding: "var(--of-field-padding, 8px 10px)",
2997
+ fontSize: "var(--of-field-font-size, 13px)",
2998
+ labelColor: "var(--of-field-label-color, #374151)",
2999
+ labelSize: "var(--of-field-label-size, 12px)",
3000
+ hintColor: "var(--of-field-hint-color, #6B7280)",
3001
+ errorColor: "var(--of-field-error-color, #DC2626)"
3002
+ },
3003
+ button: {
3004
+ primaryBg: "var(--of-button-primary-bg, #3B82F6)",
3005
+ primaryColor: "var(--of-button-primary-color, #fff)",
3006
+ primaryBgHover: "var(--of-button-primary-bg-hover, #2563EB)",
3007
+ primaryBgDisabled: "var(--of-button-primary-bg-disabled, #93C5FD)",
3008
+ dangerBg: "var(--of-button-danger-bg, #FEE2E2)",
3009
+ dangerColor: "var(--of-button-danger-color, #DC2626)",
3010
+ radius: "var(--of-button-radius, 6px)",
3011
+ padding: "var(--of-button-padding, 8px 16px)"
3012
+ }
3013
+ };
3014
+ export {
3015
+ ActionNodeDetail,
3016
+ ActionNodeView,
3017
+ BaseNodeView,
3018
+ CheckboxField,
3019
+ ConditionBuilder,
3020
+ ConditionBuilderDialog,
3021
+ ConditionNodeDetail,
3022
+ ConditionNodeView,
3023
+ ControlPanel,
3024
+ DetailPanel,
3025
+ DurationField,
3026
+ ExitNodeDetail,
3027
+ ExitNodeView,
3028
+ Field,
3029
+ FieldGroup,
3030
+ JsonField,
3031
+ NodesPanel,
3032
+ NumberField,
3033
+ OptionsPanel,
3034
+ SelectField,
3035
+ TextAreaField,
3036
+ TextField,
3037
+ TranslationProvider,
3038
+ TriggerNodeDetail,
3039
+ TriggerNodeView,
3040
+ TriggerOrTimeoutNodeDetail,
3041
+ TriggerOrTimeoutNodeView,
3042
+ WaitNodeDetail,
3043
+ WaitNodeView,
3044
+ WorkflowEditor,
3045
+ WorkflowEditorProvider,
3046
+ createTranslationFunction,
3047
+ cssVar,
3048
+ defaultNodeTypes,
3049
+ defaultOperators,
3050
+ defaultTranslations,
3051
+ mergeNodeTypes,
3052
+ themeVars,
3053
+ useDragAndDrop,
3054
+ useEdges,
3055
+ useNodeRegistry,
3056
+ useNodes,
3057
+ useSelectedNode,
3058
+ useTranslation,
3059
+ useWorkflowEditor,
3060
+ useWorkflowEditorContext
3061
+ };