@ship-ui/core 0.19.5 → 0.22.2

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 (153) hide show
  1. package/README.md +3 -0
  2. package/assets/mcp/components.json +66 -4243
  3. package/bin/mcp/index.js +6027 -273
  4. package/bin/ship-fg-scanner +0 -0
  5. package/bin/ship-fg.mjs +14 -12
  6. package/bin/src/subset.ts +3 -1
  7. package/fesm2022/ship-ui-core-sh-form-field-experimental.mjs +42 -0
  8. package/fesm2022/ship-ui-core-sh-form-field-experimental.mjs.map +1 -0
  9. package/fesm2022/ship-ui-core-ship-accordion.mjs +127 -0
  10. package/fesm2022/ship-ui-core-ship-accordion.mjs.map +1 -0
  11. package/fesm2022/ship-ui-core-ship-alert.mjs +305 -0
  12. package/fesm2022/ship-ui-core-ship-alert.mjs.map +1 -0
  13. package/fesm2022/ship-ui-core-ship-blueprint.mjs +1156 -0
  14. package/fesm2022/ship-ui-core-ship-blueprint.mjs.map +1 -0
  15. package/fesm2022/ship-ui-core-ship-button-group.mjs +41 -0
  16. package/fesm2022/ship-ui-core-ship-button-group.mjs.map +1 -0
  17. package/fesm2022/ship-ui-core-ship-button.mjs +38 -0
  18. package/fesm2022/ship-ui-core-ship-button.mjs.map +1 -0
  19. package/fesm2022/ship-ui-core-ship-card.mjs +35 -0
  20. package/fesm2022/ship-ui-core-ship-card.mjs.map +1 -0
  21. package/fesm2022/ship-ui-core-ship-checkbox.mjs +113 -0
  22. package/fesm2022/ship-ui-core-ship-checkbox.mjs.map +1 -0
  23. package/fesm2022/ship-ui-core-ship-chip.mjs +44 -0
  24. package/fesm2022/ship-ui-core-ship-chip.mjs.map +1 -0
  25. package/fesm2022/ship-ui-core-ship-color-picker.mjs +947 -0
  26. package/fesm2022/ship-ui-core-ship-color-picker.mjs.map +1 -0
  27. package/fesm2022/ship-ui-core-ship-datepicker.mjs +951 -0
  28. package/fesm2022/ship-ui-core-ship-datepicker.mjs.map +1 -0
  29. package/fesm2022/ship-ui-core-ship-dialog.mjs +263 -0
  30. package/fesm2022/ship-ui-core-ship-dialog.mjs.map +1 -0
  31. package/fesm2022/ship-ui-core-ship-divider.mjs +22 -0
  32. package/fesm2022/ship-ui-core-ship-divider.mjs.map +1 -0
  33. package/fesm2022/ship-ui-core-ship-event-card.mjs +50 -0
  34. package/fesm2022/ship-ui-core-ship-event-card.mjs.map +1 -0
  35. package/fesm2022/ship-ui-core-ship-file-upload.mjs +112 -0
  36. package/fesm2022/ship-ui-core-ship-file-upload.mjs.map +1 -0
  37. package/fesm2022/ship-ui-core-ship-form-field.mjs +310 -0
  38. package/fesm2022/ship-ui-core-ship-form-field.mjs.map +1 -0
  39. package/fesm2022/ship-ui-core-ship-icon.mjs +81 -0
  40. package/fesm2022/ship-ui-core-ship-icon.mjs.map +1 -0
  41. package/fesm2022/ship-ui-core-ship-list.mjs +22 -0
  42. package/fesm2022/ship-ui-core-ship-list.mjs.map +1 -0
  43. package/fesm2022/ship-ui-core-ship-menu.mjs +545 -0
  44. package/fesm2022/ship-ui-core-ship-menu.mjs.map +1 -0
  45. package/fesm2022/ship-ui-core-ship-popover.mjs +286 -0
  46. package/fesm2022/ship-ui-core-ship-popover.mjs.map +1 -0
  47. package/fesm2022/ship-ui-core-ship-progress-bar.mjs +37 -0
  48. package/fesm2022/ship-ui-core-ship-progress-bar.mjs.map +1 -0
  49. package/fesm2022/ship-ui-core-ship-radio.mjs +102 -0
  50. package/fesm2022/ship-ui-core-ship-radio.mjs.map +1 -0
  51. package/fesm2022/ship-ui-core-ship-range-slider.mjs +277 -0
  52. package/fesm2022/ship-ui-core-ship-range-slider.mjs.map +1 -0
  53. package/fesm2022/ship-ui-core-ship-select.mjs +971 -0
  54. package/fesm2022/ship-ui-core-ship-select.mjs.map +1 -0
  55. package/fesm2022/ship-ui-core-ship-sidenav.mjs +248 -0
  56. package/fesm2022/ship-ui-core-ship-sidenav.mjs.map +1 -0
  57. package/fesm2022/ship-ui-core-ship-sortable.mjs +485 -0
  58. package/fesm2022/ship-ui-core-ship-sortable.mjs.map +1 -0
  59. package/fesm2022/ship-ui-core-ship-spinner.mjs +28 -0
  60. package/fesm2022/ship-ui-core-ship-spinner.mjs.map +1 -0
  61. package/fesm2022/ship-ui-core-ship-stepper.mjs +76 -0
  62. package/fesm2022/ship-ui-core-ship-stepper.mjs.map +1 -0
  63. package/fesm2022/ship-ui-core-ship-table-filter-bar.mjs +28 -0
  64. package/fesm2022/ship-ui-core-ship-table-filter-bar.mjs.map +1 -0
  65. package/fesm2022/ship-ui-core-ship-table.mjs +442 -0
  66. package/fesm2022/ship-ui-core-ship-table.mjs.map +1 -0
  67. package/fesm2022/ship-ui-core-ship-tabs.mjs +38 -0
  68. package/fesm2022/ship-ui-core-ship-tabs.mjs.map +1 -0
  69. package/fesm2022/ship-ui-core-ship-theme-toggle.mjs +119 -0
  70. package/fesm2022/ship-ui-core-ship-theme-toggle.mjs.map +1 -0
  71. package/fesm2022/ship-ui-core-ship-toggle-card.mjs +75 -0
  72. package/fesm2022/ship-ui-core-ship-toggle-card.mjs.map +1 -0
  73. package/fesm2022/ship-ui-core-ship-toggle.mjs +105 -0
  74. package/fesm2022/ship-ui-core-ship-toggle.mjs.map +1 -0
  75. package/fesm2022/ship-ui-core-ship-virtual-scroll.mjs +186 -0
  76. package/fesm2022/ship-ui-core-ship-virtual-scroll.mjs.map +1 -0
  77. package/fesm2022/ship-ui-core.mjs +880 -8782
  78. package/fesm2022/ship-ui-core.mjs.map +1 -1
  79. package/package.json +147 -3
  80. package/styles/core.scss +43 -0
  81. package/styles/helpers.scss +2 -0
  82. package/styles/index.scss +12 -123
  83. package/types/ship-ui-core-sh-form-field-experimental.d.ts +11 -0
  84. package/types/ship-ui-core-ship-accordion.d.ts +19 -0
  85. package/types/ship-ui-core-ship-alert.d.ts +68 -0
  86. package/types/ship-ui-core-ship-blueprint.d.ts +112 -0
  87. package/types/ship-ui-core-ship-button-group.d.ts +15 -0
  88. package/types/ship-ui-core-ship-button.d.ts +13 -0
  89. package/types/ship-ui-core-ship-card.d.ts +11 -0
  90. package/types/ship-ui-core-ship-checkbox.d.ts +22 -0
  91. package/types/ship-ui-core-ship-chip.d.ts +15 -0
  92. package/types/ship-ui-core-ship-color-picker.d.ts +105 -0
  93. package/types/ship-ui-core-ship-datepicker.d.ts +96 -0
  94. package/types/ship-ui-core-ship-dialog.d.ts +76 -0
  95. package/types/ship-ui-core-ship-divider.d.ts +8 -0
  96. package/types/ship-ui-core-ship-event-card.d.ts +11 -0
  97. package/types/ship-ui-core-ship-file-upload.d.ts +20 -0
  98. package/types/ship-ui-core-ship-form-field.d.ts +32 -0
  99. package/types/ship-ui-core-ship-icon.d.ts +18 -0
  100. package/types/ship-ui-core-ship-list.d.ts +8 -0
  101. package/types/ship-ui-core-ship-menu.d.ts +49 -0
  102. package/types/ship-ui-core-ship-popover.d.ts +40 -0
  103. package/types/ship-ui-core-ship-progress-bar.d.ts +14 -0
  104. package/types/ship-ui-core-ship-radio.d.ts +22 -0
  105. package/types/ship-ui-core-ship-range-slider.d.ts +31 -0
  106. package/types/ship-ui-core-ship-select.d.ts +81 -0
  107. package/types/ship-ui-core-ship-sidenav.d.ts +36 -0
  108. package/types/ship-ui-core-ship-sortable.d.ts +72 -0
  109. package/types/ship-ui-core-ship-spinner.d.ts +10 -0
  110. package/types/ship-ui-core-ship-stepper.d.ts +13 -0
  111. package/types/ship-ui-core-ship-table-filter-bar.d.ts +8 -0
  112. package/types/ship-ui-core-ship-table.d.ts +69 -0
  113. package/types/ship-ui-core-ship-tabs.d.ts +14 -0
  114. package/types/ship-ui-core-ship-theme-toggle.d.ts +28 -0
  115. package/types/ship-ui-core-ship-toggle-card.d.ts +15 -0
  116. package/types/ship-ui-core-ship-toggle.d.ts +21 -0
  117. package/types/ship-ui-core-ship-virtual-scroll.d.ts +22 -0
  118. package/types/ship-ui-core.d.ts +88 -1070
  119. package/styles/components/ship-accordion.scss +0 -113
  120. package/styles/components/ship-alert-container.scss +0 -49
  121. package/styles/components/ship-alert.scss +0 -177
  122. package/styles/components/ship-blueprint.scss +0 -242
  123. package/styles/components/ship-button-group.scss +0 -165
  124. package/styles/components/ship-button.scss +0 -141
  125. package/styles/components/ship-card.scss +0 -57
  126. package/styles/components/ship-checkbox.scss +0 -116
  127. package/styles/components/ship-chip.scss +0 -104
  128. package/styles/components/ship-color-picker.scss +0 -150
  129. package/styles/components/ship-datepicker.scss +0 -317
  130. package/styles/components/ship-dialog.scss +0 -152
  131. package/styles/components/ship-divider.scss +0 -27
  132. package/styles/components/ship-event-card.scss +0 -51
  133. package/styles/components/ship-file-upload.scss +0 -47
  134. package/styles/components/ship-form-field.scss +0 -408
  135. package/styles/components/ship-icon.scss +0 -54
  136. package/styles/components/ship-list.scss +0 -165
  137. package/styles/components/ship-menu.scss +0 -237
  138. package/styles/components/ship-popover.scss +0 -205
  139. package/styles/components/ship-progress-bar.scss +0 -173
  140. package/styles/components/ship-radio.scss +0 -113
  141. package/styles/components/ship-range-slider.scss +0 -421
  142. package/styles/components/ship-select.scss +0 -153
  143. package/styles/components/ship-sidenav.scss +0 -195
  144. package/styles/components/ship-sortable.scss +0 -45
  145. package/styles/components/ship-spinner.scss +0 -53
  146. package/styles/components/ship-stepper.scss +0 -158
  147. package/styles/components/ship-table.scss +0 -443
  148. package/styles/components/ship-tabs.scss +0 -125
  149. package/styles/components/ship-theme-toggle.scss +0 -41
  150. package/styles/components/ship-toggle-card.scss +0 -69
  151. package/styles/components/ship-toggle.scss +0 -255
  152. package/styles/components/ship-tooltip.scss +0 -151
  153. package/styles/components/ship-virtual-scroll.scss +0 -12
@@ -0,0 +1,1156 @@
1
+ import { isPlatformBrowser, JsonPipe } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, DOCUMENT, PLATFORM_ID, ElementRef, computed, input, model, signal, effect, HostListener, ViewChild, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
4
+ import { ShipButton } from '@ship-ui/core/ship-button';
5
+ import { ShipCard } from '@ship-ui/core/ship-card';
6
+ import { ShipIcon } from '@ship-ui/core/ship-icon';
7
+ import { classMutationSignal } from '@ship-ui/core';
8
+
9
+ function layoutNodes(nodes) {
10
+ if (!nodes || nodes.length === 0)
11
+ return nodes;
12
+ const layoutedNodes = structuredClone(nodes);
13
+ const visited = new Set();
14
+ const unconnectedNodes = [];
15
+ const connectedGraphs = [];
16
+ const allToNodes = new Set(layoutedNodes.flatMap((node) => node.connections).map((conn) => conn.toNode));
17
+ const rootNodes = layoutedNodes.filter((node) => !allToNodes.has(node.id));
18
+ for (const node of layoutedNodes) {
19
+ if (!node.connections.length) {
20
+ unconnectedNodes.push(node);
21
+ }
22
+ }
23
+ for (const root of rootNodes) {
24
+ if (!visited.has(root.id)) {
25
+ const graph = [];
26
+ traverseGraph(root, graph, visited, layoutedNodes);
27
+ if (graph.length > 0) {
28
+ connectedGraphs.push(graph);
29
+ }
30
+ }
31
+ }
32
+ positionUnconnectedNodes(unconnectedNodes);
33
+ let currentX = 20;
34
+ let currentY = 20;
35
+ connectedGraphs.forEach((graph) => {
36
+ const layoutedGraph = traverseAndLayout(graph);
37
+ positionGraph(layoutedGraph, currentX, currentY);
38
+ currentY += getGraphHeight(layoutedGraph) + 200;
39
+ });
40
+ return layoutedNodes;
41
+ }
42
+ function traverseGraph(startNode, graph, visited, allNodes) {
43
+ const queue = [startNode];
44
+ visited.add(startNode.id);
45
+ let head = 0;
46
+ while (head < queue.length) {
47
+ const node = queue[head++];
48
+ graph.push(node);
49
+ node.connections.forEach((conn) => {
50
+ const toNode = allNodes.find((n) => n.id === conn.toNode);
51
+ if (toNode && !visited.has(toNode.id)) {
52
+ visited.add(toNode.id);
53
+ queue.push(toNode);
54
+ }
55
+ });
56
+ }
57
+ }
58
+ function positionUnconnectedNodes(nodes) {
59
+ nodes.forEach((node, index) => {
60
+ node.coordinates[0] = 20 + index * 200;
61
+ node.coordinates[1] = 20;
62
+ });
63
+ }
64
+ function traverseAndLayout(graph) {
65
+ const graphNodeMap = new Map(graph.map((n) => [n.id, n]));
66
+ const incomingConnectionCount = new Map();
67
+ graph.forEach((node) => {
68
+ incomingConnectionCount.set(node.id, 0);
69
+ });
70
+ graph
71
+ .flatMap((n) => n.connections)
72
+ .forEach((conn) => {
73
+ if (graphNodeMap.has(conn.toNode)) {
74
+ incomingConnectionCount.set(conn.toNode, (incomingConnectionCount.get(conn.toNode) || 0) + 1);
75
+ }
76
+ });
77
+ const roots = graph.filter((node) => (incomingConnectionCount.get(node.id) || 0) === 0);
78
+ const layoutedNodes = new Set();
79
+ const queue = [...roots];
80
+ let head = 0;
81
+ while (head < queue.length) {
82
+ const currentNode = queue[head++];
83
+ layoutedNodes.add(currentNode.id);
84
+ let outputConnectionIndex = 0;
85
+ const connectedNodes = [];
86
+ currentNode.connections.forEach((conn) => {
87
+ const toNode = graphNodeMap.get(conn.toNode);
88
+ if (toNode && !layoutedNodes.has(toNode.id)) {
89
+ connectedNodes.push(toNode);
90
+ }
91
+ });
92
+ connectedNodes.forEach((toNode) => {
93
+ toNode.coordinates[0] = currentNode.coordinates[0] + 200;
94
+ if (connectedNodes.length > 1) {
95
+ const verticalOffset = (outputConnectionIndex - (connectedNodes.length - 1) / 2) * 200;
96
+ toNode.coordinates[1] = currentNode.coordinates[1] + verticalOffset;
97
+ }
98
+ else {
99
+ toNode.coordinates[1] = currentNode.coordinates[1];
100
+ }
101
+ queue.push(toNode);
102
+ outputConnectionIndex++;
103
+ });
104
+ }
105
+ return graph;
106
+ }
107
+ function positionGraph(graph, startX, startY) {
108
+ const minX = Math.min(...graph.map((n) => n.coordinates[0]));
109
+ const minY = Math.min(...graph.map((n) => n.coordinates[1]));
110
+ graph.forEach((node) => {
111
+ node.coordinates[0] += startX - minX;
112
+ node.coordinates[1] += startY - minY;
113
+ });
114
+ }
115
+ function getGraphHeight(graph) {
116
+ if (!graph.length)
117
+ return 0;
118
+ const maxY = Math.max(...graph.map((n) => n.coordinates[1]));
119
+ const minY = Math.min(...graph.map((n) => n.coordinates[1]));
120
+ return maxY - minY;
121
+ }
122
+
123
+ function findDuplicatePortIDs(nodes) {
124
+ const errors = [];
125
+ for (const node of nodes) {
126
+ const portIdCounts = new Map();
127
+ const allPorts = [...node.inputs, ...node.outputs];
128
+ for (const port of allPorts) {
129
+ const count = portIdCounts.get(port.id) || 0;
130
+ portIdCounts.set(port.id, count + 1);
131
+ }
132
+ const duplicatePortIds = [];
133
+ for (const [id, count] of portIdCounts.entries()) {
134
+ if (count > 1) {
135
+ duplicatePortIds.push(id);
136
+ }
137
+ }
138
+ if (duplicatePortIds.length > 0) {
139
+ errors.push({
140
+ nodeId: node.id,
141
+ duplicatePortIds: duplicatePortIds,
142
+ });
143
+ }
144
+ }
145
+ return errors;
146
+ }
147
+ function findDuplicateNodeIDs(nodes) {
148
+ const nodeIdCounts = new Map();
149
+ for (const node of nodes) {
150
+ const count = nodeIdCounts.get(node.id) || 0;
151
+ nodeIdCounts.set(node.id, count + 1);
152
+ }
153
+ const duplicateNodeIds = [];
154
+ for (const [id, count] of nodeIdCounts.entries()) {
155
+ if (count > 1) {
156
+ duplicateNodeIds.push(id);
157
+ }
158
+ }
159
+ return duplicateNodeIds;
160
+ }
161
+
162
+ const TEST_NODES = [
163
+ {
164
+ id: 'a1',
165
+ name: 'hello a1',
166
+ coordinates: [0, 0],
167
+ inputs: [],
168
+ outputs: [{ id: 'out-1', name: 'Start Output' }],
169
+ connections: [{ fromNode: 'a1', fromPort: 'out-1', toNode: 'b1', toPort: 'in-1' }],
170
+ },
171
+ {
172
+ id: 'b1',
173
+ coordinates: [0, 0],
174
+ inputs: [
175
+ { id: 'in-1', name: 'Input A' },
176
+ { id: 'in-1', name: 'Another Input A' },
177
+ ],
178
+ outputs: [{ id: 'out-1', name: 'Output B' }],
179
+ connections: [
180
+ { fromNode: 'b1', fromPort: 'out-1', toNode: 'c6', toPort: 'in-1' },
181
+ { fromNode: 'a1', fromPort: 'out-1', toNode: 'b1', toPort: 'in-1' },
182
+ ],
183
+ },
184
+ {
185
+ id: 'c6',
186
+ name: 'hello c6',
187
+ coordinates: [0, 0],
188
+ inputs: [{ id: 'in-1', name: 'Input C' }],
189
+ outputs: [{ id: 'out-1', name: 'Output D' }],
190
+ connections: [{ fromNode: 'b1', fromPort: 'out-1', toNode: 'c6', toPort: 'in-1' }],
191
+ },
192
+ ];
193
+ class ShipBlueprint {
194
+ #ZOOM_SPEED;
195
+ #MAX_ZOOM;
196
+ #MIN_ZOOM;
197
+ #NODE_WIDTH;
198
+ #NODE_HEADER_HEIGHT;
199
+ #PORT_ROW_HEIGHT;
200
+ #document;
201
+ #platformId;
202
+ #selfRef;
203
+ #currentClass;
204
+ #htmlClass;
205
+ #currentGridColor;
206
+ #draggingNodeCoordinates;
207
+ #isDragging;
208
+ #lastMouseX;
209
+ #lastMouseY;
210
+ #initialPinchDistance;
211
+ #isNodeDragging;
212
+ #draggedNodeId;
213
+ #dragOffset;
214
+ #connections;
215
+ #canvasWidth;
216
+ #canvasHeight;
217
+ #ctx;
218
+ #resizeObserver;
219
+ constructor() {
220
+ this.#ZOOM_SPEED = 0.01;
221
+ this.#MAX_ZOOM = 1.5;
222
+ this.#MIN_ZOOM = 0.5;
223
+ this.#NODE_WIDTH = 180;
224
+ this.#NODE_HEADER_HEIGHT = 40;
225
+ this.#PORT_ROW_HEIGHT = 28;
226
+ this.#document = inject(DOCUMENT);
227
+ this.#platformId = inject(PLATFORM_ID);
228
+ this.#selfRef = inject((ElementRef));
229
+ this.#currentClass = classMutationSignal();
230
+ this.#htmlClass = classMutationSignal(this.#document.documentElement);
231
+ this.asDots = computed(() => this.#currentClass().includes('dots'), /* @ts-ignore */
232
+ ...(ngDevMode ? [{ debugName: "asDots" }] : /* istanbul ignore next */ []));
233
+ this.lightMode = computed(() => this.#htmlClass().includes('light'), /* @ts-ignore */
234
+ ...(ngDevMode ? [{ debugName: "lightMode" }] : /* istanbul ignore next */ []));
235
+ this.forceUnique = input(true, /* @ts-ignore */
236
+ ...(ngDevMode ? [{ debugName: "forceUnique" }] : /* istanbul ignore next */ []));
237
+ this.autoLayout = input(false, /* @ts-ignore */
238
+ ...(ngDevMode ? [{ debugName: "autoLayout" }] : /* istanbul ignore next */ []));
239
+ this.gridSize = input(20, /* @ts-ignore */
240
+ ...(ngDevMode ? [{ debugName: "gridSize" }] : /* istanbul ignore next */ []));
241
+ this.snapToGrid = input(true, /* @ts-ignore */
242
+ ...(ngDevMode ? [{ debugName: "snapToGrid" }] : /* istanbul ignore next */ []));
243
+ this.gridColor = input(['#d8d8d8', '#2c2c2c'], /* @ts-ignore */
244
+ ...(ngDevMode ? [{ debugName: "gridColor" }] : /* istanbul ignore next */ []));
245
+ this.nodes = model([], /* @ts-ignore */
246
+ ...(ngDevMode ? [{ debugName: "nodes" }] : /* istanbul ignore next */ []));
247
+ this.#currentGridColor = computed(() => this.gridColor()[this.lightMode() ? 0 : 1], /* @ts-ignore */
248
+ ...(ngDevMode ? [{ debugName: "#currentGridColor" }] : /* istanbul ignore next */ []));
249
+ this.panX = signal(0, /* @ts-ignore */
250
+ ...(ngDevMode ? [{ debugName: "panX" }] : /* istanbul ignore next */ []));
251
+ this.panY = signal(0, /* @ts-ignore */
252
+ ...(ngDevMode ? [{ debugName: "panY" }] : /* istanbul ignore next */ []));
253
+ this.zoomLevel = signal(1, /* @ts-ignore */
254
+ ...(ngDevMode ? [{ debugName: "zoomLevel" }] : /* istanbul ignore next */ []));
255
+ this.gridSnapSize = signal(20, /* @ts-ignore */
256
+ ...(ngDevMode ? [{ debugName: "gridSnapSize" }] : /* istanbul ignore next */ []));
257
+ this.isHoveringNode = signal(false, /* @ts-ignore */
258
+ ...(ngDevMode ? [{ debugName: "isHoveringNode" }] : /* istanbul ignore next */ []));
259
+ this.midpointDivPosition = signal(null, /* @ts-ignore */
260
+ ...(ngDevMode ? [{ debugName: "midpointDivPosition" }] : /* istanbul ignore next */ []));
261
+ this.showMidpointDiv = signal(false, /* @ts-ignore */
262
+ ...(ngDevMode ? [{ debugName: "showMidpointDiv" }] : /* istanbul ignore next */ []));
263
+ this.isLocked = signal(false, /* @ts-ignore */
264
+ ...(ngDevMode ? [{ debugName: "isLocked" }] : /* istanbul ignore next */ []));
265
+ this.draggingConnection = signal(null, /* @ts-ignore */
266
+ ...(ngDevMode ? [{ debugName: "draggingConnection" }] : /* istanbul ignore next */ []));
267
+ this.validationErrors = signal(null, /* @ts-ignore */
268
+ ...(ngDevMode ? [{ debugName: "validationErrors" }] : /* istanbul ignore next */ []));
269
+ this.highlightedConnection = signal(null, /* @ts-ignore */
270
+ ...(ngDevMode ? [{ debugName: "highlightedConnection" }] : /* istanbul ignore next */ []));
271
+ this.#draggingNodeCoordinates = signal(null, /* @ts-ignore */
272
+ ...(ngDevMode ? [{ debugName: "#draggingNodeCoordinates" }] : /* istanbul ignore next */ []));
273
+ this.#isDragging = signal(false, /* @ts-ignore */
274
+ ...(ngDevMode ? [{ debugName: "#isDragging" }] : /* istanbul ignore next */ []));
275
+ this.#lastMouseX = signal(0, /* @ts-ignore */
276
+ ...(ngDevMode ? [{ debugName: "#lastMouseX" }] : /* istanbul ignore next */ []));
277
+ this.#lastMouseY = signal(0, /* @ts-ignore */
278
+ ...(ngDevMode ? [{ debugName: "#lastMouseY" }] : /* istanbul ignore next */ []));
279
+ this.#initialPinchDistance = signal(0, /* @ts-ignore */
280
+ ...(ngDevMode ? [{ debugName: "#initialPinchDistance" }] : /* istanbul ignore next */ []));
281
+ this.#isNodeDragging = signal(false, /* @ts-ignore */
282
+ ...(ngDevMode ? [{ debugName: "#isNodeDragging" }] : /* istanbul ignore next */ []));
283
+ this.#draggedNodeId = signal(null, /* @ts-ignore */
284
+ ...(ngDevMode ? [{ debugName: "#draggedNodeId" }] : /* istanbul ignore next */ []));
285
+ this.#dragOffset = signal(null, /* @ts-ignore */
286
+ ...(ngDevMode ? [{ debugName: "#dragOffset" }] : /* istanbul ignore next */ []));
287
+ this.#connections = signal([], /* @ts-ignore */
288
+ ...(ngDevMode ? [{ debugName: "#connections" }] : /* istanbul ignore next */ []));
289
+ this.#canvasWidth = signal(0, /* @ts-ignore */
290
+ ...(ngDevMode ? [{ debugName: "#canvasWidth" }] : /* istanbul ignore next */ []));
291
+ this.#canvasHeight = signal(0, /* @ts-ignore */
292
+ ...(ngDevMode ? [{ debugName: "#canvasHeight" }] : /* istanbul ignore next */ []));
293
+ this.visibleNodes = computed(() => {
294
+ const nodes = this.nodes();
295
+ const panX = this.panX();
296
+ const panY = this.panY();
297
+ const zoom = this.zoomLevel();
298
+ const width = this.#canvasWidth();
299
+ const height = this.#canvasHeight();
300
+ if (width === 0 || height === 0) {
301
+ return nodes;
302
+ }
303
+ const bufferX = width / zoom;
304
+ const bufferY = height / zoom;
305
+ const viewbox = {
306
+ x1: -panX / zoom - bufferX,
307
+ y1: -panY / zoom - bufferY,
308
+ x2: (-panX + width) / zoom + bufferX,
309
+ y2: (-panY + height) / zoom + bufferY,
310
+ };
311
+ return nodes.filter((node) => {
312
+ const [x, y] = node.coordinates;
313
+ return x > viewbox.x1 && x < viewbox.x2 && y > viewbox.y1 && y < viewbox.y2;
314
+ });
315
+ }, /* @ts-ignore */
316
+ ...(ngDevMode ? [{ debugName: "visibleNodes" }] : /* istanbul ignore next */ []));
317
+ if (isPlatformBrowser(this.#platformId)) {
318
+ effect(() => {
319
+ this.asDots();
320
+ this.panX();
321
+ this.panY();
322
+ this.zoomLevel();
323
+ this.nodes();
324
+ this.#connections();
325
+ this.draggingConnection();
326
+ this.#currentGridColor();
327
+ this.highlightedConnection();
328
+ this.#draggingNodeCoordinates();
329
+ requestAnimationFrame(() => this.drawCanvas());
330
+ });
331
+ effect(() => {
332
+ const nodes = this.nodes();
333
+ const connectionsFromNodes = nodes.flatMap((node) => node.connections);
334
+ const uniqueConnections = connectionsFromNodes.filter((conn, index, self) => index ===
335
+ self.findIndex((c) => c.fromNode === conn.fromNode &&
336
+ c.fromPort === conn.fromPort &&
337
+ c.toNode === conn.toNode &&
338
+ c.toPort === conn.toPort));
339
+ this.#connections.set(uniqueConnections);
340
+ });
341
+ }
342
+ }
343
+ ngAfterViewInit() {
344
+ if (isPlatformBrowser(this.#platformId)) {
345
+ const canvas = this.canvasRef.nativeElement;
346
+ this.#ctx = canvas.getContext('2d');
347
+ if (typeof ResizeObserver !== 'undefined') {
348
+ this.#resizeObserver = new ResizeObserver(() => this.updateCanvasSize());
349
+ this.#resizeObserver.observe(this.#selfRef.nativeElement);
350
+ }
351
+ this.updateCanvasSize();
352
+ if (this.autoLayout()) {
353
+ this.applyAutolayout();
354
+ }
355
+ if (this.forceUnique()) {
356
+ this.#removeDuplicatesAndReinitiate();
357
+ }
358
+ else {
359
+ this.#validateNodes();
360
+ }
361
+ }
362
+ }
363
+ ngOnDestroy() {
364
+ if (this.#resizeObserver) {
365
+ this.#resizeObserver.disconnect();
366
+ }
367
+ }
368
+ onMouseUp(event) {
369
+ if (this.isLocked())
370
+ return;
371
+ this.endPan();
372
+ this.endNodeDrag();
373
+ }
374
+ onClick(event) {
375
+ const target = event.target;
376
+ if (this.draggingConnection()) {
377
+ if (!target.closest('.port')) {
378
+ this.cancelPortDrag();
379
+ }
380
+ }
381
+ else if (this.isLocked() && !target.closest('.midpoint-div')) {
382
+ this.closeMidpointDiv();
383
+ }
384
+ else {
385
+ this.#handleConnectionClick(event);
386
+ }
387
+ }
388
+ onEscape() {
389
+ if (this.draggingConnection()) {
390
+ this.cancelPortDrag();
391
+ }
392
+ else if (this.isLocked()) {
393
+ this.closeMidpointDiv();
394
+ }
395
+ }
396
+ onMouseMove(event) {
397
+ if (this.isLocked())
398
+ return;
399
+ if (this.#isNodeDragging()) {
400
+ this.nodeDrag(event);
401
+ }
402
+ else if (this.draggingConnection()) {
403
+ this.updatePathOnMove(event);
404
+ }
405
+ else {
406
+ this.pan(event);
407
+ if (this.isHoveringNode()) {
408
+ this.highlightedConnection.set(null);
409
+ }
410
+ else {
411
+ this.#checkConnectionHover(event);
412
+ }
413
+ }
414
+ }
415
+ onTouchMove(event) {
416
+ event.preventDefault();
417
+ if (this.isLocked())
418
+ return;
419
+ if (this.#isNodeDragging()) {
420
+ this.nodeDrag(event.touches[0]);
421
+ }
422
+ else if (this.draggingConnection()) {
423
+ this.updatePathOnMove(event.touches[0]);
424
+ }
425
+ else {
426
+ this.handleTouchMove(event);
427
+ }
428
+ }
429
+ onDocumentTouchEnd(event) {
430
+ if (this.isLocked())
431
+ return;
432
+ this.handleTouchEnd();
433
+ }
434
+ applyAutolayout() {
435
+ const newNodes = layoutNodes(this.nodes());
436
+ this.nodes.set(newNodes);
437
+ }
438
+ updateCanvasSize() {
439
+ const canvas = this.canvasRef.nativeElement;
440
+ const rect = this.#selfRef.nativeElement.getBoundingClientRect();
441
+ const dpr = window.devicePixelRatio || 1;
442
+ canvas.width = rect.width * dpr;
443
+ canvas.height = rect.height * dpr;
444
+ this.#ctx.scale(dpr, dpr);
445
+ canvas.style.width = `${rect.width}px`;
446
+ canvas.style.height = `${rect.height}px`;
447
+ this.#canvasWidth.set(rect.width);
448
+ this.#canvasHeight.set(rect.height);
449
+ this.drawCanvas();
450
+ }
451
+ drawCanvas() {
452
+ if (!this.#ctx)
453
+ return;
454
+ const ctx = this.#ctx;
455
+ const { width, height } = this.canvasRef.nativeElement;
456
+ const dpr = window.devicePixelRatio || 1;
457
+ ctx.clearRect(0, 0, width / dpr, height / dpr);
458
+ ctx.save();
459
+ ctx.translate(this.panX(), this.panY());
460
+ ctx.scale(this.zoomLevel(), this.zoomLevel());
461
+ this.drawGrid(ctx);
462
+ this.drawConnections(ctx);
463
+ if (this.draggingConnection()) {
464
+ this.drawDraggingPath(ctx);
465
+ }
466
+ ctx.restore();
467
+ }
468
+ drawGrid(ctx) {
469
+ const { width, height } = this.canvasRef.nativeElement;
470
+ const dpr = window.devicePixelRatio || 1;
471
+ const zoom = this.zoomLevel();
472
+ const panX = this.panX();
473
+ const panY = this.panY();
474
+ const gridSize = this.gridSize();
475
+ const gridColor = this.#currentGridColor();
476
+ const scaledGridSize = gridSize * zoom;
477
+ const dynamicGridSize = scaledGridSize < 20 ? gridSize * 4 : gridSize;
478
+ const startX = Math.floor(-panX / zoom / dynamicGridSize) * dynamicGridSize;
479
+ const startY = Math.floor(-panY / zoom / dynamicGridSize) * dynamicGridSize;
480
+ const endX = startX + width / dpr / zoom + dynamicGridSize;
481
+ const endY = startY + height / dpr / zoom + dynamicGridSize;
482
+ if (this.asDots()) {
483
+ const dotRadius = 1 / zoom;
484
+ ctx.fillStyle = gridColor;
485
+ for (let x = startX; x < endX; x += dynamicGridSize) {
486
+ for (let y = startY; y < endY; y += dynamicGridSize) {
487
+ ctx.beginPath();
488
+ ctx.arc(x, y, dotRadius, 0, 2 * Math.PI);
489
+ ctx.fill();
490
+ }
491
+ }
492
+ }
493
+ else {
494
+ ctx.beginPath();
495
+ ctx.strokeStyle = gridColor;
496
+ ctx.lineWidth = 1 / zoom;
497
+ for (let x = startX; x < endX; x += dynamicGridSize) {
498
+ ctx.moveTo(x, startY);
499
+ ctx.lineTo(x, endY);
500
+ }
501
+ for (let y = startY; y < endY; y += dynamicGridSize) {
502
+ ctx.moveTo(startX, y);
503
+ ctx.lineTo(endX, y);
504
+ }
505
+ ctx.stroke();
506
+ }
507
+ }
508
+ drawConnections(ctx) {
509
+ const highlighted = this.highlightedConnection();
510
+ ctx.lineWidth = 2 / this.zoomLevel();
511
+ for (const conn of this.#connections()) {
512
+ const isHighlighted = highlighted?.fromNode === conn.fromNode &&
513
+ highlighted?.fromPort === conn.fromPort &&
514
+ highlighted?.toNode === conn.toNode &&
515
+ highlighted?.toPort === conn.toPort;
516
+ ctx.strokeStyle = isHighlighted ? '#ffc107' : '#888';
517
+ ctx.beginPath();
518
+ const startPos = this.getNodePortPosition(conn.fromNode, conn.fromPort);
519
+ const endPos = this.getNodePortPosition(conn.toNode, conn.toPort);
520
+ this.drawCurvedPath(ctx, startPos, endPos);
521
+ ctx.stroke();
522
+ }
523
+ }
524
+ drawDraggingPath(ctx) {
525
+ const conn = this.draggingConnection();
526
+ const startPos = this.getNodePortPosition(conn.fromNode, conn.fromPort);
527
+ const endPos = [conn.x2, conn.y2];
528
+ ctx.strokeStyle = '#5a9cf8';
529
+ ctx.lineWidth = 2 / this.zoomLevel();
530
+ ctx.beginPath();
531
+ this.drawCurvedPath(ctx, startPos, endPos);
532
+ ctx.stroke();
533
+ }
534
+ drawCurvedPath(ctx, start, end) {
535
+ const [x1, y1] = start;
536
+ const [x2, y2] = end;
537
+ const dx = Math.abs(x1 - x2) * 0.7;
538
+ ctx.moveTo(x1, y1);
539
+ ctx.bezierCurveTo(x1 + dx, y1, x2 - dx, y2, x2, y2);
540
+ }
541
+ startNodeDrag(event, nodeId) {
542
+ event.stopPropagation();
543
+ event.preventDefault();
544
+ this.#isNodeDragging.set(true);
545
+ this.#draggedNodeId.set(nodeId);
546
+ const clientX = event instanceof MouseEvent ? event.clientX : event.touches[0].clientX;
547
+ const clientY = event instanceof MouseEvent ? event.clientY : event.touches[0].clientY;
548
+ const blueprintRect = this.#selfRef.nativeElement.getBoundingClientRect();
549
+ const worldX = (clientX - blueprintRect.left - this.panX()) / this.zoomLevel();
550
+ const worldY = (clientY - blueprintRect.top - this.panY()) / this.zoomLevel();
551
+ const draggedNode = this.nodes().find((n) => n.id === nodeId);
552
+ if (draggedNode) {
553
+ this.#dragOffset.set([worldX - draggedNode.coordinates[0], worldY - draggedNode.coordinates[1]]);
554
+ }
555
+ this.#lastMouseX.set(clientX);
556
+ this.#lastMouseY.set(clientY);
557
+ }
558
+ endNodeDrag() {
559
+ const draggedId = this.#draggedNodeId();
560
+ const finalCoords = this.#draggingNodeCoordinates();
561
+ if (draggedId && finalCoords) {
562
+ this.nodes.update((nodes) => {
563
+ const node = nodes.find((n) => n.id === draggedId);
564
+ if (node) {
565
+ node.coordinates = finalCoords;
566
+ }
567
+ return [...nodes];
568
+ });
569
+ }
570
+ this.#isNodeDragging.set(false);
571
+ this.#draggedNodeId.set(null);
572
+ this.#dragOffset.set(null);
573
+ this.#draggingNodeCoordinates.set(null);
574
+ }
575
+ nodeDrag(event) {
576
+ const draggedId = this.#draggedNodeId();
577
+ if (!this.#isNodeDragging() || !draggedId)
578
+ return;
579
+ const { clientX, clientY } = event;
580
+ const blueprintRect = this.#selfRef.nativeElement.getBoundingClientRect();
581
+ const worldX = (clientX - blueprintRect.left - this.panX()) / this.zoomLevel();
582
+ const worldY = (clientY - blueprintRect.top - this.panY()) / this.zoomLevel();
583
+ let newX = worldX - this.#dragOffset()[0];
584
+ let newY = worldY - this.#dragOffset()[1];
585
+ if (this.snapToGrid() || (event instanceof MouseEvent && event.shiftKey)) {
586
+ const grid = this.gridSnapSize();
587
+ newX = Math.round(newX / grid) * grid;
588
+ newY = Math.round(newY / grid) * grid;
589
+ }
590
+ this.#draggingNodeCoordinates.set([newX, newY]);
591
+ this.#lastMouseX.set(clientX);
592
+ this.#lastMouseY.set(clientY);
593
+ }
594
+ startPortDrag(event, nodeId, portId) {
595
+ event.preventDefault();
596
+ event.stopPropagation();
597
+ if (this.draggingConnection())
598
+ this.cancelPortDrag();
599
+ const node = this.nodes().find((n) => n.id === nodeId);
600
+ if (node) {
601
+ this.draggingConnection.set({
602
+ fromNode: nodeId,
603
+ fromPort: portId,
604
+ x2: node.coordinates[0],
605
+ y2: node.coordinates[1],
606
+ });
607
+ }
608
+ this.updatePathOnMove(event);
609
+ }
610
+ endPortDrag(_, toNodeId, toPortId) {
611
+ if (!this.draggingConnection())
612
+ return;
613
+ const from = this.draggingConnection();
614
+ if (from.fromNode === toNodeId) {
615
+ this.cancelPortDrag();
616
+ return;
617
+ }
618
+ const newConnection = {
619
+ fromNode: from.fromNode,
620
+ fromPort: from.fromPort,
621
+ toNode: toNodeId,
622
+ toPort: toPortId,
623
+ };
624
+ this.nodes.update((nodes) => {
625
+ const fromNode = nodes.find((n) => n.id === newConnection.fromNode);
626
+ const toNode = nodes.find((n) => n.id === newConnection.toNode);
627
+ if (fromNode && toNode) {
628
+ const isDuplicateFrom = fromNode.connections.some((c) => c.fromNode === newConnection.fromNode &&
629
+ c.fromPort === newConnection.fromPort &&
630
+ c.toNode === newConnection.toNode &&
631
+ c.toPort === newConnection.toPort);
632
+ if (!isDuplicateFrom) {
633
+ fromNode.connections = [...fromNode.connections, newConnection];
634
+ }
635
+ const isDuplicateTo = toNode.connections.some((c) => c.fromNode === newConnection.fromNode &&
636
+ c.fromPort === newConnection.fromPort &&
637
+ c.toNode === newConnection.toNode &&
638
+ c.toPort === newConnection.toPort);
639
+ if (!isDuplicateTo) {
640
+ toNode.connections = [...toNode.connections, newConnection];
641
+ }
642
+ }
643
+ return [...nodes];
644
+ });
645
+ this.cancelPortDrag();
646
+ }
647
+ cancelPortDrag() {
648
+ this.draggingConnection.set(null);
649
+ }
650
+ updatePathOnMove(event) {
651
+ if (!this.draggingConnection())
652
+ return;
653
+ const blueprintRect = this.#selfRef.nativeElement.getBoundingClientRect();
654
+ const x2 = (event.clientX - blueprintRect.left - this.panX()) / this.zoomLevel();
655
+ const y2 = (event.clientY - blueprintRect.top - this.panY()) / this.zoomLevel();
656
+ this.draggingConnection.update((conn) => (conn ? { ...conn, x2, y2 } : conn));
657
+ }
658
+ getNodePortPosition(nodeId, portId) {
659
+ const node = this.nodes().find((n) => n.id === nodeId);
660
+ if (!node)
661
+ return [0, 0];
662
+ const portEl = this.#selfRef.nativeElement.querySelector(`[data-node-id="${nodeId}"][data-port-id="${portId}"]`);
663
+ if (portEl) {
664
+ const nodeWrapper = this.#selfRef.nativeElement.querySelector('.nodes-wrapper');
665
+ const wrapperRect = nodeWrapper.getBoundingClientRect();
666
+ const portRect = portEl.getBoundingClientRect();
667
+ const portCenterX = portRect.left + portRect.width / 2;
668
+ const portCenterY = portRect.top + portRect.height / 2;
669
+ const worldX = (portCenterX - wrapperRect.left) / this.zoomLevel();
670
+ const worldY = (portCenterY - wrapperRect.top) / this.zoomLevel();
671
+ return [worldX, worldY];
672
+ }
673
+ const isInput = node.inputs.some((p) => p.id === portId);
674
+ const portIndex = isInput
675
+ ? node.inputs.findIndex((p) => p.id === portId)
676
+ : node.outputs.findIndex((p) => p.id === portId);
677
+ if (portIndex === -1)
678
+ return node.coordinates;
679
+ const portYOffset = this.#NODE_HEADER_HEIGHT + portIndex * this.#PORT_ROW_HEIGHT + this.#PORT_ROW_HEIGHT / 2;
680
+ const portXOffset = isInput ? 0 : this.#NODE_WIDTH;
681
+ return [node.coordinates[0] + portXOffset, node.coordinates[1] + portYOffset];
682
+ }
683
+ startPan(event) {
684
+ if (this.isLocked())
685
+ return;
686
+ if (event.target instanceof HTMLElement && event.target.closest('.node'))
687
+ return;
688
+ event.preventDefault();
689
+ this.#isDragging.set(true);
690
+ this.#lastMouseX.set(event.clientX);
691
+ this.#lastMouseY.set(event.clientY);
692
+ }
693
+ endPan() {
694
+ this.#isDragging.set(false);
695
+ }
696
+ pan(event) {
697
+ if (!this.#isDragging() || this.isLocked())
698
+ return;
699
+ const dx = event.clientX - this.#lastMouseX();
700
+ const dy = event.clientY - this.#lastMouseY();
701
+ this.panX.update((x) => x + dx);
702
+ this.panY.update((y) => y + dy);
703
+ this.#lastMouseX.set(event.clientX);
704
+ this.#lastMouseY.set(event.clientY);
705
+ }
706
+ zoom(event) {
707
+ event.preventDefault();
708
+ const oldZoom = this.zoomLevel();
709
+ const newZoom = this.#clamp(oldZoom * (1 - event.deltaY * this.#ZOOM_SPEED), this.#MIN_ZOOM, this.#MAX_ZOOM);
710
+ const panRatio = newZoom / oldZoom;
711
+ const newPanX = event.clientX - (event.clientX - this.panX()) * panRatio;
712
+ const newPanY = event.clientY - (event.clientY - this.panY()) * panRatio;
713
+ this.zoomLevel.set(newZoom);
714
+ this.panX.set(newPanX);
715
+ this.panY.set(newPanY);
716
+ }
717
+ handleTouchStart(event) {
718
+ if (event.target instanceof HTMLElement && event.target.closest('.node'))
719
+ return;
720
+ event.preventDefault();
721
+ if (event.touches.length === 1) {
722
+ this.#isDragging.set(true);
723
+ this.#lastMouseX.set(event.touches[0].clientX);
724
+ this.#lastMouseY.set(event.touches[0].clientY);
725
+ }
726
+ else if (event.touches.length === 2) {
727
+ this.#isDragging.set(false);
728
+ this.#initialPinchDistance.set(this.#getDistance(event.touches[0], event.touches[1]));
729
+ }
730
+ }
731
+ handleTouchMove(event) {
732
+ event.preventDefault();
733
+ if (event.touches.length === 1 && this.#isDragging()) {
734
+ const dx = event.touches[0].clientX - this.#lastMouseX();
735
+ const dy = event.touches[0].clientY - this.#lastMouseY();
736
+ this.panX.update((x) => x + dx);
737
+ this.panY.update((y) => y + dy);
738
+ this.#lastMouseX.set(event.touches[0].clientX);
739
+ this.#lastMouseY.set(event.touches[0].clientY);
740
+ }
741
+ else if (event.touches.length === 2) {
742
+ const newPinchDistance = this.#getDistance(event.touches[0], event.touches[1]);
743
+ const pinchRatio = newPinchDistance / this.#initialPinchDistance();
744
+ const oldZoom = this.zoomLevel();
745
+ const newZoom = this.#clamp(oldZoom * pinchRatio, this.#MIN_ZOOM, this.#MAX_ZOOM);
746
+ const pinchCenterX = (event.touches[0].clientX + event.touches[1].clientX) / 2;
747
+ const pinchCenterY = (event.touches[0].clientY + event.touches[1].clientY) / 2;
748
+ const panRatio = newZoom / oldZoom;
749
+ const newPanX = pinchCenterX - (pinchCenterX - this.panX()) * panRatio;
750
+ const newPanY = pinchCenterY - (pinchCenterY - this.panY()) * panRatio;
751
+ this.zoomLevel.set(newZoom);
752
+ this.panX.set(newPanX);
753
+ this.panY.set(newPanY);
754
+ this.#initialPinchDistance.set(newPinchDistance);
755
+ }
756
+ }
757
+ handleTouchEnd() {
758
+ this.#isDragging.set(false);
759
+ this.endNodeDrag();
760
+ if (this.draggingConnection()) {
761
+ this.cancelPortDrag();
762
+ }
763
+ }
764
+ closeMidpointDiv() {
765
+ this.showMidpointDiv.set(false);
766
+ this.midpointDivPosition.set(null);
767
+ this.isLocked.set(false);
768
+ }
769
+ getDisplayCoordinates(node) {
770
+ const draggedId = this.#draggedNodeId();
771
+ const draggingCoords = this.#draggingNodeCoordinates();
772
+ if (draggedId === node.id && draggingCoords) {
773
+ return `translate(${draggingCoords[0]}px, ${draggingCoords[1]}px)`;
774
+ }
775
+ return `translate(${node.coordinates[0]}px, ${node.coordinates[1]}px)`;
776
+ }
777
+ removeConnection() {
778
+ const conn = this.highlightedConnection();
779
+ if (!conn)
780
+ return;
781
+ this.#connections.update((conns) => conns.filter((c) => !(c.fromNode === conn.fromNode &&
782
+ c.fromPort === conn.fromPort &&
783
+ c.toNode === conn.toNode &&
784
+ c.toPort === conn.toPort)));
785
+ this.nodes.update((nodes) => nodes.map((n) => ({
786
+ ...n,
787
+ connections: n.connections.filter((c) => !(c.fromNode === conn.fromNode &&
788
+ c.fromPort === conn.fromPort &&
789
+ c.toNode === conn.toNode &&
790
+ c.toPort === conn.toPort)),
791
+ })));
792
+ this.highlightedConnection.set(null);
793
+ this.closeMidpointDiv();
794
+ }
795
+ #removeDuplicatesAndReinitiate() {
796
+ const nodes = this.nodes();
797
+ const uniqueNodesMap = new Map();
798
+ const uniqueNodes = [];
799
+ for (const node of nodes) {
800
+ if (!uniqueNodesMap.has(node.id)) {
801
+ uniqueNodesMap.set(node.id, node);
802
+ uniqueNodes.push(node);
803
+ }
804
+ }
805
+ const nodesWithUniquePorts = uniqueNodes.map((node) => {
806
+ const uniqueInputs = new Map();
807
+ const uniqueOutputs = new Map();
808
+ const uniqueNodeInputs = node.inputs.filter((port) => {
809
+ if (!uniqueInputs.has(port.id)) {
810
+ uniqueInputs.set(port.id, port);
811
+ return true;
812
+ }
813
+ return false;
814
+ });
815
+ const uniqueNodeOutputs = node.outputs.filter((port) => {
816
+ if (!uniqueOutputs.has(port.id)) {
817
+ uniqueOutputs.set(port.id, port);
818
+ return true;
819
+ }
820
+ return false;
821
+ });
822
+ return {
823
+ ...node,
824
+ inputs: uniqueNodeInputs,
825
+ outputs: uniqueNodeOutputs,
826
+ };
827
+ });
828
+ const finalNodes = nodesWithUniquePorts.map((node) => {
829
+ const validConnections = node.connections.filter((conn) => {
830
+ const fromNodeExists = nodesWithUniquePorts.some((n) => n.id === conn.fromNode);
831
+ const toNodeExists = nodesWithUniquePorts.some((n) => n.id === conn.toNode);
832
+ const fromPortExists = nodesWithUniquePorts
833
+ .find((n) => n.id === conn.fromNode)
834
+ ?.outputs.some((p) => p.id === conn.fromPort);
835
+ const toPortExists = nodesWithUniquePorts
836
+ .find((n) => n.id === conn.toNode)
837
+ ?.inputs.some((p) => p.id === conn.toPort);
838
+ return fromNodeExists && toNodeExists && fromPortExists && toPortExists;
839
+ });
840
+ return { ...node, connections: validConnections };
841
+ });
842
+ this.nodes.set(finalNodes);
843
+ }
844
+ #handleConnectionClick(event) {
845
+ if (this.highlightedConnection()) {
846
+ const conn = this.highlightedConnection();
847
+ const startPos = this.getNodePortPosition(conn.fromNode, conn.fromPort);
848
+ const endPos = this.getNodePortPosition(conn.toNode, conn.toPort);
849
+ const midpoint = this.#getBezierMidpoint(startPos, endPos);
850
+ this.midpointDivPosition.set({ x: midpoint[0], y: midpoint[1] });
851
+ this.showMidpointDiv.set(true);
852
+ this.isLocked.set(true);
853
+ }
854
+ }
855
+ #getBezierMidpoint(start, end) {
856
+ const t = 0.5;
857
+ const [x1, y1] = start;
858
+ const [x2, y2] = end;
859
+ const dx = Math.abs(x1 - x2) * 0.7;
860
+ const cp1x = x1 + dx;
861
+ const cp1y = y1;
862
+ const cp2x = x2 - dx;
863
+ const cp2y = y2;
864
+ const mx = Math.pow(1 - t, 3) * x1 +
865
+ 3 * Math.pow(1 - t, 2) * t * cp1x +
866
+ 3 * (1 - t) * Math.pow(t, 2) * cp2x +
867
+ Math.pow(t, 3) * x2;
868
+ const my = Math.pow(1 - t, 3) * y1 +
869
+ 3 * Math.pow(1 - t, 2) * t * cp1y +
870
+ 3 * (1 - t) * Math.pow(t, 2) * cp2y +
871
+ Math.pow(t, 3) * y2;
872
+ return [mx, my];
873
+ }
874
+ #checkConnectionHover(event) {
875
+ const blueprintRect = this.#selfRef.nativeElement.getBoundingClientRect();
876
+ const worldX = (event.clientX - blueprintRect.left - this.panX()) / this.zoomLevel();
877
+ const worldY = (event.clientY - blueprintRect.top - this.panY()) / this.zoomLevel();
878
+ const cursorPoint = [worldX, worldY];
879
+ let hoveredConn = null;
880
+ const tolerance = 10 / this.zoomLevel();
881
+ for (const conn of this.#connections()) {
882
+ const startPos = this.getNodePortPosition(conn.fromNode, conn.fromPort);
883
+ const endPos = this.getNodePortPosition(conn.toNode, conn.toPort);
884
+ if (this.#isPointNearBezierCurve(cursorPoint, startPos, endPos, tolerance)) {
885
+ hoveredConn = conn;
886
+ break;
887
+ }
888
+ }
889
+ this.highlightedConnection.set(hoveredConn);
890
+ }
891
+ #isPointNearBezierCurve(point, start, end, tolerance = 10) {
892
+ const [x1, y1] = start;
893
+ const [x2, y2] = end;
894
+ const dx = Math.abs(x1 - x2) * 0.7;
895
+ const cp1x = x1 + dx;
896
+ const cp1y = y1;
897
+ const cp2x = x2 - dx;
898
+ const cp2y = y2;
899
+ const steps = 20;
900
+ for (let i = 0; i <= steps; i++) {
901
+ const t = i / steps;
902
+ const x = Math.pow(1 - t, 3) * x1 +
903
+ 3 * Math.pow(1 - t, 2) * t * cp1x +
904
+ 3 * (1 - t) * Math.pow(t, 2) * cp2x +
905
+ Math.pow(t, 3) * x2;
906
+ const y = Math.pow(1 - t, 3) * y1 +
907
+ 3 * Math.pow(1 - t, 2) * t * cp1y +
908
+ 3 * (1 - t) * Math.pow(t, 2) * cp2y +
909
+ Math.pow(t, 3) * y2;
910
+ const distance = Math.sqrt(Math.pow(point[0] - x, 2) + Math.pow(point[1] - y, 2));
911
+ if (distance < tolerance) {
912
+ return true;
913
+ }
914
+ }
915
+ return false;
916
+ }
917
+ getNewNodeCoordinates(panToCoordinates = false) {
918
+ let lowestY = 0;
919
+ if (this.nodes().length > 0) {
920
+ lowestY = this.nodes().reduce((max, node) => Math.max(max, node.coordinates[1]), 0);
921
+ }
922
+ const newCoordinates = [20, lowestY + 200];
923
+ if (panToCoordinates) {
924
+ this.#panToCoordinates(newCoordinates);
925
+ }
926
+ return newCoordinates;
927
+ }
928
+ #validateNodes() {
929
+ const duplicatePortIds = findDuplicatePortIDs(this.nodes());
930
+ const duplicateNodeIds = findDuplicateNodeIDs(this.nodes());
931
+ this.validationErrors.set({
932
+ duplicateNodeIds,
933
+ duplicatePortIds,
934
+ });
935
+ }
936
+ #panToCoordinates(coords) {
937
+ const [x, y] = coords;
938
+ const rect = this.#selfRef.nativeElement.getBoundingClientRect();
939
+ this.panX.set(rect.width / 2 - x * this.zoomLevel());
940
+ this.panY.set(rect.height / 2 - y * this.zoomLevel());
941
+ }
942
+ #getDistance(touch1, touch2) {
943
+ const dx = touch1.clientX - touch2.clientX;
944
+ const dy = touch1.clientY - touch2.clientY;
945
+ return Math.sqrt(dx * dx + dy * dy);
946
+ }
947
+ #clamp(value, min, max) {
948
+ return Math.max(min, Math.min(max, value));
949
+ }
950
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ShipBlueprint, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
951
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: ShipBlueprint, isStandalone: true, selector: "sh-blueprint", inputs: { forceUnique: { classPropertyName: "forceUnique", publicName: "forceUnique", isSignal: true, isRequired: false, transformFunction: null }, autoLayout: { classPropertyName: "autoLayout", publicName: "autoLayout", isSignal: true, isRequired: false, transformFunction: null }, gridSize: { classPropertyName: "gridSize", publicName: "gridSize", isSignal: true, isRequired: false, transformFunction: null }, snapToGrid: { classPropertyName: "snapToGrid", publicName: "snapToGrid", isSignal: true, isRequired: false, transformFunction: null }, gridColor: { classPropertyName: "gridColor", publicName: "gridColor", isSignal: true, isRequired: false, transformFunction: null }, nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { nodes: "nodesChange" }, host: { listeners: { "document:mouseup": "onMouseUp($event)", "document:click": "onClick($event)", "document:keydown.escape": "onEscape()", "document:mousemove": "onMouseMove($event)", "document:touchmove": "onTouchMove($event)", "document:touchend": "onDocumentTouchEnd($event)" } }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["blueprintCanvas"], descendants: true, static: true }], ngImport: i0, template: `
952
+ <div
953
+ class="canvas-container"
954
+ [class.locked]="isLocked()"
955
+ [class.hovering-connection]="highlightedConnection()"
956
+ (mousedown)="startPan($event)"
957
+ (mousemove)="pan($event)"
958
+ (wheel)="zoom($event)"
959
+ (touchstart)="handleTouchStart($event)"
960
+ (touchmove)="handleTouchMove($event)"
961
+ (touchend)="handleTouchEnd()">
962
+ <div class="action-panel">
963
+ <button shButton (click)="applyAutolayout()" class="small">
964
+ Autolayout
965
+ <sh-icon>tree-structure</sh-icon>
966
+ </button>
967
+
968
+ <ng-content />
969
+ </div>
970
+
971
+ <canvas #blueprintCanvas></canvas>
972
+
973
+ @if (validationErrors()) {
974
+ <sh-card class="validation-errors type-c">
975
+ Something went wrong you have
976
+ <br />
977
+ duplicate node IDs or port IDs
978
+ <pre>{{ validationErrors() | json }}</pre>
979
+ </sh-card>
980
+ } @else {
981
+ <div
982
+ class="nodes-wrapper"
983
+ [style.transform]="'translate(' + panX() + 'px, ' + panY() + 'px) scale(' + zoomLevel() + ')'"
984
+ [style.transform-origin]="'0 0'">
985
+ @for (node of visibleNodes(); track node.id) {
986
+ <sh-card
987
+ class="node type-c"
988
+ [style.transform]="getDisplayCoordinates(node)"
989
+ (mouseenter)="isHoveringNode.set(true)"
990
+ (mouseleave)="isHoveringNode.set(false)">
991
+ <header (mousedown)="startNodeDrag($event, node.id)" (touchstart)="startNodeDrag($event, node.id)">
992
+ <span class="node-title">{{ node.name ? node.name : node.id }}</span>
993
+ <sh-icon>list</sh-icon>
994
+ </header>
995
+ <div class="ports">
996
+ <div class="inputs">
997
+ @for (inputPort of node.inputs; track inputPort.id) {
998
+ <div
999
+ class="port-wrap"
1000
+ [attr.data-node-id]="node.id"
1001
+ [attr.data-port-id]="inputPort.id"
1002
+ (click)="endPortDrag($event, node.id, inputPort.id)">
1003
+ <div class="port input-port"></div>
1004
+ <span class="port-name">{{ inputPort.name }}</span>
1005
+ </div>
1006
+ }
1007
+ </div>
1008
+ <div class="outputs">
1009
+ @for (outputPort of node.outputs; track outputPort.id) {
1010
+ <div
1011
+ class="port-wrap"
1012
+ [attr.data-node-id]="node.id"
1013
+ [attr.data-port-id]="outputPort.id"
1014
+ (click)="startPortDrag($event, node.id, outputPort.id)">
1015
+ <span class="port-name">{{ outputPort.name }}</span>
1016
+ <div class="port output-port"></div>
1017
+ </div>
1018
+ }
1019
+ </div>
1020
+ </div>
1021
+ </sh-card>
1022
+ }
1023
+
1024
+ @if (showMidpointDiv()) {
1025
+ <div
1026
+ class="midpoint-div"
1027
+ (click)="removeConnection()"
1028
+ [style.left.px]="midpointDivPosition()?.x"
1029
+ [style.top.px]="midpointDivPosition()?.y">
1030
+ Remove connection
1031
+ <sh-icon>trash</sh-icon>
1032
+ </div>
1033
+ }
1034
+ </div>
1035
+ }
1036
+ </div>
1037
+ `, isInline: true, styles: ["sh-blueprint{--bp-bg: var(--base-1);--bp-icon-c: var(--base-6);--bp-stroke-c: var(--base-7);--bp-stroke-ca: var(--primary-8);display:flex;position:relative;width:100%;height:100%}sh-blueprint>*{box-sizing:border-box}sh-blueprint sh-icon{font-size:1rem;color:var(--bp-icon-c)}sh-blueprint sh-card.type-c{--card-bg: var(--base-1)}sh-blueprint .validation-errors{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);padding:1rem}sh-blueprint .midpoint-div{position:absolute;background-color:var(--base-2);border:var(--border-10);padding:.5rem;gap:.5rem;border-radius:var(--shape-2);transform:translate(-50%,-50%);z-index:100;display:flex;align-items:center;cursor:pointer}sh-blueprint .midpoint-div sh-icon{color:var(--base-12)}sh-blueprint .canvas-container{position:relative;width:100%;height:100%;overflow:hidden}sh-blueprint .canvas-container.hovering-connection{cursor:pointer}sh-blueprint .canvas-container.locked .node{pointer-events:none}sh-blueprint .action-panel{position:absolute;top:.5rem;right:.5rem;gap:.5rem;display:flex;align-items:center;z-index:1000}sh-blueprint canvas{position:absolute;top:0;left:0;width:100%;height:100%}sh-blueprint .nodes-wrapper{position:absolute;top:0;left:0;width:100%;height:100%;will-change:transform}sh-blueprint .canvas{height:100%;width:100%;background-color:var(--bp-bg);overflow:hidden;touch-action:none;position:relative}sh-blueprint .grid{position:absolute;inset:0;will-change:transform}sh-blueprint .tile{position:absolute;box-sizing:border-box;width:800px;height:800px;background-image:repeating-linear-gradient(0deg,var(--bp-grid-c) 0 2px,transparent 2px 100%),repeating-linear-gradient(90deg,var(--bp-grid-c) 0 2px,transparent 2px 100%);background-size:20px 20px}sh-blueprint .node{position:absolute;min-width:150px;padding:0;transition:box-shadow .2s ease-in-out;box-shadow:var(--box-shadow-10);will-change:transform;display:flex;flex-direction:column}sh-blueprint .node header{padding:.5rem;display:flex;align-items:center;justify-content:space-between;background-color:var(--base-2);border-bottom:var(--border-10);cursor:grab;-webkit-user-select:none;user-select:none}sh-blueprint .node header:active{cursor:grabbing}sh-blueprint .node:has(header:hover){box-shadow:0 0 10px 2px var(--primary-6)}sh-blueprint .ports{padding:.5rem 0;display:flex;gap:.5rem}sh-blueprint .inputs,sh-blueprint .outputs{display:flex;flex-direction:column;justify-content:space-around;flex:1 0;height:100%}sh-blueprint .inputs .port{border-radius:0 50% 50% 0;border-left-width:0}sh-blueprint .outputs{align-items:flex-end}sh-blueprint .outputs .port{border-radius:50% 0 0 50%;border-right-width:0}sh-blueprint .port-wrap{display:flex;gap:.25rem;align-items:center}sh-blueprint .port{width:.75rem;height:.75rem;background-color:var(--bp-stroke-c);border-radius:50%;border:var(--border-20);cursor:pointer;position:relative}sh-blueprint .port:hover{background-color:var(--bp-stroke-ca)}sh-blueprint .port-name{font:var(--paragraph-40);color:var(--base-12);white-space:nowrap}sh-blueprint .svg-wrap{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}sh-blueprint .connections-svg{width:100%;height:100%;pointer-events:none}sh-blueprint .connection-path{stroke:var(--bp-stroke-c);stroke-width:3;fill:none;pointer-events:none}sh-blueprint .drag-path{stroke:var(--bp-stroke-ca);stroke-width:3;fill:none;stroke-dasharray:5,5;pointer-events:none}\n"], dependencies: [{ kind: "component", type: ShipCard, selector: "sh-card", inputs: ["color", "variant"] }, { kind: "component", type: ShipIcon, selector: "sh-icon", inputs: ["color", "size"] }, { kind: "component", type: ShipButton, selector: "[shButton]", inputs: ["color", "variant", "size", "readonly"] }, { kind: "pipe", type: JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1038
+ }
1039
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ShipBlueprint, decorators: [{
1040
+ type: Component,
1041
+ args: [{ selector: 'sh-blueprint', encapsulation: ViewEncapsulation.None, imports: [ShipCard, ShipIcon, JsonPipe, ShipButton], template: `
1042
+ <div
1043
+ class="canvas-container"
1044
+ [class.locked]="isLocked()"
1045
+ [class.hovering-connection]="highlightedConnection()"
1046
+ (mousedown)="startPan($event)"
1047
+ (mousemove)="pan($event)"
1048
+ (wheel)="zoom($event)"
1049
+ (touchstart)="handleTouchStart($event)"
1050
+ (touchmove)="handleTouchMove($event)"
1051
+ (touchend)="handleTouchEnd()">
1052
+ <div class="action-panel">
1053
+ <button shButton (click)="applyAutolayout()" class="small">
1054
+ Autolayout
1055
+ <sh-icon>tree-structure</sh-icon>
1056
+ </button>
1057
+
1058
+ <ng-content />
1059
+ </div>
1060
+
1061
+ <canvas #blueprintCanvas></canvas>
1062
+
1063
+ @if (validationErrors()) {
1064
+ <sh-card class="validation-errors type-c">
1065
+ Something went wrong you have
1066
+ <br />
1067
+ duplicate node IDs or port IDs
1068
+ <pre>{{ validationErrors() | json }}</pre>
1069
+ </sh-card>
1070
+ } @else {
1071
+ <div
1072
+ class="nodes-wrapper"
1073
+ [style.transform]="'translate(' + panX() + 'px, ' + panY() + 'px) scale(' + zoomLevel() + ')'"
1074
+ [style.transform-origin]="'0 0'">
1075
+ @for (node of visibleNodes(); track node.id) {
1076
+ <sh-card
1077
+ class="node type-c"
1078
+ [style.transform]="getDisplayCoordinates(node)"
1079
+ (mouseenter)="isHoveringNode.set(true)"
1080
+ (mouseleave)="isHoveringNode.set(false)">
1081
+ <header (mousedown)="startNodeDrag($event, node.id)" (touchstart)="startNodeDrag($event, node.id)">
1082
+ <span class="node-title">{{ node.name ? node.name : node.id }}</span>
1083
+ <sh-icon>list</sh-icon>
1084
+ </header>
1085
+ <div class="ports">
1086
+ <div class="inputs">
1087
+ @for (inputPort of node.inputs; track inputPort.id) {
1088
+ <div
1089
+ class="port-wrap"
1090
+ [attr.data-node-id]="node.id"
1091
+ [attr.data-port-id]="inputPort.id"
1092
+ (click)="endPortDrag($event, node.id, inputPort.id)">
1093
+ <div class="port input-port"></div>
1094
+ <span class="port-name">{{ inputPort.name }}</span>
1095
+ </div>
1096
+ }
1097
+ </div>
1098
+ <div class="outputs">
1099
+ @for (outputPort of node.outputs; track outputPort.id) {
1100
+ <div
1101
+ class="port-wrap"
1102
+ [attr.data-node-id]="node.id"
1103
+ [attr.data-port-id]="outputPort.id"
1104
+ (click)="startPortDrag($event, node.id, outputPort.id)">
1105
+ <span class="port-name">{{ outputPort.name }}</span>
1106
+ <div class="port output-port"></div>
1107
+ </div>
1108
+ }
1109
+ </div>
1110
+ </div>
1111
+ </sh-card>
1112
+ }
1113
+
1114
+ @if (showMidpointDiv()) {
1115
+ <div
1116
+ class="midpoint-div"
1117
+ (click)="removeConnection()"
1118
+ [style.left.px]="midpointDivPosition()?.x"
1119
+ [style.top.px]="midpointDivPosition()?.y">
1120
+ Remove connection
1121
+ <sh-icon>trash</sh-icon>
1122
+ </div>
1123
+ }
1124
+ </div>
1125
+ }
1126
+ </div>
1127
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["sh-blueprint{--bp-bg: var(--base-1);--bp-icon-c: var(--base-6);--bp-stroke-c: var(--base-7);--bp-stroke-ca: var(--primary-8);display:flex;position:relative;width:100%;height:100%}sh-blueprint>*{box-sizing:border-box}sh-blueprint sh-icon{font-size:1rem;color:var(--bp-icon-c)}sh-blueprint sh-card.type-c{--card-bg: var(--base-1)}sh-blueprint .validation-errors{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);padding:1rem}sh-blueprint .midpoint-div{position:absolute;background-color:var(--base-2);border:var(--border-10);padding:.5rem;gap:.5rem;border-radius:var(--shape-2);transform:translate(-50%,-50%);z-index:100;display:flex;align-items:center;cursor:pointer}sh-blueprint .midpoint-div sh-icon{color:var(--base-12)}sh-blueprint .canvas-container{position:relative;width:100%;height:100%;overflow:hidden}sh-blueprint .canvas-container.hovering-connection{cursor:pointer}sh-blueprint .canvas-container.locked .node{pointer-events:none}sh-blueprint .action-panel{position:absolute;top:.5rem;right:.5rem;gap:.5rem;display:flex;align-items:center;z-index:1000}sh-blueprint canvas{position:absolute;top:0;left:0;width:100%;height:100%}sh-blueprint .nodes-wrapper{position:absolute;top:0;left:0;width:100%;height:100%;will-change:transform}sh-blueprint .canvas{height:100%;width:100%;background-color:var(--bp-bg);overflow:hidden;touch-action:none;position:relative}sh-blueprint .grid{position:absolute;inset:0;will-change:transform}sh-blueprint .tile{position:absolute;box-sizing:border-box;width:800px;height:800px;background-image:repeating-linear-gradient(0deg,var(--bp-grid-c) 0 2px,transparent 2px 100%),repeating-linear-gradient(90deg,var(--bp-grid-c) 0 2px,transparent 2px 100%);background-size:20px 20px}sh-blueprint .node{position:absolute;min-width:150px;padding:0;transition:box-shadow .2s ease-in-out;box-shadow:var(--box-shadow-10);will-change:transform;display:flex;flex-direction:column}sh-blueprint .node header{padding:.5rem;display:flex;align-items:center;justify-content:space-between;background-color:var(--base-2);border-bottom:var(--border-10);cursor:grab;-webkit-user-select:none;user-select:none}sh-blueprint .node header:active{cursor:grabbing}sh-blueprint .node:has(header:hover){box-shadow:0 0 10px 2px var(--primary-6)}sh-blueprint .ports{padding:.5rem 0;display:flex;gap:.5rem}sh-blueprint .inputs,sh-blueprint .outputs{display:flex;flex-direction:column;justify-content:space-around;flex:1 0;height:100%}sh-blueprint .inputs .port{border-radius:0 50% 50% 0;border-left-width:0}sh-blueprint .outputs{align-items:flex-end}sh-blueprint .outputs .port{border-radius:50% 0 0 50%;border-right-width:0}sh-blueprint .port-wrap{display:flex;gap:.25rem;align-items:center}sh-blueprint .port{width:.75rem;height:.75rem;background-color:var(--bp-stroke-c);border-radius:50%;border:var(--border-20);cursor:pointer;position:relative}sh-blueprint .port:hover{background-color:var(--bp-stroke-ca)}sh-blueprint .port-name{font:var(--paragraph-40);color:var(--base-12);white-space:nowrap}sh-blueprint .svg-wrap{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}sh-blueprint .connections-svg{width:100%;height:100%;pointer-events:none}sh-blueprint .connection-path{stroke:var(--bp-stroke-c);stroke-width:3;fill:none;pointer-events:none}sh-blueprint .drag-path{stroke:var(--bp-stroke-ca);stroke-width:3;fill:none;stroke-dasharray:5,5;pointer-events:none}\n"] }]
1128
+ }], ctorParameters: () => [], propDecorators: { forceUnique: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceUnique", required: false }] }], autoLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoLayout", required: false }] }], gridSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridSize", required: false }] }], snapToGrid: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapToGrid", required: false }] }], gridColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridColor", required: false }] }], nodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodes", required: false }] }, { type: i0.Output, args: ["nodesChange"] }], canvasRef: [{
1129
+ type: ViewChild,
1130
+ args: ['blueprintCanvas', { static: true }]
1131
+ }], onMouseUp: [{
1132
+ type: HostListener,
1133
+ args: ['document:mouseup', ['$event']]
1134
+ }], onClick: [{
1135
+ type: HostListener,
1136
+ args: ['document:click', ['$event']]
1137
+ }], onEscape: [{
1138
+ type: HostListener,
1139
+ args: ['document:keydown.escape', []]
1140
+ }], onMouseMove: [{
1141
+ type: HostListener,
1142
+ args: ['document:mousemove', ['$event']]
1143
+ }], onTouchMove: [{
1144
+ type: HostListener,
1145
+ args: ['document:touchmove', ['$event']]
1146
+ }], onDocumentTouchEnd: [{
1147
+ type: HostListener,
1148
+ args: ['document:touchend', ['$event']]
1149
+ }] } });
1150
+
1151
+ /**
1152
+ * Generated bundle index. Do not edit.
1153
+ */
1154
+
1155
+ export { ShipBlueprint, TEST_NODES, findDuplicateNodeIDs, findDuplicatePortIDs, layoutNodes };
1156
+ //# sourceMappingURL=ship-ui-core-ship-blueprint.mjs.map