@redvars/peacock 3.5.1 → 3.6.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.
Files changed (198) hide show
  1. package/dist/{BaseButton-DuASuVth.js → BaseButton-BNFAYn-S.js} +2 -2
  2. package/dist/{BaseButton-DuASuVth.js.map → BaseButton-BNFAYn-S.js.map} +1 -1
  3. package/dist/BaseInput-14YmcfK7.js +27 -0
  4. package/dist/BaseInput-14YmcfK7.js.map +1 -0
  5. package/dist/banner.js +2 -3
  6. package/dist/banner.js.map +1 -1
  7. package/dist/{button-DouvOfEU.js → button-colors-Ccys3hvS.js} +5 -294
  8. package/dist/button-colors-Ccys3hvS.js.map +1 -0
  9. package/dist/button-group.js +226 -6
  10. package/dist/button-group.js.map +1 -1
  11. package/dist/button.js +294 -8
  12. package/dist/button.js.map +1 -1
  13. package/dist/calendar-column-view.js +634 -0
  14. package/dist/calendar-column-view.js.map +1 -0
  15. package/dist/calendar-event-BrQ_SEKD.js +199 -0
  16. package/dist/calendar-event-BrQ_SEKD.js.map +1 -0
  17. package/dist/calendar-month-view.js +376 -0
  18. package/dist/calendar-month-view.js.map +1 -0
  19. package/dist/calendar.js +339 -0
  20. package/dist/calendar.js.map +1 -0
  21. package/dist/canvas.js +361 -0
  22. package/dist/canvas.js.map +1 -0
  23. package/dist/cb-compound-expression.js +125 -0
  24. package/dist/cb-compound-expression.js.map +1 -0
  25. package/dist/cb-divider.js +150 -0
  26. package/dist/cb-divider.js.map +1 -0
  27. package/dist/cb-expression.js +75 -0
  28. package/dist/cb-expression.js.map +1 -0
  29. package/dist/cb-predicate.js +137 -0
  30. package/dist/cb-predicate.js.map +1 -0
  31. package/dist/code-editor.js +2 -1
  32. package/dist/code-editor.js.map +1 -1
  33. package/dist/condition-builder.js +58 -0
  34. package/dist/condition-builder.js.map +1 -0
  35. package/dist/custom-elements-jsdocs.json +7976 -4294
  36. package/dist/custom-elements.json +14358 -7589
  37. package/dist/dropdown-button.js +216 -0
  38. package/dist/dropdown-button.js.map +1 -0
  39. package/dist/event-manager-D-QCmUgR.js +113 -0
  40. package/dist/event-manager-D-QCmUgR.js.map +1 -0
  41. package/dist/fab.js +1 -1
  42. package/dist/flow-designer-dZnLJOQT.js +1656 -0
  43. package/dist/flow-designer-dZnLJOQT.js.map +1 -0
  44. package/dist/flow-designer-node-XMe-jlKg.js +548 -0
  45. package/dist/flow-designer-node-XMe-jlKg.js.map +1 -0
  46. package/dist/flow-designer-node.js +4 -0
  47. package/dist/flow-designer-node.js.map +1 -0
  48. package/dist/flow-designer.js +16 -0
  49. package/dist/flow-designer.js.map +1 -0
  50. package/dist/html-editor.js +358 -0
  51. package/dist/html-editor.js.map +1 -0
  52. package/dist/icon-button-CK1ZuE-2.js +247 -0
  53. package/dist/icon-button-CK1ZuE-2.js.map +1 -0
  54. package/dist/index.js +29 -6
  55. package/dist/index.js.map +1 -1
  56. package/dist/{is-dark-mode-DicqGkCJ.js → is-dark-mode-DOcaw4Yq.js} +2 -27
  57. package/dist/is-dark-mode-DOcaw4Yq.js.map +1 -0
  58. package/dist/modal.js +418 -0
  59. package/dist/modal.js.map +1 -0
  60. package/dist/{navigation-rail-Lxetd5-Z.js → navigation-rail-DyO0oAZU.js} +306 -2197
  61. package/dist/navigation-rail-DyO0oAZU.js.map +1 -0
  62. package/dist/notification-manager.js +268 -0
  63. package/dist/notification-manager.js.map +1 -0
  64. package/dist/peacock-loader.js +84 -8
  65. package/dist/peacock-loader.js.map +1 -1
  66. package/dist/popover-NC7b1lTq.js +1971 -0
  67. package/dist/popover-NC7b1lTq.js.map +1 -0
  68. package/dist/popover-content.js +125 -0
  69. package/dist/popover-content.js.map +1 -0
  70. package/dist/popover.js +4 -0
  71. package/dist/popover.js.map +1 -0
  72. package/dist/split-button.js +388 -0
  73. package/dist/split-button.js.map +1 -0
  74. package/dist/src/__controllers/floating-controller.d.ts +35 -0
  75. package/dist/src/calendar/base-event.d.ts +10 -0
  76. package/dist/src/calendar/calendar-column-view.d.ts +41 -0
  77. package/dist/src/calendar/calendar-event.d.ts +7 -0
  78. package/dist/src/calendar/calendar-month-view.d.ts +31 -0
  79. package/dist/src/calendar/calendar.d.ts +65 -0
  80. package/dist/src/calendar/event-manager.d.ts +17 -0
  81. package/dist/src/calendar/index.d.ts +4 -0
  82. package/dist/src/calendar/types.d.ts +13 -0
  83. package/dist/src/calendar/utils.d.ts +31 -0
  84. package/dist/src/canvas/canvas.d.ts +92 -0
  85. package/dist/src/canvas/index.d.ts +2 -0
  86. package/dist/src/condition-builder/cb-compound-expression.d.ts +31 -0
  87. package/dist/src/condition-builder/cb-divider.d.ts +26 -0
  88. package/dist/src/condition-builder/cb-expression.d.ts +31 -0
  89. package/dist/src/condition-builder/cb-predicate.d.ts +30 -0
  90. package/dist/src/condition-builder/condition-builder.d.ts +27 -0
  91. package/dist/src/condition-builder/index.d.ts +5 -0
  92. package/dist/src/dropdown-button/dropdown-button.d.ts +68 -0
  93. package/dist/src/dropdown-button/index.d.ts +1 -0
  94. package/dist/src/flow-designer/commands.d.ts +66 -0
  95. package/dist/src/flow-designer/flow-designer-node.d.ts +46 -0
  96. package/dist/src/flow-designer/flow-designer.d.ts +133 -0
  97. package/dist/src/flow-designer/index.d.ts +7 -0
  98. package/dist/src/flow-designer/layout.d.ts +30 -0
  99. package/dist/src/flow-designer/types.d.ts +142 -0
  100. package/dist/src/flow-designer/validation.d.ts +43 -0
  101. package/dist/src/flow-designer/workflow-utils.d.ts +40 -0
  102. package/dist/src/html-editor/html-editor.d.ts +56 -0
  103. package/dist/src/html-editor/index.d.ts +2 -0
  104. package/dist/src/index.d.ts +13 -0
  105. package/dist/src/menu/menu/menu.d.ts +5 -7
  106. package/dist/src/menu/menu-item/menu-item.d.ts +14 -13
  107. package/dist/src/modal/index.d.ts +1 -0
  108. package/dist/src/modal/modal.d.ts +63 -0
  109. package/dist/src/notification-manager/index.d.ts +1 -0
  110. package/dist/src/notification-manager/notification-manager.d.ts +44 -0
  111. package/dist/src/popover/index.d.ts +2 -0
  112. package/dist/src/popover/popover-content.d.ts +29 -0
  113. package/dist/src/popover/popover.d.ts +62 -0
  114. package/dist/src/split-button/index.d.ts +1 -0
  115. package/dist/src/split-button/split-button.d.ts +72 -0
  116. package/dist/src/tooltip/tooltip.d.ts +2 -15
  117. package/dist/test/flow-designer.test.d.ts +1 -0
  118. package/dist/tsconfig.tsbuildinfo +1 -1
  119. package/package.json +4 -2
  120. package/readme.md +2 -2
  121. package/src/__controllers/floating-controller.ts +237 -0
  122. package/src/banner/banner.scss +2 -3
  123. package/src/button/button/button.ts +1 -0
  124. package/src/calendar/base-event.ts +49 -0
  125. package/src/calendar/calendar-column-view.scss +326 -0
  126. package/src/calendar/calendar-column-view.ts +392 -0
  127. package/src/calendar/calendar-event.ts +20 -0
  128. package/src/calendar/calendar-month-view.scss +192 -0
  129. package/src/calendar/calendar-month-view.ts +244 -0
  130. package/src/calendar/calendar.scss +71 -0
  131. package/src/calendar/calendar.ts +298 -0
  132. package/src/calendar/event-manager.ts +117 -0
  133. package/src/calendar/index.ts +4 -0
  134. package/src/calendar/types.ts +14 -0
  135. package/src/calendar/utils.ts +180 -0
  136. package/src/canvas/canvas.scss +60 -0
  137. package/src/canvas/canvas.ts +391 -0
  138. package/src/canvas/index.ts +2 -0
  139. package/src/condition-builder/cb-compound-expression.scss +37 -0
  140. package/src/condition-builder/cb-compound-expression.ts +80 -0
  141. package/src/condition-builder/cb-divider.scss +93 -0
  142. package/src/condition-builder/cb-divider.ts +56 -0
  143. package/src/condition-builder/cb-expression.scss +14 -0
  144. package/src/condition-builder/cb-expression.ts +49 -0
  145. package/src/condition-builder/cb-predicate.scss +35 -0
  146. package/src/condition-builder/cb-predicate.ts +102 -0
  147. package/src/condition-builder/condition-builder.scss +13 -0
  148. package/src/condition-builder/condition-builder.ts +38 -0
  149. package/src/condition-builder/index.ts +5 -0
  150. package/src/dropdown-button/demo/index.html +110 -0
  151. package/src/dropdown-button/dropdown-button.scss +22 -0
  152. package/src/dropdown-button/dropdown-button.ts +206 -0
  153. package/src/dropdown-button/index.ts +1 -0
  154. package/src/flow-designer/DEMO.md +239 -0
  155. package/src/flow-designer/commands.ts +278 -0
  156. package/src/flow-designer/flow-designer-node.ts +172 -0
  157. package/src/flow-designer/flow-designer.scss +457 -0
  158. package/src/flow-designer/flow-designer.ts +611 -0
  159. package/src/flow-designer/index.ts +41 -0
  160. package/src/flow-designer/layout.ts +357 -0
  161. package/src/flow-designer/types.ts +166 -0
  162. package/src/flow-designer/validation.ts +284 -0
  163. package/src/flow-designer/workflow-utils.ts +282 -0
  164. package/src/html-editor/html-editor.scss +146 -0
  165. package/src/html-editor/html-editor.ts +276 -0
  166. package/src/html-editor/index.ts +3 -0
  167. package/src/index.ts +25 -0
  168. package/src/menu/menu/menu.scss +2 -2
  169. package/src/menu/menu/menu.ts +91 -101
  170. package/src/menu/menu-item/menu-item.scss +4 -0
  171. package/src/menu/menu-item/menu-item.ts +82 -78
  172. package/src/modal/index.ts +1 -0
  173. package/src/modal/modal.scss +206 -0
  174. package/src/modal/modal.ts +201 -0
  175. package/src/notification-manager/index.ts +1 -0
  176. package/src/notification-manager/notification-manager.scss +113 -0
  177. package/src/notification-manager/notification-manager.ts +199 -0
  178. package/src/peacock-loader.ts +71 -0
  179. package/src/popover/index.ts +2 -0
  180. package/src/popover/popover-content.scss +69 -0
  181. package/src/popover/popover-content.ts +51 -0
  182. package/src/popover/popover.scss +7 -0
  183. package/src/popover/popover.ts +170 -0
  184. package/src/split-button/index.ts +1 -0
  185. package/src/split-button/split-button-colors.scss +56 -0
  186. package/src/split-button/split-button-sizes.scss +28 -0
  187. package/src/split-button/split-button.scss +79 -0
  188. package/src/split-button/split-button.ts +236 -0
  189. package/src/table/table.ts +2 -2
  190. package/src/tooltip/tooltip.scss +4 -3
  191. package/src/tooltip/tooltip.ts +46 -104
  192. package/dist/button-DouvOfEU.js.map +0 -1
  193. package/dist/button-group-CEdMwvJJ.js +0 -464
  194. package/dist/button-group-CEdMwvJJ.js.map +0 -1
  195. package/dist/is-dark-mode-DicqGkCJ.js.map +0 -1
  196. package/dist/navigation-rail-Lxetd5-Z.js.map +0 -1
  197. package/dist/src/menu/menu/MenuSurfaceController.d.ts +0 -18
  198. package/src/menu/menu/MenuSurfaceController.ts +0 -61
@@ -0,0 +1,239 @@
1
+ # Flow Designer Component Demo
2
+
3
+ ## Basic Usage
4
+
5
+ ```html
6
+ <wc-flow-designer id="designer"></wc-flow-designer>
7
+
8
+ <script type="module">
9
+ import { FlowDesigner, Workflow } from '@redvars/peacock';
10
+
11
+ // Define a workflow
12
+ const workflow = {
13
+ workflow_id: 'demo_workflow',
14
+ nodes: {
15
+ id: 'start',
16
+ type: 'trigger',
17
+ label: 'Start Process',
18
+ children: [
19
+ {
20
+ id: 'validate',
21
+ type: 'decision',
22
+ label: 'Data Valid?',
23
+ branches: {
24
+ yes: [
25
+ {
26
+ id: 'process',
27
+ type: 'action',
28
+ label: 'Process Data',
29
+ }
30
+ ],
31
+ no: [
32
+ {
33
+ id: 'error',
34
+ type: 'action',
35
+ label: 'Log Error',
36
+ }
37
+ ]
38
+ }
39
+ }
40
+ ]
41
+ }
42
+ };
43
+
44
+ // Set workflow on component
45
+ const designer = document.getElementById('designer');
46
+ designer.workflow = workflow;
47
+
48
+ // Listen for changes
49
+ designer.addEventListener('workflow-changed', (e) => {
50
+ console.log('Workflow changed:', e.detail.type);
51
+ console.log('Updated workflow:', e.detail.workflow);
52
+ });
53
+
54
+ // Add a new node
55
+ const newNode = {
56
+ id: 'new_action',
57
+ type: 'action',
58
+ label: 'New Action'
59
+ };
60
+ designer.addNode(newNode, 'start');
61
+
62
+ // Undo/Redo
63
+ if (designer.canUndo()) {
64
+ designer.undo();
65
+ }
66
+
67
+ if (designer.canRedo()) {
68
+ designer.redo();
69
+ }
70
+
71
+ // Validate
72
+ designer.validate();
73
+
74
+ // Export
75
+ const json = designer.exportWorkflow();
76
+ </script>
77
+ ```
78
+
79
+ ## Customizing Node Templates
80
+
81
+ ```html
82
+ <wc-flow-designer id="designer">
83
+ <!-- Custom trigger node template -->
84
+ <div slot="trigger-header" style="color: green; font-weight: bold;">
85
+ 🚀 Custom Trigger
86
+ </div>
87
+
88
+ <!-- Custom action node template -->
89
+ <div slot="action-header" style="color: blue;">
90
+ ⚙️ Custom Action
91
+ </div>
92
+ <p slot="action-body">Custom action content here</p>
93
+
94
+ <!-- Custom decision template -->
95
+ <div slot="decision-header" style="color: orange;">
96
+ ❓ Custom Decision
97
+ </div>
98
+ </wc-flow-designer>
99
+ ```
100
+
101
+ ## Workflow Structure
102
+
103
+ The component supports complex workflows:
104
+
105
+ ```javascript
106
+ {
107
+ workflow_id: 'complex_workflow',
108
+ nodes: {
109
+ id: 'trigger_1',
110
+ type: 'trigger',
111
+ label: 'Input Received',
112
+
113
+ // Sequential flow
114
+ children: [
115
+ {
116
+ id: 'loop_1',
117
+ type: 'loop_start',
118
+ label: 'Begin Loop',
119
+
120
+ children: [
121
+ {
122
+ id: 'decision_1',
123
+ type: 'decision',
124
+ label: 'Check Condition',
125
+
126
+ // Conditional branching
127
+ branches: {
128
+ yes: [
129
+ {
130
+ id: 'fork_1',
131
+ type: 'fork',
132
+ label: 'Parallel Tasks',
133
+
134
+ // Parallel execution
135
+ tasks: [
136
+ { id: 'task_1', type: 'action', label: 'Task A' },
137
+ { id: 'task_2', type: 'action', label: 'Task B' }
138
+ ],
139
+
140
+ // Join point
141
+ join: {
142
+ id: 'join_1',
143
+ type: 'join',
144
+ label: 'Merge',
145
+ children: [
146
+ {
147
+ id: 'loop_end_1',
148
+ type: 'loop_end',
149
+ label: 'Loop Back',
150
+ target_id: 'loop_1'
151
+ }
152
+ ]
153
+ }
154
+ }
155
+ ],
156
+ no: [
157
+ { id: 'skip', type: 'action', label: 'Skip' }
158
+ ]
159
+ }
160
+ }
161
+ ]
162
+ }
163
+ ]
164
+ }
165
+ }
166
+ ```
167
+
168
+ ## Available Node Types
169
+
170
+ - **trigger** - Start of workflow
171
+ - **action** - Perform an action
172
+ - **decision** - Branch on condition (yes/no)
173
+ - **loop_start** - Begin loop
174
+ - **loop_end** - End loop (loop_start target)
175
+ - **fork** - Parallel execution start
176
+ - **join** - Parallel execution end
177
+
178
+ ## Events
179
+
180
+ - `workflow-changed` - Emitted when workflow is modified
181
+ - `node-selected` - Emitted when node is clicked
182
+ - `node-edited` - Emitted when node is edited
183
+ - `node-deleted` - Emitted when node is deleted
184
+ - `validation-result` - Emitted when validate() is called
185
+
186
+ ## Properties
187
+
188
+ - `workflow: Workflow` - Current workflow definition
189
+ - `readonly: boolean` - Read-only mode (default: false)
190
+ - `disabled: boolean` - Disabled state (default: false)
191
+ - `showValidation: boolean` - Show validation errors (default: false)
192
+
193
+ ## Methods
194
+
195
+ - `addNode(node, parentId, connectionType?, branchKey?)` - Add node
196
+ - `deleteNode(nodeId)` - Delete node
197
+ - `editNode(nodeId, updates)` - Edit node
198
+ - `moveNode(nodeId, newParentId, newIndex, connectionType?, branchKey?)` - Move node
199
+ - `undo()` - Undo last operation
200
+ - `redo()` - Redo last undone operation
201
+ - `canUndo()` - Check if undo available
202
+ - `canRedo()` - Check if redo available
203
+ - `validate()` - Validate workflow
204
+ - `exportWorkflow()` - Export as JSON
205
+
206
+ ## Validation
207
+
208
+ The component automatically validates workflows after each change:
209
+
210
+ - ✅ Detects circular loops
211
+ - ✅ Detects orphaned nodes
212
+ - ✅ Validates decision branches (yes/no)
213
+ - ✅ Validates fork/join pairs
214
+ - ✅ Validates loop targets
215
+
216
+ Invalid workflows will prompt user confirmation before accepting changes.
217
+
218
+ ## Styling
219
+
220
+ Custom CSS properties available:
221
+
222
+ ```css
223
+ wc-flow-designer {
224
+ --flow-designer-height: 600px;
225
+ --flow-designer-border-color: var(--color-outline-variant);
226
+ --flow-designer-background: var(--color-surface);
227
+ --flow-designer-border-radius: var(--shape-corner-medium);
228
+ --flow-designer-action-bar-bg: var(--color-surface-container);
229
+ }
230
+ ```
231
+
232
+ ## Keyboard Shortcuts
233
+
234
+ - **Ctrl+Z** / **Cmd+Z** - Undo
235
+ - **Ctrl+Y** / **Cmd+Y** - Redo
236
+ - **Delete** - Delete selected node
237
+ - **Enter** / **Space** - Select/activate node
238
+ - **Click + Drag** - Pan canvas
239
+ - **Scroll** - Zoom in/out (via toolbar)
@@ -0,0 +1,278 @@
1
+ import type { Workflow, WorkflowNode, WorkflowCommand } from './types.js';
2
+ import {
3
+ findNodeById,
4
+ cloneWorkflow,
5
+ removeNodeById,
6
+ insertNodeIntoWorkflow,
7
+ } from './workflow-utils.js';
8
+
9
+ /**
10
+ * Add Node Command
11
+ */
12
+ export class AddNodeCommand implements WorkflowCommand {
13
+ description = 'Add node';
14
+
15
+ constructor(
16
+ private nodeToAdd: WorkflowNode,
17
+ private parentNodeId: string,
18
+ private connectionType: 'child' | 'branch' | 'task' = 'child',
19
+ private branchKey?: string
20
+ ) {}
21
+
22
+ execute(workflow: Workflow): Workflow {
23
+ const result = cloneWorkflow(workflow);
24
+ const parent = findNodeById(result.nodes, this.parentNodeId);
25
+ if (!parent) return workflow; // Validation in parent component
26
+
27
+ insertNodeIntoWorkflow(parent, this.nodeToAdd, this.connectionType, this.branchKey);
28
+ return result;
29
+ }
30
+
31
+ undo(workflow: Workflow): Workflow {
32
+ const result = cloneWorkflow(workflow);
33
+ removeNodeById(result.nodes, this.nodeToAdd.id);
34
+ return result;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Delete Node Command
40
+ */
41
+ export class DeleteNodeCommand implements WorkflowCommand {
42
+ description = 'Delete node';
43
+ private deletedNode: WorkflowNode | null = null;
44
+ private parentReference: {
45
+ parentId: string;
46
+ connectionType: 'child' | 'branch' | 'task';
47
+ branchKey?: string;
48
+ } | null = null;
49
+
50
+ constructor(
51
+ private nodeId: string,
52
+ workflow?: Workflow
53
+ ) {
54
+ if (workflow) {
55
+ this.captureNodeContext(workflow);
56
+ }
57
+ }
58
+
59
+ private captureNodeContext(workflow: Workflow) {
60
+ const node = findNodeById(workflow.nodes, this.nodeId);
61
+ if (!node) return;
62
+ this.deletedNode = cloneWorkflow({ workflow_id: '', nodes: node }).nodes;
63
+
64
+ // Find parent reference
65
+ this.findParentReference(workflow.nodes);
66
+ }
67
+
68
+ private findParentReference(node: WorkflowNode) {
69
+ if (node.children) {
70
+ const idx = node.children.findIndex((n) => n.id === this.nodeId);
71
+ if (idx !== -1) {
72
+ this.parentReference = {
73
+ parentId: node.id,
74
+ connectionType: 'child',
75
+ };
76
+ return;
77
+ }
78
+ for (const child of node.children) {
79
+ this.findParentReference(child);
80
+ }
81
+ }
82
+
83
+ if (node.tasks) {
84
+ const idx = node.tasks.findIndex((n) => n.id === this.nodeId);
85
+ if (idx !== -1) {
86
+ this.parentReference = {
87
+ parentId: node.id,
88
+ connectionType: 'task',
89
+ };
90
+ return;
91
+ }
92
+ for (const task of node.tasks) {
93
+ this.findParentReference(task);
94
+ }
95
+ }
96
+
97
+ if (node.branches) {
98
+ for (const [branchKey, branchNodes] of Object.entries(node.branches)) {
99
+ const idx = branchNodes.findIndex((n) => n.id === this.nodeId);
100
+ if (idx !== -1) {
101
+ this.parentReference = {
102
+ parentId: node.id,
103
+ connectionType: 'branch',
104
+ branchKey,
105
+ };
106
+ return;
107
+ }
108
+ for (const branchNode of branchNodes) {
109
+ this.findParentReference(branchNode);
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ execute(workflow: Workflow): Workflow {
116
+ const result = cloneWorkflow(workflow);
117
+ removeNodeById(result.nodes, this.nodeId);
118
+ return result;
119
+ }
120
+
121
+ undo(workflow: Workflow): Workflow {
122
+ if (!this.deletedNode || !this.parentReference) return workflow;
123
+ const result = cloneWorkflow(workflow);
124
+ const parent = findNodeById(result.nodes, this.parentReference.parentId);
125
+ if (!parent) return workflow;
126
+
127
+ insertNodeIntoWorkflow(
128
+ parent,
129
+ this.deletedNode,
130
+ this.parentReference.connectionType,
131
+ this.parentReference.branchKey
132
+ );
133
+ return result;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Edit Node Command
139
+ */
140
+ export class EditNodeCommand implements WorkflowCommand {
141
+ description = 'Edit node';
142
+ private previousState: Record<string, any> = {};
143
+
144
+ constructor(
145
+ private nodeId: string,
146
+ private updates: Partial<WorkflowNode>,
147
+ workflow?: Workflow
148
+ ) {
149
+ if (workflow) {
150
+ const node = findNodeById(workflow.nodes, nodeId);
151
+ if (node) {
152
+ // Store only edited fields
153
+ Object.keys(updates).forEach((key) => {
154
+ this.previousState[key] = (node as Record<string, any>)[key];
155
+ });
156
+ }
157
+ }
158
+ }
159
+
160
+ execute(workflow: Workflow): Workflow {
161
+ const result = cloneWorkflow(workflow);
162
+ const node = findNodeById(result.nodes, this.nodeId);
163
+ if (!node) return workflow;
164
+
165
+ Object.assign(node, this.updates);
166
+ return result;
167
+ }
168
+
169
+ undo(workflow: Workflow): Workflow {
170
+ const result = cloneWorkflow(workflow);
171
+ const node = findNodeById(result.nodes, this.nodeId);
172
+ if (!node) return workflow;
173
+
174
+ Object.assign(node, this.previousState);
175
+ return result;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Move Node Command - reorder in array or change parent
181
+ */
182
+ export class MoveNodeCommand implements WorkflowCommand {
183
+ description = 'Move node';
184
+ private previousState: {
185
+ parentId: string;
186
+ index: number;
187
+ connectionType: 'child' | 'branch' | 'task';
188
+ branchKey?: string;
189
+ } | null = null;
190
+
191
+ constructor(
192
+ private nodeId: string,
193
+ private newParentId: string,
194
+ private newIndex: number,
195
+ private newConnectionType: 'child' | 'branch' | 'task' = 'child',
196
+ private newBranchKey?: string,
197
+ workflow?: Workflow
198
+ ) {
199
+ if (workflow) {
200
+ this.captureCurrentPosition(workflow);
201
+ }
202
+ }
203
+
204
+ private captureCurrentPosition(workflow: Workflow) {
205
+ // Store current parent/position for undo
206
+ // Implementation depends on finding current parent location
207
+ }
208
+
209
+ execute(workflow: Workflow): Workflow {
210
+ // Remove from old parent, insert at new parent
211
+ let result = cloneWorkflow(workflow);
212
+ result.nodes = removeNodeById(result.nodes, this.nodeId);
213
+ const newParent = findNodeById(result.nodes, this.newParentId);
214
+ if (!newParent) return workflow;
215
+
216
+ const node = findNodeById(workflow.nodes, this.nodeId);
217
+ if (!node) return workflow;
218
+
219
+ insertNodeIntoWorkflow(
220
+ newParent,
221
+ node,
222
+ this.newConnectionType,
223
+ this.newBranchKey
224
+ );
225
+ return result;
226
+ }
227
+
228
+ undo(workflow: Workflow): Workflow {
229
+ // Restore to previous position
230
+ if (!this.previousState) return workflow;
231
+ let result = cloneWorkflow(workflow);
232
+ result.nodes = removeNodeById(result.nodes, this.nodeId);
233
+ const prevParent = findNodeById(result.nodes, this.previousState.parentId);
234
+ if (!prevParent) return workflow;
235
+
236
+ const node = findNodeById(workflow.nodes, this.nodeId);
237
+ if (!node) return workflow;
238
+
239
+ insertNodeIntoWorkflow(
240
+ prevParent,
241
+ node,
242
+ this.previousState.connectionType,
243
+ this.previousState.branchKey
244
+ );
245
+ return result;
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Batch Command - combine multiple commands into one undo/redo step
251
+ */
252
+ export class BatchCommand implements WorkflowCommand {
253
+ description: string;
254
+
255
+ constructor(
256
+ private commands: WorkflowCommand[],
257
+ description: string = `Batch operation (${commands.length} steps)`
258
+ ) {
259
+ this.description = description;
260
+ }
261
+
262
+ execute(workflow: Workflow): Workflow {
263
+ let result = workflow;
264
+ for (const command of this.commands) {
265
+ result = command.execute(result);
266
+ }
267
+ return result;
268
+ }
269
+
270
+ undo(workflow: Workflow): Workflow {
271
+ let result = workflow;
272
+ // Execute commands in reverse order to undo
273
+ for (let i = this.commands.length - 1; i >= 0; i--) {
274
+ result = this.commands[i].undo(result);
275
+ }
276
+ return result;
277
+ }
278
+ }
@@ -0,0 +1,172 @@
1
+ import { html, LitElement, nothing } from 'lit';
2
+ import { property, state } from 'lit/decorators.js';
3
+ import IndividualComponent from '@/IndividualComponent.js';
4
+ import styles from './flow-designer.scss';
5
+ import type { WorkflowNode } from './types.js';
6
+
7
+ /**
8
+ * Individual node component for flow designer
9
+ * Renders a single workflow node with customizable slot templates
10
+ *
11
+ * @tag wc-flow-designer-node
12
+ * @rawTag flow-designer-node
13
+ * @wip true
14
+ */
15
+ @IndividualComponent
16
+ export class FlowDesignerNode extends LitElement {
17
+ static styles = [styles];
18
+
19
+ /**
20
+ * The workflow node to render
21
+ */
22
+ @property({ type: Object })
23
+ node: WorkflowNode = { id: '', type: 'action', label: '' };
24
+
25
+ /**
26
+ * Whether this node is currently selected
27
+ */
28
+ @property({ type: Boolean, reflect: true, attribute: 'selected' })
29
+ isSelected: boolean = false;
30
+
31
+ /**
32
+ * Whether this node is in edit mode
33
+ */
34
+ @property({ type: Boolean, reflect: true, attribute: 'editing' })
35
+ isEditing: boolean = false;
36
+
37
+ /**
38
+ * Whether this node is disabled
39
+ */
40
+ @property({ type: Boolean, reflect: true })
41
+ disabled: boolean = false;
42
+
43
+ /**
44
+ * Whether to show the delete button
45
+ */
46
+ @property({ type: Boolean, attribute: 'show-delete' })
47
+ showDelete: boolean = true;
48
+
49
+ private _handleClick = () => {
50
+ this.dispatchEvent(
51
+ new CustomEvent('node-click', {
52
+ detail: { nodeId: this.node.id },
53
+ bubbles: true,
54
+ composed: true,
55
+ })
56
+ );
57
+ };
58
+
59
+ private _handleDoubleClick = () => {
60
+ this.dispatchEvent(
61
+ new CustomEvent('node-edit-start', {
62
+ detail: { nodeId: this.node.id },
63
+ bubbles: true,
64
+ composed: true,
65
+ })
66
+ );
67
+ };
68
+
69
+ private _handleDelete = (e: Event) => {
70
+ e.stopPropagation();
71
+ if (confirm(`Delete "${this.node.label}"?`)) {
72
+ this.dispatchEvent(
73
+ new CustomEvent('node-delete', {
74
+ detail: { nodeId: this.node.id },
75
+ bubbles: true,
76
+ composed: true,
77
+ })
78
+ );
79
+ }
80
+ };
81
+
82
+ private _handleMouseEnter = () => {
83
+ // Node hover state handled via CSS
84
+ };
85
+
86
+ private _handleMouseLeave = () => {
87
+ // Node hover state handled via CSS
88
+ };
89
+
90
+ render() {
91
+ const { node, isSelected, isEditing, disabled } = this;
92
+ const nodeType = node.type || 'action';
93
+
94
+ return html`
95
+ <div
96
+ class="node-card ${nodeType}"
97
+ ?selected=${isSelected}
98
+ ?editing=${isEditing}
99
+ ?disabled=${disabled}
100
+ @click=${this._handleClick}
101
+ @dblclick=${this._handleDoubleClick}
102
+ role="button"
103
+ tabindex="0"
104
+ @keydown=${(e: KeyboardEvent) => {
105
+ if (e.key === 'Enter' || e.key === ' ') this._handleClick();
106
+ }}
107
+ >
108
+ <!-- Customizable header slot -->
109
+ <slot name="${nodeType}-header">
110
+ ${this._renderDefaultHeader()}
111
+ </slot>
112
+
113
+ <!-- Customizable body slot -->
114
+ <slot name="${nodeType}-body">${this._renderDefaultBody()}</slot>
115
+
116
+ <!-- Action buttons -->
117
+ ${this.isEditing
118
+ ? html`
119
+ <div class="node-actions">
120
+ <button class="btn-sm" @click=${this._handleDelete}>
121
+ Delete
122
+ </button>
123
+ </div>
124
+ `
125
+ : nothing}
126
+ </div>
127
+ `;
128
+ }
129
+
130
+ private _renderDefaultHeader() {
131
+ const { node } = this;
132
+ const iconMap: Record<string, string> = {
133
+ trigger: 'play-circle',
134
+ action: 'check-circle',
135
+ decision: 'help-circle',
136
+ loop_start: 'repeat',
137
+ loop_end: 'repeat',
138
+ fork: 'git-branch',
139
+ join: 'git-merge',
140
+ };
141
+
142
+ const icon = iconMap[node.type] || 'activity';
143
+
144
+ return html`
145
+ <div class="node-header">
146
+ <wc-icon name=${icon} class="node-icon"></wc-icon>
147
+ <span class="node-title">${node.label}</span>
148
+ </div>
149
+ `;
150
+ }
151
+
152
+ private _renderDefaultBody() {
153
+ const { node } = this;
154
+ return html`
155
+ <div class="node-body">
156
+ ${node.description
157
+ ? html`<p class="node-description">${node.description}</p>`
158
+ : nothing}
159
+ <div class="node-metadata">
160
+ <span class="node-type-tag">${node.type}</span>
161
+ ${node.id ? html`<span class="node-id">${node.id}</span>` : nothing}
162
+ </div>
163
+ </div>
164
+ `;
165
+ }
166
+ }
167
+
168
+ declare global {
169
+ interface HTMLElementTagNameMap {
170
+ 'wc-flow-designer-node': FlowDesignerNode;
171
+ }
172
+ }