@dryui/ui 0.2.2 → 0.3.0
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.
- package/dist/adjust/adjust.svelte +1 -1
- package/dist/aurora/aurora.svelte +58 -11
- package/dist/aurora/aurora.svelte.d.ts +1 -0
- package/dist/aurora/index.d.ts +2 -0
- package/dist/diagram/diagram.svelte +769 -0
- package/dist/diagram/diagram.svelte.d.ts +10 -0
- package/dist/diagram/edge-routing.d.ts +9 -0
- package/dist/diagram/edge-routing.js +281 -0
- package/dist/diagram/index.d.ts +9 -0
- package/dist/diagram/index.js +1 -0
- package/dist/diagram/layout.d.ts +2 -0
- package/dist/diagram/layout.js +985 -0
- package/dist/diagram/types.d.ts +196 -0
- package/dist/displacement/displacement.svelte +39 -3
- package/dist/drag-and-drop/context.svelte.d.ts +1 -0
- package/dist/drag-and-drop/drag-and-drop-group.svelte +37 -0
- package/dist/drag-and-drop/drag-and-drop-group.svelte.d.ts +8 -0
- package/dist/drag-and-drop/drag-and-drop-handle.svelte +2 -0
- package/dist/drag-and-drop/drag-and-drop-item.svelte +25 -6
- package/dist/drag-and-drop/drag-and-drop-root.svelte +624 -34
- package/dist/drag-and-drop/group-context.svelte.d.ts +13 -0
- package/dist/drag-and-drop/group-context.svelte.js +8 -0
- package/dist/drag-and-drop/index.d.ts +2 -0
- package/dist/drag-and-drop/index.js +3 -1
- package/dist/drawer/drawer-content.svelte +2 -1
- package/dist/drawer/drawer-footer.svelte +1 -1
- package/dist/gradient-mesh/gradient-mesh.svelte +42 -5
- package/dist/halftone/halftone.svelte +16 -36
- package/dist/index.d.ts +1 -4
- package/dist/index.js +1 -2
- package/dist/logo-mark/logo-mark.svelte +2 -2
- package/dist/noise/noise.svelte +2 -2
- package/dist/shader-canvas/presets.js +1 -1
- package/dist/shader-canvas/shader-canvas.svelte +4 -1
- package/dist/sidebar/sidebar-footer.svelte +5 -4
- package/dist/spotlight/spotlight.svelte +42 -4
- package/dist/themes/aurora.css +229 -0
- package/dist/themes/dark.css +134 -0
- package/dist/themes/default.css +76 -0
- package/dist/themes/midnight.css +142 -0
- package/dist/themes/terminal.css +175 -0
- package/package.json +22 -27
- package/skills/dryui/SKILL.md +1 -0
- package/dist/system-map/index.d.ts +0 -17
- package/dist/system-map/index.js +0 -1
- package/dist/system-map/system-map.svelte +0 -311
- package/dist/system-map/system-map.svelte.d.ts +0 -25
- package/dist/system-map/types.d.ts +0 -99
- package/dist/thumbnail/_layout-content.svelte +0 -89
- package/dist/thumbnail/_layout-content.svelte.d.ts +0 -9
- package/dist/thumbnail/_layout-footer.svelte +0 -54
- package/dist/thumbnail/_layout-footer.svelte.d.ts +0 -9
- package/dist/thumbnail/_layout-header.svelte +0 -76
- package/dist/thumbnail/_layout-header.svelte.d.ts +0 -9
- package/dist/thumbnail/_layout-sidebar.svelte +0 -75
- package/dist/thumbnail/_layout-sidebar.svelte.d.ts +0 -9
- package/dist/thumbnail/accordion.svelte +0 -144
- package/dist/thumbnail/accordion.svelte.d.ts +0 -7
- package/dist/thumbnail/add-on-selector.svelte +0 -213
- package/dist/thumbnail/add-on-selector.svelte.d.ts +0 -7
- package/dist/thumbnail/affix-group.svelte +0 -55
- package/dist/thumbnail/affix-group.svelte.d.ts +0 -7
- package/dist/thumbnail/alert-dialog.svelte +0 -100
- package/dist/thumbnail/alert-dialog.svelte.d.ts +0 -7
- package/dist/thumbnail/alert.svelte +0 -79
- package/dist/thumbnail/alert.svelte.d.ts +0 -7
- package/dist/thumbnail/alpha-slider.svelte +0 -55
- package/dist/thumbnail/alpha-slider.svelte.d.ts +0 -7
- package/dist/thumbnail/amenity-grid.svelte +0 -236
- package/dist/thumbnail/amenity-grid.svelte.d.ts +0 -7
- package/dist/thumbnail/app-frame.svelte.d.ts +0 -7
- package/dist/thumbnail/apply-size.d.ts +0 -3
- package/dist/thumbnail/apply-size.js +0 -14
- package/dist/thumbnail/aspect-ratio.svelte +0 -64
- package/dist/thumbnail/aspect-ratio.svelte.d.ts +0 -7
- package/dist/thumbnail/aurora.svelte +0 -88
- package/dist/thumbnail/aurora.svelte.d.ts +0 -7
- package/dist/thumbnail/avatar-group.svelte.d.ts +0 -7
- package/dist/thumbnail/avatar.svelte +0 -60
- package/dist/thumbnail/avatar.svelte.d.ts +0 -7
- package/dist/thumbnail/backdrop.svelte +0 -73
- package/dist/thumbnail/backdrop.svelte.d.ts +0 -7
- package/dist/thumbnail/badge.svelte +0 -100
- package/dist/thumbnail/badge.svelte.d.ts +0 -7
- package/dist/thumbnail/booking-confirmation.svelte +0 -109
- package/dist/thumbnail/booking-confirmation.svelte.d.ts +0 -7
- package/dist/thumbnail/breadcrumb.svelte +0 -80
- package/dist/thumbnail/breadcrumb.svelte.d.ts +0 -7
- package/dist/thumbnail/button-group.svelte +0 -76
- package/dist/thumbnail/button-group.svelte.d.ts +0 -7
- package/dist/thumbnail/button.svelte +0 -62
- package/dist/thumbnail/button.svelte.d.ts +0 -7
- package/dist/thumbnail/calendar.svelte +0 -86
- package/dist/thumbnail/calendar.svelte.d.ts +0 -7
- package/dist/thumbnail/card.svelte +0 -113
- package/dist/thumbnail/card.svelte.d.ts +0 -7
- package/dist/thumbnail/carousel.svelte +0 -111
- package/dist/thumbnail/carousel.svelte.d.ts +0 -7
- package/dist/thumbnail/chart.svelte +0 -93
- package/dist/thumbnail/chart.svelte.d.ts +0 -7
- package/dist/thumbnail/chat-message.svelte.d.ts +0 -7
- package/dist/thumbnail/chat-thread.svelte +0 -140
- package/dist/thumbnail/chat-thread.svelte.d.ts +0 -7
- package/dist/thumbnail/checkbox.svelte +0 -71
- package/dist/thumbnail/checkbox.svelte.d.ts +0 -7
- package/dist/thumbnail/chip-group.svelte +0 -79
- package/dist/thumbnail/chip-group.svelte.d.ts +0 -7
- package/dist/thumbnail/chip.svelte +0 -76
- package/dist/thumbnail/chip.svelte.d.ts +0 -7
- package/dist/thumbnail/chromatic-shift.svelte +0 -55
- package/dist/thumbnail/chromatic-shift.svelte.d.ts +0 -7
- package/dist/thumbnail/clipboard.svelte +0 -68
- package/dist/thumbnail/clipboard.svelte.d.ts +0 -7
- package/dist/thumbnail/code-block.svelte +0 -125
- package/dist/thumbnail/code-block.svelte.d.ts +0 -7
- package/dist/thumbnail/collapsible.svelte +0 -101
- package/dist/thumbnail/collapsible.svelte.d.ts +0 -7
- package/dist/thumbnail/color-picker.svelte +0 -108
- package/dist/thumbnail/color-picker.svelte.d.ts +0 -7
- package/dist/thumbnail/combobox.svelte +0 -122
- package/dist/thumbnail/combobox.svelte.d.ts +0 -7
- package/dist/thumbnail/command-palette.svelte +0 -134
- package/dist/thumbnail/command-palette.svelte.d.ts +0 -7
- package/dist/thumbnail/commerce-header.svelte +0 -55
- package/dist/thumbnail/commerce-header.svelte.d.ts +0 -7
- package/dist/thumbnail/comparison-table.svelte +0 -193
- package/dist/thumbnail/comparison-table.svelte.d.ts +0 -7
- package/dist/thumbnail/container.svelte +0 -119
- package/dist/thumbnail/container.svelte.d.ts +0 -7
- package/dist/thumbnail/context-menu.svelte +0 -108
- package/dist/thumbnail/context-menu.svelte.d.ts +0 -7
- package/dist/thumbnail/country-select.svelte +0 -111
- package/dist/thumbnail/country-select.svelte.d.ts +0 -7
- package/dist/thumbnail/currency-selector.svelte +0 -111
- package/dist/thumbnail/currency-selector.svelte.d.ts +0 -7
- package/dist/thumbnail/data-grid.svelte +0 -230
- package/dist/thumbnail/data-grid.svelte.d.ts +0 -7
- package/dist/thumbnail/date-field.svelte +0 -132
- package/dist/thumbnail/date-field.svelte.d.ts +0 -7
- package/dist/thumbnail/date-picker.svelte +0 -126
- package/dist/thumbnail/date-picker.svelte.d.ts +0 -7
- package/dist/thumbnail/date-range-picker.svelte +0 -100
- package/dist/thumbnail/date-range-picker.svelte.d.ts +0 -7
- package/dist/thumbnail/date-time-input.svelte +0 -182
- package/dist/thumbnail/date-time-input.svelte.d.ts +0 -7
- package/dist/thumbnail/description-list.svelte +0 -142
- package/dist/thumbnail/description-list.svelte.d.ts +0 -7
- package/dist/thumbnail/dialog.svelte +0 -136
- package/dist/thumbnail/dialog.svelte.d.ts +0 -7
- package/dist/thumbnail/displacement.svelte +0 -55
- package/dist/thumbnail/displacement.svelte.d.ts +0 -7
- package/dist/thumbnail/drag-and-drop.svelte +0 -139
- package/dist/thumbnail/drag-and-drop.svelte.d.ts +0 -7
- package/dist/thumbnail/drawer.svelte +0 -112
- package/dist/thumbnail/drawer.svelte.d.ts +0 -7
- package/dist/thumbnail/drop-zone.svelte +0 -55
- package/dist/thumbnail/drop-zone.svelte.d.ts +0 -7
- package/dist/thumbnail/dropdown-menu.svelte +0 -128
- package/dist/thumbnail/dropdown-menu.svelte.d.ts +0 -7
- package/dist/thumbnail/empty-state.svelte.d.ts +0 -7
- package/dist/thumbnail/fare-class-picker.svelte +0 -225
- package/dist/thumbnail/fare-class-picker.svelte.d.ts +0 -7
- package/dist/thumbnail/feature-split-section.svelte.d.ts +0 -7
- package/dist/thumbnail/field.svelte +0 -82
- package/dist/thumbnail/field.svelte.d.ts +0 -7
- package/dist/thumbnail/fieldset.svelte +0 -123
- package/dist/thumbnail/fieldset.svelte.d.ts +0 -7
- package/dist/thumbnail/file-select.svelte +0 -90
- package/dist/thumbnail/file-select.svelte.d.ts +0 -7
- package/dist/thumbnail/file-upload.svelte +0 -84
- package/dist/thumbnail/file-upload.svelte.d.ts +0 -7
- package/dist/thumbnail/filter-sidebar.svelte +0 -201
- package/dist/thumbnail/filter-sidebar.svelte.d.ts +0 -7
- package/dist/thumbnail/flexible-dates-grid.svelte +0 -210
- package/dist/thumbnail/flexible-dates-grid.svelte.d.ts +0 -7
- package/dist/thumbnail/flight-timeline.svelte +0 -155
- package/dist/thumbnail/flight-timeline.svelte.d.ts +0 -7
- package/dist/thumbnail/flip-card.svelte +0 -128
- package/dist/thumbnail/flip-card.svelte.d.ts +0 -7
- package/dist/thumbnail/float-button.svelte +0 -63
- package/dist/thumbnail/float-button.svelte.d.ts +0 -7
- package/dist/thumbnail/focus-trap.svelte +0 -110
- package/dist/thumbnail/focus-trap.svelte.d.ts +0 -7
- package/dist/thumbnail/format-bytes.svelte +0 -51
- package/dist/thumbnail/format-bytes.svelte.d.ts +0 -7
- package/dist/thumbnail/format-date.svelte +0 -51
- package/dist/thumbnail/format-date.svelte.d.ts +0 -7
- package/dist/thumbnail/format-number.svelte +0 -51
- package/dist/thumbnail/format-number.svelte.d.ts +0 -7
- package/dist/thumbnail/gauge.svelte +0 -90
- package/dist/thumbnail/gauge.svelte.d.ts +0 -7
- package/dist/thumbnail/glass-surface.svelte.d.ts +0 -7
- package/dist/thumbnail/glow.svelte +0 -55
- package/dist/thumbnail/glow.svelte.d.ts +0 -7
- package/dist/thumbnail/gradient-mesh.svelte +0 -55
- package/dist/thumbnail/gradient-mesh.svelte.d.ts +0 -7
- package/dist/thumbnail/guest-room-selector.svelte +0 -214
- package/dist/thumbnail/guest-room-selector.svelte.d.ts +0 -7
- package/dist/thumbnail/halftone.svelte +0 -55
- package/dist/thumbnail/halftone.svelte.d.ts +0 -7
- package/dist/thumbnail/heading.svelte +0 -91
- package/dist/thumbnail/heading.svelte.d.ts +0 -7
- package/dist/thumbnail/hotel-gallery.svelte +0 -117
- package/dist/thumbnail/hotel-gallery.svelte.d.ts +0 -7
- package/dist/thumbnail/hotkey.svelte +0 -108
- package/dist/thumbnail/hotkey.svelte.d.ts +0 -7
- package/dist/thumbnail/hover-card.svelte +0 -117
- package/dist/thumbnail/hover-card.svelte.d.ts +0 -7
- package/dist/thumbnail/icon.svelte +0 -55
- package/dist/thumbnail/icon.svelte.d.ts +0 -7
- package/dist/thumbnail/image-comparison.svelte +0 -96
- package/dist/thumbnail/image-comparison.svelte.d.ts +0 -7
- package/dist/thumbnail/image.svelte +0 -74
- package/dist/thumbnail/image.svelte.d.ts +0 -7
- package/dist/thumbnail/index.d.ts +0 -12
- package/dist/thumbnail/index.js +0 -359
- package/dist/thumbnail/infinite-scroll.svelte +0 -129
- package/dist/thumbnail/infinite-scroll.svelte.d.ts +0 -7
- package/dist/thumbnail/input-group.svelte +0 -55
- package/dist/thumbnail/input-group.svelte.d.ts +0 -7
- package/dist/thumbnail/input.svelte +0 -74
- package/dist/thumbnail/input.svelte.d.ts +0 -7
- package/dist/thumbnail/itinerary-timeline.svelte +0 -143
- package/dist/thumbnail/itinerary-timeline.svelte.d.ts +0 -7
- package/dist/thumbnail/kbd.svelte +0 -112
- package/dist/thumbnail/kbd.svelte.d.ts +0 -7
- package/dist/thumbnail/label.svelte +0 -54
- package/dist/thumbnail/label.svelte.d.ts +0 -7
- package/dist/thumbnail/layout-header-content-footer.svelte +0 -64
- package/dist/thumbnail/layout-header-content-footer.svelte.d.ts +0 -7
- package/dist/thumbnail/layout-header-sidebar-main.svelte +0 -64
- package/dist/thumbnail/layout-header-sidebar-main.svelte.d.ts +0 -7
- package/dist/thumbnail/layout-sidebar-main.svelte +0 -57
- package/dist/thumbnail/layout-sidebar-main.svelte.d.ts +0 -7
- package/dist/thumbnail/link-preview.svelte +0 -141
- package/dist/thumbnail/link-preview.svelte.d.ts +0 -7
- package/dist/thumbnail/link.svelte +0 -70
- package/dist/thumbnail/link.svelte.d.ts +0 -7
- package/dist/thumbnail/list.svelte +0 -86
- package/dist/thumbnail/list.svelte.d.ts +0 -7
- package/dist/thumbnail/listbox.svelte +0 -102
- package/dist/thumbnail/listbox.svelte.d.ts +0 -7
- package/dist/thumbnail/location-autocomplete.svelte +0 -219
- package/dist/thumbnail/location-autocomplete.svelte.d.ts +0 -7
- package/dist/thumbnail/logo-cloud.svelte.d.ts +0 -7
- package/dist/thumbnail/loyalty-points-display.svelte +0 -147
- package/dist/thumbnail/loyalty-points-display.svelte.d.ts +0 -7
- package/dist/thumbnail/map-list-toggle.svelte +0 -140
- package/dist/thumbnail/map-list-toggle.svelte.d.ts +0 -7
- package/dist/thumbnail/map.svelte +0 -112
- package/dist/thumbnail/map.svelte.d.ts +0 -7
- package/dist/thumbnail/markdown-renderer.svelte +0 -113
- package/dist/thumbnail/markdown-renderer.svelte.d.ts +0 -7
- package/dist/thumbnail/marquee.svelte +0 -114
- package/dist/thumbnail/marquee.svelte.d.ts +0 -7
- package/dist/thumbnail/mask-reveal.svelte +0 -55
- package/dist/thumbnail/mask-reveal.svelte.d.ts +0 -7
- package/dist/thumbnail/mega-menu.svelte +0 -176
- package/dist/thumbnail/mega-menu.svelte.d.ts +0 -7
- package/dist/thumbnail/menubar.svelte +0 -99
- package/dist/thumbnail/menubar.svelte.d.ts +0 -7
- package/dist/thumbnail/multi-city-search-form.svelte +0 -241
- package/dist/thumbnail/multi-city-search-form.svelte.d.ts +0 -7
- package/dist/thumbnail/multi-select-combobox.svelte +0 -142
- package/dist/thumbnail/multi-select-combobox.svelte.d.ts +0 -7
- package/dist/thumbnail/navigation-menu.svelte +0 -102
- package/dist/thumbnail/navigation-menu.svelte.d.ts +0 -7
- package/dist/thumbnail/noise.svelte +0 -72
- package/dist/thumbnail/noise.svelte.d.ts +0 -7
- package/dist/thumbnail/notification-center.svelte +0 -152
- package/dist/thumbnail/notification-center.svelte.d.ts +0 -7
- package/dist/thumbnail/number-input.svelte +0 -113
- package/dist/thumbnail/number-input.svelte.d.ts +0 -7
- package/dist/thumbnail/option-swatch-group.svelte +0 -55
- package/dist/thumbnail/option-swatch-group.svelte.d.ts +0 -7
- package/dist/thumbnail/page-header.svelte.d.ts +0 -7
- package/dist/thumbnail/pagination.svelte +0 -121
- package/dist/thumbnail/pagination.svelte.d.ts +0 -7
- package/dist/thumbnail/passenger-class-selector.svelte +0 -142
- package/dist/thumbnail/passenger-class-selector.svelte.d.ts +0 -7
- package/dist/thumbnail/payment-card-input.svelte +0 -172
- package/dist/thumbnail/payment-card-input.svelte.d.ts +0 -7
- package/dist/thumbnail/phone-input.svelte +0 -103
- package/dist/thumbnail/phone-input.svelte.d.ts +0 -7
- package/dist/thumbnail/pin-input.svelte +0 -72
- package/dist/thumbnail/pin-input.svelte.d.ts +0 -7
- package/dist/thumbnail/popover.svelte +0 -122
- package/dist/thumbnail/popover.svelte.d.ts +0 -7
- package/dist/thumbnail/portal.svelte +0 -115
- package/dist/thumbnail/portal.svelte.d.ts +0 -7
- package/dist/thumbnail/price-calendar.svelte +0 -366
- package/dist/thumbnail/price-calendar.svelte.d.ts +0 -7
- package/dist/thumbnail/price-summary-panel.svelte +0 -165
- package/dist/thumbnail/price-summary-panel.svelte.d.ts +0 -7
- package/dist/thumbnail/progress-ring.svelte +0 -63
- package/dist/thumbnail/progress-ring.svelte.d.ts +0 -7
- package/dist/thumbnail/progress.svelte +0 -66
- package/dist/thumbnail/progress.svelte.d.ts +0 -7
- package/dist/thumbnail/promo-code-input.svelte +0 -111
- package/dist/thumbnail/promo-code-input.svelte.d.ts +0 -7
- package/dist/thumbnail/promo-mosaic.svelte +0 -55
- package/dist/thumbnail/promo-mosaic.svelte.d.ts +0 -7
- package/dist/thumbnail/prompt-input.svelte +0 -91
- package/dist/thumbnail/prompt-input.svelte.d.ts +0 -7
- package/dist/thumbnail/qr-code.svelte +0 -217
- package/dist/thumbnail/qr-code.svelte.d.ts +0 -7
- package/dist/thumbnail/radio-group.svelte +0 -97
- package/dist/thumbnail/radio-group.svelte.d.ts +0 -7
- package/dist/thumbnail/range-calendar.svelte +0 -92
- package/dist/thumbnail/range-calendar.svelte.d.ts +0 -7
- package/dist/thumbnail/rating.svelte +0 -61
- package/dist/thumbnail/rating.svelte.d.ts +0 -7
- package/dist/thumbnail/recent-searches.svelte +0 -197
- package/dist/thumbnail/recent-searches.svelte.d.ts +0 -7
- package/dist/thumbnail/relative-time.svelte +0 -51
- package/dist/thumbnail/relative-time.svelte.d.ts +0 -7
- package/dist/thumbnail/result-card-car.svelte +0 -149
- package/dist/thumbnail/result-card-car.svelte.d.ts +0 -7
- package/dist/thumbnail/result-card-flight.svelte +0 -170
- package/dist/thumbnail/result-card-flight.svelte.d.ts +0 -7
- package/dist/thumbnail/result-card-hotel.svelte +0 -174
- package/dist/thumbnail/result-card-hotel.svelte.d.ts +0 -7
- package/dist/thumbnail/reveal.svelte +0 -109
- package/dist/thumbnail/reveal.svelte.d.ts +0 -7
- package/dist/thumbnail/review-card.svelte +0 -153
- package/dist/thumbnail/review-card.svelte.d.ts +0 -7
- package/dist/thumbnail/rich-text-editor.svelte +0 -134
- package/dist/thumbnail/rich-text-editor.svelte.d.ts +0 -7
- package/dist/thumbnail/room-type-picker.svelte +0 -212
- package/dist/thumbnail/room-type-picker.svelte.d.ts +0 -7
- package/dist/thumbnail/root.svelte +0 -52
- package/dist/thumbnail/root.svelte.d.ts +0 -10
- package/dist/thumbnail/route-map.svelte +0 -132
- package/dist/thumbnail/route-map.svelte.d.ts +0 -7
- package/dist/thumbnail/scroll-area.svelte +0 -124
- package/dist/thumbnail/scroll-area.svelte.d.ts +0 -7
- package/dist/thumbnail/scroll-to-top.svelte +0 -60
- package/dist/thumbnail/scroll-to-top.svelte.d.ts +0 -7
- package/dist/thumbnail/search-form-tabs.svelte +0 -192
- package/dist/thumbnail/search-form-tabs.svelte.d.ts +0 -7
- package/dist/thumbnail/seat-map.svelte +0 -358
- package/dist/thumbnail/seat-map.svelte.d.ts +0 -7
- package/dist/thumbnail/segmented-control.svelte +0 -93
- package/dist/thumbnail/segmented-control.svelte.d.ts +0 -7
- package/dist/thumbnail/select.svelte +0 -82
- package/dist/thumbnail/select.svelte.d.ts +0 -7
- package/dist/thumbnail/selectable-tile-group.svelte +0 -55
- package/dist/thumbnail/selectable-tile-group.svelte.d.ts +0 -7
- package/dist/thumbnail/separator.svelte +0 -94
- package/dist/thumbnail/separator.svelte.d.ts +0 -7
- package/dist/thumbnail/shader-canvas.svelte +0 -55
- package/dist/thumbnail/shader-canvas.svelte.d.ts +0 -7
- package/dist/thumbnail/sidebar.svelte +0 -109
- package/dist/thumbnail/sidebar.svelte.d.ts +0 -7
- package/dist/thumbnail/skeleton.svelte +0 -94
- package/dist/thumbnail/skeleton.svelte.d.ts +0 -7
- package/dist/thumbnail/slider.svelte +0 -71
- package/dist/thumbnail/slider.svelte.d.ts +0 -7
- package/dist/thumbnail/sort-bar.svelte +0 -156
- package/dist/thumbnail/sort-bar.svelte.d.ts +0 -7
- package/dist/thumbnail/spacer.svelte +0 -95
- package/dist/thumbnail/spacer.svelte.d.ts +0 -7
- package/dist/thumbnail/sparkline.svelte +0 -59
- package/dist/thumbnail/sparkline.svelte.d.ts +0 -7
- package/dist/thumbnail/spinner.svelte +0 -63
- package/dist/thumbnail/spinner.svelte.d.ts +0 -7
- package/dist/thumbnail/splitter.svelte +0 -79
- package/dist/thumbnail/splitter.svelte.d.ts +0 -7
- package/dist/thumbnail/spotlight.svelte +0 -90
- package/dist/thumbnail/spotlight.svelte.d.ts +0 -7
- package/dist/thumbnail/star-rating.svelte +0 -106
- package/dist/thumbnail/star-rating.svelte.d.ts +0 -7
- package/dist/thumbnail/stat-card.svelte.d.ts +0 -7
- package/dist/thumbnail/stepper.svelte +0 -94
- package/dist/thumbnail/stepper.svelte.d.ts +0 -7
- package/dist/thumbnail/svg.svelte +0 -55
- package/dist/thumbnail/svg.svelte.d.ts +0 -8
- package/dist/thumbnail/swatch-strip.svelte.d.ts +0 -7
- package/dist/thumbnail/switch.svelte.d.ts +0 -7
- package/dist/thumbnail/system-map.svelte +0 -154
- package/dist/thumbnail/system-map.svelte.d.ts +0 -7
- package/dist/thumbnail/table-of-contents.svelte +0 -101
- package/dist/thumbnail/table-of-contents.svelte.d.ts +0 -7
- package/dist/thumbnail/table.svelte +0 -214
- package/dist/thumbnail/table.svelte.d.ts +0 -7
- package/dist/thumbnail/tabs.svelte +0 -133
- package/dist/thumbnail/tabs.svelte.d.ts +0 -7
- package/dist/thumbnail/tag.svelte +0 -95
- package/dist/thumbnail/tag.svelte.d.ts +0 -7
- package/dist/thumbnail/tags-input.svelte +0 -142
- package/dist/thumbnail/tags-input.svelte.d.ts +0 -7
- package/dist/thumbnail/text.svelte +0 -72
- package/dist/thumbnail/text.svelte.d.ts +0 -7
- package/dist/thumbnail/textarea.svelte +0 -111
- package/dist/thumbnail/textarea.svelte.d.ts +0 -7
- package/dist/thumbnail/time-input.svelte +0 -94
- package/dist/thumbnail/time-input.svelte.d.ts +0 -7
- package/dist/thumbnail/timeline.svelte +0 -123
- package/dist/thumbnail/timeline.svelte.d.ts +0 -7
- package/dist/thumbnail/toast.svelte +0 -105
- package/dist/thumbnail/toast.svelte.d.ts +0 -7
- package/dist/thumbnail/toggle-group.svelte +0 -76
- package/dist/thumbnail/toggle-group.svelte.d.ts +0 -7
- package/dist/thumbnail/toggle.svelte +0 -46
- package/dist/thumbnail/toggle.svelte.d.ts +0 -7
- package/dist/thumbnail/token-preview.svelte.d.ts +0 -7
- package/dist/thumbnail/toolbar.svelte +0 -109
- package/dist/thumbnail/toolbar.svelte.d.ts +0 -7
- package/dist/thumbnail/tooltip.svelte +0 -85
- package/dist/thumbnail/tooltip.svelte.d.ts +0 -7
- package/dist/thumbnail/tour.svelte +0 -149
- package/dist/thumbnail/tour.svelte.d.ts +0 -7
- package/dist/thumbnail/transfer.svelte +0 -168
- package/dist/thumbnail/transfer.svelte.d.ts +0 -7
- package/dist/thumbnail/tree.svelte +0 -215
- package/dist/thumbnail/tree.svelte.d.ts +0 -7
- package/dist/thumbnail/trip-card.svelte +0 -137
- package/dist/thumbnail/trip-card.svelte.d.ts +0 -7
- package/dist/thumbnail/trust-badges.svelte +0 -207
- package/dist/thumbnail/trust-badges.svelte.d.ts +0 -7
- package/dist/thumbnail/typing-indicator.svelte +0 -65
- package/dist/thumbnail/typing-indicator.svelte.d.ts +0 -7
- package/dist/thumbnail/typography.svelte +0 -80
- package/dist/thumbnail/typography.svelte.d.ts +0 -7
- package/dist/thumbnail/user.svelte.d.ts +0 -7
- package/dist/thumbnail/video-embed.svelte +0 -87
- package/dist/thumbnail/video-embed.svelte.d.ts +0 -7
- package/dist/thumbnail/virtual-list.svelte +0 -178
- package/dist/thumbnail/virtual-list.svelte.d.ts +0 -7
- package/dist/thumbnail/visually-hidden.svelte +0 -106
- package/dist/thumbnail/visually-hidden.svelte.d.ts +0 -7
- package/dist/thumbnail/wave-divider.svelte.d.ts +0 -7
- /package/dist/{system-map → diagram}/types.js +0 -0
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
import { computeEdgePaths, emptyEdge } from './edge-routing.js';
|
|
2
|
+
const DEFAULT_NODE_GAP = 28;
|
|
3
|
+
const DEFAULT_LAYER_GAP = 56;
|
|
4
|
+
const DEFAULT_CLUSTER_PADDING = 32;
|
|
5
|
+
const DEFAULT_NODE_HEIGHT = 44;
|
|
6
|
+
const DESC_NODE_HEIGHT = 80;
|
|
7
|
+
const MIN_NODE_WIDTH = 140;
|
|
8
|
+
const CHAR_WIDTH = 8.5;
|
|
9
|
+
const NODE_PADDING_X = 48;
|
|
10
|
+
const MARGIN = 40;
|
|
11
|
+
// ── Helpers ────────────────────────────────────────────────
|
|
12
|
+
function estimateNodeWidth(label, description) {
|
|
13
|
+
const labelWidth = label.length * CHAR_WIDTH + NODE_PADDING_X;
|
|
14
|
+
const descWidth = description ? description.length * 6.5 + NODE_PADDING_X + 16 : 0;
|
|
15
|
+
return Math.max(MIN_NODE_WIDTH, labelWidth, descWidth);
|
|
16
|
+
}
|
|
17
|
+
function isHorizontal(dir) {
|
|
18
|
+
return dir === 'LR' || dir === 'RL';
|
|
19
|
+
}
|
|
20
|
+
function isReversed(dir) {
|
|
21
|
+
return dir === 'BT' || dir === 'RL';
|
|
22
|
+
}
|
|
23
|
+
function buildGraph(nodeIds, edges) {
|
|
24
|
+
const adjacencyOut = new Map();
|
|
25
|
+
const adjacencyIn = new Map();
|
|
26
|
+
const inDegree = new Map();
|
|
27
|
+
for (const id of nodeIds) {
|
|
28
|
+
adjacencyOut.set(id, []);
|
|
29
|
+
adjacencyIn.set(id, []);
|
|
30
|
+
inDegree.set(id, 0);
|
|
31
|
+
}
|
|
32
|
+
for (const e of edges) {
|
|
33
|
+
if (!adjacencyOut.has(e.from) || !adjacencyOut.has(e.to))
|
|
34
|
+
continue;
|
|
35
|
+
adjacencyOut.get(e.from).push(e.to);
|
|
36
|
+
adjacencyIn.get(e.to).push(e.from);
|
|
37
|
+
inDegree.set(e.to, (inDegree.get(e.to) || 0) + 1);
|
|
38
|
+
}
|
|
39
|
+
// Kahn's algorithm
|
|
40
|
+
const queue = [];
|
|
41
|
+
for (const id of nodeIds) {
|
|
42
|
+
if (inDegree.get(id) === 0)
|
|
43
|
+
queue.push(id);
|
|
44
|
+
}
|
|
45
|
+
const order = [];
|
|
46
|
+
const visited = new Set();
|
|
47
|
+
while (queue.length > 0) {
|
|
48
|
+
const node = queue.shift();
|
|
49
|
+
if (visited.has(node))
|
|
50
|
+
continue;
|
|
51
|
+
visited.add(node);
|
|
52
|
+
order.push(node);
|
|
53
|
+
for (const succ of adjacencyOut.get(node)) {
|
|
54
|
+
const deg = inDegree.get(succ) - 1;
|
|
55
|
+
inDegree.set(succ, deg);
|
|
56
|
+
if (deg === 0)
|
|
57
|
+
queue.push(succ);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Handle cycles: any unvisited nodes have cycles
|
|
61
|
+
const reversedEdges = new Set();
|
|
62
|
+
if (visited.size < nodeIds.length) {
|
|
63
|
+
for (const id of nodeIds) {
|
|
64
|
+
if (!visited.has(id)) {
|
|
65
|
+
// Break cycle by removing an incoming edge
|
|
66
|
+
const incoming = adjacencyIn.get(id);
|
|
67
|
+
for (const src of incoming) {
|
|
68
|
+
if (!visited.has(src)) {
|
|
69
|
+
reversedEdges.add(`${src}->${id}`);
|
|
70
|
+
const outList = adjacencyOut.get(src);
|
|
71
|
+
const idx = outList.indexOf(id);
|
|
72
|
+
if (idx >= 0)
|
|
73
|
+
outList.splice(idx, 1);
|
|
74
|
+
inDegree.set(id, (inDegree.get(id) || 1) - 1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!visited.has(id)) {
|
|
78
|
+
visited.add(id);
|
|
79
|
+
order.push(id);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { adjacencyOut, adjacencyIn, order, reversedEdges };
|
|
85
|
+
}
|
|
86
|
+
// ── Layer Assignment (longest-path) ────────────────────────
|
|
87
|
+
function assignLayers(order, adjacencyOut) {
|
|
88
|
+
const layer = new Map();
|
|
89
|
+
for (const id of order)
|
|
90
|
+
layer.set(id, 0);
|
|
91
|
+
for (const id of order) {
|
|
92
|
+
const currentLayer = layer.get(id);
|
|
93
|
+
for (const succ of adjacencyOut.get(id)) {
|
|
94
|
+
layer.set(succ, Math.max(layer.get(succ), currentLayer + 1));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return layer;
|
|
98
|
+
}
|
|
99
|
+
// ── Within-Layer Ordering (barycenter, 2 sweeps, cluster-aware) ───────────
|
|
100
|
+
function orderWithinLayers(layers, adjacencyOut, adjacencyIn, clusterMap) {
|
|
101
|
+
const posInLayer = new Map();
|
|
102
|
+
// Initialize positions
|
|
103
|
+
for (const layer of layers) {
|
|
104
|
+
for (let i = 0; i < layer.length; i++) {
|
|
105
|
+
posInLayer.set(layer[i], i);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Down sweep
|
|
109
|
+
for (let i = 1; i < layers.length; i++) {
|
|
110
|
+
const sorted = layers[i].map((nodeId) => {
|
|
111
|
+
const preds = adjacencyIn.get(nodeId) || [];
|
|
112
|
+
const positions = preds
|
|
113
|
+
.map((p) => posInLayer.get(p))
|
|
114
|
+
.filter((p) => p !== undefined);
|
|
115
|
+
const barycenter = positions.length > 0
|
|
116
|
+
? positions.reduce((a, b) => a + b, 0) / positions.length
|
|
117
|
+
: (posInLayer.get(nodeId) ?? 0);
|
|
118
|
+
return { id: nodeId, bary: barycenter };
|
|
119
|
+
});
|
|
120
|
+
sorted.sort((a, b) => a.bary - b.bary);
|
|
121
|
+
layers[i] = sorted.map((s) => s.id);
|
|
122
|
+
for (let j = 0; j < layers[i].length; j++) {
|
|
123
|
+
posInLayer.set(layers[i][j], j);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Up sweep
|
|
127
|
+
for (let i = layers.length - 2; i >= 0; i--) {
|
|
128
|
+
const sorted = layers[i].map((nodeId) => {
|
|
129
|
+
const succs = adjacencyOut.get(nodeId) || [];
|
|
130
|
+
const positions = succs
|
|
131
|
+
.map((s) => posInLayer.get(s))
|
|
132
|
+
.filter((p) => p !== undefined);
|
|
133
|
+
const barycenter = positions.length > 0
|
|
134
|
+
? positions.reduce((a, b) => a + b, 0) / positions.length
|
|
135
|
+
: (posInLayer.get(nodeId) ?? 0);
|
|
136
|
+
return { id: nodeId, bary: barycenter };
|
|
137
|
+
});
|
|
138
|
+
sorted.sort((a, b) => a.bary - b.bary);
|
|
139
|
+
layers[i] = sorted.map((s) => s.id);
|
|
140
|
+
for (let j = 0; j < layers[i].length; j++) {
|
|
141
|
+
posInLayer.set(layers[i][j], j);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Cluster-aware grouping: after barycenter ordering, group cluster members together
|
|
145
|
+
// and push non-clustered nodes to the edges
|
|
146
|
+
if (clusterMap && clusterMap.size > 0) {
|
|
147
|
+
for (let i = 0; i < layers.length; i++) {
|
|
148
|
+
layers[i] = groupByCluster(layers[i], clusterMap, adjacencyIn, adjacencyOut, posInLayer);
|
|
149
|
+
for (let j = 0; j < layers[i].length; j++) {
|
|
150
|
+
posInLayer.set(layers[i][j], j);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return layers;
|
|
155
|
+
}
|
|
156
|
+
/** Group nodes in a layer so cluster members are adjacent, non-clustered nodes at edges */
|
|
157
|
+
function groupByCluster(layer, clusterMap, adjacencyIn, adjacencyOut, posInLayer) {
|
|
158
|
+
if (layer.length <= 1)
|
|
159
|
+
return layer;
|
|
160
|
+
// Separate into cluster groups and non-clustered nodes
|
|
161
|
+
const clusterGroups = new Map();
|
|
162
|
+
const nonClustered = [];
|
|
163
|
+
for (const nodeId of layer) {
|
|
164
|
+
const clusterId = clusterMap.get(nodeId);
|
|
165
|
+
if (clusterId) {
|
|
166
|
+
if (!clusterGroups.has(clusterId))
|
|
167
|
+
clusterGroups.set(clusterId, []);
|
|
168
|
+
clusterGroups.get(clusterId).push(nodeId);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
nonClustered.push(nodeId);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// If no clusters in this layer, nothing to reorder
|
|
175
|
+
if (clusterGroups.size === 0)
|
|
176
|
+
return layer;
|
|
177
|
+
// If no non-clustered nodes, just keep cluster groups in barycenter order
|
|
178
|
+
if (nonClustered.length === 0) {
|
|
179
|
+
// Order cluster groups by average barycenter position of their members
|
|
180
|
+
const groups = [...clusterGroups.entries()].map(([cid, nodes]) => {
|
|
181
|
+
const avgPos = nodes.reduce((s, n) => s + (posInLayer.get(n) ?? 0), 0) / nodes.length;
|
|
182
|
+
return { cid, nodes, avgPos };
|
|
183
|
+
});
|
|
184
|
+
groups.sort((a, b) => a.avgPos - b.avgPos);
|
|
185
|
+
return groups.flatMap((g) => g.nodes);
|
|
186
|
+
}
|
|
187
|
+
// Compute average barycenter position for each cluster group
|
|
188
|
+
const groupEntries = [...clusterGroups.entries()].map(([cid, nodes]) => {
|
|
189
|
+
const avgPos = nodes.reduce((s, n) => s + (posInLayer.get(n) ?? 0), 0) / nodes.length;
|
|
190
|
+
return { cid, nodes, avgPos };
|
|
191
|
+
});
|
|
192
|
+
groupEntries.sort((a, b) => a.avgPos - b.avgPos);
|
|
193
|
+
// Compute average connected position for non-clustered nodes to decide
|
|
194
|
+
// whether they go before or after the cluster groups
|
|
195
|
+
const allClusterPositions = groupEntries.flatMap((g) => g.nodes.map((n) => posInLayer.get(n) ?? 0));
|
|
196
|
+
const clusterCenter = allClusterPositions.reduce((a, b) => a + b, 0) / allClusterPositions.length;
|
|
197
|
+
// Split non-clustered into before/after based on their barycenter relative to cluster center
|
|
198
|
+
const before = [];
|
|
199
|
+
const after = [];
|
|
200
|
+
for (const nodeId of nonClustered) {
|
|
201
|
+
const pos = posInLayer.get(nodeId) ?? 0;
|
|
202
|
+
if (pos <= clusterCenter) {
|
|
203
|
+
before.push(nodeId);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
after.push(nodeId);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// If all ended up on one side, that's fine — just keep them there
|
|
210
|
+
// If none on either side, push all to the end
|
|
211
|
+
if (before.length === 0 && after.length === 0) {
|
|
212
|
+
after.push(...nonClustered);
|
|
213
|
+
}
|
|
214
|
+
return [...before, ...groupEntries.flatMap((g) => g.nodes), ...after];
|
|
215
|
+
}
|
|
216
|
+
// ── Coordinate Assignment ──────────────────────────────────
|
|
217
|
+
const CLUSTER_GROUP_GAP = 40;
|
|
218
|
+
function assignCoordinates(layers, nodeDims, direction, nodeGap, layerGap, clusterMap) {
|
|
219
|
+
const positions = new Map();
|
|
220
|
+
const horizontal = isHorizontal(direction);
|
|
221
|
+
const reversed = isReversed(direction);
|
|
222
|
+
// Compute layer extents (max node size in layer direction)
|
|
223
|
+
const layerSizes = [];
|
|
224
|
+
for (const layer of layers) {
|
|
225
|
+
let maxSize = 0;
|
|
226
|
+
for (const id of layer) {
|
|
227
|
+
const dims = nodeDims.get(id);
|
|
228
|
+
maxSize = Math.max(maxSize, horizontal ? dims.w : dims.h);
|
|
229
|
+
}
|
|
230
|
+
layerSizes.push(maxSize);
|
|
231
|
+
}
|
|
232
|
+
// Find max cross-axis extent per layer for centering (including cluster group gaps)
|
|
233
|
+
const layerCrossExtents = [];
|
|
234
|
+
for (const layer of layers) {
|
|
235
|
+
let total = 0;
|
|
236
|
+
for (const id of layer) {
|
|
237
|
+
const dims = nodeDims.get(id);
|
|
238
|
+
total += horizontal ? dims.h : dims.w;
|
|
239
|
+
}
|
|
240
|
+
total += (layer.length - 1) * nodeGap;
|
|
241
|
+
// Add extra gap at cluster/non-cluster boundaries
|
|
242
|
+
if (clusterMap && clusterMap.size > 0) {
|
|
243
|
+
total += countClusterBoundaries(layer, clusterMap) * CLUSTER_GROUP_GAP;
|
|
244
|
+
}
|
|
245
|
+
layerCrossExtents.push(total);
|
|
246
|
+
}
|
|
247
|
+
const maxCrossExtent = Math.max(...layerCrossExtents);
|
|
248
|
+
// Assign positions
|
|
249
|
+
let layerOffset = MARGIN;
|
|
250
|
+
const layerOrder = reversed ? [...layers].reverse() : layers;
|
|
251
|
+
const sizeOrder = reversed ? [...layerSizes].reverse() : layerSizes;
|
|
252
|
+
for (let li = 0; li < layerOrder.length; li++) {
|
|
253
|
+
const layer = layerOrder[li];
|
|
254
|
+
const layerSize = sizeOrder[li];
|
|
255
|
+
// Center this layer's nodes
|
|
256
|
+
const crossExtent = layerCrossExtents[reversed ? layerOrder.length - 1 - li : li];
|
|
257
|
+
let crossOffset = MARGIN + (maxCrossExtent - crossExtent) / 2;
|
|
258
|
+
for (let ni = 0; ni < layer.length; ni++) {
|
|
259
|
+
const id = layer[ni];
|
|
260
|
+
const dims = nodeDims.get(id);
|
|
261
|
+
const primaryOffset = reversed ? (horizontal ? layerSize - dims.w : layerSize - dims.h) : 0;
|
|
262
|
+
// Add extra gap at cluster/non-cluster boundary
|
|
263
|
+
if (ni > 0 && clusterMap && clusterMap.size > 0) {
|
|
264
|
+
const prevCluster = clusterMap.get(layer[ni - 1]);
|
|
265
|
+
const currCluster = clusterMap.get(id);
|
|
266
|
+
if (prevCluster !== currCluster &&
|
|
267
|
+
(prevCluster !== undefined || currCluster !== undefined)) {
|
|
268
|
+
crossOffset += CLUSTER_GROUP_GAP;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (horizontal) {
|
|
272
|
+
positions.set(id, {
|
|
273
|
+
x: layerOffset + primaryOffset,
|
|
274
|
+
y: crossOffset
|
|
275
|
+
});
|
|
276
|
+
crossOffset += dims.h + nodeGap;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
positions.set(id, {
|
|
280
|
+
x: crossOffset,
|
|
281
|
+
y: layerOffset + primaryOffset
|
|
282
|
+
});
|
|
283
|
+
crossOffset += dims.w + nodeGap;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
layerOffset += layerSize + layerGap;
|
|
287
|
+
}
|
|
288
|
+
return positions;
|
|
289
|
+
}
|
|
290
|
+
/** Count how many cluster/non-cluster boundaries exist in a layer ordering */
|
|
291
|
+
function countClusterBoundaries(layer, clusterMap) {
|
|
292
|
+
let count = 0;
|
|
293
|
+
for (let i = 1; i < layer.length; i++) {
|
|
294
|
+
const prevCluster = clusterMap.get(layer[i - 1]);
|
|
295
|
+
const currCluster = clusterMap.get(layer[i]);
|
|
296
|
+
if (prevCluster !== currCluster && (prevCluster !== undefined || currCluster !== undefined)) {
|
|
297
|
+
count++;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return count;
|
|
301
|
+
}
|
|
302
|
+
// ── Cluster Bounds ─────────────────────────────────────────
|
|
303
|
+
function computeClusterBounds(config, positions, nodeDims, padding) {
|
|
304
|
+
if (!config.clusters)
|
|
305
|
+
return [];
|
|
306
|
+
return config.clusters.map((cluster) => {
|
|
307
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
308
|
+
for (const nodeId of cluster.nodes) {
|
|
309
|
+
const pos = positions.get(nodeId);
|
|
310
|
+
const dims = nodeDims.get(nodeId);
|
|
311
|
+
if (!pos || !dims)
|
|
312
|
+
continue;
|
|
313
|
+
minX = Math.min(minX, pos.x);
|
|
314
|
+
minY = Math.min(minY, pos.y);
|
|
315
|
+
maxX = Math.max(maxX, pos.x + dims.w);
|
|
316
|
+
maxY = Math.max(maxY, pos.y + dims.h);
|
|
317
|
+
}
|
|
318
|
+
if (minX === Infinity) {
|
|
319
|
+
return {
|
|
320
|
+
id: cluster.id,
|
|
321
|
+
x: 0,
|
|
322
|
+
y: 0,
|
|
323
|
+
width: 0,
|
|
324
|
+
height: 0,
|
|
325
|
+
label: cluster.label,
|
|
326
|
+
color: cluster.color || 'neutral',
|
|
327
|
+
dashed: cluster.dashed ?? true
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
// Add space for label above
|
|
331
|
+
const labelPad = cluster.label ? 24 : 0;
|
|
332
|
+
return {
|
|
333
|
+
id: cluster.id,
|
|
334
|
+
x: minX - padding,
|
|
335
|
+
y: minY - padding - labelPad,
|
|
336
|
+
width: maxX - minX + padding * 2,
|
|
337
|
+
height: maxY - minY + padding * 2 + labelPad,
|
|
338
|
+
label: cluster.label,
|
|
339
|
+
color: cluster.color || 'neutral',
|
|
340
|
+
dashed: cluster.dashed ?? true
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
// ── Annotations ────────────────────────────────────────────
|
|
345
|
+
const ANNOTATION_CHAR_WIDTH = 6;
|
|
346
|
+
const ANNOTATION_HEIGHT = 14;
|
|
347
|
+
const ANNOTATION_COLLISION_PAD = 20;
|
|
348
|
+
function resolveAnnotations(config, positions, nodeDims) {
|
|
349
|
+
if (!config.annotations)
|
|
350
|
+
return [];
|
|
351
|
+
const resolved = config.annotations.map((ann) => {
|
|
352
|
+
let x, y;
|
|
353
|
+
if (typeof ann.anchor === 'string') {
|
|
354
|
+
const pos = positions.get(ann.anchor);
|
|
355
|
+
const dims = nodeDims.get(ann.anchor);
|
|
356
|
+
if (pos && dims) {
|
|
357
|
+
x = pos.x + dims.w / 2;
|
|
358
|
+
y = pos.y - 12;
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
x = 0;
|
|
362
|
+
y = 0;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
x = ann.anchor.x;
|
|
367
|
+
y = ann.anchor.y;
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
text: ann.text,
|
|
371
|
+
x: x + (ann.offset?.dx ?? 0),
|
|
372
|
+
y: y + (ann.offset?.dy ?? 0),
|
|
373
|
+
color: ann.color || 'neutral'
|
|
374
|
+
};
|
|
375
|
+
});
|
|
376
|
+
// Collision avoidance: check annotations against nodes and other annotations
|
|
377
|
+
for (let i = 0; i < resolved.length; i++) {
|
|
378
|
+
const ann = resolved[i];
|
|
379
|
+
const annW = ann.text.length * ANNOTATION_CHAR_WIDTH;
|
|
380
|
+
const annH = ANNOTATION_HEIGHT;
|
|
381
|
+
// Check against all nodes — shift upward if overlapping
|
|
382
|
+
let shifted = true;
|
|
383
|
+
let maxIter = 10; // prevent infinite loop
|
|
384
|
+
while (shifted && maxIter-- > 0) {
|
|
385
|
+
shifted = false;
|
|
386
|
+
for (const [nodeId, pos] of positions) {
|
|
387
|
+
const dims = nodeDims.get(nodeId);
|
|
388
|
+
if (!dims)
|
|
389
|
+
continue;
|
|
390
|
+
// Check overlap with padding
|
|
391
|
+
if (ann.x + annW > pos.x - ANNOTATION_COLLISION_PAD &&
|
|
392
|
+
ann.x < pos.x + dims.w + ANNOTATION_COLLISION_PAD &&
|
|
393
|
+
ann.y + annH > pos.y - ANNOTATION_COLLISION_PAD &&
|
|
394
|
+
ann.y < pos.y + dims.h + ANNOTATION_COLLISION_PAD) {
|
|
395
|
+
// Shift annotation above the node
|
|
396
|
+
ann.y = pos.y - ANNOTATION_COLLISION_PAD - annH;
|
|
397
|
+
shifted = true;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// Check against previously placed annotations
|
|
402
|
+
for (let j = 0; j < i; j++) {
|
|
403
|
+
const other = resolved[j];
|
|
404
|
+
const otherW = other.text.length * ANNOTATION_CHAR_WIDTH;
|
|
405
|
+
const otherH = ANNOTATION_HEIGHT;
|
|
406
|
+
if (ann.x + annW > other.x - ANNOTATION_COLLISION_PAD &&
|
|
407
|
+
ann.x < other.x + otherW + ANNOTATION_COLLISION_PAD &&
|
|
408
|
+
ann.y + annH > other.y - ANNOTATION_COLLISION_PAD &&
|
|
409
|
+
ann.y < other.y + otherH + ANNOTATION_COLLISION_PAD) {
|
|
410
|
+
// Shift this annotation above the other
|
|
411
|
+
ann.y = other.y - ANNOTATION_COLLISION_PAD - annH;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return resolved;
|
|
416
|
+
}
|
|
417
|
+
// ── Layered Layout (main entry) ────────────────────────────
|
|
418
|
+
function layoutLayered(config) {
|
|
419
|
+
const direction = config.direction || 'TB';
|
|
420
|
+
const nodeGap = config.spacing?.nodeGap ?? DEFAULT_NODE_GAP;
|
|
421
|
+
const layerGap = config.spacing?.layerGap ?? DEFAULT_LAYER_GAP;
|
|
422
|
+
const clusterPadding = config.spacing?.clusterPadding ?? DEFAULT_CLUSTER_PADDING;
|
|
423
|
+
const nodeIds = config.nodes.map((n) => n.id);
|
|
424
|
+
const nodeDims = buildNodeDims(config.nodes);
|
|
425
|
+
// Build cluster membership map: nodeId -> clusterId
|
|
426
|
+
const clusterMap = buildClusterMap(config);
|
|
427
|
+
// Build graph and sort
|
|
428
|
+
const graph = buildGraph(nodeIds, config.edges);
|
|
429
|
+
// Assign layers
|
|
430
|
+
const layerMap = assignLayers(graph.order, graph.adjacencyOut);
|
|
431
|
+
// Group by layer
|
|
432
|
+
const maxLayer = Math.max(0, ...layerMap.values());
|
|
433
|
+
const layers = Array.from({ length: maxLayer + 1 }, () => []);
|
|
434
|
+
for (const id of graph.order) {
|
|
435
|
+
layers[layerMap.get(id)].push(id);
|
|
436
|
+
}
|
|
437
|
+
// Order within layers (cluster-aware)
|
|
438
|
+
const orderedLayers = orderWithinLayers(layers, graph.adjacencyOut, graph.adjacencyIn, clusterMap);
|
|
439
|
+
// Assign coordinates (cluster-aware gaps)
|
|
440
|
+
const positions = assignCoordinates(orderedLayers, nodeDims, direction, nodeGap, layerGap, clusterMap);
|
|
441
|
+
// Build positioned nodes
|
|
442
|
+
const positionedNodes = buildPositionedNodes(config.nodes, positions, nodeDims);
|
|
443
|
+
// Compute clusters
|
|
444
|
+
const clusters = computeClusterBounds(config, positions, nodeDims, clusterPadding);
|
|
445
|
+
// Compute edges
|
|
446
|
+
const positionedEdges = computeEdgePaths(config.edges, positions, nodeDims, direction);
|
|
447
|
+
// Compute annotations
|
|
448
|
+
const annotations = resolveAnnotations(config, positions, nodeDims);
|
|
449
|
+
// Compute viewBox with full bounds coverage
|
|
450
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
451
|
+
// Include all node bounds
|
|
452
|
+
for (const n of positionedNodes) {
|
|
453
|
+
minX = Math.min(minX, n.x);
|
|
454
|
+
minY = Math.min(minY, n.y);
|
|
455
|
+
maxX = Math.max(maxX, n.x + n.width);
|
|
456
|
+
maxY = Math.max(maxY, n.y + n.height);
|
|
457
|
+
}
|
|
458
|
+
// Include all cluster bounds
|
|
459
|
+
for (const c of clusters) {
|
|
460
|
+
minX = Math.min(minX, c.x);
|
|
461
|
+
minY = Math.min(minY, c.y);
|
|
462
|
+
maxX = Math.max(maxX, c.x + c.width);
|
|
463
|
+
maxY = Math.max(maxY, c.y + c.height);
|
|
464
|
+
}
|
|
465
|
+
// Include annotation positions (estimated text width)
|
|
466
|
+
for (const ann of annotations) {
|
|
467
|
+
const annW = ann.text.length * ANNOTATION_CHAR_WIDTH;
|
|
468
|
+
minX = Math.min(minX, ann.x);
|
|
469
|
+
minY = Math.min(minY, ann.y - ANNOTATION_HEIGHT);
|
|
470
|
+
maxX = Math.max(maxX, ann.x + annW);
|
|
471
|
+
maxY = Math.max(maxY, ann.y);
|
|
472
|
+
}
|
|
473
|
+
// Include edge label positions (estimated text width)
|
|
474
|
+
for (const e of positionedEdges) {
|
|
475
|
+
if (e.label) {
|
|
476
|
+
const labelW = e.label.length * 5;
|
|
477
|
+
minX = Math.min(minX, e.labelX - labelW / 2);
|
|
478
|
+
minY = Math.min(minY, e.labelY - 7);
|
|
479
|
+
maxX = Math.max(maxX, e.labelX + labelW / 2);
|
|
480
|
+
maxY = Math.max(maxY, e.labelY + 7);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// Handle empty diagram
|
|
484
|
+
if (minX === Infinity) {
|
|
485
|
+
minX = 0;
|
|
486
|
+
minY = 0;
|
|
487
|
+
maxX = MARGIN * 2;
|
|
488
|
+
maxY = MARGIN * 2;
|
|
489
|
+
}
|
|
490
|
+
// Shift everything so all coordinates are non-negative
|
|
491
|
+
const shiftX = minX < 0 ? -minX + MARGIN : 0;
|
|
492
|
+
const shiftY = minY < 0 ? -minY + MARGIN : 0;
|
|
493
|
+
if (shiftX > 0 || shiftY > 0) {
|
|
494
|
+
shiftAllPositions(positionedNodes, clusters, annotations, positionedEdges, shiftX, shiftY);
|
|
495
|
+
maxX += shiftX;
|
|
496
|
+
maxY += shiftY;
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
nodes: positionedNodes,
|
|
500
|
+
edges: positionedEdges,
|
|
501
|
+
clusters,
|
|
502
|
+
swimlanes: [],
|
|
503
|
+
regions: [],
|
|
504
|
+
annotations,
|
|
505
|
+
messages: [],
|
|
506
|
+
lifelines: [],
|
|
507
|
+
positionedFragments: [],
|
|
508
|
+
viewBox: { width: maxX + MARGIN, height: maxY + MARGIN }
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
// ── Swimlane Layout ────────────────────────────────────────
|
|
512
|
+
function layoutSwimlane(config) {
|
|
513
|
+
const nodeGap = config.spacing?.nodeGap ?? 32;
|
|
514
|
+
const lanes = config.swimlanes || [];
|
|
515
|
+
const nodeDims = buildNodeDims(config.nodes);
|
|
516
|
+
const headerHeight = 50;
|
|
517
|
+
// Assign lanes
|
|
518
|
+
const laneMap = new Map();
|
|
519
|
+
for (let i = 0; i < lanes.length; i++) {
|
|
520
|
+
for (const nid of lanes[i].nodes) {
|
|
521
|
+
laneMap.set(nid, i);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
// Compute dynamic lane widths: max(180, widest node in lane + 40)
|
|
525
|
+
const laneWidths = lanes.map((lane) => {
|
|
526
|
+
let maxW = 0;
|
|
527
|
+
for (const nid of lane.nodes) {
|
|
528
|
+
const dims = nodeDims.get(nid);
|
|
529
|
+
if (dims)
|
|
530
|
+
maxW = Math.max(maxW, dims.w);
|
|
531
|
+
}
|
|
532
|
+
return Math.max(180, maxW + 40);
|
|
533
|
+
});
|
|
534
|
+
// Compute lane start X offsets
|
|
535
|
+
const laneStartX = [];
|
|
536
|
+
let laneX = MARGIN;
|
|
537
|
+
for (let i = 0; i < laneWidths.length; i++) {
|
|
538
|
+
laneStartX.push(laneX);
|
|
539
|
+
laneX += laneWidths[i];
|
|
540
|
+
}
|
|
541
|
+
// Topological order for Y positions
|
|
542
|
+
const nodeIds = config.nodes.map((n) => n.id);
|
|
543
|
+
const graph = buildGraph(nodeIds, config.edges);
|
|
544
|
+
// Assign Y positions sequentially
|
|
545
|
+
const positions = new Map();
|
|
546
|
+
let currentY = MARGIN + headerHeight + 20;
|
|
547
|
+
for (const id of graph.order) {
|
|
548
|
+
const laneIdx = laneMap.get(id) ?? 0;
|
|
549
|
+
const dims = nodeDims.get(id);
|
|
550
|
+
const lw = laneWidths[laneIdx] ?? 180;
|
|
551
|
+
const startX = laneStartX[laneIdx] ?? MARGIN;
|
|
552
|
+
const x = startX + (lw - dims.w) / 2;
|
|
553
|
+
positions.set(id, { x, y: currentY });
|
|
554
|
+
currentY += dims.h + nodeGap;
|
|
555
|
+
}
|
|
556
|
+
const totalHeight = currentY + MARGIN;
|
|
557
|
+
const totalWidth = MARGIN + laneX;
|
|
558
|
+
const positionedNodes = buildPositionedNodes(config.nodes, positions, nodeDims);
|
|
559
|
+
// Build swimlane positioned data
|
|
560
|
+
const positionedSwimlanes = lanes.map((lane, i) => ({
|
|
561
|
+
id: lane.id,
|
|
562
|
+
label: lane.label,
|
|
563
|
+
x: laneStartX[i],
|
|
564
|
+
lineX: laneStartX[i] + laneWidths[i] / 2,
|
|
565
|
+
headerY: MARGIN,
|
|
566
|
+
footerY: totalHeight - MARGIN,
|
|
567
|
+
lineY1: MARGIN + headerHeight,
|
|
568
|
+
lineY2: totalHeight - MARGIN,
|
|
569
|
+
color: lane.color || 'neutral'
|
|
570
|
+
}));
|
|
571
|
+
// Edges: horizontal arrows between lanes
|
|
572
|
+
const positionedEdges = computeSwimEdgePaths(config.edges, positions, nodeDims, laneMap, laneWidths[0] ?? 180);
|
|
573
|
+
// Regions
|
|
574
|
+
const positionedRegions = (config.regions || []).map((region) => {
|
|
575
|
+
let minY = Infinity, maxY = -Infinity;
|
|
576
|
+
let minLane = Infinity, maxLane = -Infinity;
|
|
577
|
+
for (const nid of region.contains) {
|
|
578
|
+
const pos = positions.get(nid);
|
|
579
|
+
const dims = nodeDims.get(nid);
|
|
580
|
+
const laneIdx = laneMap.get(nid);
|
|
581
|
+
if (pos && dims) {
|
|
582
|
+
minY = Math.min(minY, pos.y);
|
|
583
|
+
maxY = Math.max(maxY, pos.y + dims.h);
|
|
584
|
+
}
|
|
585
|
+
if (laneIdx !== undefined) {
|
|
586
|
+
minLane = Math.min(minLane, laneIdx);
|
|
587
|
+
maxLane = Math.max(maxLane, laneIdx);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (minY === Infinity) {
|
|
591
|
+
return {
|
|
592
|
+
id: region.id,
|
|
593
|
+
x: 0,
|
|
594
|
+
y: 0,
|
|
595
|
+
width: 0,
|
|
596
|
+
height: 0,
|
|
597
|
+
label: region.label,
|
|
598
|
+
color: region.color || 'neutral',
|
|
599
|
+
dashed: region.dashed ?? true
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
const pad = 16;
|
|
603
|
+
const regionX = laneStartX[minLane] - pad;
|
|
604
|
+
let regionEndX = laneStartX[maxLane] + laneWidths[maxLane] + pad;
|
|
605
|
+
return {
|
|
606
|
+
id: region.id,
|
|
607
|
+
x: regionX,
|
|
608
|
+
y: minY - pad - 20,
|
|
609
|
+
width: regionEndX - regionX,
|
|
610
|
+
height: maxY - minY + pad * 2 + 20,
|
|
611
|
+
label: region.label,
|
|
612
|
+
color: region.color || 'neutral',
|
|
613
|
+
dashed: region.dashed ?? true
|
|
614
|
+
};
|
|
615
|
+
});
|
|
616
|
+
// Annotations
|
|
617
|
+
const annotations = resolveAnnotations(config, positions, nodeDims);
|
|
618
|
+
// Compute viewBox with full bounds coverage
|
|
619
|
+
let minX = Infinity, minY = Infinity, maxViewX = -Infinity, maxViewY = -Infinity;
|
|
620
|
+
for (const n of positionedNodes) {
|
|
621
|
+
minX = Math.min(minX, n.x);
|
|
622
|
+
minY = Math.min(minY, n.y);
|
|
623
|
+
maxViewX = Math.max(maxViewX, n.x + n.width);
|
|
624
|
+
maxViewY = Math.max(maxViewY, n.y + n.height);
|
|
625
|
+
}
|
|
626
|
+
// Include swimlane headers
|
|
627
|
+
for (let si = 0; si < positionedSwimlanes.length; si++) {
|
|
628
|
+
const sl = positionedSwimlanes[si];
|
|
629
|
+
minX = Math.min(minX, sl.x);
|
|
630
|
+
minY = Math.min(minY, sl.headerY);
|
|
631
|
+
maxViewX = Math.max(maxViewX, sl.x + (laneWidths[si] ?? 180));
|
|
632
|
+
maxViewY = Math.max(maxViewY, sl.lineY2);
|
|
633
|
+
}
|
|
634
|
+
// Include annotation positions
|
|
635
|
+
for (const ann of annotations) {
|
|
636
|
+
const annW = ann.text.length * ANNOTATION_CHAR_WIDTH;
|
|
637
|
+
minX = Math.min(minX, ann.x);
|
|
638
|
+
minY = Math.min(minY, ann.y - ANNOTATION_HEIGHT);
|
|
639
|
+
maxViewX = Math.max(maxViewX, ann.x + annW);
|
|
640
|
+
maxViewY = Math.max(maxViewY, ann.y);
|
|
641
|
+
}
|
|
642
|
+
// Include edge labels
|
|
643
|
+
for (const e of positionedEdges) {
|
|
644
|
+
if (e.label) {
|
|
645
|
+
const labelW = e.label.length * 5;
|
|
646
|
+
minX = Math.min(minX, e.labelX - labelW / 2);
|
|
647
|
+
minY = Math.min(minY, e.labelY - 7);
|
|
648
|
+
maxViewX = Math.max(maxViewX, e.labelX + labelW / 2);
|
|
649
|
+
maxViewY = Math.max(maxViewY, e.labelY + 7);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// Include regions
|
|
653
|
+
for (const r of positionedRegions) {
|
|
654
|
+
minX = Math.min(minX, r.x);
|
|
655
|
+
minY = Math.min(minY, r.y);
|
|
656
|
+
maxViewX = Math.max(maxViewX, r.x + r.width);
|
|
657
|
+
maxViewY = Math.max(maxViewY, r.y + r.height);
|
|
658
|
+
}
|
|
659
|
+
// Handle empty diagram
|
|
660
|
+
if (minX === Infinity) {
|
|
661
|
+
minX = 0;
|
|
662
|
+
minY = 0;
|
|
663
|
+
maxViewX = totalWidth;
|
|
664
|
+
maxViewY = totalHeight;
|
|
665
|
+
}
|
|
666
|
+
// Shift everything so all coordinates are non-negative
|
|
667
|
+
const shiftX = minX < 0 ? -minX + MARGIN : 0;
|
|
668
|
+
const shiftY = minY < 0 ? -minY + MARGIN : 0;
|
|
669
|
+
if (shiftX > 0 || shiftY > 0) {
|
|
670
|
+
shiftAllPositions(positionedNodes, [], annotations, positionedEdges, shiftX, shiftY);
|
|
671
|
+
// Also shift swimlanes and regions
|
|
672
|
+
for (const sl of positionedSwimlanes) {
|
|
673
|
+
sl.x += shiftX;
|
|
674
|
+
sl.lineX += shiftX;
|
|
675
|
+
sl.headerY += shiftY;
|
|
676
|
+
sl.lineY1 += shiftY;
|
|
677
|
+
sl.lineY2 += shiftY;
|
|
678
|
+
}
|
|
679
|
+
for (const r of positionedRegions) {
|
|
680
|
+
r.x += shiftX;
|
|
681
|
+
r.y += shiftY;
|
|
682
|
+
}
|
|
683
|
+
maxViewX += shiftX;
|
|
684
|
+
maxViewY += shiftY;
|
|
685
|
+
}
|
|
686
|
+
return {
|
|
687
|
+
nodes: positionedNodes,
|
|
688
|
+
edges: positionedEdges,
|
|
689
|
+
clusters: [],
|
|
690
|
+
swimlanes: positionedSwimlanes,
|
|
691
|
+
regions: positionedRegions,
|
|
692
|
+
annotations,
|
|
693
|
+
messages: [],
|
|
694
|
+
lifelines: [],
|
|
695
|
+
positionedFragments: [],
|
|
696
|
+
viewBox: {
|
|
697
|
+
width: Math.max(totalWidth, maxViewX + MARGIN),
|
|
698
|
+
height: Math.max(totalHeight, maxViewY + MARGIN)
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
// ── Shared Helpers ─────────────────────────────────────────
|
|
703
|
+
/** Build a map from nodeId -> clusterId for cluster-aware ordering */
|
|
704
|
+
function buildClusterMap(config) {
|
|
705
|
+
const map = new Map();
|
|
706
|
+
if (!config.clusters)
|
|
707
|
+
return map;
|
|
708
|
+
for (const cluster of config.clusters) {
|
|
709
|
+
for (const nodeId of cluster.nodes) {
|
|
710
|
+
map.set(nodeId, cluster.id);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return map;
|
|
714
|
+
}
|
|
715
|
+
/** Shift all positioned elements by (dx, dy) to ensure non-negative coordinates */
|
|
716
|
+
function shiftAllPositions(nodes, clusters, annotations, edges, dx, dy) {
|
|
717
|
+
for (const n of nodes) {
|
|
718
|
+
n.x += dx;
|
|
719
|
+
n.y += dy;
|
|
720
|
+
}
|
|
721
|
+
for (const c of clusters) {
|
|
722
|
+
c.x += dx;
|
|
723
|
+
c.y += dy;
|
|
724
|
+
}
|
|
725
|
+
for (const a of annotations) {
|
|
726
|
+
a.x += dx;
|
|
727
|
+
a.y += dy;
|
|
728
|
+
}
|
|
729
|
+
for (const e of edges) {
|
|
730
|
+
e.labelX += dx;
|
|
731
|
+
e.labelY += dy;
|
|
732
|
+
// Shift SVG path coordinates
|
|
733
|
+
e.path = shiftSvgPath(e.path, dx, dy);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
/** Shift all absolute coordinates in an SVG path string by (dx, dy) */
|
|
737
|
+
function shiftSvgPath(path, dx, dy) {
|
|
738
|
+
// Match SVG path commands and their coordinate pairs
|
|
739
|
+
// This handles M, L, C, Q, S, T commands with absolute coords
|
|
740
|
+
return path.replace(/([MLCSQT])\s*([-\d.]+)\s+([-\d.]+)/gi, (_match, cmd, x, y) => {
|
|
741
|
+
const upper = cmd.toUpperCase();
|
|
742
|
+
// Only shift absolute commands (uppercase)
|
|
743
|
+
if (cmd === upper) {
|
|
744
|
+
return `${cmd} ${parseFloat(x) + dx} ${parseFloat(y) + dy}`;
|
|
745
|
+
}
|
|
746
|
+
return `${cmd} ${x} ${y}`;
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
function buildNodeDims(nodes) {
|
|
750
|
+
const dims = new Map();
|
|
751
|
+
for (const node of nodes) {
|
|
752
|
+
const w = node.width ?? estimateNodeWidth(node.label, node.description);
|
|
753
|
+
const h = node.height ?? (node.description ? DESC_NODE_HEIGHT : DEFAULT_NODE_HEIGHT);
|
|
754
|
+
dims.set(node.id, { w, h });
|
|
755
|
+
}
|
|
756
|
+
return dims;
|
|
757
|
+
}
|
|
758
|
+
function buildPositionedNodes(nodes, positions, nodeDims) {
|
|
759
|
+
return nodes.map((node) => {
|
|
760
|
+
const pos = positions.get(node.id) || { x: 0, y: 0 };
|
|
761
|
+
const dims = nodeDims.get(node.id);
|
|
762
|
+
return {
|
|
763
|
+
id: node.id,
|
|
764
|
+
x: pos.x,
|
|
765
|
+
y: pos.y,
|
|
766
|
+
width: dims.w,
|
|
767
|
+
height: dims.h,
|
|
768
|
+
label: node.label,
|
|
769
|
+
description: node.description,
|
|
770
|
+
icon: node.icon,
|
|
771
|
+
variant: node.variant || 'default',
|
|
772
|
+
color: node.color || 'neutral',
|
|
773
|
+
state: node.state || 'default'
|
|
774
|
+
};
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
// ── Swimlane Edge Paths ────────────────────────────────────
|
|
778
|
+
function computeSwimEdgePaths(edges, positions, nodeDims, laneMap, laneWidth) {
|
|
779
|
+
return edges.map((edge) => {
|
|
780
|
+
const fromPos = positions.get(edge.from);
|
|
781
|
+
const toPos = positions.get(edge.to);
|
|
782
|
+
const fromDims = nodeDims.get(edge.from);
|
|
783
|
+
const toDims = nodeDims.get(edge.to);
|
|
784
|
+
if (!fromPos || !toPos || !fromDims || !toDims) {
|
|
785
|
+
return emptyEdge(edge);
|
|
786
|
+
}
|
|
787
|
+
const fromLane = laneMap.get(edge.from) ?? 0;
|
|
788
|
+
const toLane = laneMap.get(edge.to) ?? 0;
|
|
789
|
+
let path;
|
|
790
|
+
let labelX;
|
|
791
|
+
let labelY;
|
|
792
|
+
if (fromLane === toLane) {
|
|
793
|
+
// Same lane: vertical arrow
|
|
794
|
+
const cx = fromPos.x + fromDims.w / 2;
|
|
795
|
+
const y1 = fromPos.y + fromDims.h;
|
|
796
|
+
const y2 = toPos.y;
|
|
797
|
+
path = `M ${cx} ${y1} L ${cx} ${y2}`;
|
|
798
|
+
labelX = cx + 10;
|
|
799
|
+
labelY = (y1 + y2) / 2;
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
// Cross-lane: horizontal arrow at midpoint Y
|
|
803
|
+
const midY = fromPos.y + fromDims.h / 2;
|
|
804
|
+
const x1 = fromLane < toLane ? fromPos.x + fromDims.w : fromPos.x;
|
|
805
|
+
const x2 = fromLane < toLane ? toPos.x : toPos.x + toDims.w;
|
|
806
|
+
path = `M ${x1} ${midY} L ${x2} ${midY}`;
|
|
807
|
+
labelX = (x1 + x2) / 2;
|
|
808
|
+
labelY = midY - 8;
|
|
809
|
+
}
|
|
810
|
+
return {
|
|
811
|
+
from: edge.from,
|
|
812
|
+
to: edge.to,
|
|
813
|
+
path,
|
|
814
|
+
label: edge.label,
|
|
815
|
+
labelX,
|
|
816
|
+
labelY,
|
|
817
|
+
arrow: edge.arrow || 'end',
|
|
818
|
+
dashed: edge.dashed || false,
|
|
819
|
+
color: edge.color || 'neutral'
|
|
820
|
+
};
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
// ── Public API ─────────────────────────────────────────────
|
|
824
|
+
// ── Sequence Layout ───────────────────────────────────────
|
|
825
|
+
const SEQ_ACTOR_GAP = 180;
|
|
826
|
+
const SEQ_MESSAGE_GAP = 40;
|
|
827
|
+
const SEQ_ACTOR_BOX_HEIGHT = 36;
|
|
828
|
+
const SEQ_TOP_MARGIN = 40;
|
|
829
|
+
const SEQ_SELF_LOOP_WIDTH = 30;
|
|
830
|
+
const SEQ_SELF_LOOP_HEIGHT = 24;
|
|
831
|
+
const SEQ_FRAGMENT_PAD_X = 20;
|
|
832
|
+
const SEQ_FRAGMENT_PAD_Y = 16;
|
|
833
|
+
const SEQ_FRAGMENT_TAG_HEIGHT = 20;
|
|
834
|
+
function layoutSequence(config) {
|
|
835
|
+
const actors = config.nodes;
|
|
836
|
+
const messages = config.messages || [];
|
|
837
|
+
const fragments = config.fragments || [];
|
|
838
|
+
// Position actors evenly across the x-axis
|
|
839
|
+
const actorXMap = new Map();
|
|
840
|
+
const actorColorMap = new Map();
|
|
841
|
+
for (let i = 0; i < actors.length; i++) {
|
|
842
|
+
const x = MARGIN + i * SEQ_ACTOR_GAP + SEQ_ACTOR_GAP / 2;
|
|
843
|
+
actorXMap.set(actors[i].id, x);
|
|
844
|
+
actorColorMap.set(actors[i].id, actors[i].color || 'neutral');
|
|
845
|
+
}
|
|
846
|
+
// Compute message Y positions (increment per message)
|
|
847
|
+
const messageStartY = SEQ_TOP_MARGIN + SEQ_ACTOR_BOX_HEIGHT + 30;
|
|
848
|
+
const messageYPositions = [];
|
|
849
|
+
let currentY = messageStartY;
|
|
850
|
+
for (let i = 0; i < messages.length; i++) {
|
|
851
|
+
messageYPositions.push(currentY);
|
|
852
|
+
const isSelf = messages[i].from === messages[i].to;
|
|
853
|
+
currentY += isSelf ? SEQ_MESSAGE_GAP + SEQ_SELF_LOOP_HEIGHT : SEQ_MESSAGE_GAP;
|
|
854
|
+
}
|
|
855
|
+
// Bottom of the diagram
|
|
856
|
+
const bottomY = currentY + 30 + SEQ_ACTOR_BOX_HEIGHT;
|
|
857
|
+
const totalHeight = bottomY + MARGIN;
|
|
858
|
+
const totalWidth = MARGIN * 2 + actors.length * SEQ_ACTOR_GAP;
|
|
859
|
+
// Build lifelines
|
|
860
|
+
const lifelines = actors.map((actor) => ({
|
|
861
|
+
id: actor.id,
|
|
862
|
+
label: actor.label,
|
|
863
|
+
x: actorXMap.get(actor.id),
|
|
864
|
+
topY: SEQ_TOP_MARGIN,
|
|
865
|
+
bottomY: bottomY,
|
|
866
|
+
color: (actor.color || 'neutral')
|
|
867
|
+
}));
|
|
868
|
+
// Build positioned messages
|
|
869
|
+
const positionedMessages = messages.map((msg, i) => {
|
|
870
|
+
const fromX = actorXMap.get(msg.from) ?? 0;
|
|
871
|
+
const toX = actorXMap.get(msg.to) ?? 0;
|
|
872
|
+
const y = messageYPositions[i];
|
|
873
|
+
const isSelf = msg.from === msg.to;
|
|
874
|
+
let labelX;
|
|
875
|
+
let labelY;
|
|
876
|
+
if (isSelf) {
|
|
877
|
+
labelX = fromX + SEQ_SELF_LOOP_WIDTH + 8;
|
|
878
|
+
labelY = y + SEQ_SELF_LOOP_HEIGHT / 2;
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
labelX = (fromX + toX) / 2;
|
|
882
|
+
labelY = y - 8;
|
|
883
|
+
}
|
|
884
|
+
return {
|
|
885
|
+
from: msg.from,
|
|
886
|
+
to: msg.to,
|
|
887
|
+
label: msg.label,
|
|
888
|
+
x1: fromX,
|
|
889
|
+
y,
|
|
890
|
+
x2: toX,
|
|
891
|
+
labelX,
|
|
892
|
+
labelY,
|
|
893
|
+
arrow: msg.arrow || 'end',
|
|
894
|
+
dashed: msg.dashed || false,
|
|
895
|
+
color: (msg.color || 'neutral'),
|
|
896
|
+
isSelf
|
|
897
|
+
};
|
|
898
|
+
});
|
|
899
|
+
// Build positioned fragments
|
|
900
|
+
const positionedFragments = fragments.map((frag) => {
|
|
901
|
+
if (frag.messages.length === 0) {
|
|
902
|
+
return {
|
|
903
|
+
id: frag.id,
|
|
904
|
+
label: frag.label,
|
|
905
|
+
condition: frag.condition,
|
|
906
|
+
x: 0,
|
|
907
|
+
y: 0,
|
|
908
|
+
width: 0,
|
|
909
|
+
height: 0,
|
|
910
|
+
color: (frag.color || 'neutral'),
|
|
911
|
+
dashed: frag.dashed ?? false
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
// Find bounds from contained messages
|
|
915
|
+
let minX = Infinity;
|
|
916
|
+
let maxX = -Infinity;
|
|
917
|
+
let minY = Infinity;
|
|
918
|
+
let maxY = -Infinity;
|
|
919
|
+
for (const msgIdx of frag.messages) {
|
|
920
|
+
if (msgIdx < 0 || msgIdx >= positionedMessages.length)
|
|
921
|
+
continue;
|
|
922
|
+
const msg = positionedMessages[msgIdx];
|
|
923
|
+
const leftX = Math.min(msg.x1, msg.x2);
|
|
924
|
+
const rightX = msg.isSelf ? msg.x1 + SEQ_SELF_LOOP_WIDTH : Math.max(msg.x1, msg.x2);
|
|
925
|
+
minX = Math.min(minX, leftX);
|
|
926
|
+
maxX = Math.max(maxX, rightX);
|
|
927
|
+
minY = Math.min(minY, msg.y);
|
|
928
|
+
maxY = Math.max(maxY, msg.isSelf ? msg.y + SEQ_SELF_LOOP_HEIGHT : msg.y);
|
|
929
|
+
}
|
|
930
|
+
if (minX === Infinity) {
|
|
931
|
+
return {
|
|
932
|
+
id: frag.id,
|
|
933
|
+
label: frag.label,
|
|
934
|
+
condition: frag.condition,
|
|
935
|
+
x: 0,
|
|
936
|
+
y: 0,
|
|
937
|
+
width: 0,
|
|
938
|
+
height: 0,
|
|
939
|
+
color: (frag.color || 'neutral'),
|
|
940
|
+
dashed: frag.dashed ?? false
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
return {
|
|
944
|
+
id: frag.id,
|
|
945
|
+
label: frag.label,
|
|
946
|
+
condition: frag.condition,
|
|
947
|
+
x: minX - SEQ_FRAGMENT_PAD_X,
|
|
948
|
+
y: minY - SEQ_FRAGMENT_PAD_Y - SEQ_FRAGMENT_TAG_HEIGHT,
|
|
949
|
+
width: maxX - minX + SEQ_FRAGMENT_PAD_X * 2,
|
|
950
|
+
height: maxY - minY + SEQ_FRAGMENT_PAD_Y * 2 + SEQ_FRAGMENT_TAG_HEIGHT,
|
|
951
|
+
color: (frag.color || 'neutral'),
|
|
952
|
+
dashed: frag.dashed ?? false
|
|
953
|
+
};
|
|
954
|
+
});
|
|
955
|
+
// Annotations
|
|
956
|
+
const positions = new Map();
|
|
957
|
+
const nodeDims = new Map();
|
|
958
|
+
for (const actor of actors) {
|
|
959
|
+
const x = actorXMap.get(actor.id);
|
|
960
|
+
positions.set(actor.id, { x: x - 60, y: SEQ_TOP_MARGIN });
|
|
961
|
+
nodeDims.set(actor.id, { w: 120, h: SEQ_ACTOR_BOX_HEIGHT });
|
|
962
|
+
}
|
|
963
|
+
const annotations = resolveAnnotations(config, positions, nodeDims);
|
|
964
|
+
return {
|
|
965
|
+
nodes: [],
|
|
966
|
+
edges: [],
|
|
967
|
+
clusters: [],
|
|
968
|
+
swimlanes: [],
|
|
969
|
+
regions: [],
|
|
970
|
+
annotations,
|
|
971
|
+
messages: positionedMessages,
|
|
972
|
+
lifelines,
|
|
973
|
+
positionedFragments,
|
|
974
|
+
viewBox: { width: totalWidth, height: totalHeight }
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
// ── Public API ─────────────────────────────────────────────
|
|
978
|
+
export function computeLayout(config) {
|
|
979
|
+
if (config.layout === 'sequence')
|
|
980
|
+
return layoutSequence(config);
|
|
981
|
+
if (config.layout === 'swimlane') {
|
|
982
|
+
return layoutSwimlane(config);
|
|
983
|
+
}
|
|
984
|
+
return layoutLayered(config);
|
|
985
|
+
}
|