@redvars/peacock 3.8.6 → 3.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/code-highlighter.js +1 -4
  2. package/dist/code-highlighter.js.map +1 -1
  3. package/dist/custom-elements-jsdocs.json +93 -213
  4. package/dist/custom-elements.json +488 -2149
  5. package/dist/elevation.js +55 -2
  6. package/dist/elevation.js.map +1 -1
  7. package/dist/flow.js +762 -0
  8. package/dist/flow.js.map +1 -0
  9. package/dist/index.js +3 -3
  10. package/dist/loader.js +10 -9
  11. package/dist/loader.js.map +1 -1
  12. package/dist/menu-item.js +144 -63
  13. package/dist/menu-item.js.map +1 -1
  14. package/dist/menu.js +161 -73
  15. package/dist/menu.js.map +1 -1
  16. package/dist/src/flow/flow.d.ts +102 -0
  17. package/dist/src/flow/index.d.ts +2 -0
  18. package/dist/src/index.d.ts +3 -4
  19. package/dist/src/menu/menu/menu.d.ts +10 -9
  20. package/dist/src/menu/menu-item/menu-item.d.ts +1 -3
  21. package/dist/src/timeline/index.d.ts +2 -0
  22. package/dist/src/timeline/timeline-item.d.ts +23 -0
  23. package/dist/src/timeline/timeline.d.ts +26 -0
  24. package/dist/sub-menu.js +28 -1
  25. package/dist/sub-menu.js.map +1 -1
  26. package/dist/timeline-item.js +163 -0
  27. package/dist/timeline-item.js.map +1 -0
  28. package/dist/timeline.js +52 -0
  29. package/dist/timeline.js.map +1 -0
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +3 -2
  32. package/readme.md +2 -2
  33. package/src/code-highlighter/code-highlighter.scss +1 -7
  34. package/src/elevation/demo/index.html +52 -0
  35. package/src/elevation/elevation.scss +52 -5
  36. package/src/elevation/elevation.ts +8 -1
  37. package/src/flow/flow.scss +195 -0
  38. package/src/flow/flow.ts +683 -0
  39. package/src/flow/index.ts +2 -0
  40. package/src/index.ts +3 -16
  41. package/src/loader.ts +10 -9
  42. package/src/menu/menu/copy-component-files.mjs +47 -0
  43. package/src/menu/menu/menu.scss +55 -17
  44. package/src/menu/menu/menu.ts +155 -84
  45. package/src/menu/menu-item/menu-item-colors.scss +3 -3
  46. package/src/menu/menu-item/menu-item.scss +118 -24
  47. package/src/menu/menu-item/menu-item.ts +61 -55
  48. package/src/menu/sub-menu/sub-menu.scss +30 -1
  49. package/src/timeline/index.ts +2 -0
  50. package/src/timeline/timeline-item.scss +98 -0
  51. package/src/timeline/timeline-item.ts +63 -0
  52. package/src/timeline/timeline.scss +11 -0
  53. package/src/timeline/timeline.ts +41 -0
  54. package/dist/canvas.js +0 -385
  55. package/dist/canvas.js.map +0 -1
  56. package/dist/flow-designer-node-CGSm6cUH.js +0 -553
  57. package/dist/flow-designer-node-CGSm6cUH.js.map +0 -1
  58. package/dist/flow-designer-node.js +0 -4
  59. package/dist/flow-designer-node.js.map +0 -1
  60. package/dist/flow-designer.js +0 -1417
  61. package/dist/flow-designer.js.map +0 -1
  62. package/dist/src/canvas/canvas.d.ts +0 -92
  63. package/dist/src/canvas/index.d.ts +0 -2
  64. package/dist/src/flow-designer/commands.d.ts +0 -66
  65. package/dist/src/flow-designer/flow-designer-node.d.ts +0 -46
  66. package/dist/src/flow-designer/flow-designer.d.ts +0 -133
  67. package/dist/src/flow-designer/index.d.ts +0 -7
  68. package/dist/src/flow-designer/layout.d.ts +0 -30
  69. package/dist/src/flow-designer/types.d.ts +0 -142
  70. package/dist/src/flow-designer/validation.d.ts +0 -43
  71. package/dist/src/flow-designer/workflow-utils.d.ts +0 -40
  72. package/src/canvas/canvas.scss +0 -72
  73. package/src/canvas/canvas.ts +0 -413
  74. package/src/canvas/index.ts +0 -2
  75. package/src/flow-designer/DEMO.md +0 -239
  76. package/src/flow-designer/commands.ts +0 -278
  77. package/src/flow-designer/flow-designer-node.ts +0 -172
  78. package/src/flow-designer/flow-designer.scss +0 -457
  79. package/src/flow-designer/flow-designer.ts +0 -611
  80. package/src/flow-designer/index.ts +0 -41
  81. package/src/flow-designer/layout.ts +0 -357
  82. package/src/flow-designer/types.ts +0 -166
  83. package/src/flow-designer/validation.ts +0 -284
  84. package/src/flow-designer/workflow-utils.ts +0 -282
  85. /package/dist/test/{flow-designer.test.d.ts → elevation.test.d.ts} +0 -0
@@ -0,0 +1,683 @@
1
+ import { html, LitElement, nothing, svg } from "lit";
2
+ import { property, state } from "lit/decorators.js";
3
+ import { classMap } from "lit/directives/class-map.js";
4
+ import IndividualComponent from "@/IndividualComponent.js";
5
+ import styles from "./flow.scss";
6
+
7
+ export type FlowDirection = "up" | "down" | "left" | "right";
8
+
9
+ export type FlowStrokeVariant = "solid" | "dashed" | "animated-dashed";
10
+
11
+ export interface FlowPoint {
12
+ x: number;
13
+ y: number;
14
+ }
15
+
16
+ export interface FlowPathSegment {
17
+ direction: FlowDirection;
18
+ length: number;
19
+ }
20
+
21
+ export interface FlowNode {
22
+ id: string;
23
+ position: { x: number; y: number };
24
+ inputs?: ("top" | "left" | "bottom" | "right")[];
25
+ outputs?: ("top" | "left" | "bottom" | "right")[];
26
+ data: {
27
+ label?: string;
28
+ element?: HTMLElement;
29
+ [key: string]: any;
30
+ } | HTMLElement;
31
+ }
32
+
33
+ export interface FlowEdge {
34
+ id: string;
35
+ source: string;
36
+ target: string;
37
+ sourceHandle?: "top" | "left" | "bottom" | "right";
38
+ targetHandle?: "top" | "left" | "bottom" | "right";
39
+ type?: "smoothstep" | "bezier" | "straight";
40
+ variant?: FlowStrokeVariant;
41
+ label?: string;
42
+ color?: string;
43
+ }
44
+
45
+ /**
46
+ * @label Flow
47
+ * @tag wc-flow
48
+ * @rawTag flow
49
+ * @summary An interactive node editor canvas with draggable nodes, custom HTML element rendering, and mouse-based edge connection creation on a dotted grid.
50
+ *
51
+ * @example
52
+ * ```html
53
+ * <wc-flow id="my-flow"></wc-flow>
54
+ * <script>
55
+ * const flow = document.getElementById('my-flow');
56
+ * flow.nodes = [
57
+ * { id: '1', position: { x: 50, y: 50 }, data: { label: 'Node 1' } },
58
+ * { id: '2', position: { x: 250, y: 50 }, data: { label: 'Node 2' } }
59
+ * ];
60
+ * flow.edges = [
61
+ * { id: 'e1-2', source: '1', target: '2' }
62
+ * ];
63
+ * </script>
64
+ * ```
65
+ *
66
+ * @cssprop --flow-background - Background color for the canvas wrapper. Defaults to surface-container-low.
67
+ * @cssprop --flow-dot-color - Color of the background grid dots. Defaults to outline-variant.
68
+ * @cssprop --flow-line-color - Default stroke color for lines and connectors. Defaults to on-surface.
69
+ * @cssprop --flow-hover-color - Stroke color on hover for clickable shapes. Defaults to primary.
70
+ * @cssprop --flow-arrow-color - Stroke color for arrow markers. Defaults to on-surface.
71
+ */
72
+ @IndividualComponent
73
+ export class Flow extends LitElement {
74
+ static styles = [styles];
75
+
76
+ @property({ type: Array })
77
+ nodes: FlowNode[] = [];
78
+
79
+ @property({ type: Array })
80
+ edges: FlowEdge[] = [];
81
+
82
+ @property({ type: Number, reflect: true })
83
+ zoom: number = 1;
84
+
85
+ @property({ type: Number, reflect: true })
86
+ padding: number = 50;
87
+
88
+ // Node dimensions tracking
89
+ private nodeDimensions = new Map<string, { width: number; height: number }>();
90
+
91
+ // Drag node state
92
+ @state()
93
+ private draggingNodeId: string | null = null;
94
+ private dragStartMouseX = 0;
95
+ private dragStartMouseY = 0;
96
+ private dragStartNodeX = 0;
97
+ private dragStartNodeY = 0;
98
+
99
+ // Connection drag state
100
+ @state()
101
+ private isDrawingConnection = false;
102
+ @state()
103
+ private draggingSourceId: string | null = null;
104
+ @state()
105
+ private draggingSourceHandle: "top" | "left" | "bottom" | "right" | null = null;
106
+ @state()
107
+ private connectionStartX = 0;
108
+ @state()
109
+ private connectionStartY = 0;
110
+ @state()
111
+ private currentMouseX = 0;
112
+ @state()
113
+ private currentMouseY = 0;
114
+
115
+ protected updated() {
116
+ let dimensionsChanged = false;
117
+ this.shadowRoot?.querySelectorAll(".flow-node").forEach((el) => {
118
+ const id = el.getAttribute("data-id");
119
+ if (id) {
120
+ const current = this.nodeDimensions.get(id);
121
+ const w = (el as HTMLElement).offsetWidth || 150;
122
+ const h = (el as HTMLElement).offsetHeight || 70;
123
+ if (!current || current.width !== w || current.height !== h) {
124
+ this.nodeDimensions.set(id, { width: w, height: h });
125
+ dimensionsChanged = true;
126
+ }
127
+ }
128
+ });
129
+ if (dimensionsChanged) {
130
+ this.requestUpdate();
131
+ }
132
+ }
133
+
134
+ // --- Node Dragging Handlers ---
135
+ private onNodeMouseDown(event: MouseEvent, node: FlowNode) {
136
+ const target = event.target as HTMLElement;
137
+ if (
138
+ target.closest(".handle") ||
139
+ target.closest("button") ||
140
+ target.closest("input") ||
141
+ target.closest("a")
142
+ ) {
143
+ return;
144
+ }
145
+ event.preventDefault();
146
+ this.draggingNodeId = node.id;
147
+ this.dragStartMouseX = event.clientX;
148
+ this.dragStartMouseY = event.clientY;
149
+ this.dragStartNodeX = node.position.x;
150
+ this.dragStartNodeY = node.position.y;
151
+
152
+ window.addEventListener("mousemove", this.onNodeMouseMove);
153
+ window.addEventListener("mouseup", this.onNodeMouseUp);
154
+ }
155
+
156
+ private onNodeMouseMove = (event: MouseEvent) => {
157
+ if (!this.draggingNodeId) return;
158
+ const deltaX = (event.clientX - this.dragStartMouseX) / this.zoom;
159
+ const deltaY = (event.clientY - this.dragStartMouseY) / this.zoom;
160
+
161
+ const node = this.nodes.find((n) => n.id === this.draggingNodeId);
162
+ if (node) {
163
+ node.position = {
164
+ x: this.dragStartNodeX + deltaX,
165
+ y: this.dragStartNodeY + deltaY,
166
+ };
167
+ this.dispatchEvent(
168
+ new CustomEvent("node-drag", {
169
+ detail: { id: node.id, position: node.position },
170
+ bubbles: true,
171
+ composed: true,
172
+ }),
173
+ );
174
+ this.requestUpdate();
175
+ }
176
+ };
177
+
178
+ private onNodeMouseUp = () => {
179
+ this.draggingNodeId = null;
180
+ window.removeEventListener("mousemove", this.onNodeMouseMove);
181
+ window.removeEventListener("mouseup", this.onNodeMouseUp);
182
+ };
183
+
184
+ // --- Handle Helpers ---
185
+ private getNodeInputs(node: FlowNode): ("top" | "left" | "bottom" | "right")[] {
186
+ if (node.inputs && Array.isArray(node.inputs)) {
187
+ return node.inputs;
188
+ }
189
+ if (node.data && typeof node.data === "object" && Array.isArray((node.data as any).inputs)) {
190
+ return (node.data as any).inputs;
191
+ }
192
+ return ["left"];
193
+ }
194
+
195
+ private getNodeOutputs(node: FlowNode): ("top" | "left" | "bottom" | "right")[] {
196
+ if (node.outputs && Array.isArray(node.outputs)) {
197
+ return node.outputs;
198
+ }
199
+ if (node.data && typeof node.data === "object" && Array.isArray((node.data as any).outputs)) {
200
+ return (node.data as any).outputs;
201
+ }
202
+ return ["right"];
203
+ }
204
+
205
+ private getHandleCoordinates(
206
+ node: FlowNode,
207
+ position: "top" | "left" | "bottom" | "right",
208
+ dim: { width: number; height: number },
209
+ ): FlowPoint {
210
+ const x = node.position.x;
211
+ const y = node.position.y;
212
+ const w = dim.width;
213
+ const h = dim.height;
214
+
215
+ switch (position) {
216
+ case "top":
217
+ return { x: x + w / 2, y };
218
+ case "bottom":
219
+ return { x: x + w / 2, y: y + h };
220
+ case "left":
221
+ return { x, y: y + h / 2 };
222
+ case "right":
223
+ default:
224
+ return { x: x + w, y: y + h / 2 };
225
+ }
226
+ }
227
+
228
+ // --- Connection Dragging Handlers ---
229
+ private onHandleMouseDown(
230
+ event: MouseEvent,
231
+ node: FlowNode,
232
+ position: "top" | "left" | "bottom" | "right",
233
+ ) {
234
+ event.stopPropagation();
235
+ event.preventDefault();
236
+
237
+ const dim = this.nodeDimensions.get(node.id) || { width: 150, height: 70 };
238
+ this.draggingSourceId = node.id;
239
+ this.draggingSourceHandle = position;
240
+
241
+ const coords = this.getHandleCoordinates(node, position, dim);
242
+ this.connectionStartX = coords.x;
243
+ this.connectionStartY = coords.y;
244
+
245
+ this.isDrawingConnection = true;
246
+ this.currentMouseX = this.connectionStartX;
247
+ this.currentMouseY = this.connectionStartY;
248
+
249
+ window.addEventListener("mousemove", this.onConnectionMouseMove);
250
+ window.addEventListener("mouseup", this.onConnectionMouseUp);
251
+ }
252
+
253
+ private onConnectionMouseMove = (event: MouseEvent) => {
254
+ if (!this.isDrawingConnection) return;
255
+
256
+ const workspaceEl = this.shadowRoot?.querySelector(".flow-workspace");
257
+ if (workspaceEl) {
258
+ const rect = workspaceEl.getBoundingClientRect();
259
+ const bounds = this.getBounds();
260
+ const viewBoxX = bounds.minX - this.padding;
261
+ const viewBoxY = bounds.minY - this.padding;
262
+
263
+ this.currentMouseX = viewBoxX + (event.clientX - rect.left) / this.zoom;
264
+ this.currentMouseY = viewBoxY + (event.clientY - rect.top) / this.zoom;
265
+ this.requestUpdate();
266
+ }
267
+ };
268
+
269
+ private onConnectionMouseUp = (event: MouseEvent) => {
270
+ if (!this.isDrawingConnection) return;
271
+ this.isDrawingConnection = false;
272
+
273
+ window.removeEventListener("mousemove", this.onConnectionMouseMove);
274
+ window.removeEventListener("mouseup", this.onConnectionMouseUp);
275
+
276
+ const target = this.shadowRoot?.elementFromPoint(event.clientX, event.clientY);
277
+ const inputHandle = target?.closest(".handle-input");
278
+ if (inputHandle) {
279
+ const targetNodeId = inputHandle.getAttribute("data-node-id");
280
+ const targetHandle = inputHandle.getAttribute("data-position") as "top" | "left" | "bottom" | "right";
281
+ if (targetNodeId && targetNodeId !== this.draggingSourceId) {
282
+ this.dispatchEvent(
283
+ new CustomEvent("connect", {
284
+ detail: {
285
+ source: this.draggingSourceId,
286
+ target: targetNodeId,
287
+ sourceHandle: this.draggingSourceHandle,
288
+ targetHandle: targetHandle,
289
+ },
290
+ bubbles: true,
291
+ composed: true,
292
+ }),
293
+ );
294
+ }
295
+ }
296
+ this.draggingSourceId = null;
297
+ this.draggingSourceHandle = null;
298
+ this.requestUpdate();
299
+ };
300
+
301
+ // --- Path Builders ---
302
+ private getRoundedPath(points: FlowPoint[], radius = 12) {
303
+ if (points.length < 3) {
304
+ return points.map((p, i) => (i === 0 ? `M ${p.x} ${p.y}` : `L ${p.x} ${p.y}`)).join(" ");
305
+ }
306
+
307
+ let path = `M ${points[0].x} ${points[0].y}`;
308
+
309
+ for (let i = 1; i < points.length - 1; i++) {
310
+ const prev = points[i - 1];
311
+ const curr = points[i];
312
+ const next = points[i + 1];
313
+
314
+ const dxp = prev.x - curr.x;
315
+ const dyp = prev.y - curr.y;
316
+ const lenp = Math.hypot(dxp, dyp);
317
+
318
+ const dxn = next.x - curr.x;
319
+ const dyn = next.y - curr.y;
320
+ const lenn = Math.hypot(dxn, dyn);
321
+
322
+ if (lenp === 0 || lenn === 0) {
323
+ path += ` L ${curr.x} ${curr.y}`;
324
+ continue;
325
+ }
326
+
327
+ const r = Math.min(radius, lenp / 2, lenn / 2);
328
+
329
+ if (r > 0) {
330
+ const x1 = curr.x + (dxp / lenp) * r;
331
+ const y1 = curr.y + (dyp / lenp) * r;
332
+ const x2 = curr.x + (dxn / lenn) * r;
333
+ const y2 = curr.y + (dyn / lenn) * r;
334
+
335
+ path += ` L ${x1} ${y1} Q ${curr.x} ${curr.y} ${x2} ${y2}`;
336
+ } else {
337
+ path += ` L ${curr.x} ${curr.y}`;
338
+ }
339
+ }
340
+
341
+ const last = points[points.length - 1];
342
+ path += ` L ${last.x} ${last.y}`;
343
+ return path;
344
+ }
345
+
346
+ private getSmoothStepPath(
347
+ x1: number,
348
+ y1: number,
349
+ x2: number,
350
+ y2: number,
351
+ sourceHandle: "top" | "left" | "bottom" | "right" = "right",
352
+ targetHandle: "top" | "left" | "bottom" | "right" = "left",
353
+ ) {
354
+ const isSourceHorizontal = sourceHandle === "left" || sourceHandle === "right";
355
+ const isTargetHorizontal = targetHandle === "left" || targetHandle === "right";
356
+
357
+ let points: FlowPoint[] = [];
358
+
359
+ if (isSourceHorizontal && isTargetHorizontal) {
360
+ const midX = x1 + (x2 - x1) / 2;
361
+ points = [
362
+ { x: x1, y: y1 },
363
+ { x: midX, y: y1 },
364
+ { x: midX, y: y2 },
365
+ { x: x2, y: y2 },
366
+ ];
367
+ } else if (!isSourceHorizontal && !isTargetHorizontal) {
368
+ const midY = y1 + (y2 - y1) / 2;
369
+ points = [
370
+ { x: x1, y: y1 },
371
+ { x: x1, y: midY },
372
+ { x: x2, y: midY },
373
+ { x: x2, y: y2 },
374
+ ];
375
+ } else if (isSourceHorizontal && !isTargetHorizontal) {
376
+ points = [
377
+ { x: x1, y: y1 },
378
+ { x: x2, y: y1 },
379
+ { x: x2, y: y2 },
380
+ ];
381
+ } else {
382
+ points = [
383
+ { x: x1, y: y1 },
384
+ { x: x1, y: y2 },
385
+ { x: x2, y: y2 },
386
+ ];
387
+ }
388
+
389
+ return this.getRoundedPath(points, 12);
390
+ }
391
+
392
+ private getBezierPath(
393
+ x1: number,
394
+ y1: number,
395
+ x2: number,
396
+ y2: number,
397
+ sourceHandle: "top" | "left" | "bottom" | "right" = "right",
398
+ targetHandle: "top" | "left" | "bottom" | "right" = "left",
399
+ ) {
400
+ const normals = {
401
+ left: { x: -1, y: 0 },
402
+ right: { x: 1, y: 0 },
403
+ top: { x: 0, y: -1 },
404
+ bottom: { x: 0, y: 1 },
405
+ };
406
+
407
+ const sNormal = normals[sourceHandle] || { x: 1, y: 0 };
408
+ const tNormal = normals[targetHandle] || { x: -1, y: 0 };
409
+
410
+ const dx = x2 - x1;
411
+ const dy = y2 - y1;
412
+ const dist = Math.hypot(dx, dy);
413
+ const offset = Math.max(dist / 2, 50);
414
+
415
+ const cx1 = x1 + sNormal.x * offset;
416
+ const cy1 = y1 + sNormal.y * offset;
417
+ const cx2 = x2 + tNormal.x * offset;
418
+ const cy2 = y2 + tNormal.y * offset;
419
+
420
+ return `M ${x1} ${y1} C ${cx1} ${cy1}, ${cx2} ${cy2}, ${x2} ${y2}`;
421
+ }
422
+
423
+ private getStraightPath(x1: number, y1: number, x2: number, y2: number) {
424
+ return `M ${x1} ${y1} L ${x2} ${y2}`;
425
+ }
426
+
427
+ private drawPath(
428
+ sourceId: string,
429
+ targetId: string,
430
+ type?: "smoothstep" | "bezier" | "straight",
431
+ sourceHandle?: "top" | "left" | "bottom" | "right",
432
+ targetHandle?: "top" | "left" | "bottom" | "right",
433
+ ) {
434
+ const sourceNode = this.nodes.find((n) => n.id === sourceId);
435
+ const targetNode = this.nodes.find((n) => n.id === targetId);
436
+ if (!sourceNode || !targetNode) return "";
437
+
438
+ const sDim = this.nodeDimensions.get(sourceId) || { width: 150, height: 70 };
439
+ const tDim = this.nodeDimensions.get(targetId) || { width: 150, height: 70 };
440
+
441
+ const sHandle = sourceHandle || this.getNodeOutputs(sourceNode)[0] || "right";
442
+ const tHandle = targetHandle || this.getNodeInputs(targetNode)[0] || "left";
443
+
444
+ const startCoords = this.getHandleCoordinates(sourceNode, sHandle, sDim);
445
+ const endCoords = this.getHandleCoordinates(targetNode, tHandle, tDim);
446
+
447
+ const edgeType = type || "smoothstep";
448
+ if (edgeType === "bezier") {
449
+ return this.getBezierPath(startCoords.x, startCoords.y, endCoords.x, endCoords.y, sHandle, tHandle);
450
+ }
451
+ if (edgeType === "straight") {
452
+ return this.getStraightPath(startCoords.x, startCoords.y, endCoords.x, endCoords.y);
453
+ }
454
+ return this.getSmoothStepPath(startCoords.x, startCoords.y, endCoords.x, endCoords.y, sHandle, tHandle);
455
+ }
456
+
457
+ // --- Auto viewbox math ---
458
+ private getBounds() {
459
+ if (this.nodes.length === 0) {
460
+ return { minX: 0, minY: 0, maxX: 400, maxY: 300 };
461
+ }
462
+ let minX = Infinity;
463
+ let minY = Infinity;
464
+ let maxX = -Infinity;
465
+ let maxY = -Infinity;
466
+
467
+ this.nodes.forEach((node) => {
468
+ const dim = this.nodeDimensions.get(node.id) ||
469
+ { width: 150, height: 70 };
470
+ const x = node.position.x;
471
+ const y = node.position.y;
472
+ if (x < minX) minX = x;
473
+ if (y < minY) minY = y;
474
+ if (x + dim.width > maxX) maxX = x + dim.width;
475
+ if (y + dim.height > maxY) maxY = y + dim.height;
476
+ });
477
+
478
+ return { minX, minY, maxX, maxY };
479
+ }
480
+
481
+ // --- Content Renderers ---
482
+ private isCustomNode(node: FlowNode) {
483
+ if (node.data instanceof HTMLElement) {
484
+ return true;
485
+ }
486
+ if (
487
+ node.data &&
488
+ typeof node.data === "object" &&
489
+ (node.data as any).element instanceof HTMLElement
490
+ ) {
491
+ return true;
492
+ }
493
+ return false;
494
+ }
495
+
496
+ private renderNodeContent(node: FlowNode) {
497
+ if (node.data instanceof HTMLElement) {
498
+ return node.data;
499
+ }
500
+ if (
501
+ node.data &&
502
+ typeof node.data === "object" &&
503
+ (node.data as any).element instanceof HTMLElement
504
+ ) {
505
+ return (node.data as any).element;
506
+ }
507
+ const label = node.data && typeof node.data === "object"
508
+ ? (node.data as any).label || node.id
509
+ : String(node.data);
510
+ return html`
511
+ <div class="flow-node-default-card">
512
+ <div class="flow-node-title">${label}</div>
513
+ </div>
514
+ `;
515
+ }
516
+
517
+ protected render() {
518
+ const bounds = this.getBounds();
519
+ const viewBoxX = bounds.minX - this.padding;
520
+ const viewBoxY = bounds.minY - this.padding;
521
+ const viewBoxWidth = bounds.maxX - bounds.minX + 2 * this.padding;
522
+ const viewBoxHeight = bounds.maxY - bounds.minY + 2 * this.padding;
523
+
524
+ const svgViewBox =
525
+ `${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}`;
526
+
527
+ return html`
528
+ <div class="flow-container">
529
+ <div
530
+ class="flow-scroller"
531
+ style="width: ${viewBoxWidth * this.zoom}px; height: ${viewBoxHeight * this.zoom}px; position: relative;"
532
+ >
533
+ <div
534
+ class="flow-workspace"
535
+ style="transform: scale(${this.zoom}); transform-origin: top left; width: ${viewBoxWidth}px; height: ${viewBoxHeight}px; position: absolute; left: 0; top: 0;"
536
+ >
537
+ <!-- SVG canvas for rendering grid and paths -->
538
+ <svg class="flow-svg" viewBox="${svgViewBox}">
539
+ <defs>
540
+ <pattern
541
+ id="dot-grid"
542
+ width="20"
543
+ height="20"
544
+ patternUnits="userSpaceOnUse"
545
+ >
546
+ <circle cx="1" cy="1" r="1" />
547
+ </pattern>
548
+ <marker
549
+ id="arrowhead"
550
+ markerWidth="8"
551
+ markerHeight="8"
552
+ refX="7"
553
+ refY="4"
554
+ orient="auto"
555
+ >
556
+ <polygon
557
+ points="0 0, 8 4, 0 8"
558
+ fill="var(--flow-line-color, var(--color-outline))"
559
+ />
560
+ </marker>
561
+ </defs>
562
+
563
+ <!-- Background Grid -->
564
+ <rect
565
+ x="${viewBoxX}"
566
+ y="${viewBoxY}"
567
+ width="${viewBoxWidth}"
568
+ height="${viewBoxHeight}"
569
+ fill="url(#dot-grid)"
570
+ />
571
+
572
+ <!-- Connection Edges -->
573
+ ${this.edges.map((edge) => {
574
+ const pathString = this.drawPath(
575
+ edge.source,
576
+ edge.target,
577
+ edge.type,
578
+ edge.sourceHandle,
579
+ edge.targetHandle,
580
+ );
581
+ const color = edge.color ||
582
+ "var(--flow-line-color, var(--color-outline))";
583
+ const variant = edge.variant || "solid";
584
+ return svg`
585
+ <path
586
+ class="${classMap({
587
+ "flow-edge": true,
588
+ "variant-dashed": variant === "dashed" ||
589
+ variant === "animated-dashed",
590
+ "variant-animated-dashed": variant === "animated-dashed",
591
+ })}"
592
+ d="${pathString}"
593
+ stroke="${color}"
594
+ fill="none"
595
+ stroke-width="2"
596
+ marker-end="url(#arrowhead)"
597
+ />
598
+ `;
599
+ })}
600
+
601
+ <!-- Connection Draft line preview -->
602
+ ${this.isDrawingConnection && this.draggingSourceHandle
603
+ ? svg`
604
+ <path
605
+ class="flow-edge-preview"
606
+ d="${this.getBezierPath(
607
+ this.connectionStartX,
608
+ this.connectionStartY,
609
+ this.currentMouseX,
610
+ this.currentMouseY,
611
+ this.draggingSourceHandle,
612
+ ({
613
+ left: "right",
614
+ right: "left",
615
+ top: "bottom",
616
+ bottom: "top",
617
+ })[this.draggingSourceHandle] as any
618
+ )}"
619
+ stroke="var(--flow-hover-color, var(--color-primary))"
620
+ fill="none"
621
+ stroke-width="2"
622
+ stroke-dasharray="4 4"
623
+ />
624
+ `
625
+ : nothing}
626
+ </svg>
627
+
628
+ <!-- Nodes Workspace -->
629
+ <div
630
+ class="flow-nodes"
631
+ style="position: absolute; left: ${-viewBoxX}px; top: ${-viewBoxY}px;"
632
+ >
633
+ ${this.nodes.map((node) => {
634
+ const inputs = this.getNodeInputs(node);
635
+ const outputs = this.getNodeOutputs(node);
636
+ return html`
637
+ <div
638
+ class="${classMap({
639
+ "flow-node": true,
640
+ "flow-node-custom": this.isCustomNode(node),
641
+ })}"
642
+ data-id="${node.id}"
643
+ style="position: absolute; left: ${node.position
644
+ .x}px; top: ${node.position.y}px;"
645
+ @mousedown="${(e: MouseEvent) =>
646
+ this.onNodeMouseDown(e, node)}"
647
+ >
648
+ <!-- Input connection handles -->
649
+ ${inputs.map((pos) => html`
650
+ <div
651
+ class="handle handle-input handle-position-${pos}"
652
+ data-node-id="${node.id}"
653
+ data-position="${pos}"
654
+ >
655
+ </div>
656
+ `)}
657
+
658
+ <!-- Node Inner Content -->
659
+ <div class="flow-node-content">
660
+ ${this.renderNodeContent(node)}
661
+ </div>
662
+
663
+ <!-- Output connection handles -->
664
+ ${outputs.map((pos) => html`
665
+ <div
666
+ class="handle handle-output handle-position-${pos}"
667
+ data-node-id="${node.id}"
668
+ data-position="${pos}"
669
+ @mousedown="${(e: MouseEvent) =>
670
+ this.onHandleMouseDown(e, node, pos)}"
671
+ >
672
+ </div>
673
+ `)}
674
+ </div>
675
+ `;
676
+ })}
677
+ </div>
678
+ </div>
679
+ </div>
680
+ </div>
681
+ `;
682
+ }
683
+ }
@@ -0,0 +1,2 @@
1
+ export { Flow } from './flow.js';
2
+ export type { FlowNode, FlowEdge, FlowStrokeVariant } from './flow.js';