@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.
- package/dist/accordion/accordion-root.svelte +1 -1
- package/dist/adjust/adjust.svelte +12 -8
- package/dist/alpha-slider/alpha-slider-input.svelte +14 -13
- package/dist/app-frame/app-frame.svelte +4 -1
- package/dist/aspect-ratio/aspect-ratio.svelte +7 -6
- package/dist/aurora/aurora.svelte +144 -81
- package/dist/avatar/avatar.svelte +22 -15
- package/dist/badge/badge.svelte +10 -22
- package/dist/beam/beam.svelte +20 -21
- package/dist/button/button.svelte +45 -24
- package/dist/button/button.svelte.d.ts +14 -0
- package/dist/button-group/button-group.svelte +4 -5
- package/dist/calendar/calendar-button-grid.svelte +11 -4
- package/dist/calendar/calendar-button-grid.svelte.d.ts +2 -2
- package/dist/calendar/calendar-event-legend.svelte +63 -0
- package/dist/calendar/calendar-event-legend.svelte.d.ts +6 -0
- package/dist/calendar/calendar-header.svelte +3 -1
- package/dist/calendar/calendar-heading.svelte +21 -9
- package/dist/calendar/calendar-nav-button.svelte +15 -2
- package/dist/calendar/calendar-root.svelte +94 -11
- package/dist/calendar/calendar-root.svelte.d.ts +10 -1
- package/dist/calendar/calendar-week.svelte +515 -0
- package/dist/calendar/calendar-week.svelte.d.ts +12 -0
- package/dist/calendar/calendar.meta.js +2 -2
- package/dist/calendar/context.svelte.d.ts +19 -0
- package/dist/calendar/index.d.ts +11 -1
- package/dist/calendar/index.js +5 -1
- package/dist/calendar/types.d.ts +32 -0
- package/dist/calendar/types.js +1 -0
- package/dist/calendar/week-utils.d.ts +12 -0
- package/dist/calendar/week-utils.js +133 -0
- package/dist/carousel/carousel-button-dots.svelte +12 -6
- package/dist/carousel/carousel-root.svelte +201 -17
- package/dist/carousel/carousel-root.svelte.d.ts +1 -1
- package/dist/carousel/carousel-viewport.svelte +19 -1
- package/dist/chart/chart-bars.svelte +43 -19
- package/dist/chart/chart-donut.svelte +50 -23
- package/dist/chart/chart-root.svelte +19 -4
- package/dist/chart/chart-stacked-bar.svelte +3 -2
- package/dist/chart/series-color.d.ts +7 -0
- package/dist/chart/series-color.js +11 -0
- package/dist/chat-thread/chat-thread.svelte +15 -126
- package/dist/chat-thread/chat-thread.svelte.d.ts +4 -7
- package/dist/chat-thread/index.d.ts +4 -7
- package/dist/checkbox/checkbox-input.svelte +48 -41
- package/dist/chip/chip-button.svelte +14 -17
- package/dist/chip/chip-button.svelte.d.ts +2 -3
- package/dist/chip-group/chip-group-root.svelte +6 -4
- package/dist/chromatic-aberration/chromatic-aberration.svelte +34 -19
- package/dist/chromatic-shift/chromatic-shift.svelte +7 -8
- package/dist/code-block/code-block-button.svelte +72 -40
- package/dist/color-picker/color-picker-area.svelte +20 -25
- package/dist/color-picker/color-picker-input-hue-slider.svelte +12 -9
- package/dist/color-picker/color-picker-root.svelte +18 -17
- package/dist/color-picker/color-picker-swatch.svelte +17 -16
- package/dist/combobox/combobox-content.svelte +4 -14
- package/dist/combobox/combobox-input-root.svelte +6 -3
- package/dist/combobox/combobox-input-root.svelte.d.ts +2 -1
- package/dist/combobox/combobox-input.svelte +13 -10
- package/dist/combobox/combobox-item.svelte +9 -4
- package/dist/command-palette/command-palette-dialog-root.svelte +1 -0
- package/dist/command-palette/command-palette-item.svelte +7 -4
- package/dist/context-menu/context-menu-content.svelte +7 -15
- package/dist/data-grid/data-grid-button-input-column.svelte +2 -2
- package/dist/data-grid/data-grid-cell.svelte +1 -1
- package/dist/data-grid/data-grid-input-select-all.svelte +1 -1
- package/dist/data-grid/data-grid-input-select-cell.svelte +1 -1
- package/dist/date-field/date-field-segment.svelte +10 -12
- package/dist/date-picker/datepicker-button-calendar.svelte +2 -2
- package/dist/date-picker/datepicker-button-calendar.svelte.d.ts +2 -2
- package/dist/date-picker/datepicker-button-trigger.svelte +146 -44
- package/dist/date-picker/index.d.ts +4 -1
- package/dist/date-range-picker/date-range-picker-button-calendar.svelte +2 -2
- package/dist/date-range-picker/date-range-picker-button-calendar.svelte.d.ts +2 -2
- package/dist/date-range-picker/date-range-picker-button-trigger.svelte +151 -40
- package/dist/date-range-picker/date-range-picker-button-trigger.svelte.d.ts +1 -0
- package/dist/date-range-picker/index.d.ts +4 -1
- package/dist/diagram/edge-routing.js +18 -7
- package/dist/diagram/layout.js +31 -16
- package/dist/displacement/displacement.svelte +30 -43
- package/dist/drag-and-drop/drag-and-drop-root.svelte +17 -5
- package/dist/drag-and-drop/index.d.ts +14 -1
- package/dist/drop-zone/drop-zone.svelte +7 -4
- package/dist/dropdown-menu/dropdown-menu-content.svelte +15 -16
- package/dist/field/field-description.svelte +6 -9
- package/dist/field/field-error.svelte +6 -10
- package/dist/file-select/file-select-root.svelte +13 -18
- package/dist/file-select/file-select-root.svelte.d.ts +2 -1
- package/dist/file-upload/file-upload-dropzone.svelte +22 -22
- package/dist/file-upload/file-upload-item.svelte +1 -1
- package/dist/file-upload/file-upload-list.svelte +1 -1
- package/dist/flip-card/flip-card-root.svelte +2 -2
- package/dist/float-button/context.svelte.d.ts +2 -0
- package/dist/float-button/float-button-action.svelte +41 -11
- package/dist/float-button/float-button-root.svelte +52 -13
- package/dist/float-button/float-button-trigger.svelte +25 -12
- package/dist/format-bytes/format-bytes.svelte +2 -5
- package/dist/format-date/format-date.svelte +2 -6
- package/dist/format-number/format-number.svelte +2 -6
- package/dist/gauge/gauge.svelte +9 -2
- package/dist/glass/glass.svelte +4 -3
- package/dist/glow/glow.svelte +0 -5
- package/dist/god-rays/god-rays.svelte +6 -6
- package/dist/gradient-mesh/gradient-mesh.svelte +112 -111
- package/dist/halftone/halftone.svelte +17 -28
- package/dist/heading/heading.svelte +0 -6
- package/dist/hover-card/hover-card-content.svelte +8 -5
- package/dist/hover-card/hover-card.meta.js +2 -2
- package/dist/icon/icon.svelte +24 -13
- package/dist/icon/icon.svelte.d.ts +2 -2
- package/dist/icon-swap/icon-swap.svelte +4 -3
- package/dist/icon-swap/icon-swap.svelte.d.ts +2 -1
- package/dist/image/image.svelte +5 -13
- package/dist/image-comparison/image-comparison.svelte +39 -45
- package/dist/index.d.ts +3 -5
- package/dist/index.js +1 -2
- package/dist/input/input.svelte +88 -9
- package/dist/input/input.svelte.d.ts +24 -1
- package/dist/internal/anchored-overlay-content.svelte.d.ts +12 -6
- package/dist/internal/anchored-overlay-content.svelte.js +13 -16
- package/dist/internal/calendar-event-layout.d.ts +53 -0
- package/dist/internal/calendar-event-layout.js +191 -0
- package/dist/internal/calendar-grid-button.svelte +618 -76
- package/dist/internal/calendar-grid-button.svelte.d.ts +2 -2
- package/dist/internal/calendar-grid-utils.d.ts +3 -0
- package/dist/internal/calendar-grid-utils.js +24 -0
- package/dist/internal/form-control-wrapper-attrs.d.ts +9 -0
- package/dist/internal/form-control-wrapper-attrs.js +1 -0
- package/dist/internal/menu-item.svelte +1 -0
- package/dist/internal/modal-content.svelte +2 -4
- package/dist/internal/motion.d.ts +1 -1
- package/dist/internal/motion.js +5 -2
- package/dist/internal/picker-popover-content.svelte +15 -16
- package/dist/link/link.svelte +8 -5
- package/dist/link-preview/link-preview-content.svelte +12 -15
- package/dist/link-preview/link-preview-trigger.svelte +3 -6
- package/dist/listbox/listbox-item.svelte +13 -18
- package/dist/listbox/listbox-root.svelte +4 -9
- package/dist/logo-mark/logo-mark.svelte +31 -31
- package/dist/map/map-marker.svelte +2 -2
- package/dist/map/map-root.svelte +11 -2
- package/dist/markdown-renderer/markdown-renderer.svelte +141 -164
- package/dist/marquee/marquee.svelte +12 -13
- package/dist/mask-reveal/mask-reveal.svelte +14 -27
- package/dist/mega-menu/mega-menu-column.svelte +1 -1
- package/dist/mega-menu/mega-menu-link.svelte +7 -7
- package/dist/mega-menu/mega-menu-panel.svelte +18 -32
- package/dist/menubar/menubar-content.svelte +18 -19
- package/dist/menubar/menubar-item.svelte +1 -0
- package/dist/menubar/menubar-menu.svelte +1 -2
- package/dist/menubar/menubar-root.svelte +1 -11
- package/dist/motion/enter.svelte +15 -14
- package/dist/motion/enter.svelte.d.ts +2 -1
- package/dist/motion/exit.svelte +6 -5
- package/dist/motion/exit.svelte.d.ts +2 -1
- package/dist/motion/stagger.svelte +10 -9
- package/dist/motion/stagger.svelte.d.ts +2 -1
- package/dist/multi-select-combobox/multi-select-combobox-content.svelte +4 -14
- package/dist/multi-select-combobox/multi-select-combobox-input.svelte +6 -5
- package/dist/multi-select-combobox/multi-select-combobox-item.svelte +25 -31
- package/dist/multi-select-combobox/multi-select-combobox-root-input.svelte +12 -16
- package/dist/noise/noise.svelte +20 -19
- package/dist/notification-center/notification-center-item.svelte +1 -1
- package/dist/notification-center/notification-center-panel.svelte +17 -19
- package/dist/number-input/number-input-button.svelte +78 -12
- package/dist/number-input/number-input-button.svelte.d.ts +15 -1
- package/dist/numeric/numeric.svelte +27 -12
- package/dist/numeric/numeric.svelte.d.ts +2 -1
- package/dist/option-picker/option-picker-item.svelte +8 -15
- package/dist/option-picker/option-picker-preview.svelte +44 -24
- package/dist/pagination/pagination-root.svelte +7 -0
- package/dist/pin-input/pin-input-root.svelte +2 -11
- package/dist/popover/popover-content.svelte +7 -4
- package/dist/progress/progress.svelte +2 -2
- package/dist/prompt-input/prompt-input-button-textarea.svelte +2 -10
- package/dist/qr-code/qr-code.svelte +78 -38
- package/dist/qr-code/qr-code.svelte.d.ts +1 -1
- package/dist/radio-group/radio-group-item-input.svelte +60 -14
- package/dist/radio-group/radio-group-item-input.svelte.d.ts +9 -2
- package/dist/range-calendar/index.d.ts +4 -1
- package/dist/range-calendar/range-calendar-grid-button.svelte +2 -2
- package/dist/range-calendar/range-calendar-grid-button.svelte.d.ts +2 -2
- package/dist/relative-time/relative-time.svelte +15 -15
- package/dist/reveal/reveal.svelte +12 -24
- package/dist/rich-text-editor/rich-text-editor-content.svelte +1 -4
- package/dist/scroll-to-top/scroll-to-top-button.svelte +5 -3
- package/dist/scroll-to-top/scroll-to-top-button.svelte.d.ts +2 -2
- package/dist/segmented-control/segmented-control-root.svelte +0 -3
- package/dist/select/index.d.ts +7 -1
- package/dist/select/select-content.svelte +15 -16
- package/dist/select/select-item.svelte +1 -0
- package/dist/select/select-root-input.svelte +5 -4
- package/dist/select/select-root-input.svelte.d.ts +2 -2
- package/dist/select/select-trigger-button.svelte +122 -16
- package/dist/select/select-trigger-button.svelte.d.ts +2 -2
- package/dist/shader-canvas/shader-canvas.svelte +11 -20
- package/dist/shimmer/shimmer.svelte +28 -25
- package/dist/skeleton/skeleton.svelte +18 -23
- package/dist/slider/slider-input.svelte +8 -7
- package/dist/sparkline/sparkline.svelte +1 -1
- package/dist/splitter/splitter-handle.svelte +3 -3
- package/dist/splitter/splitter-root.svelte +13 -12
- package/dist/spotlight/spotlight.svelte +30 -62
- package/dist/star-rating/star-rating-root.svelte +3 -4
- package/dist/stepper/stepper-step-button.svelte +2 -2
- package/dist/table-of-contents/table-of-contents-root.svelte +5 -16
- package/dist/tabs/tabs-content.svelte +7 -1
- package/dist/tag/tag-button.svelte +70 -68
- package/dist/tags-input/tags-input-root.svelte +2 -9
- package/dist/tags-input/tags-input-tag.svelte +1 -1
- package/dist/text/index.d.ts +2 -3
- package/dist/text/text.svelte +6 -12
- package/dist/text/text.svelte.d.ts +2 -3
- package/dist/textarea/textarea.svelte +91 -16
- package/dist/textarea/textarea.svelte.d.ts +20 -1
- package/dist/themes/aurora.css +155 -113
- package/dist/themes/component-defaults.css +47 -0
- package/dist/themes/dark.css +115 -229
- package/dist/themes/default.css +71 -176
- package/dist/themes/midnight.css +80 -65
- package/dist/themes/terminal.css +69 -73
- package/dist/time-input/index.d.ts +2 -2
- package/dist/time-input/time-input.meta.js +1 -1
- package/dist/time-input/time-input.svelte +5 -3
- package/dist/time-input/time-input.svelte.d.ts +2 -2
- package/dist/timeline/timeline-title.svelte +0 -6
- package/dist/toast/toast-root.svelte +10 -9
- package/dist/toggle/toggle-button.svelte +17 -6
- package/dist/toggle-group/toggle-group-root.svelte +3 -3
- package/dist/token-scope/index.d.ts +6 -0
- package/dist/token-scope/index.js +1 -0
- package/dist/token-scope/token-scope.meta.js +7 -0
- package/dist/toolbar/toolbar-root.svelte +4 -4
- package/dist/tooltip/tooltip-content.svelte +24 -13
- package/dist/tour/tour-root.css +6 -2
- package/dist/transfer/transfer-list-input.svelte +1 -2
- package/dist/transfer/transfer-root.svelte +0 -10
- package/dist/tree/tree-root.svelte +7 -4
- package/dist/typography/blockquote.svelte +2 -2
- package/dist/typography/code.svelte +2 -2
- package/dist/typography/heading.svelte +2 -2
- package/dist/typography/index.d.ts +0 -1
- package/dist/typography/text.svelte +2 -13
- package/dist/video-embed/video-embed-button.svelte +9 -9
- package/package.json +13 -12
- package/src/accordion/accordion-button-trigger.svelte +60 -0
- package/src/accordion/accordion-content.svelte +37 -0
- package/src/accordion/accordion-item.svelte +62 -0
- package/src/accordion/accordion-root.svelte +61 -0
- package/src/accordion/accordion.meta.ts +7 -0
- package/src/accordion/context.svelte.ts +19 -0
- package/src/accordion/index.ts +41 -0
- package/src/adjust/adjust.meta.ts +7 -0
- package/src/adjust/adjust.svelte +71 -0
- package/src/adjust/index.ts +20 -0
- package/src/alert/alert.meta.ts +7 -0
- package/src/alert/alert.svelte +174 -0
- package/src/alert/index.ts +18 -0
- package/src/alert-dialog/alert-dialog-body.svelte +23 -0
- package/src/alert-dialog/alert-dialog-button-action.svelte +15 -0
- package/src/alert-dialog/alert-dialog-button-cancel.svelte +18 -0
- package/src/alert-dialog/alert-dialog-content.svelte +26 -0
- package/src/alert-dialog/alert-dialog-footer.svelte +29 -0
- package/src/alert-dialog/alert-dialog-header.svelte +26 -0
- package/src/alert-dialog/alert-dialog-overlay.svelte +34 -0
- package/src/alert-dialog/alert-dialog-root.svelte +31 -0
- package/src/alert-dialog/alert-dialog-trigger.svelte +56 -0
- package/src/alert-dialog/alert-dialog.meta.ts +7 -0
- package/src/alert-dialog/context.svelte.ts +11 -0
- package/src/alert-dialog/index.ts +43 -0
- package/src/alpha-slider/alpha-slider-input.svelte +165 -0
- package/src/alpha-slider/alpha-slider.meta.ts +7 -0
- package/src/alpha-slider/index.ts +10 -0
- package/src/app-frame/app-frame.meta.ts +8 -0
- package/src/app-frame/app-frame.svelte +141 -0
- package/src/app-frame/index.ts +10 -0
- package/src/aspect-ratio/aspect-ratio.meta.ts +7 -0
- package/src/aspect-ratio/aspect-ratio.svelte +29 -0
- package/src/aspect-ratio/index.ts +9 -0
- package/src/aurora/aurora.meta.ts +8 -0
- package/src/aurora/aurora.svelte +526 -0
- package/src/aurora/index.ts +24 -0
- package/src/avatar/avatar.meta.ts +7 -0
- package/src/avatar/avatar.svelte +213 -0
- package/src/avatar/index.ts +15 -0
- package/src/backdrop/backdrop.meta.ts +7 -0
- package/src/backdrop/backdrop.svelte +51 -0
- package/src/backdrop/index.ts +3 -0
- package/src/badge/badge.meta.ts +7 -0
- package/src/badge/badge.svelte +334 -0
- package/src/badge/index.ts +12 -0
- package/src/beam/beam.meta.ts +8 -0
- package/src/beam/beam.svelte +118 -0
- package/src/beam/index.ts +17 -0
- package/src/border-beam/border-beam.meta.ts +8 -0
- package/src/border-beam/border-beam.svelte +42 -0
- package/src/border-beam/index.ts +10 -0
- package/src/breadcrumb/breadcrumb-item.svelte +23 -0
- package/src/breadcrumb/breadcrumb-link.svelte +60 -0
- package/src/breadcrumb/breadcrumb-list.svelte +28 -0
- package/src/breadcrumb/breadcrumb-root.svelte +14 -0
- package/src/breadcrumb/breadcrumb-separator.svelte +25 -0
- package/src/breadcrumb/breadcrumb.meta.ts +7 -0
- package/src/breadcrumb/index.ts +44 -0
- package/src/button/button.meta.ts +7 -0
- package/src/button/button.svelte +675 -0
- package/src/button/index.ts +29 -0
- package/src/button-group/button-group.meta.ts +7 -0
- package/src/button-group/button-group.svelte +61 -0
- package/src/button-group/context.svelte.ts +15 -0
- package/src/button-group/index.ts +7 -0
- package/src/calendar/calendar-button-grid.svelte +58 -0
- package/src/calendar/calendar-button-next.svelte +13 -0
- package/src/calendar/calendar-button-prev.svelte +13 -0
- package/src/calendar/calendar-event-legend.svelte +63 -0
- package/src/calendar/calendar-header.svelte +28 -0
- package/src/calendar/calendar-heading.svelte +42 -0
- package/src/calendar/calendar-nav-button.svelte +39 -0
- package/src/calendar/calendar-root.svelte +211 -0
- package/src/calendar/calendar-week.svelte +515 -0
- package/src/calendar/calendar.meta.ts +7 -0
- package/src/calendar/context.svelte.ts +43 -0
- package/src/calendar/index.ts +57 -0
- package/src/calendar/types.ts +39 -0
- package/src/calendar/week-utils.ts +175 -0
- package/src/carousel/carousel-button-dots.svelte +74 -0
- package/src/carousel/carousel-button-next.svelte +13 -0
- package/src/carousel/carousel-button-prev.svelte +13 -0
- package/src/carousel/carousel-button-thumbnails.svelte +82 -0
- package/src/carousel/carousel-nav-button.svelte +31 -0
- package/src/carousel/carousel-root.svelte +432 -0
- package/src/carousel/carousel-slide.svelte +39 -0
- package/src/carousel/carousel-viewport.svelte +91 -0
- package/src/carousel/carousel.meta.ts +7 -0
- package/src/carousel/context.svelte.ts +22 -0
- package/src/carousel/index.ts +35 -0
- package/src/chart/chart-area.svelte +76 -0
- package/src/chart/chart-bars.svelte +102 -0
- package/src/chart/chart-donut.svelte +145 -0
- package/src/chart/chart-horizontal-bar.svelte +67 -0
- package/src/chart/chart-line.svelte +66 -0
- package/src/chart/chart-root.svelte +257 -0
- package/src/chart/chart-stacked-bar.svelte +77 -0
- package/src/chart/chart-x-axis.svelte +50 -0
- package/src/chart/chart-y-axis.svelte +71 -0
- package/src/chart/chart.meta.ts +7 -0
- package/src/chart/context.svelte.ts +29 -0
- package/src/chart/index.ts +86 -0
- package/src/chart/series-color.ts +12 -0
- package/src/chat-thread/chat-thread.meta.ts +7 -0
- package/src/chat-thread/chat-thread.svelte +97 -0
- package/src/chat-thread/index.ts +10 -0
- package/src/checkbox/checkbox-input.svelte +259 -0
- package/src/checkbox/checkbox.meta.ts +7 -0
- package/src/checkbox/index.ts +7 -0
- package/src/chip/chip-button.svelte +170 -0
- package/src/chip/chip.meta.ts +7 -0
- package/src/chip/index.ts +29 -0
- package/src/chip-group/chip-group-button-item.svelte +36 -0
- package/src/chip-group/chip-group-label.svelte +26 -0
- package/src/chip-group/chip-group-root.svelte +129 -0
- package/src/chip-group/chip-group.meta.ts +19 -0
- package/src/chip-group/context.svelte.ts +11 -0
- package/src/chip-group/index.ts +26 -0
- package/src/chromatic-aberration/chromatic-aberration.meta.ts +7 -0
- package/src/chromatic-aberration/chromatic-aberration.svelte +87 -0
- package/src/chromatic-aberration/index.ts +10 -0
- package/src/chromatic-shift/chromatic-shift.meta.ts +7 -0
- package/src/chromatic-shift/chromatic-shift.svelte +226 -0
- package/src/chromatic-shift/index.ts +5 -0
- package/src/clipboard/clipboard.meta.ts +7 -0
- package/src/clipboard/clipboard.svelte +38 -0
- package/src/clipboard/index.ts +3 -0
- package/src/code-block/code-block-button.svelte +371 -0
- package/src/code-block/code-block.meta.ts +7 -0
- package/src/code-block/highlighter/css.ts +387 -0
- package/src/code-block/highlighter/generic.ts +24 -0
- package/src/code-block/highlighter/index.ts +30 -0
- package/src/code-block/highlighter/svelte.ts +597 -0
- package/src/code-block/highlighter/types.ts +7 -0
- package/src/code-block/index.ts +11 -0
- package/src/collapsible/collapsible-button-trigger.svelte +50 -0
- package/src/collapsible/collapsible-content.svelte +57 -0
- package/src/collapsible/collapsible-root.svelte +48 -0
- package/src/collapsible/collapsible.meta.ts +7 -0
- package/src/collapsible/context.svelte.ts +10 -0
- package/src/collapsible/index.ts +30 -0
- package/src/color-picker/color-picker-area.svelte +181 -0
- package/src/color-picker/color-picker-button-eyedropper.svelte +61 -0
- package/src/color-picker/color-picker-channel-input.svelte +85 -0
- package/src/color-picker/color-picker-input-alpha-slider.svelte +144 -0
- package/src/color-picker/color-picker-input-hue-slider.svelte +146 -0
- package/src/color-picker/color-picker-input.svelte +129 -0
- package/src/color-picker/color-picker-root.svelte +185 -0
- package/src/color-picker/color-picker-swatch.svelte +116 -0
- package/src/color-picker/color-picker.meta.ts +7 -0
- package/src/color-picker/context.svelte.ts +19 -0
- package/src/color-picker/index.ts +45 -0
- package/src/combobox/combobox-content.svelte +124 -0
- package/src/combobox/combobox-empty.svelte +23 -0
- package/src/combobox/combobox-group.svelte +35 -0
- package/src/combobox/combobox-input-root.svelte +109 -0
- package/src/combobox/combobox-input.svelte +348 -0
- package/src/combobox/combobox-item.svelte +150 -0
- package/src/combobox/combobox.meta.ts +7 -0
- package/src/combobox/context.svelte.ts +22 -0
- package/src/combobox/index.ts +44 -0
- package/src/command-palette/command-palette-dialog-root.svelte +184 -0
- package/src/command-palette/command-palette-empty.svelte +30 -0
- package/src/command-palette/command-palette-group.svelte +37 -0
- package/src/command-palette/command-palette-input.svelte +91 -0
- package/src/command-palette/command-palette-item.svelte +100 -0
- package/src/command-palette/command-palette-list.svelte +52 -0
- package/src/command-palette/command-palette-separator.svelte +17 -0
- package/src/command-palette/command-palette.meta.ts +7 -0
- package/src/command-palette/context.svelte.ts +19 -0
- package/src/command-palette/index.ts +35 -0
- package/src/container/container.meta.ts +7 -0
- package/src/container/container.svelte +66 -0
- package/src/container/index.ts +10 -0
- package/src/context-menu/context-menu-content.svelte +114 -0
- package/src/context-menu/context-menu-group.svelte +15 -0
- package/src/context-menu/context-menu-item.svelte +27 -0
- package/src/context-menu/context-menu-label.svelte +15 -0
- package/src/context-menu/context-menu-root.svelte +26 -0
- package/src/context-menu/context-menu-separator.svelte +10 -0
- package/src/context-menu/context-menu-trigger.svelte +35 -0
- package/src/context-menu/context-menu.meta.ts +7 -0
- package/src/context-menu/context.svelte.ts +7 -0
- package/src/context-menu/index.ts +35 -0
- package/src/data-grid/context.svelte.ts +28 -0
- package/src/data-grid/data-grid-body.svelte +17 -0
- package/src/data-grid/data-grid-button-expand-trigger.svelte +45 -0
- package/src/data-grid/data-grid-button-input-column.svelte +263 -0
- package/src/data-grid/data-grid-button-pagination.svelte +130 -0
- package/src/data-grid/data-grid-cell.svelte +37 -0
- package/src/data-grid/data-grid-expandable-row.svelte +58 -0
- package/src/data-grid/data-grid-header.svelte +21 -0
- package/src/data-grid/data-grid-input-select-all.svelte +52 -0
- package/src/data-grid/data-grid-input-select-cell.svelte +40 -0
- package/src/data-grid/data-grid-root.svelte +229 -0
- package/src/data-grid/data-grid-row.svelte +38 -0
- package/src/data-grid/data-grid-table.svelte +21 -0
- package/src/data-grid/data-grid.meta.ts +7 -0
- package/src/data-grid/index.ts +75 -0
- package/src/date-field/context.svelte.ts +50 -0
- package/src/date-field/date-field-input-root.svelte +284 -0
- package/src/date-field/date-field-segment.svelte +180 -0
- package/src/date-field/date-field-separator.svelte +25 -0
- package/src/date-field/date-field.meta.ts +7 -0
- package/src/date-field/index.ts +23 -0
- package/src/date-picker/context.svelte.ts +27 -0
- package/src/date-picker/date-picker.meta.ts +7 -0
- package/src/date-picker/datepicker-button-calendar.svelte +58 -0
- package/src/date-picker/datepicker-button-trigger.svelte +174 -0
- package/src/date-picker/datepicker-content.svelte +35 -0
- package/src/date-picker/datepicker-input-root.svelte +137 -0
- package/src/date-picker/index.ts +27 -0
- package/src/date-range-picker/context.svelte.ts +30 -0
- package/src/date-range-picker/date-range-picker-button-calendar.svelte +96 -0
- package/src/date-range-picker/date-range-picker-button-preset.svelte +25 -0
- package/src/date-range-picker/date-range-picker-button-trigger.svelte +188 -0
- package/src/date-range-picker/date-range-picker-content.svelte +35 -0
- package/src/date-range-picker/date-range-picker-root.svelte +155 -0
- package/src/date-range-picker/date-range-picker.meta.ts +7 -0
- package/src/date-range-picker/index.ts +30 -0
- package/src/date-time-input/date-time-input.meta.ts +7 -0
- package/src/date-time-input/date-time-input.svelte +104 -0
- package/src/date-time-input/index.ts +7 -0
- package/src/description-list/description-list-description.svelte +23 -0
- package/src/description-list/description-list-item.svelte +34 -0
- package/src/description-list/description-list-root.svelte +22 -0
- package/src/description-list/description-list-term.svelte +23 -0
- package/src/description-list/description-list.meta.ts +7 -0
- package/src/description-list/index.ts +35 -0
- package/src/diagram/diagram.meta.ts +8 -0
- package/src/diagram/diagram.svelte +966 -0
- package/src/diagram/edge-routing.ts +782 -0
- package/src/diagram/index.ts +23 -0
- package/src/diagram/layout.ts +2024 -0
- package/src/diagram/types.ts +262 -0
- package/src/dialog/context.svelte.ts +10 -0
- package/src/dialog/dialog-body.svelte +23 -0
- package/src/dialog/dialog-close.svelte +18 -0
- package/src/dialog/dialog-content.svelte +18 -0
- package/src/dialog/dialog-footer.svelte +35 -0
- package/src/dialog/dialog-header.svelte +26 -0
- package/src/dialog/dialog-overlay.svelte +28 -0
- package/src/dialog/dialog-root.svelte +31 -0
- package/src/dialog/dialog-trigger.svelte +56 -0
- package/src/dialog/dialog.meta.ts +7 -0
- package/src/dialog/index.ts +39 -0
- package/src/displacement/displacement.meta.ts +7 -0
- package/src/displacement/displacement.svelte +140 -0
- package/src/displacement/index.ts +5 -0
- package/src/drag-and-drop/context.svelte.ts +21 -0
- package/src/drag-and-drop/drag-and-drop-group.svelte +37 -0
- package/src/drag-and-drop/drag-and-drop-handle.svelte +106 -0
- package/src/drag-and-drop/drag-and-drop-item.svelte +151 -0
- package/src/drag-and-drop/drag-and-drop-root.svelte +876 -0
- package/src/drag-and-drop/drag-and-drop.meta.ts +7 -0
- package/src/drag-and-drop/group-context.svelte.ts +20 -0
- package/src/drag-and-drop/index.ts +34 -0
- package/src/drawer/context.svelte.ts +11 -0
- package/src/drawer/drawer-body.svelte +33 -0
- package/src/drawer/drawer-close.svelte +18 -0
- package/src/drawer/drawer-dialog-content.svelte +18 -0
- package/src/drawer/drawer-footer.svelte +27 -0
- package/{dist/card/card-footer.svelte → src/drawer/drawer-header.svelte} +8 -10
- package/src/drawer/drawer-overlay.svelte +34 -0
- package/src/drawer/drawer-root.svelte +35 -0
- package/src/drawer/drawer-trigger.svelte +56 -0
- package/src/drawer/drawer.meta.ts +7 -0
- package/src/drawer/index.ts +43 -0
- package/src/drop-zone/drop-zone.meta.ts +7 -0
- package/src/drop-zone/drop-zone.svelte +95 -0
- package/src/drop-zone/index.ts +6 -0
- package/src/dropdown-menu/context.svelte.ts +7 -0
- package/src/dropdown-menu/dropdown-menu-content.svelte +137 -0
- package/src/dropdown-menu/dropdown-menu-group.svelte +15 -0
- package/src/dropdown-menu/dropdown-menu-item.svelte +27 -0
- package/src/dropdown-menu/dropdown-menu-label.svelte +15 -0
- package/src/dropdown-menu/dropdown-menu-root.svelte +26 -0
- package/src/dropdown-menu/dropdown-menu-separator.svelte +10 -0
- package/src/dropdown-menu/dropdown-menu-trigger.svelte +65 -0
- package/src/dropdown-menu/dropdown-menu.meta.ts +7 -0
- package/src/dropdown-menu/index.ts +35 -0
- package/src/enter/enter.meta.ts +8 -0
- package/src/enter/index.ts +1 -0
- package/src/exit/exit.meta.ts +8 -0
- package/src/exit/index.ts +1 -0
- package/src/field/field-description.svelte +38 -0
- package/src/field/field-error.svelte +40 -0
- package/src/field/field-root.svelte +98 -0
- package/src/field/field.meta.ts +7 -0
- package/src/field/index.ts +15 -0
- package/src/fieldset/fieldset-content.svelte +21 -0
- package/src/fieldset/fieldset-description.svelte +26 -0
- package/src/fieldset/fieldset-legend.svelte +26 -0
- package/src/fieldset/fieldset-root.svelte +27 -0
- package/src/fieldset/fieldset.meta.ts +7 -0
- package/src/fieldset/index.ts +35 -0
- package/src/file-select/context.svelte.ts +10 -0
- package/src/file-select/file-select-button-clear.svelte +37 -0
- package/src/file-select/file-select-button-trigger.svelte +26 -0
- package/src/file-select/file-select-root.svelte +168 -0
- package/src/file-select/file-select-value.svelte +43 -0
- package/src/file-select/file-select.meta.ts +7 -0
- package/src/file-select/index.ts +29 -0
- package/src/file-upload/context.svelte.ts +17 -0
- package/src/file-upload/file-upload-button-item-delete.svelte +36 -0
- package/src/file-upload/file-upload-button-trigger.svelte +31 -0
- package/src/file-upload/file-upload-dropzone.svelte +145 -0
- package/src/file-upload/file-upload-input-root.svelte +144 -0
- package/src/file-upload/file-upload-item.svelte +63 -0
- package/src/file-upload/file-upload-list.svelte +37 -0
- package/src/file-upload/file-upload.meta.ts +7 -0
- package/src/file-upload/index.ts +52 -0
- package/src/flip-card/context.svelte.ts +8 -0
- package/src/flip-card/flip-card-back.svelte +34 -0
- package/src/flip-card/flip-card-front.svelte +25 -0
- package/src/flip-card/flip-card-root.svelte +116 -0
- package/src/flip-card/flip-card.meta.ts +7 -0
- package/src/flip-card/index.ts +15 -0
- package/src/float-button/context.svelte.ts +10 -0
- package/src/float-button/float-button-action.svelte +56 -0
- package/src/float-button/float-button-root.svelte +102 -0
- package/src/float-button/float-button-trigger.svelte +43 -0
- package/src/float-button/float-button.meta.ts +7 -0
- package/src/float-button/index.ts +29 -0
- package/src/focus-trap/focus-trap.meta.ts +7 -0
- package/src/focus-trap/focus-trap.svelte +16 -0
- package/src/focus-trap/index.ts +3 -0
- package/src/format-bytes/format-bytes.meta.ts +7 -0
- package/src/format-bytes/format-bytes.svelte +60 -0
- package/src/format-bytes/index.ts +3 -0
- package/src/format-date/format-date.meta.ts +7 -0
- package/src/format-date/format-date.svelte +80 -0
- package/src/format-date/index.ts +3 -0
- package/src/format-number/format-number.meta.ts +7 -0
- package/src/format-number/format-number.svelte +59 -0
- package/src/format-number/index.ts +3 -0
- package/src/gauge/gauge.meta.ts +7 -0
- package/src/gauge/gauge.svelte +143 -0
- package/src/gauge/index.ts +2 -0
- package/src/glass/glass.meta.ts +7 -0
- package/src/glass/glass.svelte +49 -0
- package/src/glass/index.ts +11 -0
- package/src/glow/glow.meta.ts +7 -0
- package/src/glow/glow.svelte +60 -0
- package/src/glow/index.ts +15 -0
- package/src/god-rays/god-rays.meta.ts +8 -0
- package/src/god-rays/god-rays.svelte +132 -0
- package/src/god-rays/index.ts +17 -0
- package/src/gradient-mesh/gradient-mesh.meta.ts +7 -0
- package/src/gradient-mesh/gradient-mesh.svelte +295 -0
- package/src/gradient-mesh/index.ts +5 -0
- package/src/halftone/halftone.meta.ts +7 -0
- package/src/halftone/halftone.svelte +93 -0
- package/src/halftone/index.ts +14 -0
- package/src/heading/heading.meta.ts +7 -0
- package/src/heading/heading.svelte +120 -0
- package/src/heading/index.ts +17 -0
- package/src/hotkey/hotkey.meta.ts +7 -0
- package/src/hotkey/index.ts +2 -0
- package/src/hover-card/context.svelte.ts +1 -0
- package/src/hover-card/hover-card-content.svelte +121 -0
- package/src/hover-card/hover-card-root.svelte +11 -0
- package/src/hover-card/hover-card-trigger.svelte +109 -0
- package/src/hover-card/hover-card.meta.ts +7 -0
- package/src/hover-card/index.ts +19 -0
- package/src/icon/icon.meta.ts +7 -0
- package/src/icon/icon.svelte +92 -0
- package/src/icon/index.ts +11 -0
- package/src/icon-swap/icon-swap.meta.ts +7 -0
- package/src/icon-swap/icon-swap.svelte +48 -0
- package/src/icon-swap/index.ts +1 -0
- package/src/image/image.meta.ts +7 -0
- package/src/image/image.svelte +73 -0
- package/src/image/index.ts +3 -0
- package/src/image-comparison/image-comparison.meta.ts +7 -0
- package/src/image-comparison/image-comparison.svelte +272 -0
- package/src/image-comparison/index.ts +3 -0
- package/src/index.ts +863 -0
- package/src/infinite-scroll/index.ts +3 -0
- package/src/infinite-scroll/infinite-scroll.meta.ts +7 -0
- package/src/infinite-scroll/infinite-scroll.svelte +109 -0
- package/src/input/index.ts +8 -0
- package/src/input/input.meta.ts +7 -0
- package/src/input/input.svelte +261 -0
- package/src/input-group/context.svelte.ts +9 -0
- package/src/input-group/index.ts +45 -0
- package/src/input-group/input-group-action-button.svelte +29 -0
- package/src/input-group/input-group-input.svelte +57 -0
- package/src/input-group/input-group-prefix.svelte +43 -0
- package/src/input-group/input-group-root.svelte +108 -0
- package/src/input-group/input-group-select.svelte +70 -0
- package/src/input-group/input-group-separator.svelte +38 -0
- package/src/input-group/input-group-suffix.svelte +43 -0
- package/src/input-group/input-group.meta.ts +8 -0
- package/src/internal/anchored-overlay-content.svelte.ts +42 -0
- package/src/internal/calendar-event-layout.ts +294 -0
- package/src/internal/calendar-grid-button.svelte +805 -0
- package/src/internal/calendar-grid-utils.ts +123 -0
- package/src/internal/close-button-base.svelte +35 -0
- package/src/internal/color-aliases.ts +37 -0
- package/src/internal/date-family-controller.svelte.ts +165 -0
- package/src/internal/form-control-wrapper-attrs.ts +20 -0
- package/src/internal/menu-group.svelte +15 -0
- package/src/internal/menu-item.svelte +89 -0
- package/src/internal/menu-label.svelte +24 -0
- package/src/internal/menu-root-state.svelte.ts +73 -0
- package/src/internal/menu-separator.svelte +19 -0
- package/src/internal/modal-content.svelte +351 -0
- package/src/internal/motion.ts +85 -0
- package/src/internal/nav-arrow-button.svelte +42 -0
- package/src/internal/picker-popover-content.svelte +111 -0
- package/src/kbd/index.ts +9 -0
- package/src/kbd/kbd.meta.ts +7 -0
- package/src/kbd/kbd.svelte +56 -0
- package/src/label/index.ts +7 -0
- package/src/label/label.meta.ts +7 -0
- package/src/label/label.svelte +74 -0
- package/src/link/index.ts +7 -0
- package/src/link/link.meta.ts +7 -0
- package/src/link/link.svelte +104 -0
- package/src/link-preview/context.svelte.ts +15 -0
- package/src/link-preview/index.ts +19 -0
- package/src/link-preview/link-preview-content.svelte +87 -0
- package/src/link-preview/link-preview-root.svelte +72 -0
- package/src/link-preview/link-preview-trigger.svelte +61 -0
- package/src/link-preview/link-preview.meta.ts +7 -0
- package/src/list/context.svelte.ts +7 -0
- package/src/list/index.ts +27 -0
- package/src/list/list-item-icon.svelte +22 -0
- package/src/list/list-item-text.svelte +42 -0
- package/src/list/list-item.svelte +116 -0
- package/src/list/list-root.svelte +71 -0
- package/src/list/list-subheader.svelte +25 -0
- package/src/list/list.meta.ts +7 -0
- package/src/listbox/context.svelte.ts +10 -0
- package/src/listbox/index.ts +12 -0
- package/src/listbox/listbox-item.svelte +90 -0
- package/src/listbox/listbox-root.svelte +115 -0
- package/src/listbox/listbox.meta.ts +7 -0
- package/src/logo-mark/index.ts +12 -0
- package/src/logo-mark/logo-mark.meta.ts +7 -0
- package/src/logo-mark/logo-mark.svelte +154 -0
- package/src/map/context.svelte.ts +19 -0
- package/src/map/index.ts +77 -0
- package/src/map/map-controls.svelte +68 -0
- package/src/map/map-layer.svelte +101 -0
- package/src/map/map-marker.svelte +83 -0
- package/src/map/map-popup.svelte +71 -0
- package/src/map/map-root.svelte +244 -0
- package/src/map/map.meta.ts +7 -0
- package/src/markdown-renderer/index.ts +4 -0
- package/src/markdown-renderer/markdown-renderer.meta.ts +7 -0
- package/src/markdown-renderer/markdown-renderer.svelte +202 -0
- package/src/marquee/index.ts +3 -0
- package/src/marquee/marquee.meta.ts +7 -0
- package/src/marquee/marquee.svelte +218 -0
- package/src/mask-reveal/index.ts +5 -0
- package/src/mask-reveal/mask-reveal.meta.ts +7 -0
- package/src/mask-reveal/mask-reveal.svelte +214 -0
- package/src/mega-menu/context.svelte.ts +17 -0
- package/src/mega-menu/index.ts +31 -0
- package/src/mega-menu/mega-menu-button-trigger.svelte +37 -0
- package/src/mega-menu/mega-menu-column.svelte +34 -0
- package/src/mega-menu/mega-menu-item.svelte +51 -0
- package/src/mega-menu/mega-menu-link.svelte +137 -0
- package/src/mega-menu/mega-menu-panel.svelte +134 -0
- package/src/mega-menu/mega-menu-root.svelte +98 -0
- package/src/mega-menu/mega-menu.meta.ts +7 -0
- package/src/menubar/context.svelte.ts +24 -0
- package/src/menubar/index.ts +35 -0
- package/src/menubar/menubar-button-trigger.svelte +75 -0
- package/src/menubar/menubar-content.svelte +147 -0
- package/src/menubar/menubar-item.svelte +96 -0
- package/{dist/card/card-header.svelte → src/menubar/menubar-label.svelte} +6 -10
- package/src/menubar/menubar-menu.svelte +32 -0
- package/src/menubar/menubar-root.svelte +78 -0
- package/src/menubar/menubar-separator.svelte +17 -0
- package/src/menubar/menubar.meta.ts +7 -0
- package/src/motion/enter.svelte +29 -0
- package/src/motion/enter.ts +46 -0
- package/src/motion/exit.svelte +21 -0
- package/src/motion/index.ts +7 -0
- package/src/motion/leave.ts +41 -0
- package/src/motion/motion.meta.ts +8 -0
- package/src/motion/stagger.svelte +30 -0
- package/src/multi-select-combobox/context.svelte.ts +31 -0
- package/src/multi-select-combobox/index.ts +43 -0
- package/src/multi-select-combobox/multi-select-combobox-content.svelte +118 -0
- package/src/multi-select-combobox/multi-select-combobox-empty.svelte +28 -0
- package/src/multi-select-combobox/multi-select-combobox-group.svelte +33 -0
- package/src/multi-select-combobox/multi-select-combobox-input.svelte +161 -0
- package/src/multi-select-combobox/multi-select-combobox-item.svelte +140 -0
- package/src/multi-select-combobox/multi-select-combobox-root-input.svelte +286 -0
- package/src/multi-select-combobox/multi-select-combobox-selection-item.svelte +63 -0
- package/src/multi-select-combobox/multi-select-combobox-selection-list.svelte +29 -0
- package/src/multi-select-combobox/multi-select-combobox-selection-remove-button.svelte +36 -0
- package/src/multi-select-combobox/multi-select-combobox.meta.ts +7 -0
- package/src/navigation-menu/context.svelte.ts +18 -0
- package/src/navigation-menu/index.ts +50 -0
- package/src/navigation-menu/navigation-menu-content.svelte +55 -0
- package/src/navigation-menu/navigation-menu-item.svelte +35 -0
- package/src/navigation-menu/navigation-menu-link.svelte +49 -0
- package/src/navigation-menu/navigation-menu-list.svelte +47 -0
- package/src/navigation-menu/navigation-menu-root.svelte +46 -0
- package/src/navigation-menu/navigation-menu-trigger-button.svelte +62 -0
- package/src/navigation-menu/navigation-menu.meta.ts +7 -0
- package/src/noise/index.ts +15 -0
- package/src/noise/noise.meta.ts +7 -0
- package/src/noise/noise.svelte +179 -0
- package/src/notification-center/context.svelte.ts +24 -0
- package/src/notification-center/index.ts +28 -0
- package/src/notification-center/notification-center-group.svelte +30 -0
- package/src/notification-center/notification-center-item.svelte +70 -0
- package/src/notification-center/notification-center-panel.svelte +162 -0
- package/src/notification-center/notification-center-root.svelte +73 -0
- package/src/notification-center/notification-center-trigger-button.svelte +28 -0
- package/src/notification-center/notification-center.meta.ts +7 -0
- package/src/number-input/index.ts +7 -0
- package/src/number-input/number-input-button.svelte +222 -0
- package/src/number-input/number-input.meta.ts +7 -0
- package/src/numeric/index.ts +1 -0
- package/src/numeric/numeric.meta.ts +7 -0
- package/src/numeric/numeric.svelte +64 -0
- package/src/option-picker/context.svelte.ts +11 -0
- package/src/option-picker/index.ts +52 -0
- package/src/option-picker/option-picker-description.svelte +25 -0
- package/src/option-picker/option-picker-item.svelte +242 -0
- package/src/option-picker/option-picker-label.svelte +24 -0
- package/src/option-picker/option-picker-meta.svelte +24 -0
- package/src/option-picker/option-picker-preview.svelte +164 -0
- package/src/option-picker/option-picker-root.svelte +82 -0
- package/src/option-picker/option-picker.meta.ts +8 -0
- package/src/pagination/context.svelte.ts +12 -0
- package/src/pagination/index.ts +35 -0
- package/src/pagination/pagination-content.svelte +27 -0
- package/src/pagination/pagination-ellipsis.svelte +19 -0
- package/src/pagination/pagination-item.svelte +14 -0
- package/src/pagination/pagination-link-button.svelte +28 -0
- package/src/pagination/pagination-nav-button.svelte +28 -0
- package/src/pagination/pagination-next-button.svelte +13 -0
- package/src/pagination/pagination-previous-button.svelte +13 -0
- package/src/pagination/pagination-root.svelte +48 -0
- package/src/pagination/pagination.meta.ts +7 -0
- package/src/phone-input/index.ts +9 -0
- package/src/phone-input/phone-input-select.svelte +286 -0
- package/src/phone-input/phone-input.meta.ts +7 -0
- package/src/pin-input/context.svelte.ts +18 -0
- package/src/pin-input/index.ts +31 -0
- package/src/pin-input/pin-input-cell.svelte +117 -0
- package/src/pin-input/pin-input-group.svelte +24 -0
- package/src/pin-input/pin-input-root.svelte +315 -0
- package/src/pin-input/pin-input-separator.svelte +28 -0
- package/src/pin-input/pin-input.meta.ts +7 -0
- package/src/popover/context.svelte.ts +13 -0
- package/src/popover/index.ts +15 -0
- package/src/popover/popover-content.svelte +112 -0
- package/src/popover/popover-root.svelte +39 -0
- package/src/popover/popover-trigger.svelte +62 -0
- package/src/popover/popover.meta.ts +7 -0
- package/src/portal/index.ts +3 -0
- package/src/portal/portal.meta.ts +7 -0
- package/src/portal/portal.svelte +14 -0
- package/src/progress/index.ts +14 -0
- package/src/progress/progress.meta.ts +7 -0
- package/src/progress/progress.svelte +302 -0
- package/src/progress-ring/index.ts +8 -0
- package/src/progress-ring/progress-ring.meta.ts +7 -0
- package/src/progress-ring/progress-ring.svelte +169 -0
- package/src/prompt-input/index.ts +16 -0
- package/src/prompt-input/prompt-input-button-textarea.svelte +171 -0
- package/src/prompt-input/prompt-input.meta.ts +7 -0
- package/src/qr-code/index.ts +3 -0
- package/src/qr-code/qr-code.meta.ts +7 -0
- package/src/qr-code/qr-code.svelte +149 -0
- package/src/radio-group/context.svelte.ts +10 -0
- package/src/radio-group/index.ts +12 -0
- package/src/radio-group/radio-group-item-input.svelte +197 -0
- package/src/radio-group/radio-group.meta.ts +7 -0
- package/src/radio-group/radio-group.svelte +117 -0
- package/src/range-calendar/context.svelte.ts +22 -0
- package/src/range-calendar/index.ts +16 -0
- package/src/range-calendar/range-calendar-grid-button.svelte +77 -0
- package/src/range-calendar/range-calendar-root.svelte +146 -0
- package/src/range-calendar/range-calendar.meta.ts +7 -0
- package/src/rating/index.ts +7 -0
- package/src/rating/rating-button-input.svelte +265 -0
- package/src/rating/rating.meta.ts +7 -0
- package/src/relative-time/index.ts +3 -0
- package/src/relative-time/relative-time.meta.ts +7 -0
- package/src/relative-time/relative-time.svelte +71 -0
- package/src/reveal/index.ts +7 -0
- package/src/reveal/reveal.meta.ts +8 -0
- package/src/reveal/reveal.svelte +190 -0
- package/src/rich-text-editor/index.ts +19 -0
- package/src/rich-text-editor/rich-text-editor-content.svelte +184 -0
- package/src/rich-text-editor/rich-text-editor-root.svelte +213 -0
- package/src/rich-text-editor/rich-text-editor-toolbar-button-input.svelte +541 -0
- package/src/rich-text-editor/rich-text-editor.meta.ts +7 -0
- package/src/scroll-area/index.ts +3 -0
- package/src/scroll-area/scroll-area.meta.ts +7 -0
- package/src/scroll-area/scroll-area.svelte +85 -0
- package/src/scroll-to-top/index.ts +8 -0
- package/src/scroll-to-top/scroll-to-top-button.svelte +89 -0
- package/src/scroll-to-top/scroll-to-top.meta.ts +7 -0
- package/src/segmented-control/context.svelte.ts +10 -0
- package/src/segmented-control/index.ts +12 -0
- package/src/segmented-control/segmented-control-item-button.svelte +37 -0
- package/src/segmented-control/segmented-control-root.svelte +125 -0
- package/src/segmented-control/segmented-control.meta.ts +7 -0
- package/src/select/context.svelte.ts +17 -0
- package/src/select/index.ts +33 -0
- package/src/select/select-content.svelte +202 -0
- package/src/select/select-item.svelte +118 -0
- package/src/select/select-root-input.svelte +105 -0
- package/src/select/select-trigger-button.svelte +183 -0
- package/src/select/select-value.svelte +27 -0
- package/src/select/select.meta.ts +7 -0
- package/src/separator/index.ts +9 -0
- package/src/separator/separator.meta.ts +7 -0
- package/src/separator/separator.svelte +70 -0
- package/src/shader-canvas/index.ts +28 -0
- package/src/shader-canvas/presets.ts +212 -0
- package/src/shader-canvas/shader-canvas.meta.ts +7 -0
- package/src/shader-canvas/shader-canvas.svelte +115 -0
- package/src/shimmer/index.ts +10 -0
- package/src/shimmer/shimmer.meta.ts +7 -0
- package/src/shimmer/shimmer.svelte +125 -0
- package/src/sidebar/context.svelte.ts +10 -0
- package/src/sidebar/index.ts +66 -0
- package/src/sidebar/sidebar-content.svelte +43 -0
- package/src/sidebar/sidebar-footer.svelte +28 -0
- package/src/sidebar/sidebar-group-label.svelte +45 -0
- package/src/sidebar/sidebar-group.svelte +29 -0
- package/src/sidebar/sidebar-header.svelte +34 -0
- package/src/sidebar/sidebar-item.svelte +57 -0
- package/src/sidebar/sidebar-root.svelte +76 -0
- package/src/sidebar/sidebar-trigger-button.svelte +29 -0
- package/src/sidebar/sidebar.meta.ts +7 -0
- package/src/skeleton/index.ts +9 -0
- package/src/skeleton/skeleton.meta.ts +7 -0
- package/src/skeleton/skeleton.svelte +97 -0
- package/src/slider/index.ts +12 -0
- package/src/slider/slider-input.svelte +277 -0
- package/src/slider/slider.meta.ts +7 -0
- package/src/spacer/index.ts +8 -0
- package/src/spacer/spacer.meta.ts +7 -0
- package/src/spacer/spacer.svelte +84 -0
- package/src/sparkline/index.ts +3 -0
- package/src/sparkline/sparkline.meta.ts +7 -0
- package/src/sparkline/sparkline.svelte +113 -0
- package/src/spinner/index.ts +9 -0
- package/src/spinner/spinner.meta.ts +7 -0
- package/src/spinner/spinner.svelte +110 -0
- package/src/splitter/context.svelte.ts +11 -0
- package/src/splitter/index.ts +15 -0
- package/src/splitter/splitter-handle.svelte +171 -0
- package/src/splitter/splitter-panel.svelte +28 -0
- package/src/splitter/splitter-root.svelte +128 -0
- package/src/splitter/splitter.meta.ts +7 -0
- package/src/spotlight/index.ts +5 -0
- package/src/spotlight/spotlight.meta.ts +8 -0
- package/src/spotlight/spotlight.svelte +249 -0
- package/src/stagger/index.ts +1 -0
- package/src/stagger/stagger.meta.ts +8 -0
- package/src/star-rating/index.ts +16 -0
- package/src/star-rating/star-rating-root.svelte +109 -0
- package/src/star-rating/star-rating.meta.ts +7 -0
- package/src/stepper/context.svelte.ts +9 -0
- package/src/stepper/index.ts +23 -0
- package/src/stepper/stepper-list.svelte +37 -0
- package/src/stepper/stepper-root.svelte +51 -0
- package/src/stepper/stepper-separator.svelte +45 -0
- package/src/stepper/stepper-step-button.svelte +113 -0
- package/src/stepper/stepper.meta.ts +7 -0
- package/src/svg/index.ts +5 -0
- package/src/svg/svg.meta.ts +7 -0
- package/src/svg/svg.svelte +19 -0
- package/src/table/index.ts +39 -0
- package/src/table/table-body.svelte +14 -0
- package/src/table/table-caption.svelte +25 -0
- package/src/table/table-cell.svelte +24 -0
- package/src/table/table-footer.svelte +21 -0
- package/src/table/table-head.svelte +26 -0
- package/src/table/table-header.svelte +21 -0
- package/src/table/table-root.svelte +64 -0
- package/src/table/table-row.svelte +28 -0
- package/src/table/table.meta.ts +7 -0
- package/src/table-of-contents/context.svelte.ts +14 -0
- package/src/table-of-contents/index.ts +20 -0
- package/src/table-of-contents/table-of-contents-item.svelte +86 -0
- package/src/table-of-contents/table-of-contents-list.svelte +87 -0
- package/src/table-of-contents/table-of-contents-root.svelte +88 -0
- package/src/table-of-contents/table-of-contents.meta.ts +7 -0
- package/src/tabs/context.svelte.ts +16 -0
- package/src/tabs/index.ts +43 -0
- package/src/tabs/tabs-content.svelte +42 -0
- package/src/tabs/tabs-list.svelte +102 -0
- package/src/tabs/tabs-root.svelte +59 -0
- package/src/tabs/tabs-trigger-button.svelte +41 -0
- package/src/tabs/tabs.meta.ts +7 -0
- package/src/tag/index.ts +12 -0
- package/src/tag/tag-button.svelte +250 -0
- package/src/tag/tag.meta.ts +7 -0
- package/src/tags-input/context.svelte.ts +13 -0
- package/src/tags-input/index.ts +32 -0
- package/src/tags-input/tags-input-input.svelte +85 -0
- package/src/tags-input/tags-input-list.svelte +20 -0
- package/src/tags-input/tags-input-root.svelte +136 -0
- package/src/tags-input/tags-input-tag-delete-button.svelte +31 -0
- package/src/tags-input/tags-input-tag.svelte +61 -0
- package/src/tags-input/tags-input.meta.ts +7 -0
- package/src/text/index.ts +20 -0
- package/src/text/text.meta.ts +7 -0
- package/src/text/text.svelte +150 -0
- package/src/textarea/index.ts +7 -0
- package/src/textarea/textarea.meta.ts +7 -0
- package/src/textarea/textarea.svelte +210 -0
- package/src/theme-toggle/index.ts +30 -0
- package/src/theme-toggle/theme-controller.svelte.ts +171 -0
- package/src/theme-toggle/theme-flash.ts +39 -0
- package/src/theme-toggle/theme-toggle.meta.ts +7 -0
- package/src/theme-toggle/theme-toggle.svelte +199 -0
- package/src/themes/aurora.css +291 -0
- package/src/themes/component-defaults.css +47 -0
- package/src/themes/dark.css +377 -0
- package/src/themes/default.css +643 -0
- package/src/themes/midnight.css +177 -0
- package/src/themes/terminal.css +191 -0
- package/src/themes/token-scope.ts +1 -0
- package/src/time-input/index.ts +14 -0
- package/src/time-input/time-input.meta.ts +7 -0
- package/src/time-input/time-input.svelte +157 -0
- package/src/timeline/index.ts +40 -0
- package/src/timeline/timeline-content.svelte +22 -0
- package/src/timeline/timeline-description.svelte +22 -0
- package/src/timeline/timeline-icon.svelte +41 -0
- package/src/timeline/timeline-item.svelte +57 -0
- package/src/timeline/timeline-root.svelte +43 -0
- package/src/timeline/timeline-time.svelte +22 -0
- package/src/timeline/timeline-title.svelte +55 -0
- package/src/timeline/timeline.meta.ts +7 -0
- package/src/toast/context.svelte.ts +7 -0
- package/src/toast/index.ts +33 -0
- package/src/toast/toast-action-button.svelte +16 -0
- package/src/toast/toast-close-button.svelte +35 -0
- package/src/toast/toast-description.svelte +24 -0
- package/src/toast/toast-provider.svelte +120 -0
- package/src/toast/toast-root.svelte +169 -0
- package/src/toast/toast-title.svelte +25 -0
- package/src/toast/toast.meta.ts +7 -0
- package/src/toggle/index.ts +9 -0
- package/src/toggle/toggle-button.svelte +218 -0
- package/src/toggle/toggle.meta.ts +7 -0
- package/src/toggle-group/context.svelte.ts +12 -0
- package/src/toggle-group/index.ts +18 -0
- package/src/toggle-group/toggle-group-item-button.svelte +36 -0
- package/src/toggle-group/toggle-group-root.svelte +106 -0
- package/src/toggle-group/toggle-group.meta.ts +7 -0
- package/src/token-scope/index.ts +8 -0
- package/src/token-scope/token-scope.meta.ts +7 -0
- package/src/toolbar/context.svelte.ts +6 -0
- package/src/toolbar/index.ts +23 -0
- package/src/toolbar/toolbar-button.svelte +16 -0
- package/src/toolbar/toolbar-link.svelte +46 -0
- package/src/toolbar/toolbar-root.svelte +100 -0
- package/src/toolbar/toolbar-separator.svelte +33 -0
- package/src/toolbar/toolbar.meta.ts +7 -0
- package/src/tooltip/context.svelte.ts +14 -0
- package/src/tooltip/index.ts +15 -0
- package/src/tooltip/tooltip-content.svelte +106 -0
- package/src/tooltip/tooltip-root.svelte +73 -0
- package/src/tooltip/tooltip-trigger.svelte +74 -0
- package/src/tooltip/tooltip.meta.ts +7 -0
- package/src/tour/index.ts +12 -0
- package/src/tour/tour-root.css +215 -0
- package/src/tour/tour-root.css.d.ts +2 -0
- package/src/tour/tour-root.svelte +19 -0
- package/src/tour/tour-tooltip-button.svelte +48 -0
- package/src/tour/tour.meta.ts +7 -0
- package/src/transfer/context.svelte.ts +31 -0
- package/src/transfer/index.ts +29 -0
- package/src/transfer/transfer-actions-button.svelte +91 -0
- package/src/transfer/transfer-item.svelte +35 -0
- package/src/transfer/transfer-list-input.svelte +204 -0
- package/src/transfer/transfer-root.svelte +145 -0
- package/src/transfer/transfer.meta.ts +7 -0
- package/src/tree/context.svelte.ts +24 -0
- package/src/tree/index.ts +23 -0
- package/src/tree/tree-item-children.svelte +51 -0
- package/src/tree/tree-item-label.svelte +56 -0
- package/src/tree/tree-item.svelte +63 -0
- package/src/tree/tree-root.svelte +246 -0
- package/src/tree/tree.meta.ts +7 -0
- package/src/typing-indicator/index.ts +5 -0
- package/src/typing-indicator/typing-indicator.meta.ts +7 -0
- package/src/typing-indicator/typing-indicator.svelte +72 -0
- package/src/typography/blockquote.svelte +24 -0
- package/src/typography/code.svelte +25 -0
- package/src/typography/heading.svelte +17 -0
- package/src/typography/index.ts +30 -0
- package/src/typography/text.svelte +25 -0
- package/src/typography/typography.meta.ts +7 -0
- package/src/video-embed/index.ts +3 -0
- package/src/video-embed/video-embed-button.svelte +177 -0
- package/src/video-embed/video-embed.meta.ts +7 -0
- package/src/virtual-list/index.ts +3 -0
- package/src/virtual-list/virtual-list.meta.ts +7 -0
- package/src/virtual-list/virtual-list.svelte +239 -0
- package/src/visually-hidden/index.ts +8 -0
- package/src/visually-hidden/visually-hidden.meta.ts +7 -0
- package/src/visually-hidden/visually-hidden.svelte +28 -0
- package/dist/card/card-content.svelte +0 -32
- package/dist/card/card-content.svelte.d.ts +0 -9
- package/dist/card/card-footer.svelte.d.ts +0 -8
- package/dist/card/card-header.svelte.d.ts +0 -8
- package/dist/card/card-root.svelte +0 -184
- package/dist/card/card-root.svelte.d.ts +0 -15
- package/dist/card/card.meta.js +0 -7
- package/dist/card/index.d.ts +0 -31
- package/dist/card/index.js +0 -10
- package/skills/dryui/SKILL.md +0 -306
- package/skills/dryui/agents/openai.yaml +0 -10
- package/skills/dryui/rules/accessibility.md +0 -215
- package/skills/dryui/rules/composition.md +0 -500
- package/skills/dryui/rules/compound-components.md +0 -312
- package/skills/dryui/rules/native-web-transitions.md +0 -99
- package/skills/dryui/rules/svelte.md +0 -234
- package/skills/dryui/rules/theming.md +0 -305
- /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
|
+
}
|