@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.mjs ADDED
@@ -0,0 +1,1919 @@
1
+ // src/components/WorkflowEditor.tsx
2
+ import { ReactFlowProvider } from "@xyflow/react";
3
+
4
+ // src/context/WorkflowEditorContext.tsx
5
+ import {
6
+ createContext,
7
+ useContext,
8
+ useReducer,
9
+ useCallback,
10
+ useMemo,
11
+ useEffect as useEffect3
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: "10px 15px",
23
+ borderRadius: "8px",
24
+ border: "2px solid",
25
+ backgroundColor: "#fff",
26
+ minWidth: "150px",
27
+ fontSize: "12px",
28
+ fontFamily: "system-ui, sans-serif"
29
+ };
30
+ var headerStyle = {
31
+ display: "flex",
32
+ alignItems: "center",
33
+ gap: "6px",
34
+ fontWeight: 600,
35
+ marginBottom: "4px"
36
+ };
37
+ var contentStyle = {
38
+ color: "#666",
39
+ fontSize: "11px"
40
+ };
41
+ var handleStyle = {
42
+ width: "10px",
43
+ height: "10px",
44
+ borderRadius: "50%"
45
+ };
46
+ function BaseNodeView({
47
+ label,
48
+ color = "#666",
49
+ icon,
50
+ sourceHandles = [],
51
+ targetHandles = [],
52
+ selected,
53
+ children
54
+ }) {
55
+ return /* @__PURE__ */ jsxs(
56
+ "div",
57
+ {
58
+ style: {
59
+ ...baseStyle,
60
+ borderColor: color,
61
+ boxShadow: selected ? `0 0 0 2px ${color}40` : "0 2px 4px rgba(0,0,0,0.1)"
62
+ },
63
+ children: [
64
+ targetHandles.map((handle, index) => /* @__PURE__ */ jsx(
65
+ Handle,
66
+ {
67
+ type: "target",
68
+ position: Position.Top,
69
+ id: handle.id,
70
+ style: {
71
+ ...handleStyle,
72
+ backgroundColor: color,
73
+ left: `${(index + 1) / (targetHandles.length + 1) * 100}%`
74
+ }
75
+ },
76
+ handle.id
77
+ )),
78
+ /* @__PURE__ */ jsxs("div", { style: headerStyle, children: [
79
+ icon && /* @__PURE__ */ jsx("span", { style: { color }, children: icon }),
80
+ /* @__PURE__ */ jsx("span", { children: label })
81
+ ] }),
82
+ children && /* @__PURE__ */ jsx("div", { style: contentStyle, children }),
83
+ sourceHandles.map((handle, index) => /* @__PURE__ */ jsx(
84
+ Handle,
85
+ {
86
+ type: "source",
87
+ position: Position.Bottom,
88
+ id: handle.id,
89
+ style: {
90
+ ...handleStyle,
91
+ backgroundColor: color,
92
+ left: `${(index + 1) / (sourceHandles.length + 1) * 100}%`
93
+ }
94
+ },
95
+ handle.id
96
+ ))
97
+ ]
98
+ }
99
+ );
100
+ }
101
+
102
+ // src/nodes/views/TriggerNodeView.tsx
103
+ import { jsx as jsx2 } from "react/jsx-runtime";
104
+ var TRIGGER_COLOR = "#4CAF50";
105
+ function TriggerNodeView({ id, data, selected }) {
106
+ const nodeData = data;
107
+ const params = nodeData.params;
108
+ const eventName = params?.event;
109
+ return /* @__PURE__ */ jsx2(
110
+ BaseNodeView,
111
+ {
112
+ id,
113
+ data: nodeData,
114
+ selected,
115
+ label: "Trigger",
116
+ color: TRIGGER_COLOR,
117
+ icon: "\u25B6",
118
+ sourceHandles: [{ id: "output" }],
119
+ targetHandles: [],
120
+ children: eventName ? eventName : /* @__PURE__ */ jsx2("em", { children: "No event set" })
121
+ }
122
+ );
123
+ }
124
+
125
+ // src/nodes/views/ActionNodeView.tsx
126
+ import { jsx as jsx3 } from "react/jsx-runtime";
127
+ var ACTION_COLOR = "#2196F3";
128
+ function ActionNodeView({ id, data, selected }) {
129
+ const nodeData = data;
130
+ const actionName = nodeData.action;
131
+ return /* @__PURE__ */ jsx3(
132
+ BaseNodeView,
133
+ {
134
+ id,
135
+ data: nodeData,
136
+ selected,
137
+ label: "Action",
138
+ color: ACTION_COLOR,
139
+ icon: "\u26A1",
140
+ sourceHandles: [{ id: "output" }],
141
+ targetHandles: [{ id: "input" }],
142
+ children: actionName ? actionName : /* @__PURE__ */ jsx3("em", { children: "No action set" })
143
+ }
144
+ );
145
+ }
146
+
147
+ // src/nodes/views/ConditionNodeView.tsx
148
+ import { jsx as jsx4 } from "react/jsx-runtime";
149
+ var CONDITION_COLOR = "#FF9800";
150
+ function ConditionNodeView({ id, data, selected }) {
151
+ const nodeData = data;
152
+ const conditions = nodeData.conditions;
153
+ const ruleCount = (conditions?.all?.length ?? 0) + (conditions?.any?.length ?? 0);
154
+ return /* @__PURE__ */ jsx4(
155
+ BaseNodeView,
156
+ {
157
+ id,
158
+ data: nodeData,
159
+ selected,
160
+ label: "Condition",
161
+ color: CONDITION_COLOR,
162
+ icon: "?",
163
+ sourceHandles: [
164
+ { id: "true", label: "True" },
165
+ { id: "false", label: "False" }
166
+ ],
167
+ targetHandles: [{ id: "input" }],
168
+ children: ruleCount > 0 ? `${ruleCount} rule${ruleCount > 1 ? "s" : ""}` : /* @__PURE__ */ jsx4("em", { children: "No rules" })
169
+ }
170
+ );
171
+ }
172
+
173
+ // src/nodes/views/ExitNodeView.tsx
174
+ import { jsx as jsx5 } from "react/jsx-runtime";
175
+ var EXIT_COLOR = "#F44336";
176
+ function ExitNodeView({ id, data, selected }) {
177
+ return /* @__PURE__ */ jsx5(
178
+ BaseNodeView,
179
+ {
180
+ id,
181
+ data,
182
+ selected,
183
+ label: "Exit",
184
+ color: EXIT_COLOR,
185
+ icon: "\u23F9",
186
+ sourceHandles: [],
187
+ targetHandles: [{ id: "input" }],
188
+ children: "End workflow"
189
+ }
190
+ );
191
+ }
192
+
193
+ // src/nodes/views/WaitNodeView.tsx
194
+ import { jsx as jsx6 } from "react/jsx-runtime";
195
+ var WAIT_COLOR = "#9C27B0";
196
+ function formatDuration(ms) {
197
+ if (ms < 1e3) return `${ms}ms`;
198
+ if (ms < 6e4) return `${ms / 1e3}s`;
199
+ if (ms < 36e5) return `${ms / 6e4}min`;
200
+ return `${ms / 36e5}h`;
201
+ }
202
+ function WaitNodeView({ id, data, selected }) {
203
+ const nodeData = data;
204
+ const params = nodeData.params;
205
+ const duration = params?.duration;
206
+ return /* @__PURE__ */ jsx6(
207
+ BaseNodeView,
208
+ {
209
+ id,
210
+ data: nodeData,
211
+ selected,
212
+ label: "Wait",
213
+ color: WAIT_COLOR,
214
+ icon: "\u23F1",
215
+ sourceHandles: [{ id: "output" }],
216
+ targetHandles: [{ id: "input" }],
217
+ children: duration != null ? formatDuration(duration) : /* @__PURE__ */ jsx6("em", { children: "No duration set" })
218
+ }
219
+ );
220
+ }
221
+
222
+ // src/nodes/views/TriggerOrTimeoutNodeView.tsx
223
+ import { jsx as jsx7 } from "react/jsx-runtime";
224
+ var TRIGGER_OR_TIMEOUT_COLOR = "#607D8B";
225
+ function formatDuration2(ms) {
226
+ if (ms < 1e3) return `${ms}ms`;
227
+ if (ms < 6e4) return `${ms / 1e3}s`;
228
+ if (ms < 36e5) return `${ms / 6e4}min`;
229
+ return `${ms / 36e5}h`;
230
+ }
231
+ function TriggerOrTimeoutNodeView({ id, data, selected }) {
232
+ const nodeData = data;
233
+ const params = nodeData.params;
234
+ const eventName = params?.event;
235
+ const duration = params?.duration;
236
+ const description = [
237
+ eventName || "event",
238
+ duration != null ? `or ${formatDuration2(duration)}` : ""
239
+ ].filter(Boolean).join(" ");
240
+ return /* @__PURE__ */ jsx7(
241
+ BaseNodeView,
242
+ {
243
+ id,
244
+ data: nodeData,
245
+ selected,
246
+ label: "Trigger or Timeout",
247
+ color: TRIGGER_OR_TIMEOUT_COLOR,
248
+ icon: "\u23F0",
249
+ sourceHandles: [{ id: "output" }],
250
+ targetHandles: [{ id: "input" }],
251
+ children: description || /* @__PURE__ */ jsx7("em", { children: "Not configured" })
252
+ }
253
+ );
254
+ }
255
+
256
+ // src/primitives/Field.tsx
257
+ import { jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
258
+ var fieldStyle = {
259
+ display: "flex",
260
+ flexDirection: "column",
261
+ gap: "4px",
262
+ marginBottom: "12px"
263
+ };
264
+ var labelStyle = {
265
+ fontSize: "12px",
266
+ fontWeight: 500,
267
+ color: "#374151"
268
+ };
269
+ var errorStyle = {
270
+ fontSize: "11px",
271
+ color: "#DC2626"
272
+ };
273
+ var hintStyle = {
274
+ fontSize: "11px",
275
+ color: "#6B7280"
276
+ };
277
+ function Field({ label, children, error, hint }) {
278
+ return /* @__PURE__ */ jsxs2("div", { style: fieldStyle, children: [
279
+ /* @__PURE__ */ jsx8("label", { style: labelStyle, children: label }),
280
+ children,
281
+ error && /* @__PURE__ */ jsx8("span", { style: errorStyle, children: error }),
282
+ hint && !error && /* @__PURE__ */ jsx8("span", { style: hintStyle, children: hint })
283
+ ] });
284
+ }
285
+
286
+ // src/primitives/TextField.tsx
287
+ import { jsx as jsx9 } from "react/jsx-runtime";
288
+ var inputStyle = {
289
+ padding: "8px 10px",
290
+ borderRadius: "6px",
291
+ border: "1px solid #D1D5DB",
292
+ fontSize: "13px",
293
+ outline: "none",
294
+ transition: "border-color 0.15s"
295
+ };
296
+ function TextField({
297
+ label,
298
+ value,
299
+ onChange,
300
+ placeholder,
301
+ disabled,
302
+ error,
303
+ hint
304
+ }) {
305
+ return /* @__PURE__ */ jsx9(Field, { label, error, hint, children: /* @__PURE__ */ jsx9(
306
+ "input",
307
+ {
308
+ type: "text",
309
+ value,
310
+ onChange: (e) => onChange(e.target.value),
311
+ placeholder,
312
+ disabled,
313
+ style: {
314
+ ...inputStyle,
315
+ borderColor: error ? "#DC2626" : "#D1D5DB",
316
+ backgroundColor: disabled ? "#F3F4F6" : "#fff"
317
+ },
318
+ onFocus: (e) => {
319
+ if (!error) e.target.style.borderColor = "#3B82F6";
320
+ },
321
+ onBlur: (e) => {
322
+ if (!error) e.target.style.borderColor = "#D1D5DB";
323
+ }
324
+ }
325
+ ) });
326
+ }
327
+
328
+ // src/primitives/NumberField.tsx
329
+ import { jsx as jsx10 } from "react/jsx-runtime";
330
+ var inputStyle2 = {
331
+ padding: "8px 10px",
332
+ borderRadius: "6px",
333
+ border: "1px solid #D1D5DB",
334
+ fontSize: "13px",
335
+ outline: "none",
336
+ transition: "border-color 0.15s"
337
+ };
338
+ function NumberField({
339
+ label,
340
+ value,
341
+ onChange,
342
+ min,
343
+ max,
344
+ step,
345
+ disabled,
346
+ error,
347
+ hint
348
+ }) {
349
+ return /* @__PURE__ */ jsx10(Field, { label, error, hint, children: /* @__PURE__ */ jsx10(
350
+ "input",
351
+ {
352
+ type: "number",
353
+ value,
354
+ onChange: (e) => onChange(parseFloat(e.target.value) || 0),
355
+ min,
356
+ max,
357
+ step,
358
+ disabled,
359
+ style: {
360
+ ...inputStyle2,
361
+ borderColor: error ? "#DC2626" : "#D1D5DB",
362
+ backgroundColor: disabled ? "#F3F4F6" : "#fff"
363
+ },
364
+ onFocus: (e) => {
365
+ if (!error) e.target.style.borderColor = "#3B82F6";
366
+ },
367
+ onBlur: (e) => {
368
+ if (!error) e.target.style.borderColor = "#D1D5DB";
369
+ }
370
+ }
371
+ ) });
372
+ }
373
+
374
+ // src/primitives/SelectField.tsx
375
+ import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
376
+ var selectStyle = {
377
+ padding: "8px 10px",
378
+ borderRadius: "6px",
379
+ border: "1px solid #D1D5DB",
380
+ fontSize: "13px",
381
+ outline: "none",
382
+ transition: "border-color 0.15s",
383
+ backgroundColor: "#fff",
384
+ cursor: "pointer"
385
+ };
386
+ function SelectField({
387
+ label,
388
+ value,
389
+ options,
390
+ onChange,
391
+ placeholder,
392
+ disabled,
393
+ error,
394
+ hint
395
+ }) {
396
+ return /* @__PURE__ */ jsx11(Field, { label, error, hint, children: /* @__PURE__ */ jsxs3(
397
+ "select",
398
+ {
399
+ value,
400
+ onChange: (e) => onChange(e.target.value),
401
+ disabled,
402
+ style: {
403
+ ...selectStyle,
404
+ borderColor: error ? "#DC2626" : "#D1D5DB",
405
+ backgroundColor: disabled ? "#F3F4F6" : "#fff"
406
+ },
407
+ onFocus: (e) => {
408
+ if (!error) e.target.style.borderColor = "#3B82F6";
409
+ },
410
+ onBlur: (e) => {
411
+ if (!error) e.target.style.borderColor = "#D1D5DB";
412
+ },
413
+ children: [
414
+ placeholder && /* @__PURE__ */ jsx11("option", { value: "", disabled: true, children: placeholder }),
415
+ options.map((option) => /* @__PURE__ */ jsx11("option", { value: option.value, children: option.label }, option.value))
416
+ ]
417
+ }
418
+ ) });
419
+ }
420
+
421
+ // src/primitives/CheckboxField.tsx
422
+ import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
423
+ var containerStyle = {
424
+ display: "flex",
425
+ alignItems: "center",
426
+ gap: "8px",
427
+ marginBottom: "12px",
428
+ cursor: "pointer"
429
+ };
430
+ var checkboxStyle = {
431
+ width: "16px",
432
+ height: "16px",
433
+ cursor: "pointer"
434
+ };
435
+ var labelStyle2 = {
436
+ fontSize: "13px",
437
+ color: "#374151",
438
+ cursor: "pointer"
439
+ };
440
+ function CheckboxField({
441
+ label,
442
+ checked,
443
+ onChange,
444
+ disabled
445
+ }) {
446
+ return /* @__PURE__ */ jsxs4(
447
+ "label",
448
+ {
449
+ style: {
450
+ ...containerStyle,
451
+ cursor: disabled ? "not-allowed" : "pointer",
452
+ opacity: disabled ? 0.6 : 1
453
+ },
454
+ children: [
455
+ /* @__PURE__ */ jsx12(
456
+ "input",
457
+ {
458
+ type: "checkbox",
459
+ checked,
460
+ onChange: (e) => onChange(e.target.checked),
461
+ disabled,
462
+ style: checkboxStyle
463
+ }
464
+ ),
465
+ /* @__PURE__ */ jsx12("span", { style: labelStyle2, children: label })
466
+ ]
467
+ }
468
+ );
469
+ }
470
+
471
+ // src/primitives/TextAreaField.tsx
472
+ import { jsx as jsx13 } from "react/jsx-runtime";
473
+ var textareaStyle = {
474
+ padding: "8px 10px",
475
+ borderRadius: "6px",
476
+ border: "1px solid #D1D5DB",
477
+ fontSize: "13px",
478
+ outline: "none",
479
+ transition: "border-color 0.15s",
480
+ resize: "vertical",
481
+ fontFamily: "inherit"
482
+ };
483
+ function TextAreaField({
484
+ label,
485
+ value,
486
+ onChange,
487
+ rows = 4,
488
+ placeholder,
489
+ disabled,
490
+ error,
491
+ hint
492
+ }) {
493
+ return /* @__PURE__ */ jsx13(Field, { label, error, hint, children: /* @__PURE__ */ jsx13(
494
+ "textarea",
495
+ {
496
+ value,
497
+ onChange: (e) => onChange(e.target.value),
498
+ rows,
499
+ placeholder,
500
+ disabled,
501
+ style: {
502
+ ...textareaStyle,
503
+ borderColor: error ? "#DC2626" : "#D1D5DB",
504
+ backgroundColor: disabled ? "#F3F4F6" : "#fff"
505
+ },
506
+ onFocus: (e) => {
507
+ if (!error) e.target.style.borderColor = "#3B82F6";
508
+ },
509
+ onBlur: (e) => {
510
+ if (!error) e.target.style.borderColor = "#D1D5DB";
511
+ }
512
+ }
513
+ ) });
514
+ }
515
+
516
+ // src/primitives/DurationField.tsx
517
+ import { useState, useEffect } from "react";
518
+ import { jsx as jsx14, jsxs as jsxs5 } from "react/jsx-runtime";
519
+ var units = [
520
+ { value: "ms", label: "Milliseconds", multiplier: 1 },
521
+ { value: "s", label: "Seconds", multiplier: 1e3 },
522
+ { value: "min", label: "Minutes", multiplier: 6e4 },
523
+ { value: "h", label: "Hours", multiplier: 36e5 }
524
+ ];
525
+ function msToUnit(ms) {
526
+ if (ms >= 36e5 && ms % 36e5 === 0) {
527
+ return { value: ms / 36e5, unit: "h" };
528
+ }
529
+ if (ms >= 6e4 && ms % 6e4 === 0) {
530
+ return { value: ms / 6e4, unit: "min" };
531
+ }
532
+ if (ms >= 1e3 && ms % 1e3 === 0) {
533
+ return { value: ms / 1e3, unit: "s" };
534
+ }
535
+ return { value: ms, unit: "ms" };
536
+ }
537
+ var containerStyle2 = {
538
+ display: "flex",
539
+ gap: "8px"
540
+ };
541
+ var inputStyle3 = {
542
+ flex: 1,
543
+ padding: "8px 10px",
544
+ borderRadius: "6px",
545
+ border: "1px solid #D1D5DB",
546
+ fontSize: "13px",
547
+ outline: "none"
548
+ };
549
+ var selectStyle2 = {
550
+ padding: "8px 10px",
551
+ borderRadius: "6px",
552
+ border: "1px solid #D1D5DB",
553
+ fontSize: "13px",
554
+ outline: "none",
555
+ backgroundColor: "#fff"
556
+ };
557
+ function DurationField({
558
+ label,
559
+ value,
560
+ onChange,
561
+ disabled,
562
+ error,
563
+ hint
564
+ }) {
565
+ const initial = msToUnit(value);
566
+ const [displayValue, setDisplayValue] = useState(initial.value);
567
+ const [unit, setUnit] = useState(initial.unit);
568
+ useEffect(() => {
569
+ const converted = msToUnit(value);
570
+ setDisplayValue(converted.value);
571
+ setUnit(converted.unit);
572
+ }, [value]);
573
+ const handleValueChange = (newValue) => {
574
+ setDisplayValue(newValue);
575
+ const unitInfo = units.find((u) => u.value === unit);
576
+ onChange(newValue * unitInfo.multiplier);
577
+ };
578
+ const handleUnitChange = (newUnit) => {
579
+ setUnit(newUnit);
580
+ const unitInfo = units.find((u) => u.value === newUnit);
581
+ onChange(displayValue * unitInfo.multiplier);
582
+ };
583
+ return /* @__PURE__ */ jsx14(Field, { label, error, hint, children: /* @__PURE__ */ jsxs5("div", { style: containerStyle2, children: [
584
+ /* @__PURE__ */ jsx14(
585
+ "input",
586
+ {
587
+ type: "number",
588
+ value: displayValue,
589
+ onChange: (e) => handleValueChange(parseFloat(e.target.value) || 0),
590
+ min: 0,
591
+ disabled,
592
+ style: {
593
+ ...inputStyle3,
594
+ borderColor: error ? "#DC2626" : "#D1D5DB",
595
+ backgroundColor: disabled ? "#F3F4F6" : "#fff"
596
+ }
597
+ }
598
+ ),
599
+ /* @__PURE__ */ jsx14(
600
+ "select",
601
+ {
602
+ value: unit,
603
+ onChange: (e) => handleUnitChange(e.target.value),
604
+ disabled,
605
+ style: {
606
+ ...selectStyle2,
607
+ backgroundColor: disabled ? "#F3F4F6" : "#fff"
608
+ },
609
+ children: units.map((u) => /* @__PURE__ */ jsx14("option", { value: u.value, children: u.label }, u.value))
610
+ }
611
+ )
612
+ ] }) });
613
+ }
614
+
615
+ // src/primitives/JsonField.tsx
616
+ import { useState as useState2, useEffect as useEffect2 } from "react";
617
+ import { jsx as jsx15 } from "react/jsx-runtime";
618
+ var textareaStyle2 = {
619
+ padding: "8px 10px",
620
+ borderRadius: "6px",
621
+ border: "1px solid #D1D5DB",
622
+ fontSize: "12px",
623
+ fontFamily: "monospace",
624
+ outline: "none",
625
+ transition: "border-color 0.15s",
626
+ resize: "vertical"
627
+ };
628
+ function JsonField({
629
+ label,
630
+ value,
631
+ onChange,
632
+ rows = 6,
633
+ disabled,
634
+ error: externalError,
635
+ hint
636
+ }) {
637
+ const [text, setText] = useState2(() => JSON.stringify(value, null, 2));
638
+ const [parseError, setParseError] = useState2(null);
639
+ useEffect2(() => {
640
+ setText(JSON.stringify(value, null, 2));
641
+ setParseError(null);
642
+ }, [value]);
643
+ const handleChange = (newText) => {
644
+ setText(newText);
645
+ try {
646
+ const parsed = JSON.parse(newText);
647
+ setParseError(null);
648
+ onChange(parsed);
649
+ } catch (e) {
650
+ setParseError("Invalid JSON");
651
+ }
652
+ };
653
+ const error = externalError || parseError;
654
+ return /* @__PURE__ */ jsx15(Field, { label, error: error ?? void 0, hint, children: /* @__PURE__ */ jsx15(
655
+ "textarea",
656
+ {
657
+ value: text,
658
+ onChange: (e) => handleChange(e.target.value),
659
+ rows,
660
+ disabled,
661
+ style: {
662
+ ...textareaStyle2,
663
+ borderColor: error ? "#DC2626" : "#D1D5DB",
664
+ backgroundColor: disabled ? "#F3F4F6" : "#fff"
665
+ },
666
+ onFocus: (e) => {
667
+ if (!error) e.target.style.borderColor = "#3B82F6";
668
+ },
669
+ onBlur: (e) => {
670
+ if (!error) e.target.style.borderColor = "#D1D5DB";
671
+ }
672
+ }
673
+ ) });
674
+ }
675
+
676
+ // src/primitives/FieldGroup.tsx
677
+ import { useState as useState3 } from "react";
678
+ import { jsx as jsx16, jsxs as jsxs6 } from "react/jsx-runtime";
679
+ var groupStyle = {
680
+ marginBottom: "16px"
681
+ };
682
+ var headerStyle2 = {
683
+ display: "flex",
684
+ alignItems: "center",
685
+ gap: "6px",
686
+ marginBottom: "12px",
687
+ fontSize: "13px",
688
+ fontWeight: 600,
689
+ color: "#111827"
690
+ };
691
+ var toggleStyle = {
692
+ cursor: "pointer",
693
+ userSelect: "none",
694
+ display: "flex",
695
+ alignItems: "center",
696
+ gap: "6px"
697
+ };
698
+ var contentStyle2 = {
699
+ paddingLeft: "4px"
700
+ };
701
+ function FieldGroup({
702
+ label,
703
+ children,
704
+ collapsible = false,
705
+ defaultCollapsed = false
706
+ }) {
707
+ const [collapsed, setCollapsed] = useState3(defaultCollapsed);
708
+ if (!label) {
709
+ return /* @__PURE__ */ jsx16("div", { style: groupStyle, children });
710
+ }
711
+ if (collapsible) {
712
+ return /* @__PURE__ */ jsxs6("div", { style: groupStyle, children: [
713
+ /* @__PURE__ */ jsxs6(
714
+ "div",
715
+ {
716
+ style: { ...headerStyle2, ...toggleStyle },
717
+ onClick: () => setCollapsed(!collapsed),
718
+ children: [
719
+ /* @__PURE__ */ jsx16("span", { style: { transform: collapsed ? "rotate(-90deg)" : "rotate(0)", transition: "transform 0.15s" }, children: "\u25BC" }),
720
+ label
721
+ ]
722
+ }
723
+ ),
724
+ !collapsed && /* @__PURE__ */ jsx16("div", { style: contentStyle2, children })
725
+ ] });
726
+ }
727
+ return /* @__PURE__ */ jsxs6("div", { style: groupStyle, children: [
728
+ /* @__PURE__ */ jsx16("div", { style: headerStyle2, children: label }),
729
+ /* @__PURE__ */ jsx16("div", { style: contentStyle2, children })
730
+ ] });
731
+ }
732
+
733
+ // src/nodes/details/TriggerNodeDetail.tsx
734
+ import { jsx as jsx17 } from "react/jsx-runtime";
735
+ function TriggerNodeDetail({ node, onChange }) {
736
+ const data = node.data;
737
+ const handleEventChange = (event) => {
738
+ onChange({
739
+ ...data,
740
+ params: { ...data.params, event }
741
+ });
742
+ };
743
+ return /* @__PURE__ */ jsx17(FieldGroup, { label: "Trigger Configuration", children: /* @__PURE__ */ jsx17(
744
+ TextField,
745
+ {
746
+ label: "Event Type",
747
+ value: data.params?.event ?? "",
748
+ onChange: handleEventChange,
749
+ placeholder: "e.g., user.signup, order.placed",
750
+ hint: "The event type that will start this workflow"
751
+ }
752
+ ) });
753
+ }
754
+
755
+ // src/nodes/details/ActionNodeDetail.tsx
756
+ import { jsx as jsx18, jsxs as jsxs7 } from "react/jsx-runtime";
757
+ function ActionNodeDetail({ node, onChange }) {
758
+ const data = node.data;
759
+ const handleActionChange = (action) => {
760
+ onChange({
761
+ ...data,
762
+ action
763
+ });
764
+ };
765
+ const handleParamsChange = (params) => {
766
+ onChange({
767
+ ...data,
768
+ params
769
+ });
770
+ };
771
+ return /* @__PURE__ */ jsxs7(FieldGroup, { label: "Action Configuration", children: [
772
+ /* @__PURE__ */ jsx18(
773
+ TextField,
774
+ {
775
+ label: "Action Name",
776
+ value: data.action ?? "",
777
+ onChange: handleActionChange,
778
+ placeholder: "e.g., sendEmail, createTask",
779
+ hint: "The action to perform when this node is reached"
780
+ }
781
+ ),
782
+ /* @__PURE__ */ jsx18(
783
+ JsonField,
784
+ {
785
+ label: "Parameters",
786
+ value: data.params ?? {},
787
+ onChange: handleParamsChange,
788
+ hint: "JSON object with action parameters",
789
+ rows: 4
790
+ }
791
+ )
792
+ ] });
793
+ }
794
+
795
+ // src/nodes/details/ConditionNodeDetail.tsx
796
+ import { jsx as jsx19 } from "react/jsx-runtime";
797
+ function ConditionNodeDetail({ node, onChange }) {
798
+ const data = node.data;
799
+ const handleConditionsChange = (conditions) => {
800
+ onChange({
801
+ ...data,
802
+ conditions
803
+ });
804
+ };
805
+ return /* @__PURE__ */ jsx19(FieldGroup, { label: "Condition Configuration", children: /* @__PURE__ */ jsx19(
806
+ JsonField,
807
+ {
808
+ label: "Conditions",
809
+ value: data.conditions ?? { all: [] },
810
+ onChange: handleConditionsChange,
811
+ hint: "JSON rules engine format. Use 'all' or 'any' arrays with conditions like: { fact: 'amount', operator: 'greaterThan', value: 100 }",
812
+ rows: 10
813
+ }
814
+ ) });
815
+ }
816
+
817
+ // src/nodes/details/ExitNodeDetail.tsx
818
+ import { jsx as jsx20 } from "react/jsx-runtime";
819
+ function ExitNodeDetail(_props) {
820
+ return /* @__PURE__ */ jsx20(FieldGroup, { label: "Exit Node", children: /* @__PURE__ */ jsx20("p", { style: { fontSize: "13px", color: "#6B7280", margin: 0 }, children: "This node ends the workflow. No configuration needed." }) });
821
+ }
822
+
823
+ // src/nodes/details/WaitNodeDetail.tsx
824
+ import { jsx as jsx21 } from "react/jsx-runtime";
825
+ function WaitNodeDetail({ node, onChange }) {
826
+ const data = node.data;
827
+ const handleDurationChange = (duration) => {
828
+ onChange({
829
+ ...data,
830
+ params: { ...data.params, duration }
831
+ });
832
+ };
833
+ return /* @__PURE__ */ jsx21(FieldGroup, { label: "Wait Configuration", children: /* @__PURE__ */ jsx21(
834
+ DurationField,
835
+ {
836
+ label: "Duration",
837
+ value: data.params?.duration ?? 6e4,
838
+ onChange: handleDurationChange,
839
+ hint: "How long to pause before continuing to the next node"
840
+ }
841
+ ) });
842
+ }
843
+
844
+ // src/nodes/details/TriggerOrTimeoutNodeDetail.tsx
845
+ import { jsx as jsx22, jsxs as jsxs8 } from "react/jsx-runtime";
846
+ function TriggerOrTimeoutNodeDetail({ node, onChange }) {
847
+ const data = node.data;
848
+ const handleEventChange = (event) => {
849
+ onChange({
850
+ ...data,
851
+ params: { ...data.params, event }
852
+ });
853
+ };
854
+ const handleDurationChange = (duration) => {
855
+ onChange({
856
+ ...data,
857
+ params: { ...data.params, duration }
858
+ });
859
+ };
860
+ return /* @__PURE__ */ jsxs8(FieldGroup, { label: "Trigger or Timeout Configuration", children: [
861
+ /* @__PURE__ */ jsx22(
862
+ TextField,
863
+ {
864
+ label: "Event Type",
865
+ value: data.params?.event ?? "",
866
+ onChange: handleEventChange,
867
+ placeholder: "e.g., payment.received",
868
+ hint: "The event type to wait for"
869
+ }
870
+ ),
871
+ /* @__PURE__ */ jsx22(
872
+ DurationField,
873
+ {
874
+ label: "Timeout Duration",
875
+ value: data.params?.duration ?? 6e4,
876
+ onChange: handleDurationChange,
877
+ hint: "Max time to wait before timing out"
878
+ }
879
+ )
880
+ ] });
881
+ }
882
+
883
+ // src/nodes/index.ts
884
+ var defaultNodeTypes = [
885
+ {
886
+ type: "Trigger",
887
+ label: "Trigger",
888
+ description: "Starts the workflow when a specific event occurs",
889
+ color: "#4CAF50",
890
+ defaultData: { params: { event: "" } },
891
+ sourceHandles: [{ id: "output", label: "Next" }],
892
+ targetHandles: [],
893
+ ViewComponent: TriggerNodeView,
894
+ DetailComponent: TriggerNodeDetail
895
+ },
896
+ {
897
+ type: "Action",
898
+ label: "Action",
899
+ description: "Performs an action and continues to the next node",
900
+ color: "#2196F3",
901
+ defaultData: { action: "", params: {} },
902
+ sourceHandles: [{ id: "output", label: "Next" }],
903
+ targetHandles: [{ id: "input", label: "In" }],
904
+ ViewComponent: ActionNodeView,
905
+ DetailComponent: ActionNodeDetail
906
+ },
907
+ {
908
+ type: "Condition",
909
+ label: "Condition",
910
+ description: "Branches the workflow based on conditions",
911
+ color: "#FF9800",
912
+ defaultData: { conditions: { all: [] } },
913
+ sourceHandles: [
914
+ { id: "true", label: "True" },
915
+ { id: "false", label: "False" }
916
+ ],
917
+ targetHandles: [{ id: "input", label: "In" }],
918
+ ViewComponent: ConditionNodeView,
919
+ DetailComponent: ConditionNodeDetail
920
+ },
921
+ {
922
+ type: "Wait",
923
+ label: "Wait",
924
+ description: "Pauses the workflow for a specified duration",
925
+ color: "#9C27B0",
926
+ defaultData: { params: { duration: 6e4 } },
927
+ sourceHandles: [{ id: "output", label: "Next" }],
928
+ targetHandles: [{ id: "input", label: "In" }],
929
+ ViewComponent: WaitNodeView,
930
+ DetailComponent: WaitNodeDetail
931
+ },
932
+ {
933
+ type: "TriggerOrTimeout",
934
+ label: "Trigger or Timeout",
935
+ description: "Waits for an event or times out after a duration",
936
+ color: "#607D8B",
937
+ defaultData: { params: { event: "", duration: 6e4 } },
938
+ sourceHandles: [{ id: "output", label: "Next" }],
939
+ targetHandles: [{ id: "input", label: "In" }],
940
+ ViewComponent: TriggerOrTimeoutNodeView,
941
+ DetailComponent: TriggerOrTimeoutNodeDetail
942
+ },
943
+ {
944
+ type: "Exit",
945
+ label: "Exit",
946
+ description: "Ends the workflow",
947
+ color: "#F44336",
948
+ defaultData: {},
949
+ sourceHandles: [],
950
+ targetHandles: [{ id: "input", label: "In" }],
951
+ ViewComponent: ExitNodeView,
952
+ DetailComponent: ExitNodeDetail
953
+ }
954
+ ];
955
+
956
+ // src/context/WorkflowEditorContext.tsx
957
+ import { jsx as jsx23 } from "react/jsx-runtime";
958
+ function createInitialState(workflow, customNodeTypes) {
959
+ const nodeTypesMap = /* @__PURE__ */ new Map();
960
+ defaultNodeTypes.forEach((def) => nodeTypesMap.set(def.type, def));
961
+ customNodeTypes?.forEach((def) => nodeTypesMap.set(def.type, def));
962
+ if (workflow) {
963
+ return {
964
+ workflow,
965
+ nodes: workflow.flow.nodes,
966
+ edges: workflow.flow.edges,
967
+ options: workflow.options,
968
+ name: workflow.name,
969
+ selectedNodeId: null,
970
+ isDirty: false,
971
+ nodeTypes: nodeTypesMap
972
+ };
973
+ }
974
+ return {
975
+ workflow: null,
976
+ nodes: [],
977
+ edges: [],
978
+ options: { frequency: { type: "one_time" } },
979
+ name: "",
980
+ selectedNodeId: null,
981
+ isDirty: false,
982
+ nodeTypes: nodeTypesMap
983
+ };
984
+ }
985
+ function reducer(state, action) {
986
+ switch (action.type) {
987
+ case "LOAD_WORKFLOW": {
988
+ const workflow = action.payload;
989
+ return {
990
+ ...state,
991
+ workflow,
992
+ nodes: workflow.flow.nodes,
993
+ edges: workflow.flow.edges,
994
+ options: workflow.options,
995
+ name: workflow.name,
996
+ selectedNodeId: null,
997
+ isDirty: false
998
+ };
999
+ }
1000
+ case "RESET_WORKFLOW": {
1001
+ if (state.workflow) {
1002
+ return {
1003
+ ...state,
1004
+ nodes: state.workflow.flow.nodes,
1005
+ edges: state.workflow.flow.edges,
1006
+ options: state.workflow.options,
1007
+ name: state.workflow.name,
1008
+ selectedNodeId: null,
1009
+ isDirty: false
1010
+ };
1011
+ }
1012
+ return {
1013
+ ...state,
1014
+ nodes: [],
1015
+ edges: [],
1016
+ options: { frequency: { type: "one_time" } },
1017
+ name: "",
1018
+ selectedNodeId: null,
1019
+ isDirty: false
1020
+ };
1021
+ }
1022
+ case "SET_NODES":
1023
+ return { ...state, nodes: action.payload, isDirty: true };
1024
+ case "SET_EDGES":
1025
+ return { ...state, edges: action.payload, isDirty: true };
1026
+ case "ADD_NODE":
1027
+ return {
1028
+ ...state,
1029
+ nodes: [...state.nodes, action.payload],
1030
+ isDirty: true
1031
+ };
1032
+ case "UPDATE_NODE": {
1033
+ const { nodeId, data } = action.payload;
1034
+ return {
1035
+ ...state,
1036
+ nodes: state.nodes.map(
1037
+ (node) => node.id === nodeId ? { ...node, data: { ...node.data, ...data } } : node
1038
+ ),
1039
+ isDirty: true
1040
+ };
1041
+ }
1042
+ case "UPDATE_NODE_POSITION": {
1043
+ const { nodeId, position } = action.payload;
1044
+ return {
1045
+ ...state,
1046
+ nodes: state.nodes.map(
1047
+ (node) => node.id === nodeId ? { ...node, position } : node
1048
+ ),
1049
+ isDirty: true
1050
+ };
1051
+ }
1052
+ case "REMOVE_NODE": {
1053
+ const nodeId = action.payload;
1054
+ return {
1055
+ ...state,
1056
+ nodes: state.nodes.filter((node) => node.id !== nodeId),
1057
+ edges: state.edges.filter(
1058
+ (edge) => edge.source !== nodeId && edge.target !== nodeId
1059
+ ),
1060
+ selectedNodeId: state.selectedNodeId === nodeId ? null : state.selectedNodeId,
1061
+ isDirty: true
1062
+ };
1063
+ }
1064
+ case "SELECT_NODE":
1065
+ return { ...state, selectedNodeId: action.payload };
1066
+ case "ADD_EDGE":
1067
+ return {
1068
+ ...state,
1069
+ edges: [...state.edges, action.payload],
1070
+ isDirty: true
1071
+ };
1072
+ case "REMOVE_EDGE":
1073
+ return {
1074
+ ...state,
1075
+ edges: state.edges.filter((edge) => edge.id !== action.payload),
1076
+ isDirty: true
1077
+ };
1078
+ case "SET_NAME":
1079
+ return { ...state, name: action.payload, isDirty: true };
1080
+ case "SET_OPTIONS":
1081
+ return { ...state, options: action.payload, isDirty: true };
1082
+ case "REGISTER_NODE_TYPE": {
1083
+ const newNodeTypes = new Map(state.nodeTypes);
1084
+ newNodeTypes.set(action.payload.type, action.payload);
1085
+ return { ...state, nodeTypes: newNodeTypes };
1086
+ }
1087
+ case "MARK_CLEAN":
1088
+ return { ...state, isDirty: false };
1089
+ case "APPLY_NODE_CHANGES": {
1090
+ const newNodes = applyNodeChanges(action.payload, state.nodes);
1091
+ let newSelectedId = state.selectedNodeId;
1092
+ for (const change of action.payload) {
1093
+ if (change.type === "select" && change.selected) {
1094
+ newSelectedId = change.id;
1095
+ } else if (change.type === "select" && !change.selected && change.id === state.selectedNodeId) {
1096
+ newSelectedId = null;
1097
+ } else if (change.type === "remove" && change.id === state.selectedNodeId) {
1098
+ newSelectedId = null;
1099
+ }
1100
+ }
1101
+ const hasDirtyChange = action.payload.some(
1102
+ (c) => c.type === "position" || c.type === "dimensions" || c.type === "remove"
1103
+ );
1104
+ return {
1105
+ ...state,
1106
+ nodes: newNodes,
1107
+ selectedNodeId: newSelectedId,
1108
+ isDirty: state.isDirty || hasDirtyChange
1109
+ };
1110
+ }
1111
+ case "APPLY_EDGE_CHANGES": {
1112
+ const newEdges = applyEdgeChanges(action.payload, state.edges);
1113
+ const hasDirtyChange = action.payload.some((c) => c.type === "remove");
1114
+ return {
1115
+ ...state,
1116
+ edges: newEdges,
1117
+ isDirty: state.isDirty || hasDirtyChange
1118
+ };
1119
+ }
1120
+ default:
1121
+ return state;
1122
+ }
1123
+ }
1124
+ var WorkflowEditorContext = createContext(null);
1125
+ var nodeIdCounter = 0;
1126
+ function generateNodeId(type) {
1127
+ nodeIdCounter++;
1128
+ return `${type.toLowerCase()}-${Date.now()}-${nodeIdCounter}`;
1129
+ }
1130
+ function generateEdgeId(source, target) {
1131
+ return `edge-${source}-${target}-${Date.now()}`;
1132
+ }
1133
+ function WorkflowEditorProvider({
1134
+ children,
1135
+ workflow,
1136
+ nodeTypes: customNodeTypes,
1137
+ onWorkflowChange,
1138
+ onDirtyChange
1139
+ }) {
1140
+ const [state, dispatch] = useReducer(
1141
+ reducer,
1142
+ { workflow, customNodeTypes },
1143
+ ({ workflow: workflow2, customNodeTypes: customNodeTypes2 }) => createInitialState(workflow2, customNodeTypes2)
1144
+ );
1145
+ useEffect3(() => {
1146
+ if (workflow) {
1147
+ dispatch({ type: "LOAD_WORKFLOW", payload: workflow });
1148
+ }
1149
+ }, [workflow]);
1150
+ useEffect3(() => {
1151
+ onDirtyChange?.(state.isDirty);
1152
+ }, [state.isDirty, onDirtyChange]);
1153
+ useEffect3(() => {
1154
+ if (state.isDirty && onWorkflowChange) {
1155
+ const currentWorkflow = getWorkflowImpl();
1156
+ onWorkflowChange(currentWorkflow);
1157
+ }
1158
+ }, [state.nodes, state.edges, state.options, state.name]);
1159
+ const getWorkflowImpl = useCallback(() => {
1160
+ return {
1161
+ id: state.workflow?.id ?? "",
1162
+ name: state.name,
1163
+ flow: {
1164
+ nodes: state.nodes,
1165
+ edges: state.edges
1166
+ },
1167
+ options: state.options
1168
+ };
1169
+ }, [state.workflow?.id, state.name, state.nodes, state.edges, state.options]);
1170
+ const loadWorkflow = useCallback((workflow2) => {
1171
+ dispatch({ type: "LOAD_WORKFLOW", payload: workflow2 });
1172
+ }, []);
1173
+ const resetWorkflow = useCallback(() => {
1174
+ dispatch({ type: "RESET_WORKFLOW" });
1175
+ }, []);
1176
+ const addNode = useCallback(
1177
+ (type, position) => {
1178
+ const nodeTypeDef = state.nodeTypes.get(type);
1179
+ if (!nodeTypeDef) {
1180
+ console.warn(`Unknown node type: ${type}`);
1181
+ return;
1182
+ }
1183
+ const newNode = {
1184
+ id: generateNodeId(type),
1185
+ type,
1186
+ position,
1187
+ data: { ...nodeTypeDef.defaultData }
1188
+ };
1189
+ dispatch({ type: "ADD_NODE", payload: newNode });
1190
+ },
1191
+ [state.nodeTypes]
1192
+ );
1193
+ const updateNode = useCallback(
1194
+ (nodeId, data) => {
1195
+ dispatch({ type: "UPDATE_NODE", payload: { nodeId, data } });
1196
+ },
1197
+ []
1198
+ );
1199
+ const updateNodePosition = useCallback(
1200
+ (nodeId, position) => {
1201
+ dispatch({ type: "UPDATE_NODE_POSITION", payload: { nodeId, position } });
1202
+ },
1203
+ []
1204
+ );
1205
+ const removeNode = useCallback((nodeId) => {
1206
+ dispatch({ type: "REMOVE_NODE", payload: nodeId });
1207
+ }, []);
1208
+ const selectNode = useCallback((nodeId) => {
1209
+ dispatch({ type: "SELECT_NODE", payload: nodeId });
1210
+ }, []);
1211
+ const addEdge = useCallback(
1212
+ (connection) => {
1213
+ const newEdge = {
1214
+ id: generateEdgeId(connection.source, connection.target),
1215
+ source: connection.source,
1216
+ target: connection.target,
1217
+ sourceHandle: connection.sourceHandle,
1218
+ targetHandle: connection.targetHandle
1219
+ };
1220
+ dispatch({ type: "ADD_EDGE", payload: newEdge });
1221
+ },
1222
+ []
1223
+ );
1224
+ const removeEdge = useCallback((edgeId) => {
1225
+ dispatch({ type: "REMOVE_EDGE", payload: edgeId });
1226
+ }, []);
1227
+ const setName = useCallback((name) => {
1228
+ dispatch({ type: "SET_NAME", payload: name });
1229
+ }, []);
1230
+ const setOptions = useCallback((options) => {
1231
+ dispatch({ type: "SET_OPTIONS", payload: options });
1232
+ }, []);
1233
+ const registerNodeType = useCallback((definition) => {
1234
+ dispatch({ type: "REGISTER_NODE_TYPE", payload: definition });
1235
+ }, []);
1236
+ const getWorkflow = useCallback(() => getWorkflowImpl(), [getWorkflowImpl]);
1237
+ const markClean = useCallback(() => {
1238
+ dispatch({ type: "MARK_CLEAN" });
1239
+ }, []);
1240
+ const onNodesChange = useCallback((changes) => {
1241
+ dispatch({ type: "APPLY_NODE_CHANGES", payload: changes });
1242
+ }, []);
1243
+ const onEdgesChange = useCallback((changes) => {
1244
+ dispatch({ type: "APPLY_EDGE_CHANGES", payload: changes });
1245
+ }, []);
1246
+ const onConnect = useCallback(
1247
+ (connection) => {
1248
+ const conn = connection;
1249
+ if (conn.source && conn.target) {
1250
+ addEdge({
1251
+ source: conn.source,
1252
+ target: conn.target,
1253
+ sourceHandle: conn.sourceHandle ?? void 0,
1254
+ targetHandle: conn.targetHandle ?? void 0
1255
+ });
1256
+ }
1257
+ },
1258
+ [addEdge]
1259
+ );
1260
+ const value = useMemo(
1261
+ () => ({
1262
+ // State
1263
+ workflow: state.workflow,
1264
+ nodes: state.nodes,
1265
+ edges: state.edges,
1266
+ options: state.options,
1267
+ name: state.name,
1268
+ selectedNodeId: state.selectedNodeId,
1269
+ isDirty: state.isDirty,
1270
+ nodeTypes: state.nodeTypes,
1271
+ // Actions
1272
+ loadWorkflow,
1273
+ resetWorkflow,
1274
+ addNode,
1275
+ updateNode,
1276
+ updateNodePosition,
1277
+ removeNode,
1278
+ selectNode,
1279
+ addEdge,
1280
+ removeEdge,
1281
+ setName,
1282
+ setOptions,
1283
+ registerNodeType,
1284
+ getWorkflow,
1285
+ markClean,
1286
+ onNodesChange,
1287
+ onEdgesChange,
1288
+ onConnect
1289
+ }),
1290
+ [
1291
+ state,
1292
+ loadWorkflow,
1293
+ resetWorkflow,
1294
+ addNode,
1295
+ updateNode,
1296
+ updateNodePosition,
1297
+ removeNode,
1298
+ selectNode,
1299
+ addEdge,
1300
+ removeEdge,
1301
+ setName,
1302
+ setOptions,
1303
+ registerNodeType,
1304
+ getWorkflow,
1305
+ markClean,
1306
+ onNodesChange,
1307
+ onEdgesChange,
1308
+ onConnect
1309
+ ]
1310
+ );
1311
+ return /* @__PURE__ */ jsx23(WorkflowEditorContext.Provider, { value, children });
1312
+ }
1313
+ function useWorkflowEditorContext() {
1314
+ const context = useContext(WorkflowEditorContext);
1315
+ if (!context) {
1316
+ throw new Error(
1317
+ "useWorkflowEditorContext must be used within a WorkflowEditorProvider"
1318
+ );
1319
+ }
1320
+ return context;
1321
+ }
1322
+
1323
+ // src/components/WorkflowEditor.tsx
1324
+ import { jsx as jsx24 } from "react/jsx-runtime";
1325
+ function WorkflowEditor({
1326
+ children,
1327
+ workflow,
1328
+ nodeTypes,
1329
+ onWorkflowChange,
1330
+ onDirtyChange
1331
+ }) {
1332
+ return /* @__PURE__ */ jsx24(ReactFlowProvider, { children: /* @__PURE__ */ jsx24(
1333
+ WorkflowEditorProvider,
1334
+ {
1335
+ workflow,
1336
+ nodeTypes,
1337
+ onWorkflowChange,
1338
+ onDirtyChange,
1339
+ children
1340
+ }
1341
+ ) });
1342
+ }
1343
+
1344
+ // src/hooks/useNodeRegistry.ts
1345
+ import { useMemo as useMemo2 } from "react";
1346
+ function useNodeRegistry() {
1347
+ const context = useWorkflowEditorContext();
1348
+ const reactFlowNodeTypes = useMemo2(() => {
1349
+ const types = {};
1350
+ context.nodeTypes.forEach((def, type) => {
1351
+ types[type] = def.ViewComponent;
1352
+ });
1353
+ return types;
1354
+ }, [context.nodeTypes]);
1355
+ const nodeTypesList = useMemo2(
1356
+ () => Array.from(context.nodeTypes.values()),
1357
+ [context.nodeTypes]
1358
+ );
1359
+ return {
1360
+ /** Map of node type definitions */
1361
+ nodeTypes: context.nodeTypes,
1362
+ /** Array of node type definitions */
1363
+ nodeTypesList,
1364
+ /** ReactFlow-compatible nodeTypes object */
1365
+ reactFlowNodeTypes,
1366
+ /** Register a new node type */
1367
+ registerNodeType: context.registerNodeType,
1368
+ /** Get a specific node type definition */
1369
+ getNodeType: (type) => context.nodeTypes.get(type)
1370
+ };
1371
+ }
1372
+
1373
+ // src/hooks/useDragAndDrop.ts
1374
+ import { useCallback as useCallback2 } from "react";
1375
+ import { useReactFlow } from "@xyflow/react";
1376
+ function useDragAndDrop() {
1377
+ const { addNode } = useWorkflowEditorContext();
1378
+ const reactFlowInstance = useReactFlow();
1379
+ const onDragStart = useCallback2(
1380
+ (event, nodeType) => {
1381
+ event.dataTransfer.setData("application/reactflow", nodeType);
1382
+ event.dataTransfer.effectAllowed = "move";
1383
+ },
1384
+ []
1385
+ );
1386
+ const onDragOver = useCallback2((event) => {
1387
+ event.preventDefault();
1388
+ event.dataTransfer.dropEffect = "move";
1389
+ }, []);
1390
+ const onDrop = useCallback2(
1391
+ (event) => {
1392
+ event.preventDefault();
1393
+ const type = event.dataTransfer.getData("application/reactflow");
1394
+ if (!type) return;
1395
+ const position = reactFlowInstance.screenToFlowPosition({
1396
+ x: event.clientX,
1397
+ y: event.clientY
1398
+ });
1399
+ addNode(type, position);
1400
+ },
1401
+ [addNode, reactFlowInstance]
1402
+ );
1403
+ return {
1404
+ onDragStart,
1405
+ onDragOver,
1406
+ onDrop
1407
+ };
1408
+ }
1409
+
1410
+ // src/components/NodesPanel.tsx
1411
+ import { jsx as jsx25, jsxs as jsxs9 } from "react/jsx-runtime";
1412
+ var panelStyle = {
1413
+ backgroundColor: "#fff",
1414
+ borderRadius: "8px",
1415
+ padding: "12px",
1416
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
1417
+ minWidth: "180px"
1418
+ };
1419
+ var titleStyle = {
1420
+ fontSize: "12px",
1421
+ fontWeight: 600,
1422
+ color: "#374151",
1423
+ marginBottom: "12px",
1424
+ textTransform: "uppercase",
1425
+ letterSpacing: "0.5px"
1426
+ };
1427
+ var itemStyle = {
1428
+ display: "flex",
1429
+ alignItems: "center",
1430
+ gap: "8px",
1431
+ padding: "8px 10px",
1432
+ borderRadius: "6px",
1433
+ cursor: "grab",
1434
+ marginBottom: "4px",
1435
+ border: "1px solid #E5E7EB",
1436
+ backgroundColor: "#fff",
1437
+ transition: "all 0.15s"
1438
+ };
1439
+ var itemIconStyle = {
1440
+ width: "24px",
1441
+ height: "24px",
1442
+ borderRadius: "4px",
1443
+ display: "flex",
1444
+ alignItems: "center",
1445
+ justifyContent: "center",
1446
+ color: "#fff",
1447
+ fontSize: "12px",
1448
+ fontWeight: 600
1449
+ };
1450
+ var itemTextStyle = {
1451
+ flex: 1
1452
+ };
1453
+ var itemLabelStyle = {
1454
+ fontSize: "13px",
1455
+ fontWeight: 500,
1456
+ color: "#111827"
1457
+ };
1458
+ var itemDescStyle = {
1459
+ fontSize: "11px",
1460
+ color: "#6B7280",
1461
+ marginTop: "2px"
1462
+ };
1463
+ function NodeItem({
1464
+ nodeType,
1465
+ onDragStart,
1466
+ showDescription = true
1467
+ }) {
1468
+ return /* @__PURE__ */ jsxs9(
1469
+ "div",
1470
+ {
1471
+ style: itemStyle,
1472
+ draggable: true,
1473
+ onDragStart: (e) => onDragStart(e, nodeType.type),
1474
+ onMouseEnter: (e) => {
1475
+ e.currentTarget.style.borderColor = nodeType.color || "#3B82F6";
1476
+ e.currentTarget.style.backgroundColor = "#F9FAFB";
1477
+ },
1478
+ onMouseLeave: (e) => {
1479
+ e.currentTarget.style.borderColor = "#E5E7EB";
1480
+ e.currentTarget.style.backgroundColor = "#fff";
1481
+ },
1482
+ children: [
1483
+ /* @__PURE__ */ jsx25("div", { style: { ...itemIconStyle, backgroundColor: nodeType.color || "#666" }, children: nodeType.label.charAt(0) }),
1484
+ /* @__PURE__ */ jsxs9("div", { style: itemTextStyle, children: [
1485
+ /* @__PURE__ */ jsx25("div", { style: itemLabelStyle, children: nodeType.label }),
1486
+ showDescription && nodeType.description && /* @__PURE__ */ jsx25("div", { style: itemDescStyle, children: nodeType.description })
1487
+ ] })
1488
+ ]
1489
+ }
1490
+ );
1491
+ }
1492
+ function NodesPanel({
1493
+ className,
1494
+ showDescriptions = true,
1495
+ filter,
1496
+ renderItem
1497
+ }) {
1498
+ const { nodeTypesList } = useNodeRegistry();
1499
+ const { onDragStart } = useDragAndDrop();
1500
+ const filteredNodeTypes = filter ? nodeTypesList.filter(filter) : nodeTypesList;
1501
+ return /* @__PURE__ */ jsxs9("div", { style: panelStyle, className, children: [
1502
+ /* @__PURE__ */ jsx25("div", { style: titleStyle, children: "Nodes" }),
1503
+ filteredNodeTypes.map(
1504
+ (nodeType) => renderItem ? /* @__PURE__ */ jsx25(
1505
+ "div",
1506
+ {
1507
+ draggable: true,
1508
+ onDragStart: (e) => onDragStart(e, nodeType.type),
1509
+ children: renderItem(nodeType)
1510
+ },
1511
+ nodeType.type
1512
+ ) : /* @__PURE__ */ jsx25(
1513
+ NodeItem,
1514
+ {
1515
+ nodeType,
1516
+ onDragStart,
1517
+ showDescription: showDescriptions
1518
+ },
1519
+ nodeType.type
1520
+ )
1521
+ )
1522
+ ] });
1523
+ }
1524
+
1525
+ // src/hooks/useSelectedNode.ts
1526
+ function useSelectedNode() {
1527
+ const context = useWorkflowEditorContext();
1528
+ const selectedNode = context.selectedNodeId ? context.nodes.find((n) => n.id === context.selectedNodeId) ?? null : null;
1529
+ const nodeType = selectedNode?.type ? context.nodeTypes.get(selectedNode.type) : void 0;
1530
+ return {
1531
+ /** The currently selected node, or null if none */
1532
+ selectedNode,
1533
+ /** The ID of the selected node */
1534
+ selectedNodeId: context.selectedNodeId,
1535
+ /** The node type definition for the selected node */
1536
+ nodeType,
1537
+ /** Select a node by ID */
1538
+ selectNode: context.selectNode,
1539
+ /** Update the selected node's data */
1540
+ updateSelectedNode: (data) => {
1541
+ if (context.selectedNodeId) {
1542
+ context.updateNode(context.selectedNodeId, data);
1543
+ }
1544
+ },
1545
+ /** Remove the selected node */
1546
+ removeSelectedNode: () => {
1547
+ if (context.selectedNodeId) {
1548
+ context.removeNode(context.selectedNodeId);
1549
+ }
1550
+ }
1551
+ };
1552
+ }
1553
+
1554
+ // src/components/DetailPanel.tsx
1555
+ import { jsx as jsx26, jsxs as jsxs10 } from "react/jsx-runtime";
1556
+ var panelStyle2 = {
1557
+ backgroundColor: "#fff",
1558
+ borderRadius: "8px",
1559
+ padding: "12px",
1560
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
1561
+ minWidth: "280px",
1562
+ maxWidth: "320px"
1563
+ };
1564
+ var titleStyle2 = {
1565
+ fontSize: "12px",
1566
+ fontWeight: 600,
1567
+ color: "#374151",
1568
+ marginBottom: "12px",
1569
+ textTransform: "uppercase",
1570
+ letterSpacing: "0.5px"
1571
+ };
1572
+ var emptyStyle = {
1573
+ fontSize: "13px",
1574
+ color: "#6B7280",
1575
+ textAlign: "center",
1576
+ padding: "20px 0"
1577
+ };
1578
+ var headerStyle3 = {
1579
+ display: "flex",
1580
+ alignItems: "center",
1581
+ gap: "8px",
1582
+ marginBottom: "16px",
1583
+ paddingBottom: "12px",
1584
+ borderBottom: "1px solid #E5E7EB"
1585
+ };
1586
+ var nodeTypeStyle = {
1587
+ fontSize: "14px",
1588
+ fontWeight: 600,
1589
+ color: "#111827"
1590
+ };
1591
+ var nodeIdStyle = {
1592
+ fontSize: "11px",
1593
+ color: "#9CA3AF",
1594
+ fontFamily: "monospace"
1595
+ };
1596
+ var deleteButtonStyle = {
1597
+ marginLeft: "auto",
1598
+ padding: "4px 8px",
1599
+ fontSize: "11px",
1600
+ color: "#DC2626",
1601
+ backgroundColor: "#FEE2E2",
1602
+ border: "none",
1603
+ borderRadius: "4px",
1604
+ cursor: "pointer"
1605
+ };
1606
+ function DetailPanel({
1607
+ className,
1608
+ emptyMessage = "Select a node to edit its properties",
1609
+ showNodeType = true,
1610
+ showNodeId = false
1611
+ }) {
1612
+ const { selectedNode, nodeType, updateSelectedNode, removeSelectedNode } = useSelectedNode();
1613
+ if (!selectedNode || !nodeType) {
1614
+ return /* @__PURE__ */ jsxs10("div", { style: panelStyle2, className, children: [
1615
+ /* @__PURE__ */ jsx26("div", { style: titleStyle2, children: "Properties" }),
1616
+ /* @__PURE__ */ jsx26("div", { style: emptyStyle, children: emptyMessage })
1617
+ ] });
1618
+ }
1619
+ const DetailComponent = nodeType.DetailComponent;
1620
+ return /* @__PURE__ */ jsxs10("div", { style: panelStyle2, className, children: [
1621
+ /* @__PURE__ */ jsx26("div", { style: titleStyle2, children: "Properties" }),
1622
+ /* @__PURE__ */ jsxs10("div", { style: headerStyle3, children: [
1623
+ /* @__PURE__ */ jsxs10("div", { children: [
1624
+ showNodeType && /* @__PURE__ */ jsx26("div", { style: nodeTypeStyle, children: nodeType.label }),
1625
+ showNodeId && /* @__PURE__ */ jsx26("div", { style: nodeIdStyle, children: selectedNode.id })
1626
+ ] }),
1627
+ /* @__PURE__ */ jsx26(
1628
+ "button",
1629
+ {
1630
+ style: deleteButtonStyle,
1631
+ onClick: removeSelectedNode,
1632
+ title: "Delete node",
1633
+ children: "Delete"
1634
+ }
1635
+ )
1636
+ ] }),
1637
+ /* @__PURE__ */ jsx26(
1638
+ DetailComponent,
1639
+ {
1640
+ node: selectedNode,
1641
+ onChange: (data) => updateSelectedNode(data)
1642
+ }
1643
+ )
1644
+ ] });
1645
+ }
1646
+
1647
+ // src/components/OptionsPanel.tsx
1648
+ import { jsx as jsx27, jsxs as jsxs11 } from "react/jsx-runtime";
1649
+ var panelStyle3 = {
1650
+ backgroundColor: "#fff",
1651
+ borderRadius: "8px",
1652
+ padding: "12px",
1653
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
1654
+ minWidth: "220px"
1655
+ };
1656
+ var titleStyle3 = {
1657
+ fontSize: "12px",
1658
+ fontWeight: 600,
1659
+ color: "#374151",
1660
+ marginBottom: "12px",
1661
+ textTransform: "uppercase",
1662
+ letterSpacing: "0.5px"
1663
+ };
1664
+ var frequencyOptions = [
1665
+ { value: "one_time", label: "One time" },
1666
+ { value: "every_rematch", label: "Every rematch" }
1667
+ ];
1668
+ function OptionsPanel({
1669
+ className,
1670
+ showFrequency = true,
1671
+ customOptions
1672
+ }) {
1673
+ const { options, setOptions } = useWorkflowEditorContext();
1674
+ const handleFrequencyTypeChange = (type) => {
1675
+ const newFrequency = {
1676
+ type
1677
+ };
1678
+ if (type === "every_rematch") {
1679
+ newFrequency.interval = options.frequency?.interval ?? 3600;
1680
+ }
1681
+ setOptions({ ...options, frequency: newFrequency });
1682
+ };
1683
+ const handleIntervalChange = (interval) => {
1684
+ if (options.frequency) {
1685
+ setOptions({
1686
+ ...options,
1687
+ frequency: { ...options.frequency, interval }
1688
+ });
1689
+ }
1690
+ };
1691
+ return /* @__PURE__ */ jsxs11("div", { style: panelStyle3, className, children: [
1692
+ /* @__PURE__ */ jsx27("div", { style: titleStyle3, children: "Options" }),
1693
+ showFrequency && /* @__PURE__ */ jsxs11(FieldGroup, { label: "Frequency", children: [
1694
+ /* @__PURE__ */ jsx27(
1695
+ SelectField,
1696
+ {
1697
+ label: "Type",
1698
+ value: options.frequency?.type ?? "one_time",
1699
+ options: frequencyOptions,
1700
+ onChange: handleFrequencyTypeChange,
1701
+ hint: "How often this workflow can run for a subject"
1702
+ }
1703
+ ),
1704
+ options.frequency?.type === "every_rematch" && /* @__PURE__ */ jsx27(
1705
+ NumberField,
1706
+ {
1707
+ label: "Interval (seconds)",
1708
+ value: options.frequency?.interval ?? 3600,
1709
+ onChange: handleIntervalChange,
1710
+ min: 1,
1711
+ hint: "Minimum time between runs"
1712
+ }
1713
+ )
1714
+ ] }),
1715
+ customOptions
1716
+ ] });
1717
+ }
1718
+
1719
+ // src/components/ControlPanel.tsx
1720
+ import { useState as useState4 } from "react";
1721
+ import { jsx as jsx28, jsxs as jsxs12 } from "react/jsx-runtime";
1722
+ var panelStyle4 = {
1723
+ backgroundColor: "#fff",
1724
+ borderRadius: "8px",
1725
+ padding: "12px",
1726
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
1727
+ minWidth: "220px"
1728
+ };
1729
+ var titleStyle4 = {
1730
+ fontSize: "12px",
1731
+ fontWeight: 600,
1732
+ color: "#374151",
1733
+ marginBottom: "12px",
1734
+ textTransform: "uppercase",
1735
+ letterSpacing: "0.5px"
1736
+ };
1737
+ var actionsStyle = {
1738
+ display: "flex",
1739
+ gap: "8px",
1740
+ marginTop: "12px"
1741
+ };
1742
+ var buttonStyle = {
1743
+ padding: "8px 16px",
1744
+ fontSize: "13px",
1745
+ fontWeight: 500,
1746
+ borderRadius: "6px",
1747
+ cursor: "pointer",
1748
+ border: "none",
1749
+ transition: "all 0.15s"
1750
+ };
1751
+ var saveButtonStyle = {
1752
+ ...buttonStyle,
1753
+ backgroundColor: "#3B82F6",
1754
+ color: "#fff"
1755
+ };
1756
+ var saveButtonDisabledStyle = {
1757
+ ...saveButtonStyle,
1758
+ backgroundColor: "#93C5FD",
1759
+ cursor: "not-allowed"
1760
+ };
1761
+ var statusStyle = {
1762
+ fontSize: "11px",
1763
+ color: "#6B7280",
1764
+ marginTop: "8px"
1765
+ };
1766
+ function ControlPanel({
1767
+ className,
1768
+ showName = true,
1769
+ showSaveButton = true,
1770
+ saveButtonLabel = "Save",
1771
+ onSave,
1772
+ renderActions
1773
+ }) {
1774
+ const { name, setName, isDirty, getWorkflow, markClean } = useWorkflowEditorContext();
1775
+ const [isSaving, setIsSaving] = useState4(false);
1776
+ const [saveStatus, setSaveStatus] = useState4(null);
1777
+ const handleSave = async () => {
1778
+ if (!onSave || isSaving) return;
1779
+ setIsSaving(true);
1780
+ setSaveStatus(null);
1781
+ try {
1782
+ await onSave();
1783
+ markClean();
1784
+ setSaveStatus("saved");
1785
+ setTimeout(() => setSaveStatus(null), 2e3);
1786
+ } catch (error) {
1787
+ console.error("Failed to save workflow:", error);
1788
+ setSaveStatus("error");
1789
+ } finally {
1790
+ setIsSaving(false);
1791
+ }
1792
+ };
1793
+ return /* @__PURE__ */ jsxs12("div", { style: panelStyle4, className, children: [
1794
+ /* @__PURE__ */ jsx28("div", { style: titleStyle4, children: "Workflow" }),
1795
+ showName && /* @__PURE__ */ jsx28(
1796
+ TextField,
1797
+ {
1798
+ label: "Name",
1799
+ value: name,
1800
+ onChange: setName,
1801
+ placeholder: "Workflow name"
1802
+ }
1803
+ ),
1804
+ /* @__PURE__ */ jsxs12("div", { style: actionsStyle, children: [
1805
+ showSaveButton && onSave && /* @__PURE__ */ jsx28(
1806
+ "button",
1807
+ {
1808
+ style: isSaving || !isDirty ? saveButtonDisabledStyle : saveButtonStyle,
1809
+ onClick: handleSave,
1810
+ disabled: isSaving || !isDirty,
1811
+ children: isSaving ? "Saving..." : saveButtonLabel
1812
+ }
1813
+ ),
1814
+ renderActions?.({ isDirty, workflow: getWorkflow() })
1815
+ ] }),
1816
+ saveStatus && /* @__PURE__ */ jsx28(
1817
+ "div",
1818
+ {
1819
+ style: {
1820
+ ...statusStyle,
1821
+ color: saveStatus === "saved" ? "#059669" : "#DC2626"
1822
+ },
1823
+ children: saveStatus === "saved" ? "Saved successfully" : "Failed to save"
1824
+ }
1825
+ ),
1826
+ isDirty && !saveStatus && /* @__PURE__ */ jsx28("div", { style: statusStyle, children: "Unsaved changes" })
1827
+ ] });
1828
+ }
1829
+
1830
+ // src/hooks/useWorkflowEditor.ts
1831
+ function useWorkflowEditor() {
1832
+ const context = useWorkflowEditorContext();
1833
+ return {
1834
+ // State
1835
+ workflow: context.getWorkflow(),
1836
+ nodes: context.nodes,
1837
+ edges: context.edges,
1838
+ options: context.options,
1839
+ name: context.name,
1840
+ isDirty: context.isDirty,
1841
+ selectedNode: context.selectedNodeId ? context.nodes.find((n) => n.id === context.selectedNodeId) ?? null : null,
1842
+ selectedNodeId: context.selectedNodeId,
1843
+ // Actions
1844
+ loadWorkflow: context.loadWorkflow,
1845
+ resetWorkflow: context.resetWorkflow,
1846
+ addNode: context.addNode,
1847
+ updateNode: context.updateNode,
1848
+ removeNode: context.removeNode,
1849
+ selectNode: context.selectNode,
1850
+ addEdge: context.addEdge,
1851
+ removeEdge: context.removeEdge,
1852
+ setName: context.setName,
1853
+ setOptions: context.setOptions,
1854
+ getWorkflow: context.getWorkflow,
1855
+ markClean: context.markClean
1856
+ };
1857
+ }
1858
+
1859
+ // src/hooks/useNodes.ts
1860
+ function useNodes() {
1861
+ const context = useWorkflowEditorContext();
1862
+ return {
1863
+ nodes: context.nodes,
1864
+ onNodesChange: context.onNodesChange,
1865
+ addNode: context.addNode,
1866
+ updateNode: context.updateNode,
1867
+ removeNode: context.removeNode
1868
+ };
1869
+ }
1870
+
1871
+ // src/hooks/useEdges.ts
1872
+ function useEdges() {
1873
+ const context = useWorkflowEditorContext();
1874
+ return {
1875
+ edges: context.edges,
1876
+ onEdgesChange: context.onEdgesChange,
1877
+ onConnect: context.onConnect,
1878
+ addEdge: context.addEdge,
1879
+ removeEdge: context.removeEdge
1880
+ };
1881
+ }
1882
+ export {
1883
+ ActionNodeDetail,
1884
+ ActionNodeView,
1885
+ BaseNodeView,
1886
+ CheckboxField,
1887
+ ConditionNodeDetail,
1888
+ ConditionNodeView,
1889
+ ControlPanel,
1890
+ DetailPanel,
1891
+ DurationField,
1892
+ ExitNodeDetail,
1893
+ ExitNodeView,
1894
+ Field,
1895
+ FieldGroup,
1896
+ JsonField,
1897
+ NodesPanel,
1898
+ NumberField,
1899
+ OptionsPanel,
1900
+ SelectField,
1901
+ TextAreaField,
1902
+ TextField,
1903
+ TriggerNodeDetail,
1904
+ TriggerNodeView,
1905
+ TriggerOrTimeoutNodeDetail,
1906
+ TriggerOrTimeoutNodeView,
1907
+ WaitNodeDetail,
1908
+ WaitNodeView,
1909
+ WorkflowEditor,
1910
+ WorkflowEditorProvider,
1911
+ defaultNodeTypes,
1912
+ useDragAndDrop,
1913
+ useEdges,
1914
+ useNodeRegistry,
1915
+ useNodes,
1916
+ useSelectedNode,
1917
+ useWorkflowEditor,
1918
+ useWorkflowEditorContext
1919
+ };