@tduniec/plugin-template-designer-foundation 0.1.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 (78) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +13 -0
  3. package/dist/api/useScaffolderActions.esm.js +59 -0
  4. package/dist/api/useScaffolderActions.esm.js.map +1 -0
  5. package/dist/components/FieldEditorDialog.esm.js +55 -0
  6. package/dist/components/FieldEditorDialog.esm.js.map +1 -0
  7. package/dist/components/Nodes/ActionNode.esm.js +613 -0
  8. package/dist/components/Nodes/ActionNode.esm.js.map +1 -0
  9. package/dist/components/Nodes/OutputNode.esm.js +373 -0
  10. package/dist/components/Nodes/OutputNode.esm.js.map +1 -0
  11. package/dist/components/Nodes/ParameterInputNode.esm.js +320 -0
  12. package/dist/components/Nodes/ParameterInputNode.esm.js.map +1 -0
  13. package/dist/components/Nodes/ParameterTitlesNode.esm.js +251 -0
  14. package/dist/components/Nodes/ParameterTitlesNode.esm.js.map +1 -0
  15. package/dist/components/Nodes/ParametersNode.esm.js +147 -0
  16. package/dist/components/Nodes/ParametersNode.esm.js.map +1 -0
  17. package/dist/components/Nodes/action/schema.esm.js +68 -0
  18. package/dist/components/Nodes/action/schema.esm.js.map +1 -0
  19. package/dist/components/Nodes/action/useActionInputs.esm.js +71 -0
  20. package/dist/components/Nodes/action/useActionInputs.esm.js.map +1 -0
  21. package/dist/components/Nodes/common/AutoWidthPopper.esm.js +11 -0
  22. package/dist/components/Nodes/common/AutoWidthPopper.esm.js.map +1 -0
  23. package/dist/components/Nodes/common/nodeInteraction.esm.js +20 -0
  24. package/dist/components/Nodes/common/nodeInteraction.esm.js.map +1 -0
  25. package/dist/components/Nodes/output/useOutputController.esm.js +125 -0
  26. package/dist/components/Nodes/output/useOutputController.esm.js.map +1 -0
  27. package/dist/components/TemplateDesigner/TemplateLanding.esm.js +157 -0
  28. package/dist/components/TemplateDesigner/TemplateLanding.esm.js.map +1 -0
  29. package/dist/components/TemplateDesigner/TemplateWorkspace.esm.js +416 -0
  30. package/dist/components/TemplateDesigner/TemplateWorkspace.esm.js.map +1 -0
  31. package/dist/components/TemplateDesigner/codemirrorTheme.esm.js +30 -0
  32. package/dist/components/TemplateDesigner/codemirrorTheme.esm.js.map +1 -0
  33. package/dist/components/TemplateDesigner/useFieldEditor.esm.js +95 -0
  34. package/dist/components/TemplateDesigner/useFieldEditor.esm.js.map +1 -0
  35. package/dist/components/TemplateDesignerIcon.esm.js +33 -0
  36. package/dist/components/TemplateDesignerIcon.esm.js.map +1 -0
  37. package/dist/components/designerFlowConfig.esm.js +13 -0
  38. package/dist/components/designerFlowConfig.esm.js.map +1 -0
  39. package/dist/designerFlow/DesignerFlow.esm.js +828 -0
  40. package/dist/designerFlow/DesignerFlow.esm.js.map +1 -0
  41. package/dist/designerFlow/handlers.esm.js +317 -0
  42. package/dist/designerFlow/handlers.esm.js.map +1 -0
  43. package/dist/designerFlow/model.esm.js +166 -0
  44. package/dist/designerFlow/model.esm.js.map +1 -0
  45. package/dist/designerFlow/nodeLayout.esm.js +108 -0
  46. package/dist/designerFlow/nodeLayout.esm.js.map +1 -0
  47. package/dist/designerFlow/parameterTransforms.esm.js +124 -0
  48. package/dist/designerFlow/parameterTransforms.esm.js.map +1 -0
  49. package/dist/designerFlow/utils/stableComparators.esm.js +69 -0
  50. package/dist/designerFlow/utils/stableComparators.esm.js.map +1 -0
  51. package/dist/foundation/actionNodeCustomization.esm.js +20 -0
  52. package/dist/foundation/actionNodeCustomization.esm.js.map +1 -0
  53. package/dist/foundation/actionNodeRegistry.esm.js +30 -0
  54. package/dist/foundation/actionNodeRegistry.esm.js.map +1 -0
  55. package/dist/foundation/featureFlags.esm.js +6 -0
  56. package/dist/foundation/featureFlags.esm.js.map +1 -0
  57. package/dist/foundation/templateSources.esm.js +16 -0
  58. package/dist/foundation/templateSources.esm.js.map +1 -0
  59. package/dist/index.d.ts +382 -0
  60. package/dist/index.esm.js +25 -0
  61. package/dist/index.esm.js.map +1 -0
  62. package/dist/state/templateUtils.esm.js +46 -0
  63. package/dist/state/templateUtils.esm.js.map +1 -0
  64. package/dist/state/useParameterSections.esm.js +162 -0
  65. package/dist/state/useParameterSections.esm.js.map +1 -0
  66. package/dist/state/useTemplateState.esm.js +627 -0
  67. package/dist/state/useTemplateState.esm.js.map +1 -0
  68. package/dist/types/flowNodes.esm.js +8 -0
  69. package/dist/types/flowNodes.esm.js.map +1 -0
  70. package/dist/utils/createSequentialEdges.esm.js +15 -0
  71. package/dist/utils/createSequentialEdges.esm.js.map +1 -0
  72. package/dist/utils/mocks/mocks.esm.js +120 -0
  73. package/dist/utils/mocks/mocks.esm.js.map +1 -0
  74. package/dist/utils/sampleTemplate.esm.js +40 -0
  75. package/dist/utils/sampleTemplate.esm.js.map +1 -0
  76. package/dist/utils/yamlJsonConversion.esm.js +47 -0
  77. package/dist/utils/yamlJsonConversion.esm.js.map +1 -0
  78. package/package.json +103 -0
@@ -0,0 +1,828 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { useRef, useMemo, useState, useCallback, useEffect, createElement } from 'react';
3
+ import { useNodesState, applyNodeChanges, applyEdgeChanges, addEdge, ReactFlow } from '@xyflow/react';
4
+ import '@xyflow/react/dist/style.css';
5
+ import { buildNodesFromModel, stableStringify, collectParameterReferences, collectStepOutputReferences, extractStepsFromNodes, extractParametersFromNodes, extractOutputFromNodes } from './model.esm.js';
6
+ import { createSequentialEdges } from '../utils/createSequentialEdges.esm.js';
7
+ import { buildNodeHashMap, buildEdgeHashMap, mergeNodesWithStability, mergeEdgesWithStability } from './utils/stableComparators.esm.js';
8
+ import { createHandleReorderAndAlignNodes, createHandleUpdateField, createHandleUpdateInput, createHandleRemoveInputKey, createHandleUpdateOutput, createHandleUpdateSections, createHandleAddNode, createHandleRemoveNode } from './handlers.esm.js';
9
+ import { resolveNodeHeightForTracking, alignNodes } from './nodeLayout.esm.js';
10
+ import { nodeDefaults, FLOW_LAYOUT } from '../components/designerFlowConfig.esm.js';
11
+ import { useScaffolderActions } from '../api/useScaffolderActions.esm.js';
12
+
13
+ const EMPTY_EDGES = [];
14
+ const EMIT_DEBOUNCE_MS = 1200;
15
+ const VIEWPORT_TUNING = {
16
+ alignDebounceMs: 120,
17
+ // Debounce view/align updates so typing isn't interrupted by layout thrash.
18
+ centerDurationMs: 280,
19
+ fitFallbackDelayMs: 50
20
+ };
21
+ if (typeof window !== "undefined" && !window.__rfResizeObserverPatched) {
22
+ window.__rfResizeObserverPatched = true;
23
+ if (typeof window.ResizeObserver === "function") {
24
+ const OriginalResizeObserver = window.ResizeObserver;
25
+ window.ResizeObserver = class extends OriginalResizeObserver {
26
+ constructor(callback) {
27
+ super((entries, observer) => {
28
+ window.requestAnimationFrame(() => callback(entries, observer));
29
+ });
30
+ }
31
+ };
32
+ }
33
+ const shouldSwallow = (message) => typeof message === "string" && message.includes("ResizeObserver loop completed with undelivered");
34
+ const swallowResizeObserverError = (event) => {
35
+ const message = event?.message || event?.error?.message || String(event);
36
+ if (shouldSwallow(message)) {
37
+ event.preventDefault();
38
+ event.stopImmediatePropagation();
39
+ return true;
40
+ }
41
+ return void 0;
42
+ };
43
+ const swallowResizeObserverRejection = (event) => {
44
+ const message = event.reason?.message ?? String(event.reason);
45
+ if (shouldSwallow(message)) {
46
+ event.preventDefault();
47
+ event.stopImmediatePropagation();
48
+ return true;
49
+ }
50
+ return void 0;
51
+ };
52
+ const swallowLoopEvent = (event) => {
53
+ event.preventDefault();
54
+ event.stopImmediatePropagation();
55
+ };
56
+ const originalOnError = window.onerror;
57
+ window.onerror = function handleResizeObserverOnError(message, source, lineno, colno, error) {
58
+ if (shouldSwallow(
59
+ typeof message === "string" ? message : error?.message ?? String(message)
60
+ )) {
61
+ return true;
62
+ }
63
+ if (typeof originalOnError === "function") {
64
+ return originalOnError(message, source, lineno, colno, error);
65
+ }
66
+ return void 0;
67
+ };
68
+ const originalOnUnhandledRejection = window.onunhandledrejection;
69
+ window.onunhandledrejection = function handleResizeObserverOnUnhandled(event) {
70
+ const reason = event?.reason;
71
+ const message = reason?.message ?? (typeof reason === "string" ? reason : "");
72
+ if (shouldSwallow(message)) {
73
+ return true;
74
+ }
75
+ if (typeof originalOnUnhandledRejection === "function") {
76
+ return originalOnUnhandledRejection(event);
77
+ }
78
+ return void 0;
79
+ };
80
+ window.addEventListener("error", swallowResizeObserverError, true);
81
+ window.addEventListener(
82
+ "unhandledrejection",
83
+ swallowResizeObserverRejection,
84
+ true
85
+ );
86
+ window.addEventListener(
87
+ "resizeobserverlooperror",
88
+ swallowLoopEvent,
89
+ true
90
+ );
91
+ }
92
+ const shallowArrayEqual = (a, b) => {
93
+ if (a === b) {
94
+ return true;
95
+ }
96
+ if (!a || !b || a.length !== b.length) {
97
+ return false;
98
+ }
99
+ for (let i = 0; i < a.length; i += 1) {
100
+ if (a[i] !== b[i]) {
101
+ return false;
102
+ }
103
+ }
104
+ return true;
105
+ };
106
+ const FIXED_X_POSITION = FLOW_LAYOUT.fixedXPosition;
107
+ const VERTICAL_SPACING = FLOW_LAYOUT.verticalSpacing;
108
+ function DesignerFlow({
109
+ steps = [],
110
+ parameters,
111
+ output,
112
+ onStepsChange,
113
+ onParametersChange,
114
+ onOutputChange,
115
+ actionNodeComponent,
116
+ parametersNodeComponent,
117
+ outputNodeComponent,
118
+ decorateNodes,
119
+ decorateEdges,
120
+ nodeDefaults: nodeDefaults$1 = nodeDefaults
121
+ }) {
122
+ const nodeDataHashRef = useRef({});
123
+ const edgeDataHashRef = useRef({});
124
+ const scaffolderActionsCache = useScaffolderActions();
125
+ const {
126
+ ids: scaffolderActionIds,
127
+ inputsById: scaffolderActionInputsById,
128
+ outputsById: scaffolderActionOutputsById,
129
+ inputRequiredById: scaffolderActionInputRequiredById
130
+ } = scaffolderActionsCache;
131
+ const normalizedParametersProp = parameters ?? void 0;
132
+ const normalizedOutputProp = output ?? null;
133
+ const pendingFocusNodeIdRef = useRef(null);
134
+ const hasMountedRef = useRef(false);
135
+ const lastNodeCountRef = useRef(0);
136
+ const initialNodes = useMemo(() => {
137
+ const built = buildNodesFromModel(
138
+ steps,
139
+ normalizedParametersProp,
140
+ normalizedOutputProp,
141
+ {
142
+ scaffolderActionIds,
143
+ scaffolderActionInputsById,
144
+ scaffolderActionOutputsById,
145
+ scaffolderActionInputRequiredById
146
+ }
147
+ );
148
+ const nodes2 = decorateNodes ? decorateNodes(built) : built;
149
+ nodeDataHashRef.current = buildNodeHashMap(nodes2);
150
+ return nodes2;
151
+ }, [
152
+ steps,
153
+ normalizedParametersProp,
154
+ normalizedOutputProp,
155
+ scaffolderActionIds,
156
+ scaffolderActionInputsById,
157
+ scaffolderActionOutputsById,
158
+ scaffolderActionInputRequiredById,
159
+ decorateNodes
160
+ ]);
161
+ const [nodes, setNodes] = useNodesState(initialNodes);
162
+ const initialEdges = useMemo(() => {
163
+ const edges2 = decorateEdges ? decorateEdges(createSequentialEdges(initialNodes), initialNodes) : createSequentialEdges(initialNodes);
164
+ edgeDataHashRef.current = buildEdgeHashMap(edges2);
165
+ lastNodeCountRef.current = initialNodes.length;
166
+ return edges2;
167
+ }, [decorateEdges, initialNodes]);
168
+ const [edges, setEdges] = useState(initialEdges);
169
+ const modelHash = useMemo(
170
+ () => stableStringify({
171
+ steps,
172
+ parameters: normalizedParametersProp,
173
+ output: normalizedOutputProp
174
+ }),
175
+ [steps, normalizedParametersProp, normalizedOutputProp]
176
+ );
177
+ const cacheFingerprint = useMemo(
178
+ () => stableStringify({
179
+ ids: scaffolderActionIds,
180
+ inputs: scaffolderActionInputsById,
181
+ outputs: scaffolderActionOutputsById,
182
+ inputRequired: scaffolderActionInputRequiredById
183
+ }),
184
+ [
185
+ scaffolderActionIds,
186
+ scaffolderActionInputsById,
187
+ scaffolderActionOutputsById,
188
+ scaffolderActionInputRequiredById
189
+ ]
190
+ );
191
+ const lastAppliedModelHashRef = useRef(null);
192
+ const lastEmittedModelHashRef = useRef(null);
193
+ const lastCacheFingerprintRef = useRef(null);
194
+ const nodeHeightsRef = useRef({});
195
+ const lastViewportRef = useRef(null);
196
+ const emitDebounceRef = useRef(null);
197
+ const isDraggingRef = useRef(false);
198
+ const [isDragging, setIsDragging] = useState(false);
199
+ const emitAfterDragRef = useRef(false);
200
+ const pendingInitialFitRef = useRef(true);
201
+ const userMovedViewportRef = useRef(false);
202
+ const layoutInitializedRef = useRef(false);
203
+ const [viewport, setViewport] = useState(() => {
204
+ const existing = lastViewportRef.current;
205
+ if (existing) {
206
+ return existing;
207
+ }
208
+ const fallback = { x: 0, y: 0, zoom: 1 };
209
+ lastViewportRef.current = fallback;
210
+ return fallback;
211
+ });
212
+ const fitViewRafRef = useRef(null);
213
+ const fitViewTimeoutRef = useRef(null);
214
+ const alignDebounceRef = useRef(null);
215
+ const setViewportIfChanged = useCallback((next) => {
216
+ if (!next) {
217
+ return;
218
+ }
219
+ const prev = lastViewportRef.current;
220
+ if (prev && prev.x === next.x && prev.y === next.y && prev.zoom === next.zoom) {
221
+ return;
222
+ }
223
+ lastViewportRef.current = next;
224
+ setViewport(next);
225
+ }, []);
226
+ useEffect(() => {
227
+ const isCacheChanged = cacheFingerprint !== lastCacheFingerprintRef.current;
228
+ if (modelHash === lastAppliedModelHashRef.current && !isCacheChanged) {
229
+ return;
230
+ }
231
+ const builtNodes = buildNodesFromModel(
232
+ steps,
233
+ normalizedParametersProp,
234
+ normalizedOutputProp,
235
+ {
236
+ scaffolderActionIds,
237
+ scaffolderActionInputsById,
238
+ scaffolderActionOutputsById,
239
+ scaffolderActionInputRequiredById
240
+ }
241
+ );
242
+ const nextNodes = decorateNodes ? decorateNodes(builtNodes) : builtNodes;
243
+ const isInitialMount = !hasMountedRef.current;
244
+ lastAppliedModelHashRef.current = modelHash;
245
+ lastCacheFingerprintRef.current = cacheFingerprint;
246
+ lastEmittedModelHashRef.current = modelHash;
247
+ setNodes((currentNodes) => {
248
+ const merged = mergeNodesWithStability(
249
+ currentNodes,
250
+ nextNodes,
251
+ nodeDataHashRef
252
+ );
253
+ return merged;
254
+ });
255
+ setEdges((currentEdges) => {
256
+ const newEdges = decorateEdges ? decorateEdges(createSequentialEdges(nextNodes), nextNodes) : createSequentialEdges(nextNodes);
257
+ const merged = mergeEdgesWithStability(
258
+ currentEdges,
259
+ newEdges,
260
+ edgeDataHashRef
261
+ );
262
+ return merged;
263
+ });
264
+ hasMountedRef.current = true;
265
+ lastNodeCountRef.current = nextNodes.length;
266
+ if (isInitialMount) {
267
+ pendingInitialFitRef.current = true;
268
+ }
269
+ }, [
270
+ steps,
271
+ normalizedParametersProp,
272
+ normalizedOutputProp,
273
+ modelHash,
274
+ cacheFingerprint,
275
+ scaffolderActionIds,
276
+ scaffolderActionInputsById,
277
+ scaffolderActionOutputsById,
278
+ scaffolderActionInputRequiredById,
279
+ decorateNodes,
280
+ decorateEdges,
281
+ setNodes,
282
+ setEdges
283
+ ]);
284
+ useEffect(() => {
285
+ if (!nodes.length) {
286
+ return;
287
+ }
288
+ if (nodes.some((node) => node.dragging)) {
289
+ return;
290
+ }
291
+ const activeNodeIds = /* @__PURE__ */ new Set();
292
+ let hasMeasuredChange = false;
293
+ nodes.forEach((node) => {
294
+ activeNodeIds.add(node.id);
295
+ const measuredHeight = resolveNodeHeightForTracking(node);
296
+ if (typeof measuredHeight !== "number") {
297
+ return;
298
+ }
299
+ const previousHeight = nodeHeightsRef.current[node.id];
300
+ const heightDelta = Math.abs(
301
+ (previousHeight ?? measuredHeight) - measuredHeight
302
+ );
303
+ if (heightDelta >= 1) {
304
+ nodeHeightsRef.current[node.id] = measuredHeight;
305
+ hasMeasuredChange = true;
306
+ }
307
+ });
308
+ Object.keys(nodeHeightsRef.current).forEach((id) => {
309
+ if (!activeNodeIds.has(id)) {
310
+ delete nodeHeightsRef.current[id];
311
+ }
312
+ });
313
+ if (!hasMeasuredChange) {
314
+ return;
315
+ }
316
+ if (!layoutInitializedRef.current) {
317
+ layoutInitializedRef.current = true;
318
+ return;
319
+ }
320
+ if (alignDebounceRef.current) {
321
+ clearTimeout(alignDebounceRef.current);
322
+ }
323
+ alignDebounceRef.current = setTimeout(() => {
324
+ alignDebounceRef.current = null;
325
+ setNodes((currentNodes) => {
326
+ const alignedNodes = alignNodes(
327
+ currentNodes,
328
+ FIXED_X_POSITION,
329
+ VERTICAL_SPACING
330
+ );
331
+ const positionsChanged = alignedNodes.some((node, index) => {
332
+ const previousNode = currentNodes[index];
333
+ if (!previousNode) {
334
+ return true;
335
+ }
336
+ return node.position.x !== previousNode.position.x || node.position.y !== previousNode.position.y;
337
+ });
338
+ return positionsChanged ? alignedNodes : currentNodes;
339
+ });
340
+ }, VIEWPORT_TUNING.alignDebounceMs);
341
+ }, [nodes, setNodes]);
342
+ const parameterReferences = useMemo(
343
+ () => collectParameterReferences(normalizedParametersProp),
344
+ [normalizedParametersProp]
345
+ );
346
+ const stepOutputReferencesByNode = useMemo(
347
+ () => collectStepOutputReferences(nodes, parameterReferences),
348
+ [nodes, parameterReferences]
349
+ );
350
+ const onNodesChange = useCallback(
351
+ (changes) => setNodes((ns) => {
352
+ if (changes.some(
353
+ (change) => change.type === "position" && change.dragging
354
+ )) {
355
+ isDraggingRef.current = true;
356
+ setIsDragging(true);
357
+ } else if (changes.some(
358
+ (change) => change.type === "position" && change.dragging === false
359
+ )) {
360
+ const stillDragging = ns.some((node) => node.dragging);
361
+ isDraggingRef.current = stillDragging;
362
+ setIsDragging(stillDragging);
363
+ }
364
+ return applyNodeChanges(changes, ns);
365
+ }),
366
+ [setNodes]
367
+ );
368
+ const onEdgesChange = useCallback(
369
+ (changes) => setEdges((es) => applyEdgeChanges(changes, es)),
370
+ [setEdges]
371
+ );
372
+ const resolvedNodeTypes = useMemo(
373
+ () => ({
374
+ parametersNode: ((props) => createElement(parametersNodeComponent, {
375
+ data: props.data
376
+ })),
377
+ actionNode: ((props) => createElement(actionNodeComponent, {
378
+ data: props.data
379
+ })),
380
+ outputNode: ((props) => createElement(outputNodeComponent, {
381
+ data: props.data
382
+ }))
383
+ }),
384
+ [actionNodeComponent, outputNodeComponent, parametersNodeComponent]
385
+ );
386
+ const onConnect = useCallback(
387
+ (params) => setEdges((es) => addEdge(params, es)),
388
+ [setEdges]
389
+ );
390
+ const reorderAndAlignNodes = useMemo(
391
+ () => createHandleReorderAndAlignNodes(setNodes, setEdges, {
392
+ fixedXPosition: FIXED_X_POSITION,
393
+ verticalSpacing: VERTICAL_SPACING
394
+ }),
395
+ [setNodes, setEdges]
396
+ );
397
+ const onNodeDragStop = useCallback(
398
+ (_, node) => {
399
+ isDraggingRef.current = false;
400
+ setIsDragging(false);
401
+ reorderAndAlignNodes(node);
402
+ },
403
+ [reorderAndAlignNodes]
404
+ );
405
+ const onUpdateField = useMemo(
406
+ () => createHandleUpdateField(setNodes),
407
+ [setNodes]
408
+ );
409
+ const onUpdateInput = useMemo(
410
+ () => createHandleUpdateInput(setNodes),
411
+ [setNodes]
412
+ );
413
+ const onRemoveInputKey = useMemo(
414
+ () => createHandleRemoveInputKey(setNodes),
415
+ [setNodes]
416
+ );
417
+ const onUpdateOutput = useMemo(
418
+ () => createHandleUpdateOutput(setNodes),
419
+ [setNodes]
420
+ );
421
+ const onUpdateSections = useMemo(
422
+ () => createHandleUpdateSections(setNodes),
423
+ [setNodes]
424
+ );
425
+ const handleAddNode = useMemo(
426
+ () => createHandleAddNode(setNodes, setEdges, {
427
+ fixedXPosition: FIXED_X_POSITION,
428
+ verticalSpacing: VERTICAL_SPACING,
429
+ nodeDefaults: nodeDefaults$1,
430
+ scaffolderActionIds,
431
+ scaffolderActionInputsById,
432
+ scaffolderActionInputRequiredById,
433
+ scaffolderActionOutputsById,
434
+ onNodeAdded: (rfId) => {
435
+ pendingFocusNodeIdRef.current = rfId;
436
+ pendingInitialFitRef.current = false;
437
+ }
438
+ }),
439
+ [
440
+ scaffolderActionIds,
441
+ scaffolderActionInputsById,
442
+ scaffolderActionOutputsById,
443
+ scaffolderActionInputRequiredById,
444
+ nodeDefaults$1,
445
+ setNodes,
446
+ setEdges
447
+ ]
448
+ );
449
+ const handleRemoveNode = useMemo(
450
+ () => createHandleRemoveNode(setNodes, setEdges, {
451
+ fixedXPosition: FIXED_X_POSITION,
452
+ verticalSpacing: VERTICAL_SPACING
453
+ }),
454
+ [setNodes, setEdges]
455
+ );
456
+ const emitChanges = useCallback(() => {
457
+ if (!onStepsChange && !onParametersChange && !onOutputChange) {
458
+ return;
459
+ }
460
+ const stepsFromNodes = extractStepsFromNodes(nodes);
461
+ const parametersFromNodes = extractParametersFromNodes(nodes);
462
+ const outputFromNodes = extractOutputFromNodes(nodes);
463
+ const nextHash = stableStringify({
464
+ steps: stepsFromNodes,
465
+ parameters: parametersFromNodes,
466
+ output: outputFromNodes
467
+ });
468
+ if (nextHash === lastEmittedModelHashRef.current) {
469
+ return;
470
+ }
471
+ lastEmittedModelHashRef.current = nextHash;
472
+ onStepsChange?.(stepsFromNodes);
473
+ onParametersChange?.(parametersFromNodes);
474
+ onOutputChange?.(outputFromNodes);
475
+ }, [
476
+ nodes,
477
+ onOutputChange,
478
+ onParametersChange,
479
+ onStepsChange,
480
+ lastEmittedModelHashRef
481
+ ]);
482
+ const emitChangesDeferred = useCallback(() => {
483
+ if (emitDebounceRef.current) {
484
+ clearTimeout(emitDebounceRef.current);
485
+ }
486
+ emitDebounceRef.current = setTimeout(() => {
487
+ emitChanges();
488
+ emitDebounceRef.current = null;
489
+ }, EMIT_DEBOUNCE_MS);
490
+ }, [emitChanges]);
491
+ const flushPendingEmit = useCallback(() => {
492
+ if (emitDebounceRef.current) {
493
+ clearTimeout(emitDebounceRef.current);
494
+ emitDebounceRef.current = null;
495
+ }
496
+ emitChanges();
497
+ }, [emitChanges]);
498
+ const ensureNodeDataStability = useCallback(
499
+ (node) => {
500
+ if (node.type === "actionNode") {
501
+ const data = node.data;
502
+ const nextRefs = stepOutputReferencesByNode[node.id] ?? data.stepOutputReferences;
503
+ const refsUnchanged = shallowArrayEqual(
504
+ data.stepOutputReferences,
505
+ nextRefs
506
+ );
507
+ const hasSameHandlers = data.onAddNode === handleAddNode && data.onRemoveNode === handleRemoveNode && data.onUpdateField === onUpdateField && data.onUpdateInput === onUpdateInput && data.onRemoveInputKey === onRemoveInputKey;
508
+ const hasSameCaches = data.scaffolderActionIds === scaffolderActionIds && data.scaffolderActionInputsById === scaffolderActionInputsById && data.scaffolderActionOutputsById === scaffolderActionOutputsById && data.scaffolderActionInputRequiredById === scaffolderActionInputRequiredById;
509
+ if (hasSameHandlers && hasSameCaches && refsUnchanged) {
510
+ return node;
511
+ }
512
+ return {
513
+ ...node,
514
+ data: {
515
+ ...data,
516
+ onAddNode: handleAddNode,
517
+ onRemoveNode: handleRemoveNode,
518
+ onUpdateField,
519
+ onUpdateInput,
520
+ onRemoveInputKey,
521
+ scaffolderActionIds,
522
+ scaffolderActionInputsById,
523
+ scaffolderActionOutputsById,
524
+ scaffolderActionInputRequiredById,
525
+ stepOutputReferences: refsUnchanged ? data.stepOutputReferences : nextRefs
526
+ }
527
+ };
528
+ }
529
+ if (node.type === "outputNode") {
530
+ const data = node.data;
531
+ const nextRefs = stepOutputReferencesByNode[node.id] ?? data.stepOutputReferences;
532
+ const refsUnchanged = shallowArrayEqual(
533
+ data.stepOutputReferences,
534
+ nextRefs
535
+ );
536
+ const hasSameHandlers = data.onUpdateOutput === onUpdateOutput && data.onRemoveNode === handleRemoveNode && data.onAddNode === handleAddNode;
537
+ const hasSameCaches = data.scaffolderActionIds === scaffolderActionIds && data.scaffolderActionInputsById === scaffolderActionInputsById && data.scaffolderActionOutputsById === scaffolderActionOutputsById && data.scaffolderActionInputRequiredById === scaffolderActionInputRequiredById;
538
+ if (hasSameHandlers && hasSameCaches && refsUnchanged) {
539
+ return node;
540
+ }
541
+ return {
542
+ ...node,
543
+ data: {
544
+ ...data,
545
+ onUpdateOutput,
546
+ onRemoveNode: handleRemoveNode,
547
+ onAddNode: handleAddNode,
548
+ scaffolderActionIds,
549
+ scaffolderActionInputsById,
550
+ scaffolderActionOutputsById,
551
+ scaffolderActionInputRequiredById,
552
+ stepOutputReferences: refsUnchanged ? data.stepOutputReferences : nextRefs
553
+ }
554
+ };
555
+ }
556
+ if (node.type === "parametersNode") {
557
+ const data = node.data;
558
+ const hasSameHandlers = data.onUpdateSections === onUpdateSections && data.onRemoveNode === handleRemoveNode && data.onAddNode === handleAddNode;
559
+ const hasSameCaches = data.scaffolderActionIds === scaffolderActionIds && data.scaffolderActionInputsById === scaffolderActionInputsById && data.scaffolderActionOutputsById === scaffolderActionOutputsById && data.scaffolderActionInputRequiredById === scaffolderActionInputRequiredById;
560
+ if (hasSameHandlers && hasSameCaches) {
561
+ return node;
562
+ }
563
+ return {
564
+ ...node,
565
+ data: {
566
+ ...data,
567
+ onUpdateSections,
568
+ onRemoveNode: handleRemoveNode,
569
+ onAddNode: handleAddNode,
570
+ scaffolderActionIds,
571
+ scaffolderActionInputsById,
572
+ scaffolderActionOutputsById,
573
+ scaffolderActionInputRequiredById
574
+ }
575
+ };
576
+ }
577
+ return node;
578
+ },
579
+ [
580
+ handleAddNode,
581
+ handleRemoveNode,
582
+ onUpdateField,
583
+ onUpdateInput,
584
+ onRemoveInputKey,
585
+ onUpdateOutput,
586
+ onUpdateSections,
587
+ scaffolderActionIds,
588
+ scaffolderActionInputsById,
589
+ scaffolderActionOutputsById,
590
+ scaffolderActionInputRequiredById,
591
+ stepOutputReferencesByNode
592
+ ]
593
+ );
594
+ useEffect(() => {
595
+ setNodes((currentNodes) => {
596
+ let changed = false;
597
+ const nextNodes = currentNodes.map((node) => {
598
+ const updated = ensureNodeDataStability(node);
599
+ if (updated !== node) {
600
+ changed = true;
601
+ }
602
+ return updated;
603
+ });
604
+ return changed ? nextNodes : currentNodes;
605
+ });
606
+ }, [ensureNodeDataStability, setNodes]);
607
+ useEffect(() => {
608
+ emitChangesDeferred();
609
+ }, [nodes, emitChangesDeferred]);
610
+ useEffect(() => {
611
+ lastEmittedModelHashRef.current = null;
612
+ }, [nodes]);
613
+ useEffect(() => {
614
+ if (!nodes.length) {
615
+ return;
616
+ }
617
+ emitChangesDeferred();
618
+ }, [emitChangesDeferred, modelHash, nodes]);
619
+ useEffect(() => {
620
+ return () => {
621
+ if (emitDebounceRef.current) {
622
+ clearTimeout(emitDebounceRef.current);
623
+ }
624
+ if (fitViewRafRef.current !== null) {
625
+ cancelAnimationFrame(fitViewRafRef.current);
626
+ fitViewRafRef.current = null;
627
+ }
628
+ if (fitViewTimeoutRef.current) {
629
+ clearTimeout(fitViewTimeoutRef.current);
630
+ fitViewTimeoutRef.current = null;
631
+ }
632
+ if (alignDebounceRef.current) {
633
+ clearTimeout(alignDebounceRef.current);
634
+ }
635
+ flushPendingEmit();
636
+ };
637
+ }, [flushPendingEmit]);
638
+ const [reactFlowInstance, setReactFlowInstance] = useState(null);
639
+ const fitFlowToView = useCallback(() => {
640
+ if (!reactFlowInstance || !nodes.length) {
641
+ return;
642
+ }
643
+ if (fitViewRafRef.current !== null) {
644
+ return;
645
+ }
646
+ if (userMovedViewportRef.current && !pendingInitialFitRef.current) {
647
+ return;
648
+ }
649
+ const nodeWithWidth = nodes.find((node) => node.width);
650
+ const nodeWidth = nodeWithWidth?.width ?? 760;
651
+ const padding = Math.max((window.innerWidth - nodeWidth) / 2 - 24, 60);
652
+ const viewOptions = {
653
+ padding,
654
+ minZoom: 0.2
655
+ };
656
+ fitViewRafRef.current = window.requestAnimationFrame(() => {
657
+ fitViewRafRef.current = null;
658
+ try {
659
+ reactFlowInstance.fitView(viewOptions);
660
+ setViewportIfChanged(reactFlowInstance.getViewport());
661
+ } catch {
662
+ }
663
+ });
664
+ if (fitViewTimeoutRef.current) {
665
+ clearTimeout(fitViewTimeoutRef.current);
666
+ }
667
+ fitViewTimeoutRef.current = setTimeout(() => {
668
+ fitViewTimeoutRef.current = null;
669
+ try {
670
+ reactFlowInstance.fitView(viewOptions);
671
+ setViewportIfChanged(reactFlowInstance.getViewport());
672
+ } catch {
673
+ }
674
+ }, VIEWPORT_TUNING.fitFallbackDelayMs);
675
+ }, [nodes, reactFlowInstance, setViewportIfChanged]);
676
+ useEffect(() => {
677
+ if (!reactFlowInstance) {
678
+ return;
679
+ }
680
+ if (viewport === null) {
681
+ setViewportIfChanged(viewport);
682
+ }
683
+ if (!pendingInitialFitRef.current) {
684
+ return;
685
+ }
686
+ pendingInitialFitRef.current = false;
687
+ fitFlowToView();
688
+ }, [
689
+ fitFlowToView,
690
+ nodes,
691
+ edges,
692
+ reactFlowInstance,
693
+ viewport,
694
+ setViewportIfChanged
695
+ ]);
696
+ useEffect(() => {
697
+ const swallowResizeObserverError = (event) => {
698
+ const message = event?.message || event?.error?.message || String(event);
699
+ if (typeof message === "string" && message.includes("ResizeObserver loop")) {
700
+ event.preventDefault();
701
+ event.stopImmediatePropagation();
702
+ }
703
+ };
704
+ const swallowResizeObserverRejection = (event) => {
705
+ const message = event.reason?.message ?? String(event.reason);
706
+ if (typeof message === "string" && message.includes("ResizeObserver loop")) {
707
+ event.preventDefault();
708
+ event.stopImmediatePropagation();
709
+ }
710
+ };
711
+ window.addEventListener("error", swallowResizeObserverError, true);
712
+ window.addEventListener(
713
+ "unhandledrejection",
714
+ swallowResizeObserverRejection,
715
+ true
716
+ );
717
+ const swallowLoopErrorEvent = (event) => {
718
+ event.preventDefault();
719
+ event.stopImmediatePropagation();
720
+ };
721
+ window.addEventListener(
722
+ "resizeobserverlooperror",
723
+ swallowLoopErrorEvent,
724
+ true
725
+ );
726
+ return () => {
727
+ window.removeEventListener("error", swallowResizeObserverError, true);
728
+ window.removeEventListener(
729
+ "unhandledrejection",
730
+ swallowResizeObserverRejection,
731
+ true
732
+ );
733
+ window.removeEventListener(
734
+ "resizeobserverlooperror",
735
+ swallowLoopErrorEvent,
736
+ true
737
+ );
738
+ };
739
+ }, []);
740
+ useEffect(() => {
741
+ if (!reactFlowInstance) {
742
+ return void 0;
743
+ }
744
+ window.addEventListener("resize", fitFlowToView);
745
+ return () => {
746
+ window.removeEventListener("resize", fitFlowToView);
747
+ };
748
+ }, [fitFlowToView, reactFlowInstance]);
749
+ useEffect(() => {
750
+ if (!isDragging) {
751
+ if (emitAfterDragRef.current) {
752
+ emitAfterDragRef.current = false;
753
+ lastEmittedModelHashRef.current = null;
754
+ }
755
+ lastEmittedModelHashRef.current = null;
756
+ }
757
+ }, [isDragging]);
758
+ const handleMoveEnd = useCallback(
759
+ (_event, nextViewport) => {
760
+ setViewportIfChanged(nextViewport);
761
+ },
762
+ [setViewportIfChanged]
763
+ );
764
+ const handleMove = useCallback(
765
+ (_, nextViewport) => {
766
+ userMovedViewportRef.current = true;
767
+ setViewportIfChanged(nextViewport);
768
+ },
769
+ [setViewportIfChanged]
770
+ );
771
+ const shouldCullEdges = edges.length > 300 && (viewport?.zoom ?? 1) < 0.3 && !isDragging;
772
+ const renderedEdges = useMemo(
773
+ () => shouldCullEdges ? EMPTY_EDGES : edges,
774
+ [edges, shouldCullEdges]
775
+ );
776
+ useEffect(() => {
777
+ if (!reactFlowInstance) {
778
+ return;
779
+ }
780
+ const pendingId = pendingFocusNodeIdRef.current;
781
+ if (!pendingId) {
782
+ return;
783
+ }
784
+ const target = nodes.find((node) => node.id === pendingId);
785
+ if (!target) {
786
+ return;
787
+ }
788
+ pendingFocusNodeIdRef.current = null;
789
+ const nodeCenterX = target.position.x + (target.width ?? 0) / 2;
790
+ const nodeCenterY = target.position.y + (target.height ?? 0) / 2;
791
+ const zoom = viewport?.zoom ?? lastViewportRef.current?.zoom ?? void 0;
792
+ try {
793
+ reactFlowInstance.setCenter(nodeCenterX, nodeCenterY, {
794
+ zoom,
795
+ duration: VIEWPORT_TUNING.centerDurationMs
796
+ });
797
+ window.requestAnimationFrame(() => {
798
+ setViewportIfChanged(reactFlowInstance.getViewport());
799
+ });
800
+ } catch {
801
+ }
802
+ }, [nodes, reactFlowInstance, viewport, setViewportIfChanged]);
803
+ return /* @__PURE__ */ jsx("div", { style: { width: "100%", height: "100%", minHeight: "100%" }, children: /* @__PURE__ */ jsx(
804
+ ReactFlow,
805
+ {
806
+ nodes,
807
+ edges: renderedEdges,
808
+ nodeTypes: resolvedNodeTypes,
809
+ defaultViewport: viewport ?? {
810
+ x: 0,
811
+ y: 0,
812
+ zoom: 1
813
+ },
814
+ viewport: viewport ?? void 0,
815
+ onNodesChange,
816
+ onEdgesChange,
817
+ onNodeDragStop,
818
+ onConnect,
819
+ onMove: handleMove,
820
+ onlyRenderVisibleElements: true,
821
+ onInit: setReactFlowInstance,
822
+ onMoveEnd: handleMoveEnd
823
+ }
824
+ ) });
825
+ }
826
+
827
+ export { DesignerFlow as default };
828
+ //# sourceMappingURL=DesignerFlow.esm.js.map