@dryui/ui 3.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1075) hide show
  1. package/dist/accordion/accordion-root.svelte +1 -1
  2. package/dist/adjust/adjust.svelte +12 -8
  3. package/dist/alpha-slider/alpha-slider-input.svelte +14 -13
  4. package/dist/app-frame/app-frame.svelte +4 -1
  5. package/dist/aspect-ratio/aspect-ratio.svelte +7 -6
  6. package/dist/aurora/aurora.svelte +144 -81
  7. package/dist/avatar/avatar.svelte +22 -15
  8. package/dist/badge/badge.svelte +10 -22
  9. package/dist/beam/beam.svelte +20 -21
  10. package/dist/button/button.svelte +45 -24
  11. package/dist/button/button.svelte.d.ts +14 -0
  12. package/dist/button-group/button-group.svelte +4 -5
  13. package/dist/calendar/calendar-button-grid.svelte +11 -4
  14. package/dist/calendar/calendar-button-grid.svelte.d.ts +2 -2
  15. package/dist/calendar/calendar-event-legend.svelte +63 -0
  16. package/dist/calendar/calendar-event-legend.svelte.d.ts +6 -0
  17. package/dist/calendar/calendar-header.svelte +3 -1
  18. package/dist/calendar/calendar-heading.svelte +21 -9
  19. package/dist/calendar/calendar-nav-button.svelte +15 -2
  20. package/dist/calendar/calendar-root.svelte +94 -11
  21. package/dist/calendar/calendar-root.svelte.d.ts +10 -1
  22. package/dist/calendar/calendar-week.svelte +515 -0
  23. package/dist/calendar/calendar-week.svelte.d.ts +12 -0
  24. package/dist/calendar/calendar.meta.js +2 -2
  25. package/dist/calendar/context.svelte.d.ts +19 -0
  26. package/dist/calendar/index.d.ts +11 -1
  27. package/dist/calendar/index.js +5 -1
  28. package/dist/calendar/types.d.ts +32 -0
  29. package/dist/calendar/types.js +1 -0
  30. package/dist/calendar/week-utils.d.ts +12 -0
  31. package/dist/calendar/week-utils.js +133 -0
  32. package/dist/carousel/carousel-button-dots.svelte +12 -6
  33. package/dist/carousel/carousel-root.svelte +201 -17
  34. package/dist/carousel/carousel-root.svelte.d.ts +1 -1
  35. package/dist/carousel/carousel-viewport.svelte +19 -1
  36. package/dist/chart/chart-bars.svelte +43 -19
  37. package/dist/chart/chart-donut.svelte +50 -23
  38. package/dist/chart/chart-root.svelte +19 -4
  39. package/dist/chart/chart-stacked-bar.svelte +3 -2
  40. package/dist/chart/series-color.d.ts +7 -0
  41. package/dist/chart/series-color.js +11 -0
  42. package/dist/chat-thread/chat-thread.svelte +15 -126
  43. package/dist/chat-thread/chat-thread.svelte.d.ts +4 -7
  44. package/dist/chat-thread/index.d.ts +4 -7
  45. package/dist/checkbox/checkbox-input.svelte +48 -41
  46. package/dist/chip/chip-button.svelte +14 -17
  47. package/dist/chip/chip-button.svelte.d.ts +2 -3
  48. package/dist/chip-group/chip-group-root.svelte +6 -4
  49. package/dist/chromatic-aberration/chromatic-aberration.svelte +34 -19
  50. package/dist/chromatic-shift/chromatic-shift.svelte +7 -8
  51. package/dist/code-block/code-block-button.svelte +72 -40
  52. package/dist/color-picker/color-picker-area.svelte +20 -25
  53. package/dist/color-picker/color-picker-input-hue-slider.svelte +12 -9
  54. package/dist/color-picker/color-picker-root.svelte +18 -17
  55. package/dist/color-picker/color-picker-swatch.svelte +17 -16
  56. package/dist/combobox/combobox-content.svelte +4 -14
  57. package/dist/combobox/combobox-input-root.svelte +6 -3
  58. package/dist/combobox/combobox-input-root.svelte.d.ts +2 -1
  59. package/dist/combobox/combobox-input.svelte +13 -10
  60. package/dist/combobox/combobox-item.svelte +9 -4
  61. package/dist/command-palette/command-palette-dialog-root.svelte +1 -0
  62. package/dist/command-palette/command-palette-item.svelte +7 -4
  63. package/dist/context-menu/context-menu-content.svelte +7 -15
  64. package/dist/data-grid/data-grid-button-input-column.svelte +2 -2
  65. package/dist/data-grid/data-grid-cell.svelte +1 -1
  66. package/dist/data-grid/data-grid-input-select-all.svelte +1 -1
  67. package/dist/data-grid/data-grid-input-select-cell.svelte +1 -1
  68. package/dist/date-field/date-field-segment.svelte +10 -12
  69. package/dist/date-picker/datepicker-button-calendar.svelte +2 -2
  70. package/dist/date-picker/datepicker-button-calendar.svelte.d.ts +2 -2
  71. package/dist/date-picker/datepicker-button-trigger.svelte +146 -44
  72. package/dist/date-picker/index.d.ts +4 -1
  73. package/dist/date-range-picker/date-range-picker-button-calendar.svelte +2 -2
  74. package/dist/date-range-picker/date-range-picker-button-calendar.svelte.d.ts +2 -2
  75. package/dist/date-range-picker/date-range-picker-button-trigger.svelte +151 -40
  76. package/dist/date-range-picker/date-range-picker-button-trigger.svelte.d.ts +1 -0
  77. package/dist/date-range-picker/index.d.ts +4 -1
  78. package/dist/diagram/edge-routing.js +18 -7
  79. package/dist/diagram/layout.js +31 -16
  80. package/dist/displacement/displacement.svelte +30 -43
  81. package/dist/drag-and-drop/drag-and-drop-root.svelte +17 -5
  82. package/dist/drag-and-drop/index.d.ts +14 -1
  83. package/dist/drop-zone/drop-zone.svelte +7 -4
  84. package/dist/dropdown-menu/dropdown-menu-content.svelte +15 -16
  85. package/dist/field/field-description.svelte +6 -9
  86. package/dist/field/field-error.svelte +6 -10
  87. package/dist/file-select/file-select-root.svelte +13 -18
  88. package/dist/file-select/file-select-root.svelte.d.ts +2 -1
  89. package/dist/file-upload/file-upload-dropzone.svelte +22 -22
  90. package/dist/file-upload/file-upload-item.svelte +1 -1
  91. package/dist/file-upload/file-upload-list.svelte +1 -1
  92. package/dist/flip-card/flip-card-root.svelte +2 -2
  93. package/dist/float-button/context.svelte.d.ts +2 -0
  94. package/dist/float-button/float-button-action.svelte +41 -11
  95. package/dist/float-button/float-button-root.svelte +52 -13
  96. package/dist/float-button/float-button-trigger.svelte +25 -12
  97. package/dist/format-bytes/format-bytes.svelte +2 -5
  98. package/dist/format-date/format-date.svelte +2 -6
  99. package/dist/format-number/format-number.svelte +2 -6
  100. package/dist/gauge/gauge.svelte +9 -2
  101. package/dist/glass/glass.svelte +4 -3
  102. package/dist/glow/glow.svelte +0 -5
  103. package/dist/god-rays/god-rays.svelte +6 -6
  104. package/dist/gradient-mesh/gradient-mesh.svelte +112 -111
  105. package/dist/halftone/halftone.svelte +17 -28
  106. package/dist/heading/heading.svelte +0 -6
  107. package/dist/hover-card/hover-card-content.svelte +8 -5
  108. package/dist/hover-card/hover-card.meta.js +2 -2
  109. package/dist/icon/icon.svelte +24 -13
  110. package/dist/icon/icon.svelte.d.ts +2 -2
  111. package/dist/icon-swap/icon-swap.svelte +4 -3
  112. package/dist/icon-swap/icon-swap.svelte.d.ts +2 -1
  113. package/dist/image/image.svelte +5 -13
  114. package/dist/image-comparison/image-comparison.svelte +39 -45
  115. package/dist/index.d.ts +3 -5
  116. package/dist/index.js +1 -2
  117. package/dist/input/input.svelte +88 -9
  118. package/dist/input/input.svelte.d.ts +24 -1
  119. package/dist/internal/anchored-overlay-content.svelte.d.ts +12 -6
  120. package/dist/internal/anchored-overlay-content.svelte.js +13 -16
  121. package/dist/internal/calendar-event-layout.d.ts +53 -0
  122. package/dist/internal/calendar-event-layout.js +191 -0
  123. package/dist/internal/calendar-grid-button.svelte +618 -76
  124. package/dist/internal/calendar-grid-button.svelte.d.ts +2 -2
  125. package/dist/internal/calendar-grid-utils.d.ts +3 -0
  126. package/dist/internal/calendar-grid-utils.js +24 -0
  127. package/dist/internal/form-control-wrapper-attrs.d.ts +9 -0
  128. package/dist/internal/form-control-wrapper-attrs.js +1 -0
  129. package/dist/internal/menu-item.svelte +1 -0
  130. package/dist/internal/modal-content.svelte +2 -4
  131. package/dist/internal/motion.d.ts +1 -1
  132. package/dist/internal/motion.js +5 -2
  133. package/dist/internal/picker-popover-content.svelte +15 -16
  134. package/dist/link/link.svelte +8 -5
  135. package/dist/link-preview/link-preview-content.svelte +12 -15
  136. package/dist/link-preview/link-preview-trigger.svelte +3 -6
  137. package/dist/listbox/listbox-item.svelte +13 -18
  138. package/dist/listbox/listbox-root.svelte +4 -9
  139. package/dist/logo-mark/logo-mark.svelte +31 -31
  140. package/dist/map/map-marker.svelte +2 -2
  141. package/dist/map/map-root.svelte +11 -2
  142. package/dist/markdown-renderer/markdown-renderer.svelte +141 -164
  143. package/dist/marquee/marquee.svelte +12 -13
  144. package/dist/mask-reveal/mask-reveal.svelte +14 -27
  145. package/dist/mega-menu/mega-menu-column.svelte +1 -1
  146. package/dist/mega-menu/mega-menu-link.svelte +7 -7
  147. package/dist/mega-menu/mega-menu-panel.svelte +18 -32
  148. package/dist/menubar/menubar-content.svelte +18 -19
  149. package/dist/menubar/menubar-item.svelte +1 -0
  150. package/dist/menubar/menubar-menu.svelte +1 -2
  151. package/dist/menubar/menubar-root.svelte +1 -11
  152. package/dist/motion/enter.svelte +15 -14
  153. package/dist/motion/enter.svelte.d.ts +2 -1
  154. package/dist/motion/exit.svelte +6 -5
  155. package/dist/motion/exit.svelte.d.ts +2 -1
  156. package/dist/motion/stagger.svelte +10 -9
  157. package/dist/motion/stagger.svelte.d.ts +2 -1
  158. package/dist/multi-select-combobox/multi-select-combobox-content.svelte +4 -14
  159. package/dist/multi-select-combobox/multi-select-combobox-input.svelte +6 -5
  160. package/dist/multi-select-combobox/multi-select-combobox-item.svelte +25 -31
  161. package/dist/multi-select-combobox/multi-select-combobox-root-input.svelte +12 -16
  162. package/dist/noise/noise.svelte +20 -19
  163. package/dist/notification-center/notification-center-item.svelte +1 -1
  164. package/dist/notification-center/notification-center-panel.svelte +17 -19
  165. package/dist/number-input/number-input-button.svelte +78 -12
  166. package/dist/number-input/number-input-button.svelte.d.ts +15 -1
  167. package/dist/numeric/numeric.svelte +27 -12
  168. package/dist/numeric/numeric.svelte.d.ts +2 -1
  169. package/dist/option-picker/option-picker-item.svelte +8 -15
  170. package/dist/option-picker/option-picker-preview.svelte +44 -24
  171. package/dist/pagination/pagination-root.svelte +7 -0
  172. package/dist/pin-input/pin-input-root.svelte +2 -11
  173. package/dist/popover/popover-content.svelte +7 -4
  174. package/dist/progress/progress.svelte +2 -2
  175. package/dist/prompt-input/prompt-input-button-textarea.svelte +2 -10
  176. package/dist/qr-code/qr-code.svelte +78 -38
  177. package/dist/qr-code/qr-code.svelte.d.ts +1 -1
  178. package/dist/radio-group/radio-group-item-input.svelte +60 -14
  179. package/dist/radio-group/radio-group-item-input.svelte.d.ts +9 -2
  180. package/dist/range-calendar/index.d.ts +4 -1
  181. package/dist/range-calendar/range-calendar-grid-button.svelte +2 -2
  182. package/dist/range-calendar/range-calendar-grid-button.svelte.d.ts +2 -2
  183. package/dist/relative-time/relative-time.svelte +15 -15
  184. package/dist/reveal/reveal.svelte +12 -24
  185. package/dist/rich-text-editor/rich-text-editor-content.svelte +1 -4
  186. package/dist/scroll-to-top/scroll-to-top-button.svelte +5 -3
  187. package/dist/scroll-to-top/scroll-to-top-button.svelte.d.ts +2 -2
  188. package/dist/segmented-control/segmented-control-root.svelte +0 -3
  189. package/dist/select/index.d.ts +7 -1
  190. package/dist/select/select-content.svelte +15 -16
  191. package/dist/select/select-item.svelte +1 -0
  192. package/dist/select/select-root-input.svelte +5 -4
  193. package/dist/select/select-root-input.svelte.d.ts +2 -2
  194. package/dist/select/select-trigger-button.svelte +122 -16
  195. package/dist/select/select-trigger-button.svelte.d.ts +2 -2
  196. package/dist/shader-canvas/shader-canvas.svelte +11 -20
  197. package/dist/shimmer/shimmer.svelte +28 -25
  198. package/dist/skeleton/skeleton.svelte +18 -23
  199. package/dist/slider/slider-input.svelte +8 -7
  200. package/dist/sparkline/sparkline.svelte +1 -1
  201. package/dist/splitter/splitter-handle.svelte +3 -3
  202. package/dist/splitter/splitter-root.svelte +13 -12
  203. package/dist/spotlight/spotlight.svelte +30 -62
  204. package/dist/star-rating/star-rating-root.svelte +3 -4
  205. package/dist/stepper/stepper-step-button.svelte +2 -2
  206. package/dist/table-of-contents/table-of-contents-root.svelte +5 -16
  207. package/dist/tabs/tabs-content.svelte +7 -1
  208. package/dist/tag/tag-button.svelte +70 -68
  209. package/dist/tags-input/tags-input-root.svelte +2 -9
  210. package/dist/tags-input/tags-input-tag.svelte +1 -1
  211. package/dist/text/index.d.ts +2 -3
  212. package/dist/text/text.svelte +6 -12
  213. package/dist/text/text.svelte.d.ts +2 -3
  214. package/dist/textarea/textarea.svelte +91 -16
  215. package/dist/textarea/textarea.svelte.d.ts +20 -1
  216. package/dist/themes/aurora.css +155 -113
  217. package/dist/themes/component-defaults.css +47 -0
  218. package/dist/themes/dark.css +115 -229
  219. package/dist/themes/default.css +71 -176
  220. package/dist/themes/midnight.css +80 -65
  221. package/dist/themes/terminal.css +69 -73
  222. package/dist/time-input/index.d.ts +2 -2
  223. package/dist/time-input/time-input.meta.js +1 -1
  224. package/dist/time-input/time-input.svelte +5 -3
  225. package/dist/time-input/time-input.svelte.d.ts +2 -2
  226. package/dist/timeline/timeline-title.svelte +0 -6
  227. package/dist/toast/toast-root.svelte +10 -9
  228. package/dist/toggle/toggle-button.svelte +17 -6
  229. package/dist/toggle-group/toggle-group-root.svelte +3 -3
  230. package/dist/token-scope/index.d.ts +6 -0
  231. package/dist/token-scope/index.js +1 -0
  232. package/dist/token-scope/token-scope.meta.js +7 -0
  233. package/dist/toolbar/toolbar-root.svelte +4 -4
  234. package/dist/tooltip/tooltip-content.svelte +24 -13
  235. package/dist/tour/tour-root.css +6 -2
  236. package/dist/transfer/transfer-list-input.svelte +1 -2
  237. package/dist/transfer/transfer-root.svelte +0 -10
  238. package/dist/tree/tree-root.svelte +7 -4
  239. package/dist/typography/blockquote.svelte +2 -2
  240. package/dist/typography/code.svelte +2 -2
  241. package/dist/typography/heading.svelte +2 -2
  242. package/dist/typography/index.d.ts +0 -1
  243. package/dist/typography/text.svelte +2 -13
  244. package/dist/video-embed/video-embed-button.svelte +9 -9
  245. package/package.json +13 -12
  246. package/src/accordion/accordion-button-trigger.svelte +60 -0
  247. package/src/accordion/accordion-content.svelte +37 -0
  248. package/src/accordion/accordion-item.svelte +62 -0
  249. package/src/accordion/accordion-root.svelte +61 -0
  250. package/src/accordion/accordion.meta.ts +7 -0
  251. package/src/accordion/context.svelte.ts +19 -0
  252. package/src/accordion/index.ts +41 -0
  253. package/src/adjust/adjust.meta.ts +7 -0
  254. package/src/adjust/adjust.svelte +71 -0
  255. package/src/adjust/index.ts +20 -0
  256. package/src/alert/alert.meta.ts +7 -0
  257. package/src/alert/alert.svelte +174 -0
  258. package/src/alert/index.ts +18 -0
  259. package/src/alert-dialog/alert-dialog-body.svelte +23 -0
  260. package/src/alert-dialog/alert-dialog-button-action.svelte +15 -0
  261. package/src/alert-dialog/alert-dialog-button-cancel.svelte +18 -0
  262. package/src/alert-dialog/alert-dialog-content.svelte +26 -0
  263. package/src/alert-dialog/alert-dialog-footer.svelte +29 -0
  264. package/src/alert-dialog/alert-dialog-header.svelte +26 -0
  265. package/src/alert-dialog/alert-dialog-overlay.svelte +34 -0
  266. package/src/alert-dialog/alert-dialog-root.svelte +31 -0
  267. package/src/alert-dialog/alert-dialog-trigger.svelte +56 -0
  268. package/src/alert-dialog/alert-dialog.meta.ts +7 -0
  269. package/src/alert-dialog/context.svelte.ts +11 -0
  270. package/src/alert-dialog/index.ts +43 -0
  271. package/src/alpha-slider/alpha-slider-input.svelte +165 -0
  272. package/src/alpha-slider/alpha-slider.meta.ts +7 -0
  273. package/src/alpha-slider/index.ts +10 -0
  274. package/src/app-frame/app-frame.meta.ts +8 -0
  275. package/src/app-frame/app-frame.svelte +141 -0
  276. package/src/app-frame/index.ts +10 -0
  277. package/src/aspect-ratio/aspect-ratio.meta.ts +7 -0
  278. package/src/aspect-ratio/aspect-ratio.svelte +29 -0
  279. package/src/aspect-ratio/index.ts +9 -0
  280. package/src/aurora/aurora.meta.ts +8 -0
  281. package/src/aurora/aurora.svelte +526 -0
  282. package/src/aurora/index.ts +24 -0
  283. package/src/avatar/avatar.meta.ts +7 -0
  284. package/src/avatar/avatar.svelte +213 -0
  285. package/src/avatar/index.ts +15 -0
  286. package/src/backdrop/backdrop.meta.ts +7 -0
  287. package/src/backdrop/backdrop.svelte +51 -0
  288. package/src/backdrop/index.ts +3 -0
  289. package/src/badge/badge.meta.ts +7 -0
  290. package/src/badge/badge.svelte +334 -0
  291. package/src/badge/index.ts +12 -0
  292. package/src/beam/beam.meta.ts +8 -0
  293. package/src/beam/beam.svelte +118 -0
  294. package/src/beam/index.ts +17 -0
  295. package/src/border-beam/border-beam.meta.ts +8 -0
  296. package/src/border-beam/border-beam.svelte +42 -0
  297. package/src/border-beam/index.ts +10 -0
  298. package/src/breadcrumb/breadcrumb-item.svelte +23 -0
  299. package/src/breadcrumb/breadcrumb-link.svelte +60 -0
  300. package/src/breadcrumb/breadcrumb-list.svelte +28 -0
  301. package/src/breadcrumb/breadcrumb-root.svelte +14 -0
  302. package/src/breadcrumb/breadcrumb-separator.svelte +25 -0
  303. package/src/breadcrumb/breadcrumb.meta.ts +7 -0
  304. package/src/breadcrumb/index.ts +44 -0
  305. package/src/button/button.meta.ts +7 -0
  306. package/src/button/button.svelte +675 -0
  307. package/src/button/index.ts +29 -0
  308. package/src/button-group/button-group.meta.ts +7 -0
  309. package/src/button-group/button-group.svelte +61 -0
  310. package/src/button-group/context.svelte.ts +15 -0
  311. package/src/button-group/index.ts +7 -0
  312. package/src/calendar/calendar-button-grid.svelte +58 -0
  313. package/src/calendar/calendar-button-next.svelte +13 -0
  314. package/src/calendar/calendar-button-prev.svelte +13 -0
  315. package/src/calendar/calendar-event-legend.svelte +63 -0
  316. package/src/calendar/calendar-header.svelte +28 -0
  317. package/src/calendar/calendar-heading.svelte +42 -0
  318. package/src/calendar/calendar-nav-button.svelte +39 -0
  319. package/src/calendar/calendar-root.svelte +211 -0
  320. package/src/calendar/calendar-week.svelte +515 -0
  321. package/src/calendar/calendar.meta.ts +7 -0
  322. package/src/calendar/context.svelte.ts +43 -0
  323. package/src/calendar/index.ts +57 -0
  324. package/src/calendar/types.ts +39 -0
  325. package/src/calendar/week-utils.ts +175 -0
  326. package/src/carousel/carousel-button-dots.svelte +74 -0
  327. package/src/carousel/carousel-button-next.svelte +13 -0
  328. package/src/carousel/carousel-button-prev.svelte +13 -0
  329. package/src/carousel/carousel-button-thumbnails.svelte +82 -0
  330. package/src/carousel/carousel-nav-button.svelte +31 -0
  331. package/src/carousel/carousel-root.svelte +432 -0
  332. package/src/carousel/carousel-slide.svelte +39 -0
  333. package/src/carousel/carousel-viewport.svelte +91 -0
  334. package/src/carousel/carousel.meta.ts +7 -0
  335. package/src/carousel/context.svelte.ts +22 -0
  336. package/src/carousel/index.ts +35 -0
  337. package/src/chart/chart-area.svelte +76 -0
  338. package/src/chart/chart-bars.svelte +102 -0
  339. package/src/chart/chart-donut.svelte +145 -0
  340. package/src/chart/chart-horizontal-bar.svelte +67 -0
  341. package/src/chart/chart-line.svelte +66 -0
  342. package/src/chart/chart-root.svelte +257 -0
  343. package/src/chart/chart-stacked-bar.svelte +77 -0
  344. package/src/chart/chart-x-axis.svelte +50 -0
  345. package/src/chart/chart-y-axis.svelte +71 -0
  346. package/src/chart/chart.meta.ts +7 -0
  347. package/src/chart/context.svelte.ts +29 -0
  348. package/src/chart/index.ts +86 -0
  349. package/src/chart/series-color.ts +12 -0
  350. package/src/chat-thread/chat-thread.meta.ts +7 -0
  351. package/src/chat-thread/chat-thread.svelte +97 -0
  352. package/src/chat-thread/index.ts +10 -0
  353. package/src/checkbox/checkbox-input.svelte +259 -0
  354. package/src/checkbox/checkbox.meta.ts +7 -0
  355. package/src/checkbox/index.ts +7 -0
  356. package/src/chip/chip-button.svelte +170 -0
  357. package/src/chip/chip.meta.ts +7 -0
  358. package/src/chip/index.ts +29 -0
  359. package/src/chip-group/chip-group-button-item.svelte +36 -0
  360. package/src/chip-group/chip-group-label.svelte +26 -0
  361. package/src/chip-group/chip-group-root.svelte +129 -0
  362. package/src/chip-group/chip-group.meta.ts +19 -0
  363. package/src/chip-group/context.svelte.ts +11 -0
  364. package/src/chip-group/index.ts +26 -0
  365. package/src/chromatic-aberration/chromatic-aberration.meta.ts +7 -0
  366. package/src/chromatic-aberration/chromatic-aberration.svelte +87 -0
  367. package/src/chromatic-aberration/index.ts +10 -0
  368. package/src/chromatic-shift/chromatic-shift.meta.ts +7 -0
  369. package/src/chromatic-shift/chromatic-shift.svelte +226 -0
  370. package/src/chromatic-shift/index.ts +5 -0
  371. package/src/clipboard/clipboard.meta.ts +7 -0
  372. package/src/clipboard/clipboard.svelte +38 -0
  373. package/src/clipboard/index.ts +3 -0
  374. package/src/code-block/code-block-button.svelte +371 -0
  375. package/src/code-block/code-block.meta.ts +7 -0
  376. package/src/code-block/highlighter/css.ts +387 -0
  377. package/src/code-block/highlighter/generic.ts +24 -0
  378. package/src/code-block/highlighter/index.ts +30 -0
  379. package/src/code-block/highlighter/svelte.ts +597 -0
  380. package/src/code-block/highlighter/types.ts +7 -0
  381. package/src/code-block/index.ts +11 -0
  382. package/src/collapsible/collapsible-button-trigger.svelte +50 -0
  383. package/src/collapsible/collapsible-content.svelte +57 -0
  384. package/src/collapsible/collapsible-root.svelte +48 -0
  385. package/src/collapsible/collapsible.meta.ts +7 -0
  386. package/src/collapsible/context.svelte.ts +10 -0
  387. package/src/collapsible/index.ts +30 -0
  388. package/src/color-picker/color-picker-area.svelte +181 -0
  389. package/src/color-picker/color-picker-button-eyedropper.svelte +61 -0
  390. package/src/color-picker/color-picker-channel-input.svelte +85 -0
  391. package/src/color-picker/color-picker-input-alpha-slider.svelte +144 -0
  392. package/src/color-picker/color-picker-input-hue-slider.svelte +146 -0
  393. package/src/color-picker/color-picker-input.svelte +129 -0
  394. package/src/color-picker/color-picker-root.svelte +185 -0
  395. package/src/color-picker/color-picker-swatch.svelte +116 -0
  396. package/src/color-picker/color-picker.meta.ts +7 -0
  397. package/src/color-picker/context.svelte.ts +19 -0
  398. package/src/color-picker/index.ts +45 -0
  399. package/src/combobox/combobox-content.svelte +124 -0
  400. package/src/combobox/combobox-empty.svelte +23 -0
  401. package/src/combobox/combobox-group.svelte +35 -0
  402. package/src/combobox/combobox-input-root.svelte +109 -0
  403. package/src/combobox/combobox-input.svelte +348 -0
  404. package/src/combobox/combobox-item.svelte +150 -0
  405. package/src/combobox/combobox.meta.ts +7 -0
  406. package/src/combobox/context.svelte.ts +22 -0
  407. package/src/combobox/index.ts +44 -0
  408. package/src/command-palette/command-palette-dialog-root.svelte +184 -0
  409. package/src/command-palette/command-palette-empty.svelte +30 -0
  410. package/src/command-palette/command-palette-group.svelte +37 -0
  411. package/src/command-palette/command-palette-input.svelte +91 -0
  412. package/src/command-palette/command-palette-item.svelte +100 -0
  413. package/src/command-palette/command-palette-list.svelte +52 -0
  414. package/src/command-palette/command-palette-separator.svelte +17 -0
  415. package/src/command-palette/command-palette.meta.ts +7 -0
  416. package/src/command-palette/context.svelte.ts +19 -0
  417. package/src/command-palette/index.ts +35 -0
  418. package/src/container/container.meta.ts +7 -0
  419. package/src/container/container.svelte +66 -0
  420. package/src/container/index.ts +10 -0
  421. package/src/context-menu/context-menu-content.svelte +114 -0
  422. package/src/context-menu/context-menu-group.svelte +15 -0
  423. package/src/context-menu/context-menu-item.svelte +27 -0
  424. package/src/context-menu/context-menu-label.svelte +15 -0
  425. package/src/context-menu/context-menu-root.svelte +26 -0
  426. package/src/context-menu/context-menu-separator.svelte +10 -0
  427. package/src/context-menu/context-menu-trigger.svelte +35 -0
  428. package/src/context-menu/context-menu.meta.ts +7 -0
  429. package/src/context-menu/context.svelte.ts +7 -0
  430. package/src/context-menu/index.ts +35 -0
  431. package/src/data-grid/context.svelte.ts +28 -0
  432. package/src/data-grid/data-grid-body.svelte +17 -0
  433. package/src/data-grid/data-grid-button-expand-trigger.svelte +45 -0
  434. package/src/data-grid/data-grid-button-input-column.svelte +263 -0
  435. package/src/data-grid/data-grid-button-pagination.svelte +130 -0
  436. package/src/data-grid/data-grid-cell.svelte +37 -0
  437. package/src/data-grid/data-grid-expandable-row.svelte +58 -0
  438. package/src/data-grid/data-grid-header.svelte +21 -0
  439. package/src/data-grid/data-grid-input-select-all.svelte +52 -0
  440. package/src/data-grid/data-grid-input-select-cell.svelte +40 -0
  441. package/src/data-grid/data-grid-root.svelte +229 -0
  442. package/src/data-grid/data-grid-row.svelte +38 -0
  443. package/src/data-grid/data-grid-table.svelte +21 -0
  444. package/src/data-grid/data-grid.meta.ts +7 -0
  445. package/src/data-grid/index.ts +75 -0
  446. package/src/date-field/context.svelte.ts +50 -0
  447. package/src/date-field/date-field-input-root.svelte +284 -0
  448. package/src/date-field/date-field-segment.svelte +180 -0
  449. package/src/date-field/date-field-separator.svelte +25 -0
  450. package/src/date-field/date-field.meta.ts +7 -0
  451. package/src/date-field/index.ts +23 -0
  452. package/src/date-picker/context.svelte.ts +27 -0
  453. package/src/date-picker/date-picker.meta.ts +7 -0
  454. package/src/date-picker/datepicker-button-calendar.svelte +58 -0
  455. package/src/date-picker/datepicker-button-trigger.svelte +174 -0
  456. package/src/date-picker/datepicker-content.svelte +35 -0
  457. package/src/date-picker/datepicker-input-root.svelte +137 -0
  458. package/src/date-picker/index.ts +27 -0
  459. package/src/date-range-picker/context.svelte.ts +30 -0
  460. package/src/date-range-picker/date-range-picker-button-calendar.svelte +96 -0
  461. package/src/date-range-picker/date-range-picker-button-preset.svelte +25 -0
  462. package/src/date-range-picker/date-range-picker-button-trigger.svelte +188 -0
  463. package/src/date-range-picker/date-range-picker-content.svelte +35 -0
  464. package/src/date-range-picker/date-range-picker-root.svelte +155 -0
  465. package/src/date-range-picker/date-range-picker.meta.ts +7 -0
  466. package/src/date-range-picker/index.ts +30 -0
  467. package/src/date-time-input/date-time-input.meta.ts +7 -0
  468. package/src/date-time-input/date-time-input.svelte +104 -0
  469. package/src/date-time-input/index.ts +7 -0
  470. package/src/description-list/description-list-description.svelte +23 -0
  471. package/src/description-list/description-list-item.svelte +34 -0
  472. package/src/description-list/description-list-root.svelte +22 -0
  473. package/src/description-list/description-list-term.svelte +23 -0
  474. package/src/description-list/description-list.meta.ts +7 -0
  475. package/src/description-list/index.ts +35 -0
  476. package/src/diagram/diagram.meta.ts +8 -0
  477. package/src/diagram/diagram.svelte +966 -0
  478. package/src/diagram/edge-routing.ts +782 -0
  479. package/src/diagram/index.ts +23 -0
  480. package/src/diagram/layout.ts +2024 -0
  481. package/src/diagram/types.ts +262 -0
  482. package/src/dialog/context.svelte.ts +10 -0
  483. package/src/dialog/dialog-body.svelte +23 -0
  484. package/src/dialog/dialog-close.svelte +18 -0
  485. package/src/dialog/dialog-content.svelte +18 -0
  486. package/src/dialog/dialog-footer.svelte +35 -0
  487. package/src/dialog/dialog-header.svelte +26 -0
  488. package/src/dialog/dialog-overlay.svelte +28 -0
  489. package/src/dialog/dialog-root.svelte +31 -0
  490. package/src/dialog/dialog-trigger.svelte +56 -0
  491. package/src/dialog/dialog.meta.ts +7 -0
  492. package/src/dialog/index.ts +39 -0
  493. package/src/displacement/displacement.meta.ts +7 -0
  494. package/src/displacement/displacement.svelte +140 -0
  495. package/src/displacement/index.ts +5 -0
  496. package/src/drag-and-drop/context.svelte.ts +21 -0
  497. package/src/drag-and-drop/drag-and-drop-group.svelte +37 -0
  498. package/src/drag-and-drop/drag-and-drop-handle.svelte +106 -0
  499. package/src/drag-and-drop/drag-and-drop-item.svelte +151 -0
  500. package/src/drag-and-drop/drag-and-drop-root.svelte +876 -0
  501. package/src/drag-and-drop/drag-and-drop.meta.ts +7 -0
  502. package/src/drag-and-drop/group-context.svelte.ts +20 -0
  503. package/src/drag-and-drop/index.ts +34 -0
  504. package/src/drawer/context.svelte.ts +11 -0
  505. package/src/drawer/drawer-body.svelte +33 -0
  506. package/src/drawer/drawer-close.svelte +18 -0
  507. package/src/drawer/drawer-dialog-content.svelte +18 -0
  508. package/src/drawer/drawer-footer.svelte +27 -0
  509. package/{dist/card/card-footer.svelte → src/drawer/drawer-header.svelte} +8 -10
  510. package/src/drawer/drawer-overlay.svelte +34 -0
  511. package/src/drawer/drawer-root.svelte +35 -0
  512. package/src/drawer/drawer-trigger.svelte +56 -0
  513. package/src/drawer/drawer.meta.ts +7 -0
  514. package/src/drawer/index.ts +43 -0
  515. package/src/drop-zone/drop-zone.meta.ts +7 -0
  516. package/src/drop-zone/drop-zone.svelte +95 -0
  517. package/src/drop-zone/index.ts +6 -0
  518. package/src/dropdown-menu/context.svelte.ts +7 -0
  519. package/src/dropdown-menu/dropdown-menu-content.svelte +137 -0
  520. package/src/dropdown-menu/dropdown-menu-group.svelte +15 -0
  521. package/src/dropdown-menu/dropdown-menu-item.svelte +27 -0
  522. package/src/dropdown-menu/dropdown-menu-label.svelte +15 -0
  523. package/src/dropdown-menu/dropdown-menu-root.svelte +26 -0
  524. package/src/dropdown-menu/dropdown-menu-separator.svelte +10 -0
  525. package/src/dropdown-menu/dropdown-menu-trigger.svelte +65 -0
  526. package/src/dropdown-menu/dropdown-menu.meta.ts +7 -0
  527. package/src/dropdown-menu/index.ts +35 -0
  528. package/src/enter/enter.meta.ts +8 -0
  529. package/src/enter/index.ts +1 -0
  530. package/src/exit/exit.meta.ts +8 -0
  531. package/src/exit/index.ts +1 -0
  532. package/src/field/field-description.svelte +38 -0
  533. package/src/field/field-error.svelte +40 -0
  534. package/src/field/field-root.svelte +98 -0
  535. package/src/field/field.meta.ts +7 -0
  536. package/src/field/index.ts +15 -0
  537. package/src/fieldset/fieldset-content.svelte +21 -0
  538. package/src/fieldset/fieldset-description.svelte +26 -0
  539. package/src/fieldset/fieldset-legend.svelte +26 -0
  540. package/src/fieldset/fieldset-root.svelte +27 -0
  541. package/src/fieldset/fieldset.meta.ts +7 -0
  542. package/src/fieldset/index.ts +35 -0
  543. package/src/file-select/context.svelte.ts +10 -0
  544. package/src/file-select/file-select-button-clear.svelte +37 -0
  545. package/src/file-select/file-select-button-trigger.svelte +26 -0
  546. package/src/file-select/file-select-root.svelte +168 -0
  547. package/src/file-select/file-select-value.svelte +43 -0
  548. package/src/file-select/file-select.meta.ts +7 -0
  549. package/src/file-select/index.ts +29 -0
  550. package/src/file-upload/context.svelte.ts +17 -0
  551. package/src/file-upload/file-upload-button-item-delete.svelte +36 -0
  552. package/src/file-upload/file-upload-button-trigger.svelte +31 -0
  553. package/src/file-upload/file-upload-dropzone.svelte +145 -0
  554. package/src/file-upload/file-upload-input-root.svelte +144 -0
  555. package/src/file-upload/file-upload-item.svelte +63 -0
  556. package/src/file-upload/file-upload-list.svelte +37 -0
  557. package/src/file-upload/file-upload.meta.ts +7 -0
  558. package/src/file-upload/index.ts +52 -0
  559. package/src/flip-card/context.svelte.ts +8 -0
  560. package/src/flip-card/flip-card-back.svelte +34 -0
  561. package/src/flip-card/flip-card-front.svelte +25 -0
  562. package/src/flip-card/flip-card-root.svelte +116 -0
  563. package/src/flip-card/flip-card.meta.ts +7 -0
  564. package/src/flip-card/index.ts +15 -0
  565. package/src/float-button/context.svelte.ts +10 -0
  566. package/src/float-button/float-button-action.svelte +56 -0
  567. package/src/float-button/float-button-root.svelte +102 -0
  568. package/src/float-button/float-button-trigger.svelte +43 -0
  569. package/src/float-button/float-button.meta.ts +7 -0
  570. package/src/float-button/index.ts +29 -0
  571. package/src/focus-trap/focus-trap.meta.ts +7 -0
  572. package/src/focus-trap/focus-trap.svelte +16 -0
  573. package/src/focus-trap/index.ts +3 -0
  574. package/src/format-bytes/format-bytes.meta.ts +7 -0
  575. package/src/format-bytes/format-bytes.svelte +60 -0
  576. package/src/format-bytes/index.ts +3 -0
  577. package/src/format-date/format-date.meta.ts +7 -0
  578. package/src/format-date/format-date.svelte +80 -0
  579. package/src/format-date/index.ts +3 -0
  580. package/src/format-number/format-number.meta.ts +7 -0
  581. package/src/format-number/format-number.svelte +59 -0
  582. package/src/format-number/index.ts +3 -0
  583. package/src/gauge/gauge.meta.ts +7 -0
  584. package/src/gauge/gauge.svelte +143 -0
  585. package/src/gauge/index.ts +2 -0
  586. package/src/glass/glass.meta.ts +7 -0
  587. package/src/glass/glass.svelte +49 -0
  588. package/src/glass/index.ts +11 -0
  589. package/src/glow/glow.meta.ts +7 -0
  590. package/src/glow/glow.svelte +60 -0
  591. package/src/glow/index.ts +15 -0
  592. package/src/god-rays/god-rays.meta.ts +8 -0
  593. package/src/god-rays/god-rays.svelte +132 -0
  594. package/src/god-rays/index.ts +17 -0
  595. package/src/gradient-mesh/gradient-mesh.meta.ts +7 -0
  596. package/src/gradient-mesh/gradient-mesh.svelte +295 -0
  597. package/src/gradient-mesh/index.ts +5 -0
  598. package/src/halftone/halftone.meta.ts +7 -0
  599. package/src/halftone/halftone.svelte +93 -0
  600. package/src/halftone/index.ts +14 -0
  601. package/src/heading/heading.meta.ts +7 -0
  602. package/src/heading/heading.svelte +120 -0
  603. package/src/heading/index.ts +17 -0
  604. package/src/hotkey/hotkey.meta.ts +7 -0
  605. package/src/hotkey/index.ts +2 -0
  606. package/src/hover-card/context.svelte.ts +1 -0
  607. package/src/hover-card/hover-card-content.svelte +121 -0
  608. package/src/hover-card/hover-card-root.svelte +11 -0
  609. package/src/hover-card/hover-card-trigger.svelte +109 -0
  610. package/src/hover-card/hover-card.meta.ts +7 -0
  611. package/src/hover-card/index.ts +19 -0
  612. package/src/icon/icon.meta.ts +7 -0
  613. package/src/icon/icon.svelte +92 -0
  614. package/src/icon/index.ts +11 -0
  615. package/src/icon-swap/icon-swap.meta.ts +7 -0
  616. package/src/icon-swap/icon-swap.svelte +48 -0
  617. package/src/icon-swap/index.ts +1 -0
  618. package/src/image/image.meta.ts +7 -0
  619. package/src/image/image.svelte +73 -0
  620. package/src/image/index.ts +3 -0
  621. package/src/image-comparison/image-comparison.meta.ts +7 -0
  622. package/src/image-comparison/image-comparison.svelte +272 -0
  623. package/src/image-comparison/index.ts +3 -0
  624. package/src/index.ts +863 -0
  625. package/src/infinite-scroll/index.ts +3 -0
  626. package/src/infinite-scroll/infinite-scroll.meta.ts +7 -0
  627. package/src/infinite-scroll/infinite-scroll.svelte +109 -0
  628. package/src/input/index.ts +8 -0
  629. package/src/input/input.meta.ts +7 -0
  630. package/src/input/input.svelte +261 -0
  631. package/src/input-group/context.svelte.ts +9 -0
  632. package/src/input-group/index.ts +45 -0
  633. package/src/input-group/input-group-action-button.svelte +29 -0
  634. package/src/input-group/input-group-input.svelte +57 -0
  635. package/src/input-group/input-group-prefix.svelte +43 -0
  636. package/src/input-group/input-group-root.svelte +108 -0
  637. package/src/input-group/input-group-select.svelte +70 -0
  638. package/src/input-group/input-group-separator.svelte +38 -0
  639. package/src/input-group/input-group-suffix.svelte +43 -0
  640. package/src/input-group/input-group.meta.ts +8 -0
  641. package/src/internal/anchored-overlay-content.svelte.ts +42 -0
  642. package/src/internal/calendar-event-layout.ts +294 -0
  643. package/src/internal/calendar-grid-button.svelte +805 -0
  644. package/src/internal/calendar-grid-utils.ts +123 -0
  645. package/src/internal/close-button-base.svelte +35 -0
  646. package/src/internal/color-aliases.ts +37 -0
  647. package/src/internal/date-family-controller.svelte.ts +165 -0
  648. package/src/internal/form-control-wrapper-attrs.ts +20 -0
  649. package/src/internal/menu-group.svelte +15 -0
  650. package/src/internal/menu-item.svelte +89 -0
  651. package/src/internal/menu-label.svelte +24 -0
  652. package/src/internal/menu-root-state.svelte.ts +73 -0
  653. package/src/internal/menu-separator.svelte +19 -0
  654. package/src/internal/modal-content.svelte +351 -0
  655. package/src/internal/motion.ts +85 -0
  656. package/src/internal/nav-arrow-button.svelte +42 -0
  657. package/src/internal/picker-popover-content.svelte +111 -0
  658. package/src/kbd/index.ts +9 -0
  659. package/src/kbd/kbd.meta.ts +7 -0
  660. package/src/kbd/kbd.svelte +56 -0
  661. package/src/label/index.ts +7 -0
  662. package/src/label/label.meta.ts +7 -0
  663. package/src/label/label.svelte +74 -0
  664. package/src/link/index.ts +7 -0
  665. package/src/link/link.meta.ts +7 -0
  666. package/src/link/link.svelte +104 -0
  667. package/src/link-preview/context.svelte.ts +15 -0
  668. package/src/link-preview/index.ts +19 -0
  669. package/src/link-preview/link-preview-content.svelte +87 -0
  670. package/src/link-preview/link-preview-root.svelte +72 -0
  671. package/src/link-preview/link-preview-trigger.svelte +61 -0
  672. package/src/link-preview/link-preview.meta.ts +7 -0
  673. package/src/list/context.svelte.ts +7 -0
  674. package/src/list/index.ts +27 -0
  675. package/src/list/list-item-icon.svelte +22 -0
  676. package/src/list/list-item-text.svelte +42 -0
  677. package/src/list/list-item.svelte +116 -0
  678. package/src/list/list-root.svelte +71 -0
  679. package/src/list/list-subheader.svelte +25 -0
  680. package/src/list/list.meta.ts +7 -0
  681. package/src/listbox/context.svelte.ts +10 -0
  682. package/src/listbox/index.ts +12 -0
  683. package/src/listbox/listbox-item.svelte +90 -0
  684. package/src/listbox/listbox-root.svelte +115 -0
  685. package/src/listbox/listbox.meta.ts +7 -0
  686. package/src/logo-mark/index.ts +12 -0
  687. package/src/logo-mark/logo-mark.meta.ts +7 -0
  688. package/src/logo-mark/logo-mark.svelte +154 -0
  689. package/src/map/context.svelte.ts +19 -0
  690. package/src/map/index.ts +77 -0
  691. package/src/map/map-controls.svelte +68 -0
  692. package/src/map/map-layer.svelte +101 -0
  693. package/src/map/map-marker.svelte +83 -0
  694. package/src/map/map-popup.svelte +71 -0
  695. package/src/map/map-root.svelte +244 -0
  696. package/src/map/map.meta.ts +7 -0
  697. package/src/markdown-renderer/index.ts +4 -0
  698. package/src/markdown-renderer/markdown-renderer.meta.ts +7 -0
  699. package/src/markdown-renderer/markdown-renderer.svelte +202 -0
  700. package/src/marquee/index.ts +3 -0
  701. package/src/marquee/marquee.meta.ts +7 -0
  702. package/src/marquee/marquee.svelte +218 -0
  703. package/src/mask-reveal/index.ts +5 -0
  704. package/src/mask-reveal/mask-reveal.meta.ts +7 -0
  705. package/src/mask-reveal/mask-reveal.svelte +214 -0
  706. package/src/mega-menu/context.svelte.ts +17 -0
  707. package/src/mega-menu/index.ts +31 -0
  708. package/src/mega-menu/mega-menu-button-trigger.svelte +37 -0
  709. package/src/mega-menu/mega-menu-column.svelte +34 -0
  710. package/src/mega-menu/mega-menu-item.svelte +51 -0
  711. package/src/mega-menu/mega-menu-link.svelte +137 -0
  712. package/src/mega-menu/mega-menu-panel.svelte +134 -0
  713. package/src/mega-menu/mega-menu-root.svelte +98 -0
  714. package/src/mega-menu/mega-menu.meta.ts +7 -0
  715. package/src/menubar/context.svelte.ts +24 -0
  716. package/src/menubar/index.ts +35 -0
  717. package/src/menubar/menubar-button-trigger.svelte +75 -0
  718. package/src/menubar/menubar-content.svelte +147 -0
  719. package/src/menubar/menubar-item.svelte +96 -0
  720. package/{dist/card/card-header.svelte → src/menubar/menubar-label.svelte} +6 -10
  721. package/src/menubar/menubar-menu.svelte +32 -0
  722. package/src/menubar/menubar-root.svelte +78 -0
  723. package/src/menubar/menubar-separator.svelte +17 -0
  724. package/src/menubar/menubar.meta.ts +7 -0
  725. package/src/motion/enter.svelte +29 -0
  726. package/src/motion/enter.ts +46 -0
  727. package/src/motion/exit.svelte +21 -0
  728. package/src/motion/index.ts +7 -0
  729. package/src/motion/leave.ts +41 -0
  730. package/src/motion/motion.meta.ts +8 -0
  731. package/src/motion/stagger.svelte +30 -0
  732. package/src/multi-select-combobox/context.svelte.ts +31 -0
  733. package/src/multi-select-combobox/index.ts +43 -0
  734. package/src/multi-select-combobox/multi-select-combobox-content.svelte +118 -0
  735. package/src/multi-select-combobox/multi-select-combobox-empty.svelte +28 -0
  736. package/src/multi-select-combobox/multi-select-combobox-group.svelte +33 -0
  737. package/src/multi-select-combobox/multi-select-combobox-input.svelte +161 -0
  738. package/src/multi-select-combobox/multi-select-combobox-item.svelte +140 -0
  739. package/src/multi-select-combobox/multi-select-combobox-root-input.svelte +286 -0
  740. package/src/multi-select-combobox/multi-select-combobox-selection-item.svelte +63 -0
  741. package/src/multi-select-combobox/multi-select-combobox-selection-list.svelte +29 -0
  742. package/src/multi-select-combobox/multi-select-combobox-selection-remove-button.svelte +36 -0
  743. package/src/multi-select-combobox/multi-select-combobox.meta.ts +7 -0
  744. package/src/navigation-menu/context.svelte.ts +18 -0
  745. package/src/navigation-menu/index.ts +50 -0
  746. package/src/navigation-menu/navigation-menu-content.svelte +55 -0
  747. package/src/navigation-menu/navigation-menu-item.svelte +35 -0
  748. package/src/navigation-menu/navigation-menu-link.svelte +49 -0
  749. package/src/navigation-menu/navigation-menu-list.svelte +47 -0
  750. package/src/navigation-menu/navigation-menu-root.svelte +46 -0
  751. package/src/navigation-menu/navigation-menu-trigger-button.svelte +62 -0
  752. package/src/navigation-menu/navigation-menu.meta.ts +7 -0
  753. package/src/noise/index.ts +15 -0
  754. package/src/noise/noise.meta.ts +7 -0
  755. package/src/noise/noise.svelte +179 -0
  756. package/src/notification-center/context.svelte.ts +24 -0
  757. package/src/notification-center/index.ts +28 -0
  758. package/src/notification-center/notification-center-group.svelte +30 -0
  759. package/src/notification-center/notification-center-item.svelte +70 -0
  760. package/src/notification-center/notification-center-panel.svelte +162 -0
  761. package/src/notification-center/notification-center-root.svelte +73 -0
  762. package/src/notification-center/notification-center-trigger-button.svelte +28 -0
  763. package/src/notification-center/notification-center.meta.ts +7 -0
  764. package/src/number-input/index.ts +7 -0
  765. package/src/number-input/number-input-button.svelte +222 -0
  766. package/src/number-input/number-input.meta.ts +7 -0
  767. package/src/numeric/index.ts +1 -0
  768. package/src/numeric/numeric.meta.ts +7 -0
  769. package/src/numeric/numeric.svelte +64 -0
  770. package/src/option-picker/context.svelte.ts +11 -0
  771. package/src/option-picker/index.ts +52 -0
  772. package/src/option-picker/option-picker-description.svelte +25 -0
  773. package/src/option-picker/option-picker-item.svelte +242 -0
  774. package/src/option-picker/option-picker-label.svelte +24 -0
  775. package/src/option-picker/option-picker-meta.svelte +24 -0
  776. package/src/option-picker/option-picker-preview.svelte +164 -0
  777. package/src/option-picker/option-picker-root.svelte +82 -0
  778. package/src/option-picker/option-picker.meta.ts +8 -0
  779. package/src/pagination/context.svelte.ts +12 -0
  780. package/src/pagination/index.ts +35 -0
  781. package/src/pagination/pagination-content.svelte +27 -0
  782. package/src/pagination/pagination-ellipsis.svelte +19 -0
  783. package/src/pagination/pagination-item.svelte +14 -0
  784. package/src/pagination/pagination-link-button.svelte +28 -0
  785. package/src/pagination/pagination-nav-button.svelte +28 -0
  786. package/src/pagination/pagination-next-button.svelte +13 -0
  787. package/src/pagination/pagination-previous-button.svelte +13 -0
  788. package/src/pagination/pagination-root.svelte +48 -0
  789. package/src/pagination/pagination.meta.ts +7 -0
  790. package/src/phone-input/index.ts +9 -0
  791. package/src/phone-input/phone-input-select.svelte +286 -0
  792. package/src/phone-input/phone-input.meta.ts +7 -0
  793. package/src/pin-input/context.svelte.ts +18 -0
  794. package/src/pin-input/index.ts +31 -0
  795. package/src/pin-input/pin-input-cell.svelte +117 -0
  796. package/src/pin-input/pin-input-group.svelte +24 -0
  797. package/src/pin-input/pin-input-root.svelte +315 -0
  798. package/src/pin-input/pin-input-separator.svelte +28 -0
  799. package/src/pin-input/pin-input.meta.ts +7 -0
  800. package/src/popover/context.svelte.ts +13 -0
  801. package/src/popover/index.ts +15 -0
  802. package/src/popover/popover-content.svelte +112 -0
  803. package/src/popover/popover-root.svelte +39 -0
  804. package/src/popover/popover-trigger.svelte +62 -0
  805. package/src/popover/popover.meta.ts +7 -0
  806. package/src/portal/index.ts +3 -0
  807. package/src/portal/portal.meta.ts +7 -0
  808. package/src/portal/portal.svelte +14 -0
  809. package/src/progress/index.ts +14 -0
  810. package/src/progress/progress.meta.ts +7 -0
  811. package/src/progress/progress.svelte +302 -0
  812. package/src/progress-ring/index.ts +8 -0
  813. package/src/progress-ring/progress-ring.meta.ts +7 -0
  814. package/src/progress-ring/progress-ring.svelte +169 -0
  815. package/src/prompt-input/index.ts +16 -0
  816. package/src/prompt-input/prompt-input-button-textarea.svelte +171 -0
  817. package/src/prompt-input/prompt-input.meta.ts +7 -0
  818. package/src/qr-code/index.ts +3 -0
  819. package/src/qr-code/qr-code.meta.ts +7 -0
  820. package/src/qr-code/qr-code.svelte +149 -0
  821. package/src/radio-group/context.svelte.ts +10 -0
  822. package/src/radio-group/index.ts +12 -0
  823. package/src/radio-group/radio-group-item-input.svelte +197 -0
  824. package/src/radio-group/radio-group.meta.ts +7 -0
  825. package/src/radio-group/radio-group.svelte +117 -0
  826. package/src/range-calendar/context.svelte.ts +22 -0
  827. package/src/range-calendar/index.ts +16 -0
  828. package/src/range-calendar/range-calendar-grid-button.svelte +77 -0
  829. package/src/range-calendar/range-calendar-root.svelte +146 -0
  830. package/src/range-calendar/range-calendar.meta.ts +7 -0
  831. package/src/rating/index.ts +7 -0
  832. package/src/rating/rating-button-input.svelte +265 -0
  833. package/src/rating/rating.meta.ts +7 -0
  834. package/src/relative-time/index.ts +3 -0
  835. package/src/relative-time/relative-time.meta.ts +7 -0
  836. package/src/relative-time/relative-time.svelte +71 -0
  837. package/src/reveal/index.ts +7 -0
  838. package/src/reveal/reveal.meta.ts +8 -0
  839. package/src/reveal/reveal.svelte +190 -0
  840. package/src/rich-text-editor/index.ts +19 -0
  841. package/src/rich-text-editor/rich-text-editor-content.svelte +184 -0
  842. package/src/rich-text-editor/rich-text-editor-root.svelte +213 -0
  843. package/src/rich-text-editor/rich-text-editor-toolbar-button-input.svelte +541 -0
  844. package/src/rich-text-editor/rich-text-editor.meta.ts +7 -0
  845. package/src/scroll-area/index.ts +3 -0
  846. package/src/scroll-area/scroll-area.meta.ts +7 -0
  847. package/src/scroll-area/scroll-area.svelte +85 -0
  848. package/src/scroll-to-top/index.ts +8 -0
  849. package/src/scroll-to-top/scroll-to-top-button.svelte +89 -0
  850. package/src/scroll-to-top/scroll-to-top.meta.ts +7 -0
  851. package/src/segmented-control/context.svelte.ts +10 -0
  852. package/src/segmented-control/index.ts +12 -0
  853. package/src/segmented-control/segmented-control-item-button.svelte +37 -0
  854. package/src/segmented-control/segmented-control-root.svelte +125 -0
  855. package/src/segmented-control/segmented-control.meta.ts +7 -0
  856. package/src/select/context.svelte.ts +17 -0
  857. package/src/select/index.ts +33 -0
  858. package/src/select/select-content.svelte +202 -0
  859. package/src/select/select-item.svelte +118 -0
  860. package/src/select/select-root-input.svelte +105 -0
  861. package/src/select/select-trigger-button.svelte +183 -0
  862. package/src/select/select-value.svelte +27 -0
  863. package/src/select/select.meta.ts +7 -0
  864. package/src/separator/index.ts +9 -0
  865. package/src/separator/separator.meta.ts +7 -0
  866. package/src/separator/separator.svelte +70 -0
  867. package/src/shader-canvas/index.ts +28 -0
  868. package/src/shader-canvas/presets.ts +212 -0
  869. package/src/shader-canvas/shader-canvas.meta.ts +7 -0
  870. package/src/shader-canvas/shader-canvas.svelte +115 -0
  871. package/src/shimmer/index.ts +10 -0
  872. package/src/shimmer/shimmer.meta.ts +7 -0
  873. package/src/shimmer/shimmer.svelte +125 -0
  874. package/src/sidebar/context.svelte.ts +10 -0
  875. package/src/sidebar/index.ts +66 -0
  876. package/src/sidebar/sidebar-content.svelte +43 -0
  877. package/src/sidebar/sidebar-footer.svelte +28 -0
  878. package/src/sidebar/sidebar-group-label.svelte +45 -0
  879. package/src/sidebar/sidebar-group.svelte +29 -0
  880. package/src/sidebar/sidebar-header.svelte +34 -0
  881. package/src/sidebar/sidebar-item.svelte +57 -0
  882. package/src/sidebar/sidebar-root.svelte +76 -0
  883. package/src/sidebar/sidebar-trigger-button.svelte +29 -0
  884. package/src/sidebar/sidebar.meta.ts +7 -0
  885. package/src/skeleton/index.ts +9 -0
  886. package/src/skeleton/skeleton.meta.ts +7 -0
  887. package/src/skeleton/skeleton.svelte +97 -0
  888. package/src/slider/index.ts +12 -0
  889. package/src/slider/slider-input.svelte +277 -0
  890. package/src/slider/slider.meta.ts +7 -0
  891. package/src/spacer/index.ts +8 -0
  892. package/src/spacer/spacer.meta.ts +7 -0
  893. package/src/spacer/spacer.svelte +84 -0
  894. package/src/sparkline/index.ts +3 -0
  895. package/src/sparkline/sparkline.meta.ts +7 -0
  896. package/src/sparkline/sparkline.svelte +113 -0
  897. package/src/spinner/index.ts +9 -0
  898. package/src/spinner/spinner.meta.ts +7 -0
  899. package/src/spinner/spinner.svelte +110 -0
  900. package/src/splitter/context.svelte.ts +11 -0
  901. package/src/splitter/index.ts +15 -0
  902. package/src/splitter/splitter-handle.svelte +171 -0
  903. package/src/splitter/splitter-panel.svelte +28 -0
  904. package/src/splitter/splitter-root.svelte +128 -0
  905. package/src/splitter/splitter.meta.ts +7 -0
  906. package/src/spotlight/index.ts +5 -0
  907. package/src/spotlight/spotlight.meta.ts +8 -0
  908. package/src/spotlight/spotlight.svelte +249 -0
  909. package/src/stagger/index.ts +1 -0
  910. package/src/stagger/stagger.meta.ts +8 -0
  911. package/src/star-rating/index.ts +16 -0
  912. package/src/star-rating/star-rating-root.svelte +109 -0
  913. package/src/star-rating/star-rating.meta.ts +7 -0
  914. package/src/stepper/context.svelte.ts +9 -0
  915. package/src/stepper/index.ts +23 -0
  916. package/src/stepper/stepper-list.svelte +37 -0
  917. package/src/stepper/stepper-root.svelte +51 -0
  918. package/src/stepper/stepper-separator.svelte +45 -0
  919. package/src/stepper/stepper-step-button.svelte +113 -0
  920. package/src/stepper/stepper.meta.ts +7 -0
  921. package/src/svg/index.ts +5 -0
  922. package/src/svg/svg.meta.ts +7 -0
  923. package/src/svg/svg.svelte +19 -0
  924. package/src/table/index.ts +39 -0
  925. package/src/table/table-body.svelte +14 -0
  926. package/src/table/table-caption.svelte +25 -0
  927. package/src/table/table-cell.svelte +24 -0
  928. package/src/table/table-footer.svelte +21 -0
  929. package/src/table/table-head.svelte +26 -0
  930. package/src/table/table-header.svelte +21 -0
  931. package/src/table/table-root.svelte +64 -0
  932. package/src/table/table-row.svelte +28 -0
  933. package/src/table/table.meta.ts +7 -0
  934. package/src/table-of-contents/context.svelte.ts +14 -0
  935. package/src/table-of-contents/index.ts +20 -0
  936. package/src/table-of-contents/table-of-contents-item.svelte +86 -0
  937. package/src/table-of-contents/table-of-contents-list.svelte +87 -0
  938. package/src/table-of-contents/table-of-contents-root.svelte +88 -0
  939. package/src/table-of-contents/table-of-contents.meta.ts +7 -0
  940. package/src/tabs/context.svelte.ts +16 -0
  941. package/src/tabs/index.ts +43 -0
  942. package/src/tabs/tabs-content.svelte +42 -0
  943. package/src/tabs/tabs-list.svelte +102 -0
  944. package/src/tabs/tabs-root.svelte +59 -0
  945. package/src/tabs/tabs-trigger-button.svelte +41 -0
  946. package/src/tabs/tabs.meta.ts +7 -0
  947. package/src/tag/index.ts +12 -0
  948. package/src/tag/tag-button.svelte +250 -0
  949. package/src/tag/tag.meta.ts +7 -0
  950. package/src/tags-input/context.svelte.ts +13 -0
  951. package/src/tags-input/index.ts +32 -0
  952. package/src/tags-input/tags-input-input.svelte +85 -0
  953. package/src/tags-input/tags-input-list.svelte +20 -0
  954. package/src/tags-input/tags-input-root.svelte +136 -0
  955. package/src/tags-input/tags-input-tag-delete-button.svelte +31 -0
  956. package/src/tags-input/tags-input-tag.svelte +61 -0
  957. package/src/tags-input/tags-input.meta.ts +7 -0
  958. package/src/text/index.ts +20 -0
  959. package/src/text/text.meta.ts +7 -0
  960. package/src/text/text.svelte +150 -0
  961. package/src/textarea/index.ts +7 -0
  962. package/src/textarea/textarea.meta.ts +7 -0
  963. package/src/textarea/textarea.svelte +210 -0
  964. package/src/theme-toggle/index.ts +30 -0
  965. package/src/theme-toggle/theme-controller.svelte.ts +171 -0
  966. package/src/theme-toggle/theme-flash.ts +39 -0
  967. package/src/theme-toggle/theme-toggle.meta.ts +7 -0
  968. package/src/theme-toggle/theme-toggle.svelte +199 -0
  969. package/src/themes/aurora.css +291 -0
  970. package/src/themes/component-defaults.css +47 -0
  971. package/src/themes/dark.css +377 -0
  972. package/src/themes/default.css +643 -0
  973. package/src/themes/midnight.css +177 -0
  974. package/src/themes/terminal.css +191 -0
  975. package/src/themes/token-scope.ts +1 -0
  976. package/src/time-input/index.ts +14 -0
  977. package/src/time-input/time-input.meta.ts +7 -0
  978. package/src/time-input/time-input.svelte +157 -0
  979. package/src/timeline/index.ts +40 -0
  980. package/src/timeline/timeline-content.svelte +22 -0
  981. package/src/timeline/timeline-description.svelte +22 -0
  982. package/src/timeline/timeline-icon.svelte +41 -0
  983. package/src/timeline/timeline-item.svelte +57 -0
  984. package/src/timeline/timeline-root.svelte +43 -0
  985. package/src/timeline/timeline-time.svelte +22 -0
  986. package/src/timeline/timeline-title.svelte +55 -0
  987. package/src/timeline/timeline.meta.ts +7 -0
  988. package/src/toast/context.svelte.ts +7 -0
  989. package/src/toast/index.ts +33 -0
  990. package/src/toast/toast-action-button.svelte +16 -0
  991. package/src/toast/toast-close-button.svelte +35 -0
  992. package/src/toast/toast-description.svelte +24 -0
  993. package/src/toast/toast-provider.svelte +120 -0
  994. package/src/toast/toast-root.svelte +169 -0
  995. package/src/toast/toast-title.svelte +25 -0
  996. package/src/toast/toast.meta.ts +7 -0
  997. package/src/toggle/index.ts +9 -0
  998. package/src/toggle/toggle-button.svelte +218 -0
  999. package/src/toggle/toggle.meta.ts +7 -0
  1000. package/src/toggle-group/context.svelte.ts +12 -0
  1001. package/src/toggle-group/index.ts +18 -0
  1002. package/src/toggle-group/toggle-group-item-button.svelte +36 -0
  1003. package/src/toggle-group/toggle-group-root.svelte +106 -0
  1004. package/src/toggle-group/toggle-group.meta.ts +7 -0
  1005. package/src/token-scope/index.ts +8 -0
  1006. package/src/token-scope/token-scope.meta.ts +7 -0
  1007. package/src/toolbar/context.svelte.ts +6 -0
  1008. package/src/toolbar/index.ts +23 -0
  1009. package/src/toolbar/toolbar-button.svelte +16 -0
  1010. package/src/toolbar/toolbar-link.svelte +46 -0
  1011. package/src/toolbar/toolbar-root.svelte +100 -0
  1012. package/src/toolbar/toolbar-separator.svelte +33 -0
  1013. package/src/toolbar/toolbar.meta.ts +7 -0
  1014. package/src/tooltip/context.svelte.ts +14 -0
  1015. package/src/tooltip/index.ts +15 -0
  1016. package/src/tooltip/tooltip-content.svelte +106 -0
  1017. package/src/tooltip/tooltip-root.svelte +73 -0
  1018. package/src/tooltip/tooltip-trigger.svelte +74 -0
  1019. package/src/tooltip/tooltip.meta.ts +7 -0
  1020. package/src/tour/index.ts +12 -0
  1021. package/src/tour/tour-root.css +215 -0
  1022. package/src/tour/tour-root.css.d.ts +2 -0
  1023. package/src/tour/tour-root.svelte +19 -0
  1024. package/src/tour/tour-tooltip-button.svelte +48 -0
  1025. package/src/tour/tour.meta.ts +7 -0
  1026. package/src/transfer/context.svelte.ts +31 -0
  1027. package/src/transfer/index.ts +29 -0
  1028. package/src/transfer/transfer-actions-button.svelte +91 -0
  1029. package/src/transfer/transfer-item.svelte +35 -0
  1030. package/src/transfer/transfer-list-input.svelte +204 -0
  1031. package/src/transfer/transfer-root.svelte +145 -0
  1032. package/src/transfer/transfer.meta.ts +7 -0
  1033. package/src/tree/context.svelte.ts +24 -0
  1034. package/src/tree/index.ts +23 -0
  1035. package/src/tree/tree-item-children.svelte +51 -0
  1036. package/src/tree/tree-item-label.svelte +56 -0
  1037. package/src/tree/tree-item.svelte +63 -0
  1038. package/src/tree/tree-root.svelte +246 -0
  1039. package/src/tree/tree.meta.ts +7 -0
  1040. package/src/typing-indicator/index.ts +5 -0
  1041. package/src/typing-indicator/typing-indicator.meta.ts +7 -0
  1042. package/src/typing-indicator/typing-indicator.svelte +72 -0
  1043. package/src/typography/blockquote.svelte +24 -0
  1044. package/src/typography/code.svelte +25 -0
  1045. package/src/typography/heading.svelte +17 -0
  1046. package/src/typography/index.ts +30 -0
  1047. package/src/typography/text.svelte +25 -0
  1048. package/src/typography/typography.meta.ts +7 -0
  1049. package/src/video-embed/index.ts +3 -0
  1050. package/src/video-embed/video-embed-button.svelte +177 -0
  1051. package/src/video-embed/video-embed.meta.ts +7 -0
  1052. package/src/virtual-list/index.ts +3 -0
  1053. package/src/virtual-list/virtual-list.meta.ts +7 -0
  1054. package/src/virtual-list/virtual-list.svelte +239 -0
  1055. package/src/visually-hidden/index.ts +8 -0
  1056. package/src/visually-hidden/visually-hidden.meta.ts +7 -0
  1057. package/src/visually-hidden/visually-hidden.svelte +28 -0
  1058. package/dist/card/card-content.svelte +0 -32
  1059. package/dist/card/card-content.svelte.d.ts +0 -9
  1060. package/dist/card/card-footer.svelte.d.ts +0 -8
  1061. package/dist/card/card-header.svelte.d.ts +0 -8
  1062. package/dist/card/card-root.svelte +0 -184
  1063. package/dist/card/card-root.svelte.d.ts +0 -15
  1064. package/dist/card/card.meta.js +0 -7
  1065. package/dist/card/index.d.ts +0 -31
  1066. package/dist/card/index.js +0 -10
  1067. package/skills/dryui/SKILL.md +0 -306
  1068. package/skills/dryui/agents/openai.yaml +0 -10
  1069. package/skills/dryui/rules/accessibility.md +0 -215
  1070. package/skills/dryui/rules/composition.md +0 -500
  1071. package/skills/dryui/rules/compound-components.md +0 -312
  1072. package/skills/dryui/rules/native-web-transitions.md +0 -99
  1073. package/skills/dryui/rules/svelte.md +0 -234
  1074. package/skills/dryui/rules/theming.md +0 -305
  1075. /package/dist/{card/card.meta.d.ts → token-scope/token-scope.meta.d.ts} +0 -0
@@ -0,0 +1,2024 @@
1
+ import type {
2
+ DiagramConfig,
3
+ DiagramDirection,
4
+ DiagramEdge,
5
+ DiagramNode,
6
+ DiagramCluster,
7
+ DiagramWaypoint,
8
+ PositionedNode,
9
+ PositionedEdge,
10
+ PositionedCluster,
11
+ PositionedSwimlane,
12
+ PositionedRegion,
13
+ PositionedAnnotation,
14
+ PositionedMessage,
15
+ PositionedLifeline,
16
+ PositionedFragment,
17
+ PositionedWaypoint,
18
+ LayoutResult
19
+ } from './types.js';
20
+ import {
21
+ buildPathFromCollapsed,
22
+ computeEdgePaths,
23
+ emptyEdge,
24
+ getPointAtFraction,
25
+ splitCollapsedAtBox
26
+ } from './edge-routing.js';
27
+
28
+ const DEFAULT_NODE_GAP = 32;
29
+ const DEFAULT_LAYER_GAP = 64;
30
+ const DEFAULT_CLUSTER_PADDING = 40;
31
+ const DEFAULT_CORNER_RADIUS = 8;
32
+ const DEFAULT_BACK_EDGE_LANE_GAP = 32;
33
+ const DEFAULT_NODE_HEIGHT = 68;
34
+ const DESC_NODE_HEIGHT = 116;
35
+ const MIN_NODE_WIDTH = 176;
36
+ const CHAR_WIDTH = 9;
37
+ const NODE_PADDING_X = 64;
38
+ const MARGIN = 40;
39
+
40
+ // ── Helpers ────────────────────────────────────────────────
41
+
42
+ function estimateNodeWidth(label: string, description?: string): number {
43
+ const labelWidth = label.length * CHAR_WIDTH + NODE_PADDING_X;
44
+ const descWidth = description ? description.length * 7 + NODE_PADDING_X : 0;
45
+ return Math.max(MIN_NODE_WIDTH, labelWidth, descWidth);
46
+ }
47
+
48
+ function isHorizontal(dir: DiagramDirection): boolean {
49
+ return dir === 'LR' || dir === 'RL';
50
+ }
51
+
52
+ function isReversed(dir: DiagramDirection): boolean {
53
+ return dir === 'BT' || dir === 'RL';
54
+ }
55
+
56
+ // ── Topological Sort (Kahn's) ──────────────────────────────
57
+
58
+ interface GraphData {
59
+ adjacencyOut: Map<string, string[]>;
60
+ adjacencyIn: Map<string, string[]>;
61
+ order: string[];
62
+ reversedEdges: Set<string>; // "from->to" keys for cycle-broken edges
63
+ }
64
+
65
+ function buildGraph(nodeIds: string[], edges: { from: string; to: string }[]): GraphData {
66
+ const adjacencyOut = new Map<string, string[]>();
67
+ const adjacencyIn = new Map<string, string[]>();
68
+ const inDegree = new Map<string, number>();
69
+
70
+ for (const id of nodeIds) {
71
+ adjacencyOut.set(id, []);
72
+ adjacencyIn.set(id, []);
73
+ inDegree.set(id, 0);
74
+ }
75
+
76
+ for (const e of edges) {
77
+ if (!adjacencyOut.has(e.from) || !adjacencyOut.has(e.to)) continue;
78
+ adjacencyOut.get(e.from)!.push(e.to);
79
+ adjacencyIn.get(e.to)!.push(e.from);
80
+ inDegree.set(e.to, (inDegree.get(e.to) || 0) + 1);
81
+ }
82
+
83
+ // Kahn's algorithm
84
+ const queue: string[] = [];
85
+ for (const id of nodeIds) {
86
+ if (inDegree.get(id) === 0) queue.push(id);
87
+ }
88
+
89
+ const order: string[] = [];
90
+ const visited = new Set<string>();
91
+
92
+ while (queue.length > 0) {
93
+ const node = queue.shift()!;
94
+ if (visited.has(node)) continue;
95
+ visited.add(node);
96
+ order.push(node);
97
+
98
+ for (const succ of adjacencyOut.get(node)!) {
99
+ const deg = inDegree.get(succ)! - 1;
100
+ inDegree.set(succ, deg);
101
+ if (deg === 0) queue.push(succ);
102
+ }
103
+ }
104
+
105
+ // Handle cycles: any unvisited nodes have cycles
106
+ const reversedEdges = new Set<string>();
107
+ if (visited.size < nodeIds.length) {
108
+ for (const id of nodeIds) {
109
+ if (!visited.has(id)) {
110
+ // Break cycle by removing an incoming edge
111
+ const incoming = adjacencyIn.get(id)!;
112
+ for (const src of incoming) {
113
+ if (!visited.has(src)) {
114
+ reversedEdges.add(`${src}->${id}`);
115
+ const outList = adjacencyOut.get(src)!;
116
+ const idx = outList.indexOf(id);
117
+ if (idx >= 0) outList.splice(idx, 1);
118
+ inDegree.set(id, (inDegree.get(id) || 1) - 1);
119
+ }
120
+ }
121
+ if (!visited.has(id)) {
122
+ visited.add(id);
123
+ order.push(id);
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ return { adjacencyOut, adjacencyIn, order, reversedEdges };
130
+ }
131
+
132
+ // ── Layer Assignment (longest-path) ────────────────────────
133
+
134
+ function assignLayers(
135
+ order: string[],
136
+ adjacencyOut: Map<string, string[]>,
137
+ options?: { sequenceMode?: boolean; inputOrder?: string[] }
138
+ ): Map<string, number> {
139
+ const layer = new Map<string, number>();
140
+ if (options?.sequenceMode && options.inputOrder) {
141
+ // Inside a directed cluster: array order seeds initial layers, so a
142
+ // cluster like { direction: 'TB', nodes: [a, b, c] } stacks a→b→c by
143
+ // array order even when the user provides no internal edges.
144
+ for (const [i, id] of options.inputOrder.entries()) layer.set(id, i);
145
+ } else {
146
+ for (const id of order) layer.set(id, 0);
147
+ }
148
+
149
+ for (const id of order) {
150
+ const currentLayer = layer.get(id)!;
151
+ for (const succ of adjacencyOut.get(id)!) {
152
+ layer.set(succ, Math.max(layer.get(succ)!, currentLayer + 1));
153
+ }
154
+ }
155
+
156
+ return layer;
157
+ }
158
+
159
+ // ── Within-Layer Ordering (barycenter, 2 sweeps, cluster-aware) ───────────
160
+
161
+ function orderWithinLayers(
162
+ layers: string[][],
163
+ adjacencyOut: Map<string, string[]>,
164
+ adjacencyIn: Map<string, string[]>,
165
+ clusterMap?: Map<string, string>
166
+ ): string[][] {
167
+ const posInLayer = new Map<string, number>();
168
+
169
+ // Initialize positions
170
+ for (const layer of layers) {
171
+ for (const [i, id] of layer.entries()) {
172
+ posInLayer.set(id, i);
173
+ }
174
+ }
175
+
176
+ function resortLayer(layer: string[], neighbors: Map<string, string[]>): string[] {
177
+ const sorted = layer.map((nodeId) => {
178
+ const adj = neighbors.get(nodeId) ?? [];
179
+ const positions = adj
180
+ .map((p) => posInLayer.get(p))
181
+ .filter((p): p is number => p !== undefined);
182
+ const barycenter =
183
+ positions.length > 0
184
+ ? positions.reduce((a, b) => a + b, 0) / positions.length
185
+ : (posInLayer.get(nodeId) ?? 0);
186
+ return { id: nodeId, bary: barycenter };
187
+ });
188
+ sorted.sort((a, b) => a.bary - b.bary);
189
+ return sorted.map((s) => s.id);
190
+ }
191
+
192
+ // Down sweep
193
+ for (let i = 1; i < layers.length; i++) {
194
+ const next = resortLayer(layers[i]!, adjacencyIn);
195
+ layers[i] = next;
196
+ for (const [j, id] of next.entries()) {
197
+ posInLayer.set(id, j);
198
+ }
199
+ }
200
+
201
+ // Up sweep
202
+ for (let i = layers.length - 2; i >= 0; i--) {
203
+ const next = resortLayer(layers[i]!, adjacencyOut);
204
+ layers[i] = next;
205
+ for (const [j, id] of next.entries()) {
206
+ posInLayer.set(id, j);
207
+ }
208
+ }
209
+
210
+ // Cluster-aware grouping: after barycenter ordering, group cluster members together
211
+ // and push non-clustered nodes to the edges
212
+ if (clusterMap && clusterMap.size > 0) {
213
+ for (let i = 0; i < layers.length; i++) {
214
+ const regrouped = groupByCluster(
215
+ layers[i]!,
216
+ clusterMap,
217
+ adjacencyIn,
218
+ adjacencyOut,
219
+ posInLayer
220
+ );
221
+ layers[i] = regrouped;
222
+ for (const [j, id] of regrouped.entries()) {
223
+ posInLayer.set(id, j);
224
+ }
225
+ }
226
+ }
227
+
228
+ return layers;
229
+ }
230
+
231
+ /** Group nodes in a layer so cluster members are adjacent, non-clustered nodes at edges */
232
+ function groupByCluster(
233
+ layer: string[],
234
+ clusterMap: Map<string, string>,
235
+ adjacencyIn: Map<string, string[]>,
236
+ adjacencyOut: Map<string, string[]>,
237
+ posInLayer: Map<string, number>
238
+ ): string[] {
239
+ if (layer.length <= 1) return layer;
240
+
241
+ // Separate into cluster groups and non-clustered nodes
242
+ const clusterGroups = new Map<string, string[]>();
243
+ const nonClustered: string[] = [];
244
+
245
+ for (const nodeId of layer) {
246
+ const clusterId = clusterMap.get(nodeId);
247
+ if (clusterId) {
248
+ if (!clusterGroups.has(clusterId)) clusterGroups.set(clusterId, []);
249
+ clusterGroups.get(clusterId)!.push(nodeId);
250
+ } else {
251
+ nonClustered.push(nodeId);
252
+ }
253
+ }
254
+
255
+ // If no clusters in this layer, nothing to reorder
256
+ if (clusterGroups.size === 0) return layer;
257
+ // If no non-clustered nodes, just keep cluster groups in barycenter order
258
+ if (nonClustered.length === 0) {
259
+ // Order cluster groups by average barycenter position of their members
260
+ const groups = [...clusterGroups.entries()].map(([cid, nodes]) => {
261
+ const avgPos = nodes.reduce((s, n) => s + (posInLayer.get(n) ?? 0), 0) / nodes.length;
262
+ return { cid, nodes, avgPos };
263
+ });
264
+ groups.sort((a, b) => a.avgPos - b.avgPos);
265
+ return groups.flatMap((g) => g.nodes);
266
+ }
267
+
268
+ // Compute average barycenter position for each cluster group
269
+ const groupEntries = [...clusterGroups.entries()].map(([cid, nodes]) => {
270
+ const avgPos = nodes.reduce((s, n) => s + (posInLayer.get(n) ?? 0), 0) / nodes.length;
271
+ return { cid, nodes, avgPos };
272
+ });
273
+ groupEntries.sort((a, b) => a.avgPos - b.avgPos);
274
+
275
+ // Compute average connected position for non-clustered nodes to decide
276
+ // whether they go before or after the cluster groups
277
+ const allClusterPositions = groupEntries.flatMap((g) =>
278
+ g.nodes.map((n) => posInLayer.get(n) ?? 0)
279
+ );
280
+ const clusterCenter = allClusterPositions.reduce((a, b) => a + b, 0) / allClusterPositions.length;
281
+
282
+ // Split non-clustered into before/after based on their barycenter relative to cluster center
283
+ const before: string[] = [];
284
+ const after: string[] = [];
285
+ for (const nodeId of nonClustered) {
286
+ const pos = posInLayer.get(nodeId) ?? 0;
287
+ if (pos <= clusterCenter) {
288
+ before.push(nodeId);
289
+ } else {
290
+ after.push(nodeId);
291
+ }
292
+ }
293
+ // If all ended up on one side, that's fine — just keep them there
294
+ // If none on either side, push all to the end
295
+ if (before.length === 0 && after.length === 0) {
296
+ after.push(...nonClustered);
297
+ }
298
+
299
+ return [...before, ...groupEntries.flatMap((g) => g.nodes), ...after];
300
+ }
301
+
302
+ // ── Coordinate Assignment ──────────────────────────────────
303
+
304
+ const CLUSTER_GROUP_GAP = 40;
305
+
306
+ function assignCoordinates(
307
+ layers: string[][],
308
+ nodeDims: Map<string, { w: number; h: number }>,
309
+ direction: DiagramDirection,
310
+ nodeGap: number,
311
+ layerGap: number,
312
+ clusterMap?: Map<string, string>
313
+ ): Map<string, { x: number; y: number }> {
314
+ const positions = new Map<string, { x: number; y: number }>();
315
+ const horizontal = isHorizontal(direction);
316
+ const reversed = isReversed(direction);
317
+
318
+ // Compute layer extents (max node size in layer direction)
319
+ const layerSizes: number[] = [];
320
+ for (const layer of layers) {
321
+ let maxSize = 0;
322
+ for (const id of layer) {
323
+ const dims = nodeDims.get(id)!;
324
+ maxSize = Math.max(maxSize, horizontal ? dims.w : dims.h);
325
+ }
326
+ layerSizes.push(maxSize);
327
+ }
328
+
329
+ // Find max cross-axis extent per layer for centering (including cluster group gaps)
330
+ const layerCrossExtents: number[] = [];
331
+ for (const layer of layers) {
332
+ let total = 0;
333
+ for (const id of layer) {
334
+ const dims = nodeDims.get(id)!;
335
+ total += horizontal ? dims.h : dims.w;
336
+ }
337
+ total += (layer.length - 1) * nodeGap;
338
+ // Add extra gap at cluster/non-cluster boundaries
339
+ if (clusterMap && clusterMap.size > 0) {
340
+ total += countClusterBoundaries(layer, clusterMap) * CLUSTER_GROUP_GAP;
341
+ }
342
+ layerCrossExtents.push(total);
343
+ }
344
+ const maxCrossExtent = Math.max(...layerCrossExtents);
345
+
346
+ // Assign positions
347
+ let layerOffset = MARGIN;
348
+ const layerOrder = reversed ? [...layers].reverse() : layers;
349
+ const sizeOrder = reversed ? [...layerSizes].reverse() : layerSizes;
350
+
351
+ for (let li = 0; li < layerOrder.length; li++) {
352
+ const layer = layerOrder[li]!;
353
+ const layerSize = sizeOrder[li]!;
354
+
355
+ // Center this layer's nodes
356
+ const crossExtent = layerCrossExtents[reversed ? layerOrder.length - 1 - li : li] ?? 0;
357
+ let crossOffset = MARGIN + (maxCrossExtent - crossExtent) / 2;
358
+
359
+ for (let ni = 0; ni < layer.length; ni++) {
360
+ const id = layer[ni]!;
361
+ const dims = nodeDims.get(id)!;
362
+ const primaryOffset = reversed ? (horizontal ? layerSize - dims.w : layerSize - dims.h) : 0;
363
+
364
+ // Add extra gap at cluster/non-cluster boundary
365
+ if (ni > 0 && clusterMap && clusterMap.size > 0) {
366
+ const prevCluster = clusterMap.get(layer[ni - 1]!);
367
+ const currCluster = clusterMap.get(id);
368
+ if (
369
+ prevCluster !== currCluster &&
370
+ (prevCluster !== undefined || currCluster !== undefined)
371
+ ) {
372
+ crossOffset += CLUSTER_GROUP_GAP;
373
+ }
374
+ }
375
+
376
+ if (horizontal) {
377
+ positions.set(id, {
378
+ x: layerOffset + primaryOffset,
379
+ y: crossOffset
380
+ });
381
+ crossOffset += dims.h + nodeGap;
382
+ } else {
383
+ positions.set(id, {
384
+ x: crossOffset,
385
+ y: layerOffset + primaryOffset
386
+ });
387
+ crossOffset += dims.w + nodeGap;
388
+ }
389
+ }
390
+
391
+ layerOffset += layerSize + layerGap;
392
+ }
393
+
394
+ return positions;
395
+ }
396
+
397
+ /** Count how many cluster/non-cluster boundaries exist in a layer ordering */
398
+ function countClusterBoundaries(layer: string[], clusterMap: Map<string, string>): number {
399
+ let count = 0;
400
+ for (let i = 1; i < layer.length; i++) {
401
+ const prevCluster = clusterMap.get(layer[i - 1]!);
402
+ const currCluster = clusterMap.get(layer[i]!);
403
+ if (prevCluster !== currCluster && (prevCluster !== undefined || currCluster !== undefined)) {
404
+ count++;
405
+ }
406
+ }
407
+ return count;
408
+ }
409
+
410
+ // ── Cluster Bounds ─────────────────────────────────────────
411
+
412
+ function computeClusterBounds(
413
+ clusters: DiagramCluster[],
414
+ positions: Map<string, { x: number; y: number }>,
415
+ nodeDims: Map<string, { w: number; h: number }>,
416
+ padding: number
417
+ ): PositionedCluster[] {
418
+ return clusters.map((cluster) => {
419
+ let minX = Infinity,
420
+ minY = Infinity,
421
+ maxX = -Infinity,
422
+ maxY = -Infinity;
423
+
424
+ for (const nodeId of cluster.nodes) {
425
+ const pos = positions.get(nodeId);
426
+ const dims = nodeDims.get(nodeId);
427
+ if (!pos || !dims) continue;
428
+ minX = Math.min(minX, pos.x);
429
+ minY = Math.min(minY, pos.y);
430
+ maxX = Math.max(maxX, pos.x + dims.w);
431
+ maxY = Math.max(maxY, pos.y + dims.h);
432
+ }
433
+
434
+ if (minX === Infinity) {
435
+ return {
436
+ id: cluster.id,
437
+ x: 0,
438
+ y: 0,
439
+ width: 0,
440
+ height: 0,
441
+ label: cluster.label,
442
+ labelPosition: cluster.labelPosition,
443
+ iconComponent: cluster.iconComponent,
444
+ color: cluster.color || 'neutral',
445
+ dashed: cluster.dashed ?? true
446
+ };
447
+ }
448
+
449
+ const labelOnLeft = cluster.labelPosition === 'left';
450
+ const labelPad = cluster.label ? 24 : 0;
451
+ const padTop = labelOnLeft ? 0 : labelPad;
452
+ const padLeft = labelOnLeft ? labelPad : 0;
453
+
454
+ return {
455
+ id: cluster.id,
456
+ x: minX - padding - padLeft,
457
+ y: minY - padding - padTop,
458
+ width: maxX - minX + padding * 2 + padLeft,
459
+ height: maxY - minY + padding * 2 + padTop,
460
+ label: cluster.label,
461
+ labelPosition: cluster.labelPosition,
462
+ iconComponent: cluster.iconComponent,
463
+ color: cluster.color || 'neutral',
464
+ dashed: cluster.dashed ?? true
465
+ };
466
+ });
467
+ }
468
+
469
+ function computeNodeAndClusterBounds(
470
+ nodes: PositionedNode[],
471
+ clusters: PositionedCluster[]
472
+ ): { minX: number; minY: number; maxX: number; maxY: number } {
473
+ let minX = Infinity,
474
+ minY = Infinity,
475
+ maxX = -Infinity,
476
+ maxY = -Infinity;
477
+
478
+ for (const n of nodes) {
479
+ minX = Math.min(minX, n.x);
480
+ minY = Math.min(minY, n.y);
481
+ maxX = Math.max(maxX, n.x + n.width);
482
+ maxY = Math.max(maxY, n.y + n.height);
483
+ }
484
+ for (const c of clusters) {
485
+ minX = Math.min(minX, c.x);
486
+ minY = Math.min(minY, c.y);
487
+ maxX = Math.max(maxX, c.x + c.width);
488
+ maxY = Math.max(maxY, c.y + c.height);
489
+ }
490
+
491
+ if (minX === Infinity) {
492
+ return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
493
+ }
494
+ return { minX, minY, maxX, maxY };
495
+ }
496
+
497
+ // ── Waypoints ──────────────────────────────────────────────
498
+
499
+ const WAYPOINT_DEFAULT_WIDTH = 240;
500
+ const WAYPOINT_DEFAULT_HEIGHT_DESC = 132;
501
+ const WAYPOINT_DEFAULT_HEIGHT = 84;
502
+ const WAYPOINT_VERTICAL_EDGE_INSET_RATIO = 0.5;
503
+
504
+ function estimateWaypointDims(waypoint: DiagramWaypoint): { w: number; h: number } {
505
+ const w =
506
+ waypoint.width ??
507
+ Math.max(
508
+ MIN_NODE_WIDTH,
509
+ waypoint.label.length * CHAR_WIDTH + NODE_PADDING_X,
510
+ (waypoint.description?.length ?? 0) * 7 + NODE_PADDING_X
511
+ );
512
+ const h =
513
+ waypoint.height ??
514
+ (waypoint.description ? WAYPOINT_DEFAULT_HEIGHT_DESC : WAYPOINT_DEFAULT_HEIGHT);
515
+ return { w: Math.min(w, WAYPOINT_DEFAULT_WIDTH * 1.4), h };
516
+ }
517
+
518
+ function resolveVerticalWaypointLaneRatio(
519
+ collapsed: Array<{ x: number; y: number }>,
520
+ segmentIndex: number
521
+ ): number {
522
+ const laneX = collapsed[segmentIndex]?.x ?? collapsed[segmentIndex - 1]?.x;
523
+ if (laneX === undefined) return 0.5;
524
+
525
+ const beforeSide = Math.sign((collapsed[segmentIndex - 2]?.x ?? laneX) - laneX);
526
+ if (beforeSide < 0) return WAYPOINT_VERTICAL_EDGE_INSET_RATIO;
527
+ if (beforeSide > 0) return 1 - WAYPOINT_VERTICAL_EDGE_INSET_RATIO;
528
+
529
+ const afterSide = Math.sign((collapsed[segmentIndex + 1]?.x ?? laneX) - laneX);
530
+ if (afterSide < 0) return WAYPOINT_VERTICAL_EDGE_INSET_RATIO;
531
+ if (afterSide > 0) return 1 - WAYPOINT_VERTICAL_EDGE_INSET_RATIO;
532
+
533
+ return 0.5;
534
+ }
535
+
536
+ function placeWaypoints(
537
+ configEdges: DiagramEdge[],
538
+ positionedEdges: PositionedEdge[],
539
+ collapsedByIndex: (Array<{ x: number; y: number }> | null)[],
540
+ cornerRadius: number
541
+ ): { edges: PositionedEdge[]; waypoints: PositionedWaypoint[] } {
542
+ const newEdges: PositionedEdge[] = [];
543
+ const waypoints: PositionedWaypoint[] = [];
544
+
545
+ configEdges.forEach((edge, i) => {
546
+ const positioned = positionedEdges[i];
547
+ const collapsed = collapsedByIndex[i];
548
+ if (!edge.waypoint || !positioned || !collapsed || collapsed.length < 2) {
549
+ if (positioned) newEdges.push(positioned);
550
+ return;
551
+ }
552
+
553
+ const wp = edge.waypoint;
554
+ const t = wp.position ?? 0.5;
555
+ const at = getPointAtFraction(collapsed, t);
556
+ const dims = estimateWaypointDims(wp);
557
+ const laneRatio =
558
+ at.axis === 'v' ? resolveVerticalWaypointLaneRatio(collapsed, at.segmentIndex) : 0.5;
559
+ const box = {
560
+ x: at.point.x - dims.w * laneRatio,
561
+ y: at.point.y - dims.h / 2,
562
+ width: dims.w,
563
+ height: dims.h
564
+ };
565
+
566
+ const split = splitCollapsedAtBox(collapsed, at.segmentIndex, box, at.axis);
567
+ if (!split) {
568
+ newEdges.push(positioned);
569
+ return;
570
+ }
571
+
572
+ const entryPath = buildPathFromCollapsed(split.entry, cornerRadius);
573
+ const exitPath = buildPathFromCollapsed(split.exit, cornerRadius);
574
+
575
+ // Entry segment: no arrow marker (arrow is on the exit)
576
+ newEdges.push({
577
+ ...positioned,
578
+ path: entryPath,
579
+ label: undefined,
580
+ labelX: 0,
581
+ labelY: 0,
582
+ arrow: 'none',
583
+ kind: 'entry'
584
+ });
585
+ // Exit segment: keeps original arrow + label
586
+ const exitMid = getPointAtFraction(split.exit, 0.5).point;
587
+ newEdges.push({
588
+ ...positioned,
589
+ path: exitPath,
590
+ label: positioned.label,
591
+ labelX: exitMid.x,
592
+ labelY: exitMid.y - 12,
593
+ kind: 'exit'
594
+ });
595
+
596
+ waypoints.push({
597
+ id: wp.id ?? `${edge.from}->${edge.to}`,
598
+ x: box.x,
599
+ y: box.y,
600
+ width: dims.w,
601
+ height: dims.h,
602
+ label: wp.label,
603
+ description: wp.description,
604
+ icon: wp.icon,
605
+ iconComponent: wp.iconComponent,
606
+ variant: wp.variant ?? 'default',
607
+ color: wp.color ?? 'neutral'
608
+ });
609
+ });
610
+
611
+ return { edges: newEdges, waypoints };
612
+ }
613
+
614
+ // ── Annotations ────────────────────────────────────────────
615
+
616
+ const ANNOTATION_CHAR_WIDTH = 6;
617
+ const ANNOTATION_HEIGHT = 14;
618
+ const ANNOTATION_COLLISION_PAD = 20;
619
+
620
+ function resolveAnnotations(
621
+ config: DiagramConfig,
622
+ positions: Map<string, { x: number; y: number }>,
623
+ nodeDims: Map<string, { w: number; h: number }>
624
+ ): PositionedAnnotation[] {
625
+ if (!config.annotations) return [];
626
+
627
+ const resolved: PositionedAnnotation[] = config.annotations.map((ann) => {
628
+ let x: number, y: number;
629
+
630
+ if (typeof ann.anchor === 'string') {
631
+ const pos = positions.get(ann.anchor);
632
+ const dims = nodeDims.get(ann.anchor);
633
+ if (pos && dims) {
634
+ x = pos.x + dims.w / 2;
635
+ y = pos.y - 12;
636
+ } else {
637
+ x = 0;
638
+ y = 0;
639
+ }
640
+ } else {
641
+ x = ann.anchor.x;
642
+ y = ann.anchor.y;
643
+ }
644
+
645
+ return {
646
+ text: ann.text,
647
+ x: x + (ann.offset?.dx ?? 0),
648
+ y: y + (ann.offset?.dy ?? 0),
649
+ color: ann.color || 'neutral'
650
+ };
651
+ });
652
+
653
+ // Collision avoidance: check annotations against nodes and other annotations
654
+ for (let i = 0; i < resolved.length; i++) {
655
+ const ann = resolved[i]!;
656
+ const annW = ann.text.length * ANNOTATION_CHAR_WIDTH;
657
+ const annH = ANNOTATION_HEIGHT;
658
+
659
+ // Check against all nodes — shift upward if overlapping
660
+ let shifted = true;
661
+ let maxIter = 10; // prevent infinite loop
662
+ while (shifted && maxIter-- > 0) {
663
+ shifted = false;
664
+ for (const [nodeId, pos] of positions) {
665
+ const dims = nodeDims.get(nodeId);
666
+ if (!dims) continue;
667
+ // Check overlap with padding
668
+ if (
669
+ ann.x + annW > pos.x - ANNOTATION_COLLISION_PAD &&
670
+ ann.x < pos.x + dims.w + ANNOTATION_COLLISION_PAD &&
671
+ ann.y + annH > pos.y - ANNOTATION_COLLISION_PAD &&
672
+ ann.y < pos.y + dims.h + ANNOTATION_COLLISION_PAD
673
+ ) {
674
+ // Shift annotation above the node
675
+ ann.y = pos.y - ANNOTATION_COLLISION_PAD - annH;
676
+ shifted = true;
677
+ }
678
+ }
679
+ }
680
+
681
+ // Check against previously placed annotations
682
+ for (let j = 0; j < i; j++) {
683
+ const other = resolved[j]!;
684
+ const otherW = other.text.length * ANNOTATION_CHAR_WIDTH;
685
+ const otherH = ANNOTATION_HEIGHT;
686
+ if (
687
+ ann.x + annW > other.x - ANNOTATION_COLLISION_PAD &&
688
+ ann.x < other.x + otherW + ANNOTATION_COLLISION_PAD &&
689
+ ann.y + annH > other.y - ANNOTATION_COLLISION_PAD &&
690
+ ann.y < other.y + otherH + ANNOTATION_COLLISION_PAD
691
+ ) {
692
+ // Shift this annotation above the other
693
+ ann.y = other.y - ANNOTATION_COLLISION_PAD - annH;
694
+ }
695
+ }
696
+ }
697
+
698
+ return resolved;
699
+ }
700
+
701
+ // ── Layered Layout (main entry) ────────────────────────────
702
+
703
+ interface LayeredSpacing {
704
+ nodeGap: number;
705
+ layerGap: number;
706
+ clusterPadding: number;
707
+ cornerRadius: number;
708
+ backEdgeLaneGap: number;
709
+ }
710
+
711
+ interface LayeredPassResult {
712
+ positionedNodes: PositionedNode[];
713
+ positionedClusters: PositionedCluster[];
714
+ positionedEdges: PositionedEdge[];
715
+ waypoints: PositionedWaypoint[];
716
+ bbox: { minX: number; minY: number; maxX: number; maxY: number };
717
+ }
718
+
719
+ interface LayeredPositions {
720
+ positions: Map<string, { x: number; y: number }>;
721
+ nodeDims: Map<string, { w: number; h: number }>;
722
+ positionedNodes: PositionedNode[];
723
+ positionedClusters: PositionedCluster[];
724
+ preBounds: { minX: number; minY: number; maxX: number; maxY: number };
725
+ reversedEdges: Set<string>;
726
+ }
727
+
728
+ /** Pipeline steps 1-7: build the graph, assign layers, order within layers,
729
+ * assign coordinates, and pre-compute cluster bounds. Returned state is fed
730
+ * to `finishLayeredPass` to compute edges and waypoints. The split lets the
731
+ * recursive orchestrator inject inner-node positions between these phases
732
+ * for cross-boundary back-edge re-anchoring. */
733
+ function computeLayeredPositions(
734
+ nodes: DiagramNode[],
735
+ edges: DiagramEdge[],
736
+ clusters: DiagramCluster[],
737
+ direction: DiagramDirection,
738
+ spacing: LayeredSpacing,
739
+ sequenceMode = false
740
+ ): LayeredPositions {
741
+ const nodeIds = nodes.map((n) => n.id);
742
+ const nodeDims = buildNodeDims(nodes);
743
+
744
+ const clusterMap = new Map<string, string>();
745
+ for (const cluster of clusters) {
746
+ for (const nodeId of cluster.nodes) {
747
+ if (nodeIds.includes(nodeId)) clusterMap.set(nodeId, cluster.id);
748
+ }
749
+ }
750
+
751
+ const graph = buildGraph(nodeIds, edges);
752
+ const layerMap = assignLayers(graph.order, graph.adjacencyOut, {
753
+ sequenceMode,
754
+ inputOrder: nodeIds
755
+ });
756
+
757
+ const maxLayer = Math.max(0, ...layerMap.values());
758
+ const layers: string[][] = Array.from({ length: maxLayer + 1 }, () => []);
759
+ for (const id of graph.order) {
760
+ layers[layerMap.get(id)!]!.push(id);
761
+ }
762
+
763
+ const orderedLayers = orderWithinLayers(
764
+ layers,
765
+ graph.adjacencyOut,
766
+ graph.adjacencyIn,
767
+ clusterMap
768
+ );
769
+
770
+ const positions = assignCoordinates(
771
+ orderedLayers,
772
+ nodeDims,
773
+ direction,
774
+ spacing.nodeGap,
775
+ spacing.layerGap,
776
+ clusterMap
777
+ );
778
+
779
+ const positionedNodes = buildPositionedNodes(nodes, positions, nodeDims);
780
+
781
+ const positionedClusters = computeClusterBounds(
782
+ clusters,
783
+ positions,
784
+ nodeDims,
785
+ spacing.clusterPadding
786
+ );
787
+
788
+ const preBounds = computeNodeAndClusterBounds(positionedNodes, positionedClusters);
789
+
790
+ return {
791
+ positions,
792
+ nodeDims,
793
+ positionedNodes,
794
+ positionedClusters,
795
+ preBounds,
796
+ reversedEdges: graph.reversedEdges
797
+ };
798
+ }
799
+
800
+ /** Pipeline steps 8-10: route edges, place waypoints, compute bbox. Takes
801
+ * optional position/dim extras and back-edge anchor overrides so the
802
+ * orchestrator can re-anchor cross-boundary back edges to inner nodes. */
803
+ function finishLayeredPass(
804
+ edges: DiagramEdge[],
805
+ pos: LayeredPositions,
806
+ direction: DiagramDirection,
807
+ spacing: LayeredSpacing,
808
+ superNodeIds?: Set<string>,
809
+ extras?: {
810
+ extraPositions?: Map<string, { x: number; y: number }>;
811
+ extraDims?: Map<string, { w: number; h: number }>;
812
+ backEdgeAnchorOverrides?: Map<string, { source?: string; target?: string }>;
813
+ }
814
+ ): LayeredPassResult {
815
+ let edgePositions = pos.positions;
816
+ let edgeNodeDims = pos.nodeDims;
817
+ if (extras?.extraPositions || extras?.extraDims) {
818
+ edgePositions = new Map(pos.positions);
819
+ edgeNodeDims = new Map(pos.nodeDims);
820
+ if (extras.extraPositions) {
821
+ for (const [k, v] of extras.extraPositions) edgePositions.set(k, v);
822
+ }
823
+ if (extras.extraDims) {
824
+ for (const [k, v] of extras.extraDims) edgeNodeDims.set(k, v);
825
+ }
826
+ }
827
+
828
+ const computed = computeEdgePaths(edges, edgePositions, edgeNodeDims, direction, {
829
+ cornerRadius: spacing.cornerRadius,
830
+ reversedEdges: pos.reversedEdges,
831
+ bounds: pos.preBounds,
832
+ backEdgeLaneGap: spacing.backEdgeLaneGap,
833
+ superNodeIds,
834
+ backEdgeAnchorOverrides: extras?.backEdgeAnchorOverrides
835
+ });
836
+ let positionedEdges = computed.edges;
837
+
838
+ const waypointResult = placeWaypoints(
839
+ edges,
840
+ positionedEdges,
841
+ computed.collapsed,
842
+ spacing.cornerRadius
843
+ );
844
+ positionedEdges = waypointResult.edges;
845
+ const waypoints = waypointResult.waypoints;
846
+
847
+ const bbox = computeFullPassBounds(
848
+ pos.positionedNodes,
849
+ pos.positionedClusters,
850
+ positionedEdges,
851
+ waypoints
852
+ );
853
+
854
+ return {
855
+ positionedNodes: pos.positionedNodes,
856
+ positionedClusters: pos.positionedClusters,
857
+ positionedEdges,
858
+ waypoints,
859
+ bbox
860
+ };
861
+ }
862
+
863
+ /** Layered layout for a flat node/edge/cluster slice in local coordinates.
864
+ * Annotations and the global non-negative shift stay in layoutLayered. */
865
+ function layoutLayeredPass(
866
+ nodes: DiagramNode[],
867
+ edges: DiagramEdge[],
868
+ clusters: DiagramCluster[],
869
+ direction: DiagramDirection,
870
+ spacing: LayeredSpacing,
871
+ superNodeIds?: Set<string>,
872
+ sequenceMode = false
873
+ ): LayeredPassResult {
874
+ const pos = computeLayeredPositions(nodes, edges, clusters, direction, spacing, sequenceMode);
875
+ return finishLayeredPass(edges, pos, direction, spacing, superNodeIds);
876
+ }
877
+
878
+ function computeFullPassBounds(
879
+ nodes: PositionedNode[],
880
+ clusters: PositionedCluster[],
881
+ edges: PositionedEdge[],
882
+ waypoints: PositionedWaypoint[]
883
+ ): { minX: number; minY: number; maxX: number; maxY: number } {
884
+ const base = computeNodeAndClusterBounds(nodes, clusters);
885
+ let { minX, minY, maxX, maxY } = base;
886
+ for (const w of waypoints) {
887
+ minX = Math.min(minX, w.x);
888
+ minY = Math.min(minY, w.y);
889
+ maxX = Math.max(maxX, w.x + w.width);
890
+ maxY = Math.max(maxY, w.y + w.height);
891
+ }
892
+ for (const e of edges) {
893
+ const pb = e.bounds ?? extractPathBounds(e.path);
894
+ if (pb) {
895
+ minX = Math.min(minX, pb.minX);
896
+ minY = Math.min(minY, pb.minY);
897
+ maxX = Math.max(maxX, pb.maxX);
898
+ maxY = Math.max(maxY, pb.maxY);
899
+ }
900
+ }
901
+ return { minX, minY, maxX, maxY };
902
+ }
903
+
904
+ const SUPER_NODE_PREFIX = '__dry_super_';
905
+
906
+ function isSuperNodeId(id: string): boolean {
907
+ return id.startsWith(SUPER_NODE_PREFIX);
908
+ }
909
+
910
+ // ── Layout caches ──────────────────────────────────────────
911
+ //
912
+ // Two-tier caching:
913
+ //
914
+ // 1. `fullLayoutCache` (WeakMap by config identity): when a caller invokes
915
+ // computeLayout repeatedly with the *same* config object, return the
916
+ // already-computed result. The Svelte `<Diagram>` component re-runs its
917
+ // `$derived(computeLayout(config))` on every dependency change, so a stable
918
+ // `const config = { ... }` benefits enormously here.
919
+ //
920
+ // 2. `subLayoutCache` (LRU by content hash): each directed cluster sub-layout
921
+ // runs the full layered pipeline. When a subsequent computeLayout call
922
+ // contains a leaf directed cluster whose nodes/edges/direction/spacing
923
+ // haven't changed, we can reuse the cached LayeredPassResult instead of
924
+ // re-running the pipeline. Only LEAF sub-layouts (no further nested
925
+ // clusters) are cached — keeps the key derivation simple and the cached
926
+ // object stable. The merge code in `layoutNested` always spreads sub
927
+ // results into fresh objects, so cached entries are never mutated.
928
+
929
+ const fullLayoutCache = new WeakMap<DiagramConfig, LayoutResult>();
930
+
931
+ const SUB_LAYOUT_CACHE_MAX = 64;
932
+ const subLayoutCache = new Map<string, LayeredPassResult>();
933
+
934
+ function buildSubLayoutKey(
935
+ subNodes: DiagramNode[],
936
+ subEdges: DiagramEdge[],
937
+ direction: DiagramDirection,
938
+ spacing: LayeredSpacing
939
+ ): string {
940
+ const parts: string[] = [
941
+ direction,
942
+ `s${spacing.nodeGap}|${spacing.layerGap}|${spacing.clusterPadding}|${spacing.cornerRadius}|${spacing.backEdgeLaneGap}`
943
+ ];
944
+ for (const n of subNodes) {
945
+ parts.push(
946
+ `N|${n.id}|${n.label}|${n.description ?? ''}|${n.width ?? ''}|${n.height ?? ''}|${
947
+ n.variant ?? ''
948
+ }|${n.color ?? ''}|${n.state ?? ''}`
949
+ );
950
+ }
951
+ for (const e of subEdges) {
952
+ const wp = e.waypoint;
953
+ parts.push(
954
+ `E|${e.from}|${e.to}|${e.label ?? ''}|${e.loop ?? ''}|${e.arrow ?? ''}|${
955
+ e.dashed ?? ''
956
+ }|${e.color ?? ''}|${
957
+ wp
958
+ ? `W|${wp.id ?? ''}|${wp.label}|${wp.description ?? ''}|${wp.position ?? ''}|${
959
+ wp.width ?? ''
960
+ }|${wp.height ?? ''}|${wp.variant ?? ''}|${wp.color ?? ''}`
961
+ : ''
962
+ }`
963
+ );
964
+ }
965
+ return parts.join('||');
966
+ }
967
+
968
+ function getSubLayoutFromCache(key: string): LayeredPassResult | undefined {
969
+ const cached = subLayoutCache.get(key);
970
+ if (!cached) return undefined;
971
+ subLayoutCache.delete(key);
972
+ subLayoutCache.set(key, cached);
973
+ return cached;
974
+ }
975
+
976
+ function setSubLayoutInCache(key: string, result: LayeredPassResult): void {
977
+ if (subLayoutCache.size >= SUB_LAYOUT_CACHE_MAX) {
978
+ const oldest = subLayoutCache.keys().next().value;
979
+ if (oldest !== undefined) subLayoutCache.delete(oldest);
980
+ }
981
+ subLayoutCache.set(key, result);
982
+ }
983
+
984
+ /** True iff `inner.nodes` is a strict subset (different set, all nodes
985
+ * present) of `outer.nodes`. Used to detect cluster nesting. */
986
+ function isContainedIn(inner: DiagramCluster, outer: DiagramCluster): boolean {
987
+ if (inner === outer) return false;
988
+ const outerSet = new Set(outer.nodes);
989
+ if (!inner.nodes.every((n) => outerSet.has(n))) return false;
990
+ if (inner.nodes.length === outer.nodes.length) return false;
991
+ return true;
992
+ }
993
+
994
+ /** Recursive layered layout. Handles arbitrary nesting of directed clusters
995
+ * (and flat clusters inside directed clusters). At each level:
996
+ * 1. Sub-layout each top-level directed cluster recursively, passing any
997
+ * cluster whose nodes live entirely inside it as nested context.
998
+ * 2. Run an outer pass with super-nodes for those top-level directed
999
+ * clusters and any flat clusters that live at this level.
1000
+ * 3. Merge inner sub-layouts back into outer coordinates. */
1001
+ function layoutNested(
1002
+ nodes: DiagramNode[],
1003
+ edges: DiagramEdge[],
1004
+ allClusters: DiagramCluster[],
1005
+ direction: DiagramDirection,
1006
+ spacing: LayeredSpacing,
1007
+ sequenceMode = false
1008
+ ): LayeredPassResult {
1009
+ const nodeIdSet = new Set(nodes.map((n) => n.id));
1010
+ const localClusters = allClusters.filter((c) => c.nodes.every((n) => nodeIdSet.has(n)));
1011
+ const directedLocal = localClusters.filter((c) => c.direction);
1012
+
1013
+ if (directedLocal.length === 0) {
1014
+ const flatLocal = localClusters.filter((c) => !c.direction);
1015
+ return layoutLayeredPass(nodes, edges, flatLocal, direction, spacing, undefined, sequenceMode);
1016
+ }
1017
+
1018
+ // Top-level directed clusters at this scope: not strictly contained in any
1019
+ // other directed cluster from the same scope.
1020
+ const topLevelDirected = directedLocal.filter(
1021
+ (inner) => !directedLocal.some((outer) => isContainedIn(inner, outer))
1022
+ );
1023
+
1024
+ const directedClusterById = new Map<string, DiagramCluster>();
1025
+ const memberToDirectedCluster = new Map<string, DiagramCluster>();
1026
+ for (const cluster of topLevelDirected) {
1027
+ directedClusterById.set(cluster.id, cluster);
1028
+ for (const nodeId of cluster.nodes) {
1029
+ memberToDirectedCluster.set(nodeId, cluster);
1030
+ }
1031
+ }
1032
+
1033
+ const subLayouts = new Map<string, LayeredPassResult>();
1034
+ const subSpacings = new Map<string, LayeredSpacing>();
1035
+ for (const cluster of topLevelDirected) {
1036
+ const memberSet = new Set(cluster.nodes);
1037
+ const subNodes = nodes.filter((n) => memberSet.has(n.id));
1038
+ const subEdges = edges.filter((e) => memberSet.has(e.from) && memberSet.has(e.to));
1039
+ // Any cluster (flat or directed) whose nodes all live inside this
1040
+ // cluster is nested context for the recursive call.
1041
+ const nestedClusters = allClusters.filter(
1042
+ (c) => c !== cluster && c.nodes.every((n) => memberSet.has(n))
1043
+ );
1044
+ const subSpacing: LayeredSpacing = {
1045
+ nodeGap: cluster.spacing?.nodeGap ?? spacing.nodeGap,
1046
+ layerGap: cluster.spacing?.layerGap ?? spacing.layerGap,
1047
+ clusterPadding: cluster.spacing?.clusterPadding ?? spacing.clusterPadding,
1048
+ cornerRadius: cluster.spacing?.cornerRadius ?? spacing.cornerRadius,
1049
+ backEdgeLaneGap: cluster.spacing?.backEdgeLaneGap ?? spacing.backEdgeLaneGap
1050
+ };
1051
+ subSpacings.set(cluster.id, subSpacing);
1052
+ // Cache leaf sub-layouts (no further nested clusters) by content. The
1053
+ // merge code below always spreads the cached result into fresh objects,
1054
+ // so the cached entry stays immutable across calls.
1055
+ let subResult: LayeredPassResult;
1056
+ if (nestedClusters.length === 0) {
1057
+ const key = buildSubLayoutKey(subNodes, subEdges, cluster.direction!, subSpacing);
1058
+ const cached = getSubLayoutFromCache(key);
1059
+ if (cached) {
1060
+ subResult = cached;
1061
+ } else {
1062
+ subResult = layoutNested(subNodes, subEdges, [], cluster.direction!, subSpacing, true);
1063
+ setSubLayoutInCache(key, subResult);
1064
+ }
1065
+ } else {
1066
+ subResult = layoutNested(
1067
+ subNodes,
1068
+ subEdges,
1069
+ nestedClusters,
1070
+ cluster.direction!,
1071
+ subSpacing,
1072
+ true
1073
+ );
1074
+ }
1075
+ subLayouts.set(cluster.id, subResult);
1076
+ }
1077
+
1078
+ const outerNodes: DiagramNode[] = [];
1079
+ const seenSuperClusters = new Set<string>();
1080
+ for (const node of nodes) {
1081
+ const owner = memberToDirectedCluster.get(node.id);
1082
+ if (owner) {
1083
+ if (!seenSuperClusters.has(owner.id)) {
1084
+ const sub = subLayouts.get(owner.id)!;
1085
+ const subSpacing = subSpacings.get(owner.id)!;
1086
+ const subW = sub.bbox.maxX - sub.bbox.minX;
1087
+ const subH = sub.bbox.maxY - sub.bbox.minY;
1088
+ const labelOnLeft = owner.labelPosition === 'left';
1089
+ const labelPad = owner.label ? 32 : 0;
1090
+ const padTop = labelOnLeft ? 0 : labelPad;
1091
+ const padLeft = labelOnLeft ? labelPad : 0;
1092
+ outerNodes.push({
1093
+ id: SUPER_NODE_PREFIX + owner.id,
1094
+ label: '',
1095
+ width: subW + subSpacing.clusterPadding * 2 + padLeft,
1096
+ height: subH + subSpacing.clusterPadding * 2 + padTop
1097
+ });
1098
+ seenSuperClusters.add(owner.id);
1099
+ }
1100
+ } else {
1101
+ outerNodes.push(node);
1102
+ }
1103
+ }
1104
+
1105
+ // Edges internal to a directed cluster are handled by its sub-layout.
1106
+ const outerEdges: DiagramEdge[] = [];
1107
+ for (const edge of edges) {
1108
+ const fromCluster = memberToDirectedCluster.get(edge.from);
1109
+ const toCluster = memberToDirectedCluster.get(edge.to);
1110
+ if (fromCluster && toCluster && fromCluster.id === toCluster.id) continue;
1111
+ outerEdges.push({
1112
+ ...edge,
1113
+ from: fromCluster ? SUPER_NODE_PREFIX + fromCluster.id : edge.from,
1114
+ to: toCluster ? SUPER_NODE_PREFIX + toCluster.id : edge.to
1115
+ });
1116
+ }
1117
+
1118
+ // Flat clusters that live at THIS level — i.e. not inside any top-level
1119
+ // directed cluster, where they would have been handled by the recursive
1120
+ // sub-layout instead.
1121
+ const flatLocal = localClusters.filter(
1122
+ (c) => !c.direction && !topLevelDirected.some((td) => isContainedIn(c, td))
1123
+ );
1124
+
1125
+ const superNodeIds = new Set<string>();
1126
+ for (const cluster of topLevelDirected) {
1127
+ superNodeIds.add(SUPER_NODE_PREFIX + cluster.id);
1128
+ }
1129
+
1130
+ // Compute outer positions first (steps 1-7 of the layered pipeline) so we
1131
+ // can derive global inner-node positions from the super-node placements,
1132
+ // THEN run edge routing with overrides that re-anchor cross-boundary back
1133
+ // edges to the actual inner nodes instead of the cluster super-node.
1134
+ const outerPositions = computeLayeredPositions(
1135
+ outerNodes,
1136
+ outerEdges,
1137
+ flatLocal,
1138
+ direction,
1139
+ spacing,
1140
+ sequenceMode
1141
+ );
1142
+
1143
+ const innerExtraPositions = new Map<string, { x: number; y: number }>();
1144
+ const innerExtraDims = new Map<string, { w: number; h: number }>();
1145
+ for (const outerNode of outerPositions.positionedNodes) {
1146
+ if (!isSuperNodeId(outerNode.id)) continue;
1147
+ const clusterId = outerNode.id.slice(SUPER_NODE_PREFIX.length);
1148
+ const sub = subLayouts.get(clusterId)!;
1149
+ const cluster = directedClusterById.get(clusterId)!;
1150
+ const subSpacing = subSpacings.get(clusterId)!;
1151
+ const labelOnLeft = cluster.labelPosition === 'left';
1152
+ const labelPad = cluster.label ? 32 : 0;
1153
+ const padTop = labelOnLeft ? 0 : labelPad;
1154
+ const padLeft = labelOnLeft ? labelPad : 0;
1155
+ const offsetX = outerNode.x + subSpacing.clusterPadding + padLeft - sub.bbox.minX;
1156
+ const offsetY = outerNode.y + subSpacing.clusterPadding + padTop - sub.bbox.minY;
1157
+ for (const inner of sub.positionedNodes) {
1158
+ innerExtraPositions.set(inner.id, { x: inner.x + offsetX, y: inner.y + offsetY });
1159
+ innerExtraDims.set(inner.id, { w: inner.width, h: inner.height });
1160
+ }
1161
+ }
1162
+
1163
+ // Build cross-cluster anchor overrides: each cross-boundary edge is keyed
1164
+ // by its outer-pass form (super-node IDs), with the override pointing back
1165
+ // at the original inner node IDs. The router applies these to back edges
1166
+ // always, and to forward edges when both endpoints sit inside (different)
1167
+ // directed clusters — so the forward arrow points at the specific inner
1168
+ // node rather than the cluster super-node center. When only one endpoint
1169
+ // is inside a cluster, the forward edge keeps super-node anchoring.
1170
+ const backEdgeAnchorOverrides = new Map<string, { source?: string; target?: string }>();
1171
+ for (const edge of edges) {
1172
+ const fromCluster = memberToDirectedCluster.get(edge.from);
1173
+ const toCluster = memberToDirectedCluster.get(edge.to);
1174
+ if (fromCluster && toCluster && fromCluster.id === toCluster.id) continue;
1175
+ if (!fromCluster && !toCluster) continue;
1176
+ const outerKey = `${fromCluster ? SUPER_NODE_PREFIX + fromCluster.id : edge.from}->${toCluster ? SUPER_NODE_PREFIX + toCluster.id : edge.to}`;
1177
+ backEdgeAnchorOverrides.set(outerKey, {
1178
+ source: fromCluster ? edge.from : undefined,
1179
+ target: toCluster ? edge.to : undefined
1180
+ });
1181
+ }
1182
+
1183
+ const outerPass = finishLayeredPass(
1184
+ outerEdges,
1185
+ outerPositions,
1186
+ direction,
1187
+ spacing,
1188
+ superNodeIds,
1189
+ {
1190
+ extraPositions: innerExtraPositions,
1191
+ extraDims: innerExtraDims,
1192
+ backEdgeAnchorOverrides
1193
+ }
1194
+ );
1195
+
1196
+ const positionedNodes: PositionedNode[] = [];
1197
+ const positionedClusters: PositionedCluster[] = [...outerPass.positionedClusters];
1198
+ const positionedEdges: PositionedEdge[] = [];
1199
+ const waypoints: PositionedWaypoint[] = [...outerPass.waypoints];
1200
+
1201
+ for (const outerNode of outerPass.positionedNodes) {
1202
+ if (isSuperNodeId(outerNode.id)) {
1203
+ const clusterId = outerNode.id.slice(SUPER_NODE_PREFIX.length);
1204
+ const cluster = directedClusterById.get(clusterId)!;
1205
+ const sub = subLayouts.get(clusterId)!;
1206
+ const subSpacing = subSpacings.get(clusterId)!;
1207
+ const labelOnLeft = cluster.labelPosition === 'left';
1208
+ const labelPad = cluster.label ? 32 : 0;
1209
+ const padTop = labelOnLeft ? 0 : labelPad;
1210
+ const padLeft = labelOnLeft ? labelPad : 0;
1211
+ const offsetX = outerNode.x + subSpacing.clusterPadding + padLeft - sub.bbox.minX;
1212
+ const offsetY = outerNode.y + subSpacing.clusterPadding + padTop - sub.bbox.minY;
1213
+
1214
+ for (const inner of sub.positionedNodes) {
1215
+ positionedNodes.push({
1216
+ ...inner,
1217
+ x: inner.x + offsetX,
1218
+ y: inner.y + offsetY
1219
+ });
1220
+ }
1221
+ for (const innerCluster of sub.positionedClusters) {
1222
+ positionedClusters.push({
1223
+ ...innerCluster,
1224
+ x: innerCluster.x + offsetX,
1225
+ y: innerCluster.y + offsetY
1226
+ });
1227
+ }
1228
+ for (const innerEdge of sub.positionedEdges) {
1229
+ positionedEdges.push({
1230
+ ...innerEdge,
1231
+ path: shiftSvgPath(innerEdge.path, offsetX, offsetY),
1232
+ labelX: innerEdge.labelX + offsetX,
1233
+ labelY: innerEdge.labelY + offsetY,
1234
+ bounds: innerEdge.bounds
1235
+ ? {
1236
+ minX: innerEdge.bounds.minX + offsetX,
1237
+ minY: innerEdge.bounds.minY + offsetY,
1238
+ maxX: innerEdge.bounds.maxX + offsetX,
1239
+ maxY: innerEdge.bounds.maxY + offsetY
1240
+ }
1241
+ : undefined
1242
+ });
1243
+ }
1244
+ for (const innerWp of sub.waypoints) {
1245
+ waypoints.push({
1246
+ ...innerWp,
1247
+ x: innerWp.x + offsetX,
1248
+ y: innerWp.y + offsetY
1249
+ });
1250
+ }
1251
+ // Visible cluster chrome over the super-node slot.
1252
+ positionedClusters.push({
1253
+ id: cluster.id,
1254
+ x: outerNode.x,
1255
+ y: outerNode.y,
1256
+ width: outerNode.width,
1257
+ height: outerNode.height,
1258
+ label: cluster.label,
1259
+ labelPosition: cluster.labelPosition,
1260
+ iconComponent: cluster.iconComponent,
1261
+ color: cluster.color || 'neutral',
1262
+ dashed: cluster.dashed ?? true
1263
+ });
1264
+ } else {
1265
+ positionedNodes.push(outerNode);
1266
+ }
1267
+ }
1268
+
1269
+ // Cross-boundary edges keep their super-node anchoring: pointing the arrow
1270
+ // at the cluster as a whole reads more naturally than re-anchoring to a
1271
+ // specific inner node, which would force a Z-shape that conflicts with the
1272
+ // inner flow.
1273
+ positionedEdges.push(...outerPass.positionedEdges);
1274
+
1275
+ const bbox = computeFullPassBounds(
1276
+ positionedNodes,
1277
+ positionedClusters,
1278
+ positionedEdges,
1279
+ waypoints
1280
+ );
1281
+
1282
+ return { positionedNodes, positionedClusters, positionedEdges, waypoints, bbox };
1283
+ }
1284
+
1285
+ function layoutLayered(config: DiagramConfig): LayoutResult {
1286
+ const direction = config.direction || 'TB';
1287
+ const spacing: LayeredSpacing = {
1288
+ nodeGap: config.spacing?.nodeGap ?? DEFAULT_NODE_GAP,
1289
+ layerGap: config.spacing?.layerGap ?? DEFAULT_LAYER_GAP,
1290
+ clusterPadding: config.spacing?.clusterPadding ?? DEFAULT_CLUSTER_PADDING,
1291
+ cornerRadius: config.spacing?.cornerRadius ?? DEFAULT_CORNER_RADIUS,
1292
+ backEdgeLaneGap: config.spacing?.backEdgeLaneGap ?? DEFAULT_BACK_EDGE_LANE_GAP
1293
+ };
1294
+
1295
+ const nested = layoutNested(
1296
+ config.nodes,
1297
+ config.edges,
1298
+ config.clusters ?? [],
1299
+ direction,
1300
+ spacing
1301
+ );
1302
+
1303
+ const positionedNodes = nested.positionedNodes;
1304
+ const positionedClusters = nested.positionedClusters;
1305
+ const positionedEdges = nested.positionedEdges;
1306
+ const waypoints = nested.waypoints;
1307
+
1308
+ const globalPositions = new Map<string, { x: number; y: number }>();
1309
+ const globalDims = new Map<string, { w: number; h: number }>();
1310
+ for (const n of positionedNodes) {
1311
+ globalPositions.set(n.id, { x: n.x, y: n.y });
1312
+ globalDims.set(n.id, { w: n.width, h: n.height });
1313
+ }
1314
+ const annotations = resolveAnnotations(config, globalPositions, globalDims);
1315
+
1316
+ // Compute viewBox with full bounds coverage
1317
+ let minX = Infinity,
1318
+ minY = Infinity,
1319
+ maxX = -Infinity,
1320
+ maxY = -Infinity;
1321
+
1322
+ // Include all node bounds
1323
+ for (const n of positionedNodes) {
1324
+ minX = Math.min(minX, n.x);
1325
+ minY = Math.min(minY, n.y);
1326
+ maxX = Math.max(maxX, n.x + n.width);
1327
+ maxY = Math.max(maxY, n.y + n.height);
1328
+ }
1329
+
1330
+ // Include all cluster bounds
1331
+ for (const c of positionedClusters) {
1332
+ minX = Math.min(minX, c.x);
1333
+ minY = Math.min(minY, c.y);
1334
+ maxX = Math.max(maxX, c.x + c.width);
1335
+ maxY = Math.max(maxY, c.y + c.height);
1336
+ }
1337
+
1338
+ // Include annotation positions (estimated text width)
1339
+ for (const ann of annotations) {
1340
+ const annW = ann.text.length * ANNOTATION_CHAR_WIDTH;
1341
+ minX = Math.min(minX, ann.x);
1342
+ minY = Math.min(minY, ann.y - ANNOTATION_HEIGHT);
1343
+ maxX = Math.max(maxX, ann.x + annW);
1344
+ maxY = Math.max(maxY, ann.y);
1345
+ }
1346
+
1347
+ // Include edge label positions (estimated text width)
1348
+ for (const e of positionedEdges) {
1349
+ if (e.label) {
1350
+ const labelW = e.label.length * 5;
1351
+ minX = Math.min(minX, e.labelX - labelW / 2);
1352
+ minY = Math.min(minY, e.labelY - 7);
1353
+ maxX = Math.max(maxX, e.labelX + labelW / 2);
1354
+ maxY = Math.max(maxY, e.labelY + 7);
1355
+ }
1356
+ // Back-edge paths can extend outside the node bbox; expand viewBox to fit
1357
+ const pathBounds = e.bounds ?? extractPathBounds(e.path);
1358
+ if (pathBounds) {
1359
+ minX = Math.min(minX, pathBounds.minX);
1360
+ minY = Math.min(minY, pathBounds.minY);
1361
+ maxX = Math.max(maxX, pathBounds.maxX);
1362
+ maxY = Math.max(maxY, pathBounds.maxY);
1363
+ }
1364
+ }
1365
+
1366
+ // Handle empty diagram
1367
+ if (minX === Infinity) {
1368
+ minX = 0;
1369
+ minY = 0;
1370
+ maxX = MARGIN * 2;
1371
+ maxY = MARGIN * 2;
1372
+ }
1373
+
1374
+ // Include waypoint bounds
1375
+ for (const w of waypoints) {
1376
+ minX = Math.min(minX, w.x);
1377
+ minY = Math.min(minY, w.y);
1378
+ maxX = Math.max(maxX, w.x + w.width);
1379
+ maxY = Math.max(maxY, w.y + w.height);
1380
+ }
1381
+
1382
+ // Shift everything so all coordinates are non-negative
1383
+ const shiftX = minX < 0 ? -minX + MARGIN : 0;
1384
+ const shiftY = minY < 0 ? -minY + MARGIN : 0;
1385
+
1386
+ if (shiftX > 0 || shiftY > 0) {
1387
+ shiftAllPositions(
1388
+ positionedNodes,
1389
+ positionedClusters,
1390
+ annotations,
1391
+ positionedEdges,
1392
+ shiftX,
1393
+ shiftY
1394
+ );
1395
+ for (const w of waypoints) {
1396
+ w.x += shiftX;
1397
+ w.y += shiftY;
1398
+ }
1399
+ maxX += shiftX;
1400
+ maxY += shiftY;
1401
+ }
1402
+
1403
+ return {
1404
+ nodes: positionedNodes,
1405
+ edges: positionedEdges,
1406
+ clusters: positionedClusters,
1407
+ swimlanes: [],
1408
+ regions: [],
1409
+ annotations,
1410
+ messages: [],
1411
+ lifelines: [],
1412
+ positionedFragments: [],
1413
+ waypoints,
1414
+ viewBox: { width: maxX + MARGIN, height: maxY + MARGIN }
1415
+ };
1416
+ }
1417
+
1418
+ // ── Swimlane Layout ────────────────────────────────────────
1419
+
1420
+ function layoutSwimlane(config: DiagramConfig): LayoutResult {
1421
+ const nodeGap = config.spacing?.nodeGap ?? 32;
1422
+ const lanes = config.swimlanes || [];
1423
+ const nodeDims = buildNodeDims(config.nodes);
1424
+ const headerHeight = 50;
1425
+
1426
+ // Assign lanes
1427
+ const laneMap = new Map<string, number>();
1428
+ for (let i = 0; i < lanes.length; i++) {
1429
+ for (const nid of lanes[i]!.nodes) {
1430
+ laneMap.set(nid, i);
1431
+ }
1432
+ }
1433
+
1434
+ // Compute dynamic lane widths: max(180, widest node in lane + 40)
1435
+ const laneWidths: number[] = lanes.map((lane) => {
1436
+ let maxW = 0;
1437
+ for (const nid of lane.nodes) {
1438
+ const dims = nodeDims.get(nid);
1439
+ if (dims) maxW = Math.max(maxW, dims.w);
1440
+ }
1441
+ return Math.max(180, maxW + 40);
1442
+ });
1443
+
1444
+ // Compute lane start X offsets
1445
+ const laneStartX: number[] = [];
1446
+ let laneX = MARGIN;
1447
+ for (let i = 0; i < laneWidths.length; i++) {
1448
+ laneStartX.push(laneX);
1449
+ laneX += laneWidths[i]!;
1450
+ }
1451
+
1452
+ // Topological order for Y positions
1453
+ const nodeIds = config.nodes.map((n) => n.id);
1454
+ const graph = buildGraph(nodeIds, config.edges);
1455
+
1456
+ // Assign Y positions sequentially
1457
+ const positions = new Map<string, { x: number; y: number }>();
1458
+ let currentY = MARGIN + headerHeight + 20;
1459
+
1460
+ for (const id of graph.order) {
1461
+ const laneIdx = laneMap.get(id) ?? 0;
1462
+ const dims = nodeDims.get(id)!;
1463
+ const lw = laneWidths[laneIdx] ?? 180;
1464
+ const startX = laneStartX[laneIdx] ?? MARGIN;
1465
+ const x = startX + (lw - dims.w) / 2;
1466
+ positions.set(id, { x, y: currentY });
1467
+ currentY += dims.h + nodeGap;
1468
+ }
1469
+
1470
+ const totalHeight = currentY + MARGIN;
1471
+ const totalWidth = MARGIN + laneX;
1472
+
1473
+ const positionedNodes = buildPositionedNodes(config.nodes, positions, nodeDims);
1474
+
1475
+ // Build swimlane positioned data
1476
+ const positionedSwimlanes: PositionedSwimlane[] = lanes.map((lane, i) => ({
1477
+ id: lane.id,
1478
+ label: lane.label,
1479
+ x: laneStartX[i]!,
1480
+ lineX: laneStartX[i]! + laneWidths[i]! / 2,
1481
+ headerY: MARGIN,
1482
+ footerY: totalHeight - MARGIN,
1483
+ lineY1: MARGIN + headerHeight,
1484
+ lineY2: totalHeight - MARGIN,
1485
+ color: lane.color || 'neutral'
1486
+ }));
1487
+
1488
+ // Edges: horizontal arrows between lanes
1489
+ const positionedEdges = computeSwimEdgePaths(
1490
+ config.edges,
1491
+ positions,
1492
+ nodeDims,
1493
+ laneMap,
1494
+ laneWidths[0] ?? 180
1495
+ );
1496
+
1497
+ // Regions
1498
+ const positionedRegions: PositionedRegion[] = (config.regions || []).map((region) => {
1499
+ let minY = Infinity,
1500
+ maxY = -Infinity;
1501
+ let minLane = Infinity,
1502
+ maxLane = -Infinity;
1503
+
1504
+ for (const nid of region.contains) {
1505
+ const pos = positions.get(nid);
1506
+ const dims = nodeDims.get(nid);
1507
+ const laneIdx = laneMap.get(nid);
1508
+ if (pos && dims) {
1509
+ minY = Math.min(minY, pos.y);
1510
+ maxY = Math.max(maxY, pos.y + dims.h);
1511
+ }
1512
+ if (laneIdx !== undefined) {
1513
+ minLane = Math.min(minLane, laneIdx);
1514
+ maxLane = Math.max(maxLane, laneIdx);
1515
+ }
1516
+ }
1517
+
1518
+ if (minY === Infinity) {
1519
+ return {
1520
+ id: region.id,
1521
+ x: 0,
1522
+ y: 0,
1523
+ width: 0,
1524
+ height: 0,
1525
+ label: region.label,
1526
+ color: region.color || 'neutral',
1527
+ dashed: region.dashed ?? true
1528
+ };
1529
+ }
1530
+
1531
+ const pad = 16;
1532
+ const regionX = laneStartX[minLane]! - pad;
1533
+ let regionEndX = laneStartX[maxLane]! + laneWidths[maxLane]! + pad;
1534
+ return {
1535
+ id: region.id,
1536
+ x: regionX,
1537
+ y: minY - pad - 20,
1538
+ width: regionEndX - regionX,
1539
+ height: maxY - minY + pad * 2 + 20,
1540
+ label: region.label,
1541
+ color: region.color || 'neutral',
1542
+ dashed: region.dashed ?? true
1543
+ };
1544
+ });
1545
+
1546
+ // Annotations
1547
+ const annotations = resolveAnnotations(config, positions, nodeDims);
1548
+
1549
+ // Compute viewBox with full bounds coverage
1550
+ let minX = Infinity,
1551
+ minY = Infinity,
1552
+ maxViewX = -Infinity,
1553
+ maxViewY = -Infinity;
1554
+
1555
+ for (const n of positionedNodes) {
1556
+ minX = Math.min(minX, n.x);
1557
+ minY = Math.min(minY, n.y);
1558
+ maxViewX = Math.max(maxViewX, n.x + n.width);
1559
+ maxViewY = Math.max(maxViewY, n.y + n.height);
1560
+ }
1561
+
1562
+ // Include swimlane headers
1563
+ for (let si = 0; si < positionedSwimlanes.length; si++) {
1564
+ const sl = positionedSwimlanes[si]!;
1565
+ minX = Math.min(minX, sl.x);
1566
+ minY = Math.min(minY, sl.headerY);
1567
+ maxViewX = Math.max(maxViewX, sl.x + (laneWidths[si] ?? 180));
1568
+ maxViewY = Math.max(maxViewY, sl.lineY2);
1569
+ }
1570
+
1571
+ // Include annotation positions
1572
+ for (const ann of annotations) {
1573
+ const annW = ann.text.length * ANNOTATION_CHAR_WIDTH;
1574
+ minX = Math.min(minX, ann.x);
1575
+ minY = Math.min(minY, ann.y - ANNOTATION_HEIGHT);
1576
+ maxViewX = Math.max(maxViewX, ann.x + annW);
1577
+ maxViewY = Math.max(maxViewY, ann.y);
1578
+ }
1579
+
1580
+ // Include edge labels
1581
+ for (const e of positionedEdges) {
1582
+ if (e.label) {
1583
+ const labelW = e.label.length * 5;
1584
+ minX = Math.min(minX, e.labelX - labelW / 2);
1585
+ minY = Math.min(minY, e.labelY - 7);
1586
+ maxViewX = Math.max(maxViewX, e.labelX + labelW / 2);
1587
+ maxViewY = Math.max(maxViewY, e.labelY + 7);
1588
+ }
1589
+ }
1590
+
1591
+ // Include regions
1592
+ for (const r of positionedRegions) {
1593
+ minX = Math.min(minX, r.x);
1594
+ minY = Math.min(minY, r.y);
1595
+ maxViewX = Math.max(maxViewX, r.x + r.width);
1596
+ maxViewY = Math.max(maxViewY, r.y + r.height);
1597
+ }
1598
+
1599
+ // Handle empty diagram
1600
+ if (minX === Infinity) {
1601
+ minX = 0;
1602
+ minY = 0;
1603
+ maxViewX = totalWidth;
1604
+ maxViewY = totalHeight;
1605
+ }
1606
+
1607
+ // Shift everything so all coordinates are non-negative
1608
+ const shiftX = minX < 0 ? -minX + MARGIN : 0;
1609
+ const shiftY = minY < 0 ? -minY + MARGIN : 0;
1610
+
1611
+ if (shiftX > 0 || shiftY > 0) {
1612
+ shiftAllPositions(positionedNodes, [], annotations, positionedEdges, shiftX, shiftY);
1613
+ // Also shift swimlanes and regions
1614
+ for (const sl of positionedSwimlanes) {
1615
+ sl.x += shiftX;
1616
+ sl.lineX += shiftX;
1617
+ sl.headerY += shiftY;
1618
+ sl.lineY1 += shiftY;
1619
+ sl.lineY2 += shiftY;
1620
+ }
1621
+ for (const r of positionedRegions) {
1622
+ r.x += shiftX;
1623
+ r.y += shiftY;
1624
+ }
1625
+ maxViewX += shiftX;
1626
+ maxViewY += shiftY;
1627
+ }
1628
+
1629
+ return {
1630
+ nodes: positionedNodes,
1631
+ edges: positionedEdges,
1632
+ clusters: [],
1633
+ swimlanes: positionedSwimlanes,
1634
+ regions: positionedRegions,
1635
+ annotations,
1636
+ messages: [],
1637
+ lifelines: [],
1638
+ positionedFragments: [],
1639
+ waypoints: [],
1640
+ viewBox: {
1641
+ width: Math.max(totalWidth, maxViewX + MARGIN),
1642
+ height: Math.max(totalHeight, maxViewY + MARGIN)
1643
+ }
1644
+ };
1645
+ }
1646
+
1647
+ // ── Shared Helpers ─────────────────────────────────────────
1648
+
1649
+ /** Build a map from nodeId -> clusterId for cluster-aware ordering */
1650
+ function buildClusterMap(config: DiagramConfig): Map<string, string> {
1651
+ const map = new Map<string, string>();
1652
+ if (!config.clusters) return map;
1653
+ for (const cluster of config.clusters) {
1654
+ for (const nodeId of cluster.nodes) {
1655
+ map.set(nodeId, cluster.id);
1656
+ }
1657
+ }
1658
+ return map;
1659
+ }
1660
+
1661
+ /** Shift all positioned elements by (dx, dy) to ensure non-negative coordinates */
1662
+ function shiftAllPositions(
1663
+ nodes: PositionedNode[],
1664
+ clusters: PositionedCluster[],
1665
+ annotations: PositionedAnnotation[],
1666
+ edges: PositionedEdge[],
1667
+ dx: number,
1668
+ dy: number
1669
+ ): void {
1670
+ for (const n of nodes) {
1671
+ n.x += dx;
1672
+ n.y += dy;
1673
+ }
1674
+ for (const c of clusters) {
1675
+ c.x += dx;
1676
+ c.y += dy;
1677
+ }
1678
+ for (const a of annotations) {
1679
+ a.x += dx;
1680
+ a.y += dy;
1681
+ }
1682
+ for (const e of edges) {
1683
+ e.labelX += dx;
1684
+ e.labelY += dy;
1685
+ // Shift SVG path coordinates
1686
+ e.path = shiftSvgPath(e.path, dx, dy);
1687
+ if (e.bounds) {
1688
+ e.bounds = {
1689
+ minX: e.bounds.minX + dx,
1690
+ minY: e.bounds.minY + dy,
1691
+ maxX: e.bounds.maxX + dx,
1692
+ maxY: e.bounds.maxY + dy
1693
+ };
1694
+ }
1695
+ }
1696
+ }
1697
+
1698
+ const PATH_CMD_RE = /([MLQTSC])([^MLQTSCAHVZmlqtscahvz]*)/g;
1699
+ const NUM_RE = /-?\d+(?:\.\d+)?/g;
1700
+
1701
+ /** Extract min/max x/y from an SVG path string (absolute commands only).
1702
+ * Used to expand viewBox to include back-edge paths that extend beyond node bounds.
1703
+ */
1704
+ function extractPathBounds(
1705
+ path: string
1706
+ ): { minX: number; minY: number; maxX: number; maxY: number } | null {
1707
+ if (!path) return null;
1708
+ let minX = Infinity,
1709
+ minY = Infinity,
1710
+ maxX = -Infinity,
1711
+ maxY = -Infinity;
1712
+ for (const match of path.matchAll(PATH_CMD_RE)) {
1713
+ const args = match[2] ?? '';
1714
+ const nums = args.match(NUM_RE);
1715
+ if (!nums) continue;
1716
+ for (let i = 0; i + 1 < nums.length; i += 2) {
1717
+ const x = parseFloat(nums[i]!);
1718
+ const y = parseFloat(nums[i + 1]!);
1719
+ if (x < minX) minX = x;
1720
+ if (x > maxX) maxX = x;
1721
+ if (y < minY) minY = y;
1722
+ if (y > maxY) maxY = y;
1723
+ }
1724
+ }
1725
+ if (minX === Infinity) return null;
1726
+ return { minX, minY, maxX, maxY };
1727
+ }
1728
+
1729
+ /** Shift all absolute coordinates in an SVG path string by (dx, dy).
1730
+ * Handles multi-pair commands (Q, C, S, T) where the args contain more than one (x, y).
1731
+ */
1732
+ function shiftSvgPath(path: string, dx: number, dy: number): string {
1733
+ return path.replace(PATH_CMD_RE, (_match, cmd: string, args: string) => {
1734
+ const nums = args.match(NUM_RE);
1735
+ if (!nums || nums.length === 0) return cmd;
1736
+ const shifted = nums.map((n, i) => {
1737
+ const v = parseFloat(n);
1738
+ return (i % 2 === 0 ? v + dx : v + dy).toString();
1739
+ });
1740
+ return `${cmd} ${shifted.join(' ')}`;
1741
+ });
1742
+ }
1743
+
1744
+ function buildNodeDims(nodes: DiagramConfig['nodes']): Map<string, { w: number; h: number }> {
1745
+ const dims = new Map<string, { w: number; h: number }>();
1746
+ for (const node of nodes) {
1747
+ const w = node.width ?? estimateNodeWidth(node.label, node.description);
1748
+ const h = node.height ?? (node.description ? DESC_NODE_HEIGHT : DEFAULT_NODE_HEIGHT);
1749
+ dims.set(node.id, { w, h });
1750
+ }
1751
+ return dims;
1752
+ }
1753
+
1754
+ function buildPositionedNodes(
1755
+ nodes: DiagramConfig['nodes'],
1756
+ positions: Map<string, { x: number; y: number }>,
1757
+ nodeDims: Map<string, { w: number; h: number }>
1758
+ ): PositionedNode[] {
1759
+ return nodes.map((node) => {
1760
+ const pos = positions.get(node.id) || { x: 0, y: 0 };
1761
+ const dims = nodeDims.get(node.id)!;
1762
+ return {
1763
+ id: node.id,
1764
+ x: pos.x,
1765
+ y: pos.y,
1766
+ width: dims.w,
1767
+ height: dims.h,
1768
+ label: node.label,
1769
+ description: node.description,
1770
+ icon: node.icon,
1771
+ iconComponent: node.iconComponent,
1772
+ variant: node.variant || 'default',
1773
+ color: node.color || 'neutral',
1774
+ state: node.state || 'default'
1775
+ };
1776
+ });
1777
+ }
1778
+
1779
+ // ── Swimlane Edge Paths ────────────────────────────────────
1780
+
1781
+ function computeSwimEdgePaths(
1782
+ edges: DiagramConfig['edges'],
1783
+ positions: Map<string, { x: number; y: number }>,
1784
+ nodeDims: Map<string, { w: number; h: number }>,
1785
+ laneMap: Map<string, number>,
1786
+ laneWidth: number
1787
+ ): PositionedEdge[] {
1788
+ return edges.map((edge) => {
1789
+ const fromPos = positions.get(edge.from);
1790
+ const toPos = positions.get(edge.to);
1791
+ const fromDims = nodeDims.get(edge.from);
1792
+ const toDims = nodeDims.get(edge.to);
1793
+
1794
+ if (!fromPos || !toPos || !fromDims || !toDims) {
1795
+ return emptyEdge(edge);
1796
+ }
1797
+
1798
+ const fromLane = laneMap.get(edge.from) ?? 0;
1799
+ const toLane = laneMap.get(edge.to) ?? 0;
1800
+
1801
+ let path: string;
1802
+ let labelX: number;
1803
+ let labelY: number;
1804
+
1805
+ if (fromLane === toLane) {
1806
+ // Same lane: vertical arrow
1807
+ const cx = fromPos.x + fromDims.w / 2;
1808
+ const y1 = fromPos.y + fromDims.h;
1809
+ const y2 = toPos.y;
1810
+ path = `M ${cx} ${y1} L ${cx} ${y2}`;
1811
+ labelX = cx + 10;
1812
+ labelY = (y1 + y2) / 2;
1813
+ } else {
1814
+ // Cross-lane: horizontal arrow at midpoint Y
1815
+ const midY = fromPos.y + fromDims.h / 2;
1816
+ const x1 = fromLane < toLane ? fromPos.x + fromDims.w : fromPos.x;
1817
+ const x2 = fromLane < toLane ? toPos.x : toPos.x + toDims.w;
1818
+ path = `M ${x1} ${midY} L ${x2} ${midY}`;
1819
+ labelX = (x1 + x2) / 2;
1820
+ labelY = midY - 8;
1821
+ }
1822
+
1823
+ return {
1824
+ from: edge.from,
1825
+ to: edge.to,
1826
+ path,
1827
+ label: edge.label,
1828
+ labelX,
1829
+ labelY,
1830
+ arrow: edge.arrow || 'end',
1831
+ dashed: edge.dashed || false,
1832
+ color: edge.color || 'neutral'
1833
+ };
1834
+ });
1835
+ }
1836
+
1837
+ // ── Public API ─────────────────────────────────────────────
1838
+
1839
+ // ── Sequence Layout ───────────────────────────────────────
1840
+
1841
+ const SEQ_ACTOR_GAP = 180;
1842
+ const SEQ_MESSAGE_GAP = 40;
1843
+ const SEQ_ACTOR_BOX_HEIGHT = 36;
1844
+ const SEQ_TOP_MARGIN = 40;
1845
+ const SEQ_SELF_LOOP_WIDTH = 30;
1846
+ const SEQ_SELF_LOOP_HEIGHT = 24;
1847
+ const SEQ_FRAGMENT_PAD_X = 20;
1848
+ const SEQ_FRAGMENT_PAD_Y = 16;
1849
+ const SEQ_FRAGMENT_TAG_HEIGHT = 20;
1850
+
1851
+ function layoutSequence(config: DiagramConfig): LayoutResult {
1852
+ const actors = config.nodes;
1853
+ const messages = config.messages || [];
1854
+ const fragments = config.fragments || [];
1855
+
1856
+ // Position actors evenly across the x-axis
1857
+ const actorXMap = new Map<string, number>();
1858
+ const actorColorMap = new Map<string, string>();
1859
+ for (let i = 0; i < actors.length; i++) {
1860
+ const actor = actors[i]!;
1861
+ const x = MARGIN + i * SEQ_ACTOR_GAP + SEQ_ACTOR_GAP / 2;
1862
+ actorXMap.set(actor.id, x);
1863
+ actorColorMap.set(actor.id, actor.color || 'neutral');
1864
+ }
1865
+
1866
+ // Compute message Y positions (increment per message)
1867
+ const messageStartY = SEQ_TOP_MARGIN + SEQ_ACTOR_BOX_HEIGHT + 30;
1868
+ const messageYPositions: number[] = [];
1869
+ let currentY = messageStartY;
1870
+ for (let i = 0; i < messages.length; i++) {
1871
+ const msg = messages[i]!;
1872
+ messageYPositions.push(currentY);
1873
+ const isSelf = msg.from === msg.to;
1874
+ currentY += isSelf ? SEQ_MESSAGE_GAP + SEQ_SELF_LOOP_HEIGHT : SEQ_MESSAGE_GAP;
1875
+ }
1876
+
1877
+ // Bottom of the diagram
1878
+ const bottomY = currentY + 30 + SEQ_ACTOR_BOX_HEIGHT;
1879
+ const totalHeight = bottomY + MARGIN;
1880
+ const totalWidth = MARGIN * 2 + actors.length * SEQ_ACTOR_GAP;
1881
+
1882
+ // Build lifelines
1883
+ const lifelines: PositionedLifeline[] = actors.map((actor) => ({
1884
+ id: actor.id,
1885
+ label: actor.label,
1886
+ x: actorXMap.get(actor.id)!,
1887
+ topY: SEQ_TOP_MARGIN,
1888
+ bottomY: bottomY,
1889
+ color: (actor.color || 'neutral') as PositionedLifeline['color']
1890
+ }));
1891
+
1892
+ // Build positioned messages
1893
+ const positionedMessages: PositionedMessage[] = messages.map((msg, i) => {
1894
+ const fromX = actorXMap.get(msg.from) ?? 0;
1895
+ const toX = actorXMap.get(msg.to) ?? 0;
1896
+ const y = messageYPositions[i] ?? messageStartY;
1897
+ const isSelf = msg.from === msg.to;
1898
+
1899
+ let labelX: number;
1900
+ let labelY: number;
1901
+
1902
+ if (isSelf) {
1903
+ labelX = fromX + SEQ_SELF_LOOP_WIDTH + 8;
1904
+ labelY = y + SEQ_SELF_LOOP_HEIGHT / 2;
1905
+ } else {
1906
+ labelX = (fromX + toX) / 2;
1907
+ labelY = y - 8;
1908
+ }
1909
+
1910
+ return {
1911
+ from: msg.from,
1912
+ to: msg.to,
1913
+ label: msg.label,
1914
+ x1: fromX,
1915
+ y,
1916
+ x2: toX,
1917
+ labelX,
1918
+ labelY,
1919
+ arrow: msg.arrow || 'end',
1920
+ dashed: msg.dashed || false,
1921
+ color: (msg.color || 'neutral') as PositionedMessage['color'],
1922
+ isSelf
1923
+ };
1924
+ });
1925
+
1926
+ // Build positioned fragments
1927
+ const positionedFragments: PositionedFragment[] = fragments.map((frag) => {
1928
+ if (frag.messages.length === 0) {
1929
+ return {
1930
+ id: frag.id,
1931
+ label: frag.label,
1932
+ condition: frag.condition,
1933
+ x: 0,
1934
+ y: 0,
1935
+ width: 0,
1936
+ height: 0,
1937
+ color: (frag.color || 'neutral') as PositionedFragment['color'],
1938
+ dashed: frag.dashed ?? false
1939
+ };
1940
+ }
1941
+
1942
+ // Find bounds from contained messages
1943
+ let minX = Infinity;
1944
+ let maxX = -Infinity;
1945
+ let minY = Infinity;
1946
+ let maxY = -Infinity;
1947
+
1948
+ for (const msgIdx of frag.messages) {
1949
+ if (msgIdx < 0 || msgIdx >= positionedMessages.length) continue;
1950
+ const msg = positionedMessages[msgIdx]!;
1951
+ const leftX = Math.min(msg.x1, msg.x2);
1952
+ const rightX = msg.isSelf ? msg.x1 + SEQ_SELF_LOOP_WIDTH : Math.max(msg.x1, msg.x2);
1953
+ minX = Math.min(minX, leftX);
1954
+ maxX = Math.max(maxX, rightX);
1955
+ minY = Math.min(minY, msg.y);
1956
+ maxY = Math.max(maxY, msg.isSelf ? msg.y + SEQ_SELF_LOOP_HEIGHT : msg.y);
1957
+ }
1958
+
1959
+ if (minX === Infinity) {
1960
+ return {
1961
+ id: frag.id,
1962
+ label: frag.label,
1963
+ condition: frag.condition,
1964
+ x: 0,
1965
+ y: 0,
1966
+ width: 0,
1967
+ height: 0,
1968
+ color: (frag.color || 'neutral') as PositionedFragment['color'],
1969
+ dashed: frag.dashed ?? false
1970
+ };
1971
+ }
1972
+
1973
+ return {
1974
+ id: frag.id,
1975
+ label: frag.label,
1976
+ condition: frag.condition,
1977
+ x: minX - SEQ_FRAGMENT_PAD_X,
1978
+ y: minY - SEQ_FRAGMENT_PAD_Y - SEQ_FRAGMENT_TAG_HEIGHT,
1979
+ width: maxX - minX + SEQ_FRAGMENT_PAD_X * 2,
1980
+ height: maxY - minY + SEQ_FRAGMENT_PAD_Y * 2 + SEQ_FRAGMENT_TAG_HEIGHT,
1981
+ color: (frag.color || 'neutral') as PositionedFragment['color'],
1982
+ dashed: frag.dashed ?? false
1983
+ };
1984
+ });
1985
+
1986
+ // Annotations
1987
+ const positions = new Map<string, { x: number; y: number }>();
1988
+ const nodeDims = new Map<string, { w: number; h: number }>();
1989
+ for (const actor of actors) {
1990
+ const x = actorXMap.get(actor.id)!;
1991
+ positions.set(actor.id, { x: x - 60, y: SEQ_TOP_MARGIN });
1992
+ nodeDims.set(actor.id, { w: 120, h: SEQ_ACTOR_BOX_HEIGHT });
1993
+ }
1994
+ const annotations = resolveAnnotations(config, positions, nodeDims);
1995
+
1996
+ return {
1997
+ nodes: [],
1998
+ edges: [],
1999
+ clusters: [],
2000
+ swimlanes: [],
2001
+ regions: [],
2002
+ annotations,
2003
+ messages: positionedMessages,
2004
+ lifelines,
2005
+ positionedFragments,
2006
+ waypoints: [],
2007
+ viewBox: { width: totalWidth, height: totalHeight }
2008
+ };
2009
+ }
2010
+
2011
+ // ── Public API ─────────────────────────────────────────────
2012
+
2013
+ export function computeLayout(config: DiagramConfig): LayoutResult {
2014
+ const cached = fullLayoutCache.get(config);
2015
+ if (cached) return cached;
2016
+
2017
+ let result: LayoutResult;
2018
+ if (config.layout === 'sequence') result = layoutSequence(config);
2019
+ else if (config.layout === 'swimlane') result = layoutSwimlane(config);
2020
+ else result = layoutLayered(config);
2021
+
2022
+ fullLayoutCache.set(config, result);
2023
+ return result;
2024
+ }