@adia-ai/web-components 0.4.6 → 0.4.7

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 (284) hide show
  1. package/USAGE.md +29 -9
  2. package/components/accordion/accordion.d.ts +17 -0
  3. package/components/accordion/accordion.js +10 -117
  4. package/components/accordion/class.js +132 -0
  5. package/components/action-list/action-list.d.ts +15 -0
  6. package/components/action-list/action-list.js +9 -140
  7. package/components/action-list/class.js +156 -0
  8. package/components/agent-artifact/agent-artifact.d.ts +25 -0
  9. package/components/agent-artifact/agent-artifact.js +8 -181
  10. package/components/agent-artifact/class.js +200 -0
  11. package/components/agent-feedback-bar/agent-feedback-bar.d.ts +21 -0
  12. package/components/agent-feedback-bar/agent-feedback-bar.js +8 -143
  13. package/components/agent-feedback-bar/class.js +162 -0
  14. package/components/agent-questions/agent-questions.d.ts +23 -0
  15. package/components/agent-questions/agent-questions.js +8 -180
  16. package/components/agent-questions/class.js +199 -0
  17. package/components/agent-reasoning/agent-reasoning.d.ts +23 -0
  18. package/components/agent-reasoning/agent-reasoning.js +8 -494
  19. package/components/agent-reasoning/class.js +513 -0
  20. package/components/agent-suggestions/agent-suggestions.d.ts +21 -0
  21. package/components/agent-suggestions/agent-suggestions.js +8 -78
  22. package/components/agent-suggestions/class.js +97 -0
  23. package/components/agent-trace/agent-trace.d.ts +19 -0
  24. package/components/alert/alert.d.ts +29 -0
  25. package/components/alert/alert.js +8 -175
  26. package/components/alert/class.js +194 -0
  27. package/components/avatar/avatar.d.ts +27 -0
  28. package/components/avatar/avatar.js +9 -159
  29. package/components/avatar/class.js +173 -0
  30. package/components/badge/badge.d.ts +27 -0
  31. package/components/badge/badge.js +9 -75
  32. package/components/badge/class.js +93 -0
  33. package/components/block/block.d.ts +19 -0
  34. package/components/block/block.js +9 -15
  35. package/components/block/class.js +33 -0
  36. package/components/breadcrumb/breadcrumb.d.ts +23 -0
  37. package/components/breadcrumb/breadcrumb.js +8 -113
  38. package/components/breadcrumb/class.js +132 -0
  39. package/components/button/button.d.ts +34 -0
  40. package/components/button/button.js +15 -66
  41. package/components/button/class.js +80 -0
  42. package/components/calendar-picker/calendar-picker.a2ui.json +6 -1
  43. package/components/calendar-picker/calendar-picker.js +8 -332
  44. package/components/calendar-picker/calendar-picker.yaml +51 -177
  45. package/components/calendar-picker/class.js +351 -0
  46. package/components/canvas/canvas.a2ui.json +6 -1
  47. package/components/canvas/canvas.d.ts +17 -0
  48. package/components/canvas/canvas.yaml +19 -36
  49. package/components/card/card.a2ui.json +3 -0
  50. package/components/card/card.d.ts +27 -0
  51. package/components/card/card.js +9 -50
  52. package/components/card/card.yaml +171 -433
  53. package/components/card/class.js +68 -0
  54. package/components/chart/chart.d.ts +41 -0
  55. package/components/chart/chart.js +8 -2131
  56. package/components/chart/class.js +2150 -0
  57. package/components/chart-legend/chart-legend.d.ts +27 -0
  58. package/components/chart-legend/chart-legend.js +8 -197
  59. package/components/chart-legend/class.js +215 -0
  60. package/components/chat-thread/chat-thread.d.ts +17 -0
  61. package/components/chat-thread/chat-thread.js +8 -157
  62. package/components/chat-thread/class.js +176 -0
  63. package/components/check/check.js +11 -52
  64. package/components/check/class.js +68 -0
  65. package/components/code/class.js +501 -0
  66. package/components/code/code.js +8 -482
  67. package/components/col/class.js +30 -0
  68. package/components/col/col.d.ts +23 -0
  69. package/components/col/col.js +10 -13
  70. package/components/color-picker/class.js +550 -0
  71. package/components/color-picker/color-picker.js +8 -531
  72. package/components/command/class.js +364 -0
  73. package/components/command/command.a2ui.json +3 -0
  74. package/components/command/command.d.ts +19 -0
  75. package/components/command/command.js +8 -345
  76. package/components/command/command.yaml +105 -124
  77. package/components/demo-toggle/class.js +153 -0
  78. package/components/demo-toggle/demo-toggle.d.ts +23 -0
  79. package/components/demo-toggle/demo-toggle.js +8 -135
  80. package/components/description-list/class.js +86 -0
  81. package/components/description-list/description-list.d.ts +21 -0
  82. package/components/description-list/description-list.js +8 -67
  83. package/components/divider/class.js +57 -0
  84. package/components/divider/divider.d.ts +19 -0
  85. package/components/divider/divider.js +10 -40
  86. package/components/drawer/class.js +306 -0
  87. package/components/drawer/drawer.d.ts +25 -0
  88. package/components/drawer/drawer.js +8 -287
  89. package/components/embed/class.js +73 -0
  90. package/components/embed/embed.d.ts +23 -0
  91. package/components/embed/embed.js +9 -55
  92. package/components/empty-state/class.js +108 -0
  93. package/components/empty-state/empty-state.d.ts +21 -0
  94. package/components/empty-state/empty-state.js +9 -90
  95. package/components/feed/class.js +381 -0
  96. package/components/feed/feed.d.ts +19 -0
  97. package/components/feed/feed.js +9 -367
  98. package/components/field/class.js +266 -0
  99. package/components/field/field.d.ts +23 -0
  100. package/components/field/field.js +8 -247
  101. package/components/fields/class.js +106 -0
  102. package/components/fields/fields.d.ts +19 -0
  103. package/components/fields/fields.js +8 -87
  104. package/components/grid/class.js +31 -0
  105. package/components/grid/grid.d.ts +23 -0
  106. package/components/grid/grid.js +10 -14
  107. package/components/heatmap/class.js +305 -0
  108. package/components/heatmap/heatmap.d.ts +31 -0
  109. package/components/heatmap/heatmap.js +8 -286
  110. package/components/icon/class.js +54 -0
  111. package/components/icon/icon.d.ts +23 -0
  112. package/components/icon/icon.js +13 -40
  113. package/components/image/class.js +112 -0
  114. package/components/image/image.d.ts +33 -0
  115. package/components/image/image.js +9 -94
  116. package/components/input/class.js +773 -0
  117. package/components/input/input.a2ui.json +3 -0
  118. package/components/input/input.js +8 -755
  119. package/components/input/input.yaml +171 -442
  120. package/components/inspector/class.js +142 -0
  121. package/components/inspector/inspector.a2ui.json +8 -1
  122. package/components/inspector/inspector.d.ts +17 -0
  123. package/components/inspector/inspector.js +8 -124
  124. package/components/inspector/inspector.yaml +15 -30
  125. package/components/kbd/class.js +34 -0
  126. package/components/kbd/kbd.a2ui.json +3 -0
  127. package/components/kbd/kbd.d.ts +17 -0
  128. package/components/kbd/kbd.js +10 -17
  129. package/components/kbd/kbd.yaml +54 -185
  130. package/components/link/class.js +187 -0
  131. package/components/link/link.d.ts +55 -0
  132. package/components/link/link.js +8 -168
  133. package/components/list/class.js +249 -0
  134. package/components/list/list.d.ts +23 -0
  135. package/components/list/list.js +9 -231
  136. package/components/menu/class.js +332 -0
  137. package/components/menu/menu.d.ts +21 -0
  138. package/components/menu/menu.js +11 -316
  139. package/components/modal/class.js +231 -0
  140. package/components/modal/modal.a2ui.json +5 -1
  141. package/components/modal/modal.d.ts +23 -0
  142. package/components/modal/modal.js +8 -212
  143. package/components/modal/modal.yaml +19 -39
  144. package/components/nav/class.js +150 -0
  145. package/components/nav/nav.d.ts +31 -0
  146. package/components/nav/nav.js +8 -131
  147. package/components/nav-group/class.js +152 -0
  148. package/components/nav-group/nav-group.d.ts +35 -0
  149. package/components/nav-group/nav-group.js +9 -134
  150. package/components/nav-item/class.js +86 -0
  151. package/components/nav-item/nav-item.d.ts +37 -0
  152. package/components/nav-item/nav-item.js +10 -69
  153. package/components/noodles/class.js +510 -0
  154. package/components/noodles/noodles.d.ts +33 -0
  155. package/components/noodles/noodles.js +9 -493
  156. package/components/option-card/class.js +167 -0
  157. package/components/option-card/option-card.js +8 -149
  158. package/components/otp-input/class.js +180 -0
  159. package/components/otp-input/otp-input.a2ui.json +5 -1
  160. package/components/otp-input/otp-input.js +9 -162
  161. package/components/otp-input/otp-input.yaml +45 -174
  162. package/components/page/class.js +97 -0
  163. package/components/page/page.d.ts +46 -0
  164. package/components/page/page.js +8 -79
  165. package/components/pagination/class.js +195 -0
  166. package/components/pagination/pagination.d.ts +23 -0
  167. package/components/pagination/pagination.js +9 -177
  168. package/components/pane/class.js +186 -0
  169. package/components/pane/pane.a2ui.json +12 -1
  170. package/components/pane/pane.d.ts +31 -0
  171. package/components/pane/pane.js +8 -167
  172. package/components/pane/pane.yaml +57 -157
  173. package/components/pipeline-status/class.js +189 -0
  174. package/components/pipeline-status/pipeline-status.a2ui.json +7 -1
  175. package/components/pipeline-status/pipeline-status.d.ts +21 -0
  176. package/components/pipeline-status/pipeline-status.js +9 -172
  177. package/components/pipeline-status/pipeline-status.yaml +34 -72
  178. package/components/popover/class.js +194 -0
  179. package/components/popover/popover.d.ts +23 -0
  180. package/components/popover/popover.js +9 -176
  181. package/components/progress/class.js +74 -0
  182. package/components/progress/progress.a2ui.json +3 -0
  183. package/components/progress/progress.d.ts +19 -0
  184. package/components/progress/progress.js +10 -57
  185. package/components/progress/progress.yaml +124 -287
  186. package/components/progress-row/class.js +110 -0
  187. package/components/progress-row/progress-row.d.ts +23 -0
  188. package/components/progress-row/progress-row.js +8 -92
  189. package/components/radio/class.js +83 -0
  190. package/components/radio/radio.js +11 -67
  191. package/components/range/class.js +194 -0
  192. package/components/range/range.js +9 -176
  193. package/components/rating/class.js +148 -0
  194. package/components/rating/rating.js +9 -130
  195. package/components/richtext/class.js +87 -0
  196. package/components/richtext/richtext.a2ui.json +7 -1
  197. package/components/richtext/richtext.d.ts +19 -0
  198. package/components/richtext/richtext.js +8 -68
  199. package/components/richtext/richtext.yaml +30 -65
  200. package/components/row/class.js +50 -0
  201. package/components/row/row.d.ts +27 -0
  202. package/components/row/row.js +10 -33
  203. package/components/search/class.js +134 -0
  204. package/components/search/search.js +10 -117
  205. package/components/segment/class.js +62 -0
  206. package/components/segment/segment.d.ts +25 -0
  207. package/components/segment/segment.js +10 -45
  208. package/components/segmented/class.js +165 -0
  209. package/components/segmented/segmented.a2ui.json +4 -0
  210. package/components/segmented/segmented.js +10 -148
  211. package/components/segmented/segmented.yaml +41 -59
  212. package/components/select/class.js +408 -0
  213. package/components/select/select.js +15 -396
  214. package/components/skeleton/class.js +52 -0
  215. package/components/skeleton/skeleton.d.ts +23 -0
  216. package/components/skeleton/skeleton.js +8 -34
  217. package/components/slider/class.js +184 -0
  218. package/components/slider/slider.js +9 -166
  219. package/components/stack/class.js +28 -0
  220. package/components/stack/stack.d.ts +17 -0
  221. package/components/stack/stack.js +10 -11
  222. package/components/step-progress/class.js +98 -0
  223. package/components/step-progress/step-progress.d.ts +27 -0
  224. package/components/step-progress/step-progress.js +8 -79
  225. package/components/stepper/class.js +126 -0
  226. package/components/stepper/stepper.d.ts +19 -0
  227. package/components/stepper/stepper.js +9 -112
  228. package/components/stream/class.js +109 -0
  229. package/components/stream/stream.d.ts +19 -0
  230. package/components/stream/stream.js +8 -90
  231. package/components/swatch/class.js +131 -0
  232. package/components/swatch/swatch.d.ts +28 -0
  233. package/components/swatch/swatch.js +8 -112
  234. package/components/swiper/class.js +373 -0
  235. package/components/swiper/swiper.a2ui.json +4 -0
  236. package/components/swiper/swiper.d.ts +31 -0
  237. package/components/swiper/swiper.js +8 -354
  238. package/components/swiper/swiper.yaml +68 -212
  239. package/components/switch/class.js +63 -0
  240. package/components/switch/switch.a2ui.json +6 -1
  241. package/components/switch/switch.js +11 -47
  242. package/components/switch/switch.yaml +70 -265
  243. package/components/table/class.js +1453 -0
  244. package/components/table/table.d.ts +37 -0
  245. package/components/table/table.js +8 -1435
  246. package/components/table-toolbar/class.js +680 -0
  247. package/components/table-toolbar/table-toolbar.d.ts +33 -0
  248. package/components/table-toolbar/table-toolbar.js +8 -689
  249. package/components/tabs/class.js +242 -0
  250. package/components/tabs/tabs.d.ts +21 -0
  251. package/components/tabs/tabs.js +8 -223
  252. package/components/tag/class.js +99 -0
  253. package/components/tag/tag.d.ts +27 -0
  254. package/components/tag/tag.js +8 -80
  255. package/components/text/class.js +46 -0
  256. package/components/text/text.d.ts +25 -0
  257. package/components/text/text.js +9 -28
  258. package/components/textarea/class.js +134 -0
  259. package/components/textarea/textarea.js +11 -118
  260. package/components/timeline/class.js +176 -0
  261. package/components/timeline/timeline.d.ts +19 -0
  262. package/components/timeline/timeline.js +9 -162
  263. package/components/toast/class.js +92 -0
  264. package/components/toast/toast.d.ts +23 -0
  265. package/components/toast/toast.js +9 -76
  266. package/components/toggle-group/class.js +154 -0
  267. package/components/toggle-group/toggle-group.d.ts +19 -0
  268. package/components/toggle-group/toggle-group.js +11 -140
  269. package/components/toggle-scheme/class.js +286 -0
  270. package/components/toggle-scheme/toggle-scheme.d.ts +41 -0
  271. package/components/toggle-scheme/toggle-scheme.js +8 -268
  272. package/components/toolbar/class.js +388 -0
  273. package/components/toolbar/toolbar.d.ts +23 -0
  274. package/components/toolbar/toolbar.js +10 -376
  275. package/components/tooltip/class.js +299 -0
  276. package/components/tooltip/tooltip.d.ts +27 -0
  277. package/components/tooltip/tooltip.js +8 -280
  278. package/components/tree/class.js +245 -0
  279. package/components/tree/tree.d.ts +15 -0
  280. package/components/tree/tree.js +9 -244
  281. package/components/upload/class.js +199 -0
  282. package/components/upload/upload.js +11 -183
  283. package/index.d.ts +159 -5
  284. package/package.json +5 -1
@@ -0,0 +1,510 @@
1
+ /**
2
+ * Non-side-effect class export for `<noodles-ui>`.
3
+ *
4
+ * Importing this file gives you the class(es) without auto-registering the tag.
5
+ * Useful for test isolation, subclassing with tag-name override, or selective
6
+ * composition.
7
+ *
8
+ * The auto-register path stays at `@adia-ai/web-components/components/noodles`
9
+ * (which imports this file + calls `defineIfFree()`).
10
+ *
11
+ * @see ../../USAGE.md#registration--auto-vs-explicit
12
+ */
13
+
14
+ /**
15
+ * <noodles-ui editable show-ports>
16
+ * <div data-noodle-port="left right">Node A</div>
17
+ * <div data-noodle-port="top bottom">Node B</div>
18
+ * </noodles-ui>
19
+ *
20
+ * Renders SVG connection lines ("noodles") between children that declare
21
+ * ports via the data-noodle-port attribute (space-separated sides).
22
+ *
23
+ * Supports bezier, step, and straight curve modes.
24
+ */
25
+
26
+ import { UIElement } from '../../core/element.js';
27
+
28
+ let nextId = 0;
29
+
30
+ export class UINoodles extends UIElement {
31
+ static properties = {
32
+ editable: { type: Boolean, default: false, reflect: true },
33
+ color: { type: String, default: '', reflect: true },
34
+ strokeWidth: { type: Number, default: 2, reflect: true, attribute: 'stroke-width' },
35
+ tension: { type: Number, default: 0.5, reflect: true },
36
+ showPorts: { type: Boolean, default: false, reflect: true, attribute: 'show-ports' },
37
+ portSize: { type: Number, default: 10, attribute: 'port-size', reflect: true },
38
+ curve: { type: String, default: 'bezier', reflect: true },
39
+ animated: { type: Boolean, default: false, reflect: true },
40
+ readonly: { type: Boolean, default: false, reflect: true },
41
+ };
42
+
43
+ static template = () => null;
44
+
45
+ #svg = null;
46
+ #connections = [];
47
+ #portIndicators = new Map(); // key: "elIndex:side" -> DOM element
48
+ #resizeObs = null;
49
+ #mutationObs = null;
50
+ #rafId = null;
51
+ #bound = false;
52
+ #dragState = null;
53
+ #dragPath = null;
54
+ #dragCancel = null;
55
+
56
+ // ── Public API ──────────────────────────────────────────────
57
+
58
+ get connections() {
59
+ return this.#connections.map(c => ({ ...c }));
60
+ }
61
+
62
+ connect(from, to, fromPort = 'right', toPort = 'left') {
63
+ const id = `noodle-${++nextId}`;
64
+ const conn = { id, from, to, fromPort, toPort };
65
+ this.#connections.push(conn);
66
+ this.#scheduleUpdate();
67
+ this.dispatchEvent(new CustomEvent('noodle-connect', {
68
+ bubbles: true, detail: { ...conn },
69
+ }));
70
+ return id;
71
+ }
72
+
73
+ disconnect(id) {
74
+ const idx = this.#connections.findIndex(c => c.id === id);
75
+ if (idx === -1) return;
76
+ const [conn] = this.#connections.splice(idx, 1);
77
+ this.#scheduleUpdate();
78
+ this.dispatchEvent(new CustomEvent('noodle-disconnect', {
79
+ bubbles: true, detail: { ...conn },
80
+ }));
81
+ }
82
+
83
+ setConnections(list) {
84
+ this.#connections = list.map(c => ({
85
+ id: c.id || `noodle-${++nextId}`,
86
+ from: c.from,
87
+ to: c.to,
88
+ fromPort: c.fromPort || 'right',
89
+ toPort: c.toPort || 'left',
90
+ }));
91
+ this.#scheduleUpdate();
92
+ }
93
+
94
+ clear() {
95
+ this.#connections = [];
96
+ this.#scheduleUpdate();
97
+ }
98
+
99
+ update() {
100
+ this.#performUpdate();
101
+ }
102
+
103
+ // ── Lifecycle ───────────────────────────────────────────────
104
+
105
+ connected() {
106
+ // Create the SVG overlay
107
+ this.#svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
108
+ this.#svg.setAttribute('data-noodle-svg', '');
109
+ this.prepend(this.#svg);
110
+
111
+ // Observers for child layout changes
112
+ this.#resizeObs = new ResizeObserver(() => this.#scheduleUpdate());
113
+ this.#mutationObs = new MutationObserver(() => {
114
+ this.#rebuildPortIndicators();
115
+ this.#scheduleUpdate();
116
+ });
117
+
118
+ this.#resizeObs.observe(this);
119
+ this.#observeMutations();
120
+
121
+ // Observe each port-bearing child
122
+ for (const child of this.#getPortChildren()) {
123
+ this.#resizeObs.observe(child);
124
+ }
125
+
126
+ this.#rebuildPortIndicators();
127
+ this.#scheduleUpdate();
128
+ }
129
+
130
+ render() {
131
+ if (!this.#svg) return;
132
+ // Update SVG attributes from properties
133
+ if (this.color) {
134
+ this.#svg.style.setProperty('--noodles-stroke', this.color);
135
+ } else {
136
+ this.#svg.style.removeProperty('--noodles-stroke');
137
+ }
138
+ }
139
+
140
+ disconnected() {
141
+ // Flush any in-flight drag so per-port pointermove/up listeners + their
142
+ // closure references are released before the host detaches.
143
+ this.#dragCancel?.();
144
+ this.#resizeObs?.disconnect();
145
+ this.#mutationObs?.disconnect();
146
+ if (this.#rafId) cancelAnimationFrame(this.#rafId);
147
+ this.#rafId = null;
148
+ this.#clearPortIndicators();
149
+ this.#svg?.remove();
150
+ this.#svg = null;
151
+ this.#bound = false;
152
+ }
153
+
154
+ // ── Update scheduling ───────────────────────────────────────
155
+
156
+ #scheduleUpdate() {
157
+ if (this.#rafId) return;
158
+ this.#rafId = requestAnimationFrame(() => {
159
+ this.#rafId = null;
160
+ this.#performUpdate();
161
+ });
162
+ }
163
+
164
+ #observeMutations() {
165
+ this.#mutationObs?.observe(this, {
166
+ childList: true,
167
+ subtree: true,
168
+ attributes: true,
169
+ attributeFilter: ['data-noodle-port', 'style'],
170
+ });
171
+ }
172
+
173
+ #pauseMutations() {
174
+ this.#mutationObs?.disconnect();
175
+ }
176
+
177
+ #performUpdate() {
178
+ if (!this.#svg) return;
179
+ this.#pauseMutations();
180
+
181
+ // Sync resize observer with current port children
182
+ const portChildren = this.#getPortChildren();
183
+ for (const child of portChildren) {
184
+ this.#resizeObs.observe(child);
185
+ }
186
+
187
+ // Update port indicator positions
188
+ this.#updatePortIndicatorPositions();
189
+
190
+ // Render paths
191
+ const pathData = this.#connections.map(conn => {
192
+ const fromEl = this.#resolveElement(conn.from);
193
+ const toEl = this.#resolveElement(conn.to);
194
+ if (!fromEl || !toEl) return null;
195
+
196
+ const p1 = this.#getPortPosition(fromEl, conn.fromPort);
197
+ const p2 = this.#getPortPosition(toEl, conn.toPort);
198
+ if (!p1 || !p2) return null;
199
+
200
+ return { id: conn.id, d: this.#computePath(p1, p2, conn.fromPort, conn.toPort) };
201
+ }).filter(Boolean);
202
+
203
+ // Reconcile SVG path elements
204
+ const existing = new Map();
205
+ for (const path of this.#svg.querySelectorAll('path[data-noodle-id]')) {
206
+ existing.set(path.getAttribute('data-noodle-id'), path);
207
+ }
208
+
209
+ const activeIds = new Set(pathData.map(p => p.id));
210
+
211
+ // Remove stale paths
212
+ for (const [id, path] of existing) {
213
+ if (!activeIds.has(id)) path.remove();
214
+ }
215
+
216
+ // Add/update paths
217
+ for (const { id, d } of pathData) {
218
+ let path = existing.get(id);
219
+ if (!path) {
220
+ path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
221
+ path.setAttribute('data-noodle-id', id);
222
+ this.#svg.appendChild(path);
223
+ }
224
+ path.setAttribute('d', d);
225
+ path.setAttribute('stroke-width', this.strokeWidth);
226
+ }
227
+
228
+ // Render drag preview path
229
+ if (this.#dragState && this.#dragPath) {
230
+ let preview = this.#svg.querySelector('path[data-noodle-preview]');
231
+ if (!preview) {
232
+ preview = document.createElementNS('http://www.w3.org/2000/svg', 'path');
233
+ preview.setAttribute('data-noodle-preview', '');
234
+ preview.style.opacity = '0.5';
235
+ preview.style.strokeDasharray = '6 3';
236
+ this.#svg.appendChild(preview);
237
+ }
238
+ preview.setAttribute('d', this.#dragPath);
239
+ preview.setAttribute('stroke-width', this.strokeWidth);
240
+ } else {
241
+ this.#svg.querySelector('path[data-noodle-preview]')?.remove();
242
+ }
243
+
244
+ this.#observeMutations();
245
+ }
246
+
247
+ // ── Element resolution ──────────────────────────────────────
248
+
249
+ #resolveElement(ref) {
250
+ if (ref instanceof HTMLElement) return ref;
251
+ if (typeof ref === 'string') return this.querySelector(`#${CSS.escape(ref)}`);
252
+ if (typeof ref === 'number') return this.#getPortChildren()[ref] ?? null;
253
+ return null;
254
+ }
255
+
256
+ #getPortChildren() {
257
+ return [...this.querySelectorAll('[data-noodle-port]')];
258
+ }
259
+
260
+ // ── Port position computation ──────────────────────────────
261
+ // Uses bounding client rects so nested port-bearing descendants
262
+ // (e.g. inside an absolutely-positioned card wrapper) project
263
+ // correctly into the noodles-ui coordinate system.
264
+ //
265
+ // When an ancestor applies a CSS transform (zoom/pan in a graph
266
+ // editor), bounding rects are in *screen* pixels — but SVG paths
267
+ // and port-indicator dots are positioned in *local* (untransformed)
268
+ // pixels. We detect any ancestor scale by comparing this element's
269
+ // visible width to its layout width and divide deltas accordingly.
270
+ // No transform → ratio is 1, no-op.
271
+ #getPortPosition(el, side) {
272
+ const elRect = el.getBoundingClientRect();
273
+ const myRect = this.getBoundingClientRect();
274
+ const localScale = (this.offsetWidth && myRect.width)
275
+ ? (myRect.width / this.offsetWidth)
276
+ : 1;
277
+ const left = (elRect.left - myRect.left) / localScale;
278
+ const top = (elRect.top - myRect.top) / localScale;
279
+ const w = elRect.width / localScale;
280
+ const h = elRect.height / localScale;
281
+
282
+ switch (side) {
283
+ case 'left': return { x: left, y: top + h / 2 };
284
+ case 'right': return { x: left + w, y: top + h / 2 };
285
+ case 'top': return { x: left + w / 2, y: top };
286
+ case 'bottom': return { x: left + w / 2, y: top + h };
287
+ default: return { x: left + w / 2, y: top + h / 2 };
288
+ }
289
+ }
290
+
291
+ // ── Path computation ───────────────────────────────────────
292
+
293
+ #computePath(p1, p2, fromSide, toSide) {
294
+ switch (this.curve) {
295
+ case 'step': return this.#stepPath(p1, p2);
296
+ case 'straight': return this.#straightPath(p1, p2);
297
+ case 'bezier':
298
+ default: return this.#bezierPath(p1, p2, fromSide, toSide);
299
+ }
300
+ }
301
+
302
+ #bezierPath(p1, p2, fromSide, toSide) {
303
+ const dx = Math.abs(p2.x - p1.x);
304
+ const dy = Math.abs(p2.y - p1.y);
305
+ const dist = Math.sqrt(dx * dx + dy * dy);
306
+ const offset = Math.max(50, dist * this.tension);
307
+
308
+ const c1 = this.#controlPoint(p1, fromSide, offset);
309
+ const c2 = this.#controlPoint(p2, toSide, offset);
310
+
311
+ return `M ${p1.x} ${p1.y} C ${c1.x} ${c1.y}, ${c2.x} ${c2.y}, ${p2.x} ${p2.y}`;
312
+ }
313
+
314
+ #controlPoint(p, side, offset) {
315
+ switch (side) {
316
+ case 'left': return { x: p.x - offset, y: p.y };
317
+ case 'right': return { x: p.x + offset, y: p.y };
318
+ case 'top': return { x: p.x, y: p.y - offset };
319
+ case 'bottom': return { x: p.x, y: p.y + offset };
320
+ default: return { x: p.x + offset, y: p.y };
321
+ }
322
+ }
323
+
324
+ #stepPath(p1, p2) {
325
+ const midX = (p1.x + p2.x) / 2;
326
+ return `M ${p1.x} ${p1.y} L ${midX} ${p1.y} L ${midX} ${p2.y} L ${p2.x} ${p2.y}`;
327
+ }
328
+
329
+ #straightPath(p1, p2) {
330
+ return `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;
331
+ }
332
+
333
+ // ── Port indicators ────────────────────────────────────────
334
+
335
+ #rebuildPortIndicators() {
336
+ this.#pauseMutations();
337
+ this.#clearPortIndicators();
338
+
339
+ const children = this.#getPortChildren();
340
+ for (const child of children) {
341
+ const sides = (child.getAttribute('data-noodle-port') || '').split(/\s+/).filter(Boolean);
342
+ for (const side of sides) {
343
+ const key = this.#portKey(child, side);
344
+ const dot = document.createElement('div');
345
+ dot.setAttribute('data-noodle-port-indicator', '');
346
+ dot.setAttribute('data-noodle-side', side);
347
+ dot.dataset.portElement = child.id || '';
348
+
349
+ if (this.editable) {
350
+ dot.addEventListener('pointerdown', (e) => this.#onPortPointerDown(e, child, side));
351
+ }
352
+
353
+ this.appendChild(dot);
354
+ this.#portIndicators.set(key, dot);
355
+ }
356
+ }
357
+
358
+ this.#updatePortIndicatorPositions();
359
+ this.#observeMutations();
360
+ }
361
+
362
+ #clearPortIndicators() {
363
+ for (const [, dot] of this.#portIndicators) {
364
+ dot.remove();
365
+ }
366
+ this.#portIndicators.clear();
367
+
368
+ }
369
+
370
+ #updatePortIndicatorPositions() {
371
+ const children = this.#getPortChildren();
372
+ for (const child of children) {
373
+ const sides = (child.getAttribute('data-noodle-port') || '').split(/\s+/).filter(Boolean);
374
+ for (const side of sides) {
375
+ const key = this.#portKey(child, side);
376
+ const dot = this.#portIndicators.get(key);
377
+ if (!dot) continue;
378
+
379
+ const pos = this.#getPortPosition(child, side);
380
+ dot.style.left = `${pos.x}px`;
381
+ dot.style.top = `${pos.y}px`;
382
+ }
383
+ }
384
+ }
385
+
386
+ #portKey(el, side) {
387
+ // Use a stable identifier
388
+ if (!el._noodleId) el._noodleId = `_n${++nextId}`;
389
+ return `${el._noodleId}:${side}`;
390
+ }
391
+
392
+ // ── Drag to connect ────────────────────────────────────────
393
+
394
+ #onPortPointerDown = (e, fromEl, fromSide) => {
395
+ if (!this.editable || this.readonly) return;
396
+ e.preventDefault();
397
+ e.stopPropagation();
398
+
399
+ const dot = e.currentTarget;
400
+ dot.setPointerCapture(e.pointerId);
401
+ dot.setAttribute('data-noodle-dragging', '');
402
+
403
+ const startPos = this.#getPortPosition(fromEl, fromSide);
404
+
405
+ const onMove = (ev) => {
406
+ if (!this.#dragState) return;
407
+ const rect = this.getBoundingClientRect();
408
+ const mx = ev.clientX - rect.left;
409
+ const my = ev.clientY - rect.top;
410
+
411
+ this.#dragPath = this.#computePath(
412
+ startPos, { x: mx, y: my }, fromSide, this.#inferSide(startPos, { x: mx, y: my })
413
+ );
414
+ this.#scheduleUpdate();
415
+
416
+ // Highlight potential drop targets
417
+ this.#updateDropTargets(ev.clientX, ev.clientY, fromEl);
418
+
419
+ this.dispatchEvent(new CustomEvent('noodle-drag', {
420
+ bubbles: true,
421
+ detail: { from: fromEl, fromPort: fromSide, x: mx, y: my },
422
+ }));
423
+ };
424
+
425
+ const onUp = (ev) => {
426
+ try { dot.releasePointerCapture(ev.pointerId); } catch (_) { /* already released */ }
427
+ dot.removeAttribute('data-noodle-dragging');
428
+ dot.removeEventListener('pointermove', onMove);
429
+ dot.removeEventListener('pointerup', onUp);
430
+
431
+ // Check if dropped on a target port (skip on synthetic disconnect cancel).
432
+ if (ev.type === 'pointerup' && ev.clientX !== undefined) {
433
+ const target = this.#findDropTarget(ev.clientX, ev.clientY, fromEl);
434
+ if (target) this.connect(fromEl, target.el, fromSide, target.side);
435
+ }
436
+
437
+ this.#clearDropTargets();
438
+ this.#dragState = null;
439
+ this.#dragPath = null;
440
+ this.#dragCancel = null;
441
+ this.#scheduleUpdate();
442
+ };
443
+
444
+ // Capture the handlers so `disconnected()` can flush a mid-drag drag
445
+ // by calling the same onUp tear-down without a synthetic event.
446
+ this.#dragCancel = () => {
447
+ this.#dragState = null;
448
+ dot.removeAttribute('data-noodle-dragging');
449
+ dot.removeEventListener('pointermove', onMove);
450
+ dot.removeEventListener('pointerup', onUp);
451
+ this.#clearDropTargets();
452
+ this.#dragPath = null;
453
+ this.#dragCancel = null;
454
+ };
455
+
456
+ this.#dragState = { fromEl, fromSide, startPos, pointerId: e.pointerId };
457
+ this.#dragPath = null;
458
+
459
+ dot.addEventListener('pointermove', onMove);
460
+ dot.addEventListener('pointerup', onUp);
461
+ };
462
+
463
+ #inferSide(from, to) {
464
+ const dx = to.x - from.x;
465
+ const dy = to.y - from.y;
466
+ if (Math.abs(dx) > Math.abs(dy)) {
467
+ return dx > 0 ? 'left' : 'right';
468
+ }
469
+ return dy > 0 ? 'top' : 'bottom';
470
+ }
471
+
472
+ #updateDropTargets(clientX, clientY, excludeEl) {
473
+ this.#clearDropTargets();
474
+ const target = this.#findDropTarget(clientX, clientY, excludeEl);
475
+ if (target?.dot) {
476
+ target.dot.setAttribute('data-noodle-drop-ready', '');
477
+ }
478
+ }
479
+
480
+ #findDropTarget(clientX, clientY, excludeEl) {
481
+ const hitRadius = this.portSize * 2;
482
+ let best = null;
483
+ let bestDist = Infinity;
484
+
485
+ for (const [key, dot] of this.#portIndicators) {
486
+ const rect = dot.getBoundingClientRect();
487
+ const cx = rect.left + rect.width / 2;
488
+ const cy = rect.top + rect.height / 2;
489
+ const dist = Math.sqrt((clientX - cx) ** 2 + (clientY - cy) ** 2);
490
+
491
+ // Parse the key to find the element
492
+ const [noodleId, side] = key.split(':');
493
+ const el = this.#getPortChildren().find(c => c._noodleId === noodleId);
494
+ if (!el || el === excludeEl) continue;
495
+
496
+ if (dist < hitRadius && dist < bestDist) {
497
+ bestDist = dist;
498
+ best = { el, side, dot };
499
+ }
500
+ }
501
+
502
+ return best;
503
+ }
504
+
505
+ #clearDropTargets() {
506
+ for (const [, dot] of this.#portIndicators) {
507
+ dot.removeAttribute('data-noodle-drop-ready');
508
+ }
509
+ }
510
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `<noodles-ui>` — SVG connection lines (noodles) between children with declared ports. Supports bezier, step, and straight curves.
3
+ *
4
+ * @see https://ui-kit.exe.xyz/site/components/noodles
5
+ *
6
+ * Type declarations generated by scripts/build/dts-codegen.mjs from
7
+ * the component's `.a2ui.json` sidecar. Edit the source `.yaml`,
8
+ * run `npm run components`, then `npm run codegen:dts` to regenerate;
9
+ * or hand-author this file fully if rich event types are needed.
10
+ */
11
+
12
+ import { UIElement } from '../../core/element.js';
13
+
14
+ export class UINoodles extends UIElement {
15
+ /** Flowing dash animation on noodles */
16
+ animated: boolean;
17
+ /** Noodle stroke color. Defaults to --a-accent */
18
+ color: string;
19
+ /** Noodle curve style */
20
+ curve: 'bezier' | 'step' | 'straight';
21
+ /** Allow interactive creation/deletion of connections via drag */
22
+ editable: boolean;
23
+ /** Port indicator size */
24
+ portSize: number;
25
+ /** View-only mode; disables drag/edit while keeping connections visible */
26
+ readonly: boolean;
27
+ /** Show port indicators */
28
+ showPorts: boolean;
29
+ /** Line stroke width */
30
+ strokeWidth: number;
31
+ /** Bezier control point distance (0-1). Higher = wider curves */
32
+ tension: number;
33
+ }