@leavepulse/ui 0.77.3 → 0.78.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/README.md +68 -0
- package/dist/assets/{LpCommandPalette-DOIZHw-g.css → LpCommandPalette-DDJKICKh.css} +19 -19
- package/dist/assets/{LpDock-Cbh0wQUv.css → LpDock-Dw82tWVD.css} +3 -3
- package/dist/assets/{LpDropzone-B_qRq1-R.css → LpDropzone-DNbh_NFJ.css} +10 -10
- package/dist/assets/{LpImageEditor-XM5bAziW.css → LpImageEditor-Bgu20XLs.css} +6 -6
- package/dist/assets/{LpTable-Bv-EcQs7.css → LpTable-CCk0ZHo7.css} +19 -19
- package/dist/assets/LpTabs-Bh7a-Bdl.css +18 -0
- package/dist/chunks/AppShellContextNav-C6Ho2mof.js +49 -0
- package/dist/chunks/ChartRangeSelector-HO0sMMz6.js +129 -0
- package/dist/chunks/{ContextMenuItems-BXumicbh.js → ContextMenuItems-DmaKSJVv.js} +1 -1
- package/dist/chunks/{DropzoneLinkField-C-9BCGJn.js → DropzoneLinkField-CLLhd3WB.js} +2 -2
- package/dist/chunks/FieldLeading-mr81aEAP.js +27 -0
- package/dist/chunks/{FieldWrapper-CORPzo9s.js → FieldWrapper-DnuMTrqW.js} +1 -1
- package/dist/chunks/IconCloseButton-COZEsa9u.js +29 -0
- package/dist/chunks/{LayoutCanvas-CNOAUk7D.js → LayoutCanvas-CZKOltfA.js} +1 -1
- package/dist/chunks/{LayoutNode-DF5BMN0J.js → LayoutNode-D2f7v683.js} +45 -45
- package/dist/chunks/{LpActionBar-BP1ld7-S.js → LpActionBar-BGMk5KoG.js} +3 -3
- package/dist/chunks/{LpActivityLine-CJvzrNpj.js → LpActivityLine-Ea-t5Vza.js} +4 -2
- package/dist/chunks/{LpAddressInput-E8KtwU7-.js → LpAddressInput-BSS49jpm.js} +12 -11
- package/dist/chunks/{LpAlert-n5XPAZfv.js → LpAlert-CjvTZIKF.js} +3 -3
- package/dist/chunks/LpAppShell-BKbYCHkx.js +601 -0
- package/dist/chunks/{LpAutocomplete-C-SpPfBG.js → LpAutocomplete-DkY6CSyD.js} +24 -42
- package/dist/chunks/{LpAvatar-Cg6Az3wz.js → LpAvatar-cQVsNk6F.js} +2 -2
- package/dist/chunks/{LpBadge-wUYLN4U9.js → LpBadge-D5QQdDQ_.js} +2 -2
- package/dist/chunks/{LpBottomNav-CbNQQPuL.js → LpBottomNav-zxLGGbjm.js} +9 -2
- package/dist/chunks/{LpBreadcrumbs-DPdbth-c.js → LpBreadcrumbs-CIIBsygI.js} +4 -3
- package/dist/chunks/{LpButton-CFb44IjC.js → LpButton-DSHioIfD.js} +5 -4
- package/dist/chunks/{LpCalendar-CuoWks-0.js → LpCalendar-D4-LVjfF.js} +1 -1
- package/dist/chunks/{LpCard-r13WRqox.js → LpCard-BPtYXwnT.js} +1 -1
- package/dist/chunks/{LpCheckbox-Cs4SHaHX.js → LpCheckbox-D6RuDW4X.js} +38 -16
- package/dist/chunks/{LpCodeBlock-DRC8VbXW.js → LpCodeBlock-DBqy4RLd.js} +19 -41
- package/dist/chunks/{LpCodeDisplay-CE9TxCA4.js → LpCodeDisplay-D3BUAhV1.js} +5 -5
- package/dist/chunks/{LpCollapse-CWS9M_lm.js → LpCollapse-DrAw5DBZ.js} +5 -4
- package/dist/chunks/{LpColorPicker-1y1QJHZR.js → LpColorPicker-COHzV68R.js} +11 -12
- package/dist/chunks/{LpCommandScope-CtUhJe8T.js → LpCommandScope-DRUvHiX7.js} +1 -1
- package/dist/chunks/{LpConfirmDialog-DlD45Bi_.js → LpConfirmDialog-CnrmzkuN.js} +3 -3
- package/dist/chunks/{LpConfirmHost-BIWjBLmI.js → LpConfirmHost-CwbObVGH.js} +1 -1
- package/dist/chunks/{LpContextMenu-rx2tCjwk.js → LpContextMenu-Bx_IhQDS.js} +17 -19
- package/dist/chunks/{LpContextSwitcher-Dwh6dyID.js → LpContextSwitcher-1C1tExdl.js} +2 -2
- package/dist/chunks/{LpCopyButton-xF7wFVoU.js → LpCopyButton-sAMvWeLP.js} +3 -3
- package/dist/chunks/{LpCountdown-DgeY3cLG.js → LpCountdown-DVl7vXI1.js} +4 -3
- package/dist/chunks/{LpDataList-Jxyl9H5w.js → LpDataList-CaVfsozH.js} +13 -4
- package/dist/chunks/{LpDatePicker-CA0QTfBC.js → LpDatePicker-DwSDY0mt.js} +12 -18
- package/dist/chunks/{LpDateRangePicker-u3J51xXk.js → LpDateRangePicker-CNQXdTVv.js} +10 -16
- package/dist/chunks/{LpDisclosure-Cpa7Hc5T.js → LpDisclosure-DG7JCo-d.js} +2 -2
- package/dist/chunks/{LpDrawer-CrG8t_KP.js → LpDrawer-DXNQFpAA.js} +61 -61
- package/dist/chunks/{LpDropdownMenu-Dl7Z4ZhS.js → LpDropdownMenu-CLzxIOgM.js} +9 -17
- package/dist/chunks/{LpEditable-Cy6GLbFx.js → LpEditable-DVT0Rh8F.js} +9 -6
- package/dist/chunks/{LpEmptyState-EQHiRYhG.js → LpEmptyState-DurPF001.js} +1 -1
- package/dist/chunks/{LpFieldNotice-CzQ2_5rQ.js → LpFieldNotice-DJ10cYIo.js} +1 -1
- package/dist/chunks/LpFileTree-C-rJKzEQ.js +735 -0
- package/dist/chunks/{LpFileTreeNode-cT6BIWvz.js → LpFileTreeNode-BgYn4U1J.js} +116 -34
- package/dist/chunks/{LpFormField-Ch69Jd-X.js → LpFormField-BKYn9dyP.js} +3 -3
- package/dist/chunks/{LpFrame-BVQCQLDI.js → LpFrame-Bv5Fphte.js} +25 -11
- package/dist/chunks/{LpHintExample-9Py0wc-L.js → LpHintExample-YzCXiSUp.js} +1 -1
- package/dist/chunks/{LpHintLayer-CgObdUsM.js → LpHintLayer-BeKRsRon.js} +2 -2
- package/dist/chunks/{LpHints-Bz-BlaSl.js → LpHints-qf3xYsza.js} +3 -3
- package/dist/chunks/{LpHoverCard-spGxyw7T.js → LpHoverCard-CHE9jAJG.js} +29 -11
- package/dist/chunks/{LpIcon--5WSb3jJ.js → LpIcon-C6JpQ5td.js} +6 -3
- package/dist/chunks/{LpIdenticon-BzdC_qz9.js → LpIdenticon-Dbbr4fvt.js} +2 -1
- package/dist/chunks/{LpInfraNode-46XOCkfq.js → LpInfraNode-Bb5nbuFY.js} +19 -2
- package/dist/chunks/{LpInput-Tw-yxT4I.js → LpInput-D0O6dpuA.js} +32 -47
- package/dist/chunks/LpInspector-DO3fTCQ1.js +209 -0
- package/dist/chunks/{LpLaneNode-HtnQf2FF.js → LpLaneNode-Bqcdm8Rf.js} +19 -1
- package/dist/chunks/{LpLightbox-B95yu22B.js → LpLightbox-CtDTQV01.js} +57 -32
- package/dist/chunks/{LpLoader-DVwkOmt9.js → LpLoader-Bf1yZwaU.js} +1 -1
- package/dist/chunks/LpLogInspector-BdQlX6S-.js +119 -0
- package/dist/chunks/{LpLogViewer-ngqkns4m.js → LpLogViewer-umCZBapN.js} +234 -53
- package/dist/chunks/{LpMapPicker-DRGgh89B.js → LpMapPicker-D5_YLLBC.js} +4 -3
- package/dist/chunks/{LpMasonry--k0571qG.js → LpMasonry-DPYekFxs.js} +8 -5
- package/dist/chunks/{LpMediaGallery-Bkram-MO.js → LpMediaGallery-BYEwySoF.js} +3 -3
- package/dist/chunks/{LpModal-CoGhLTyH.js → LpModal-DDvrSAnf.js} +11 -13
- package/dist/chunks/{LpModalNav-BGvs2y5Q.js → LpModalNav-gOi1Giz1.js} +11 -37
- package/dist/chunks/{LpNotificationBell-B0034Lk-.js → LpNotificationBell-DUbVyK6R.js} +9 -9
- package/dist/chunks/{LpNumberField-e0IBkWU1.js → LpNumberField-Csg-1Zh_.js} +25 -20
- package/dist/chunks/{LpOtpInput-CeBPsM7D.js → LpOtpInput-BRpLBbOD.js} +2 -2
- package/dist/chunks/LpPageScrollbar-D2O0xhJw.js +164 -0
- package/dist/chunks/{LpPagination-BIIiYHXR.js → LpPagination-snQ020xQ.js} +6 -4
- package/dist/chunks/{LpPaletteTheme-DiLYKQiZ.js → LpPaletteTheme-BIXpZLGe.js} +4 -4
- package/dist/chunks/{LpPasswordInput-BkHKQDhM.js → LpPasswordInput-D4HPhqfV.js} +2 -2
- package/dist/chunks/{LpPhoneInput-CETEp5W7.js → LpPhoneInput-BSL7NX4z.js} +15 -26
- package/dist/chunks/{LpPie-DrZepi0p.js → LpPie-oVNHjsKD.js} +22 -21
- package/dist/chunks/{LpPipeline-B3aOVOkV.js → LpPipeline-BSXT6oUJ.js} +16 -7
- package/dist/chunks/{LpPopover-DmZ3wUtP.js → LpPopover-BQSuxtai.js} +10 -1
- package/dist/chunks/{LpProgress-Yr6ONG5t.js → LpProgress-D_MSjowb.js} +2 -1
- package/dist/chunks/{LpQuota-Dk2MyI2Z.js → LpQuota-CgWPgdVt.js} +1 -1
- package/dist/chunks/{LpRadioGroup-ggaPL92T.js → LpRadioGroup-BrazjLY_.js} +4 -4
- package/dist/chunks/{LpRangeCalendar-CYYUYqJ7.js → LpRangeCalendar-BrqlGZzE.js} +1 -1
- package/dist/chunks/{LpRangeSlider-CM-0ZITZ.js → LpRangeSlider-fz61tzc-.js} +5 -9
- package/dist/chunks/{LpRelativeTime-DwgCb5-8.js → LpRelativeTime-0zI-44ve.js} +1 -1
- package/dist/chunks/{LpRepeater-D7ZmnIYd.js → LpRepeater-BkJHSPI1.js} +3 -3
- package/dist/chunks/{LpRevealOnScroll-DcQjexLy.js → LpRevealOnScroll-CPwyiQAr.js} +1 -1
- package/dist/chunks/{LpScramble-Bn9OoOtC.js → LpScramble-DsE77iVp.js} +1 -1
- package/dist/chunks/{LpScrollArea-MyEnVB_5.js → LpScrollArea-CqvwfZs2.js} +31 -22
- package/dist/chunks/{LpSecretField-BbmqM9ye.js → LpSecretField-B2roc4Iz.js} +10 -9
- package/dist/chunks/{LpSection-DFK_uoJE.js → LpSection-BfteipxU.js} +1 -1
- package/dist/chunks/{LpSegmented-CeACsMMA.js → LpSegmented-DbEJhbv-.js} +8 -10
- package/dist/chunks/{LpSelect-Cxq4yBGd.js → LpSelect-Y-iuSymW.js} +47 -30
- package/dist/chunks/{LpServiceNode-D8uJ8sGk.js → LpServiceNode-Cln2ntdw.js} +17 -0
- package/dist/chunks/{LpShaderBackdrop-Bote0iuy.js → LpShaderBackdrop-wQygYb5h.js} +13 -10
- package/dist/chunks/{LpShift-CCq7dofl.js → LpShift-DAgS_d2x.js} +18 -15
- package/dist/chunks/{LpSidebar-DzE1783C.js → LpSidebar-DDNJzb69.js} +74 -87
- package/dist/chunks/{LpSidebarNav-DE3PhXVP.js → LpSidebarNav-CE0jQS8i.js} +8 -6
- package/dist/chunks/{LpSidebarRow-BRYhNDSo.js → LpSidebarRow-DOTg1txp.js} +6 -5
- package/dist/chunks/{LpSlider-DxYoCJJO.js → LpSlider-A4ixovgS.js} +5 -9
- package/dist/chunks/{LpSpotlight-4F3NeUMY.js → LpSpotlight-DwxkhFgR.js} +2 -1
- package/dist/chunks/{LpStat-f2LfDCJ4.js → LpStat-DPjc6_fX.js} +19 -6
- package/dist/chunks/{LpStatus-DglxttQ7.js → LpStatus-DGJFwk5F.js} +2 -2
- package/dist/chunks/{LpStepMark-DpzKVyh8.js → LpStepMark-Dw2MRTrt.js} +2 -2
- package/dist/chunks/{LpStepper-DpsocBNJ.js → LpStepper-DNnh7lCu.js} +1 -1
- package/dist/chunks/{LpSwap-C96c0LWh.js → LpSwap-M7nFeW3C.js} +1 -1
- package/dist/chunks/{LpSwitch-BnXd1llc.js → LpSwitch-DnC2-0aV.js} +3 -3
- package/dist/chunks/{LpTabFace-DPCwKkH2.js → LpTabFace-DQoV8mQG.js} +2 -2
- package/dist/chunks/{LpTableFilter-CiVBpaq0.js → LpTableFilter-B_XMOeAh.js} +4 -4
- package/dist/chunks/{LpTableOfContents-BN5zvpnx.js → LpTableOfContents-CKkWEK6R.js} +14 -9
- package/dist/chunks/{LpTagsInput-C-mDG648.js → LpTagsInput-Dt0mteSj.js} +19 -27
- package/dist/chunks/{LpTextarea-TE-yZv2U.js → LpTextarea-Co0b9PVx.js} +8 -16
- package/dist/chunks/LpThemeProvider-XK6ZeWAE.js +81 -0
- package/dist/chunks/{LpThemeStudio-BpORDvH6.js → LpThemeStudio-ChU0_Ga8.js} +12 -195
- package/dist/chunks/{LpThemeSwitcher-BnEn_mvf.js → LpThemeSwitcher-DmCZ21P_.js} +8 -6
- package/dist/chunks/{LpTimeField-BawSVsmt.js → LpTimeField-DeOcKWJq.js} +6 -9
- package/dist/chunks/{LpToaster-IwTXjbbU.js → LpToaster-Ct_ZOhCh.js} +10 -8
- package/dist/chunks/{LpTooltip-BcBvIx4z.js → LpTooltip-Bz_rrq5S.js} +56 -24
- package/dist/chunks/LpTopologyCanvas-jqz1KCq2.js +678 -0
- package/dist/chunks/{LpTour-BZq-mUMv.js → LpTour-ClKYhx4h.js} +7 -5
- package/dist/chunks/{LpTruncate-CflAIwiJ.js → LpTruncate-ellq9RYM.js} +6 -5
- package/dist/chunks/{LpTypewriter-BCL_a6wE.js → LpTypewriter-CZIzpdbe.js} +2 -1
- package/dist/chunks/{LpUptimeBar-DqNvvMBK.js → LpUptimeBar-BoxcCDVY.js} +3 -3
- package/dist/chunks/{LpVideoBackdrop-DS1wGv-X.js → LpVideoBackdrop-Ct4St4ZL.js} +10 -13
- package/dist/chunks/LpWindow-B8YSRVTH.js +297 -0
- package/dist/chunks/LpWorkspaceSwitcher-BeNHCLd5.js +139 -0
- package/dist/chunks/{MenuTitle-BmCNNTKH.js → MenuTitle-Bh5dAdV1.js} +1 -1
- package/dist/chunks/{OptionContent-22UT8zLn.js → OptionContent-CGb8QlHA.js} +1 -1
- package/dist/chunks/{PickerField-Bfh8id3d.js → PickerField-BM1dyafh.js} +4 -4
- package/dist/chunks/{RevealToggle-BEit1n6Z.js → RevealToggle-D_iyrB8d.js} +3 -3
- package/dist/chunks/SliderRail-DhPREa_R.js +18 -0
- package/dist/chunks/{TourCard-DNclH3u_.js → TourCard-DAzTYfav.js} +4 -4
- package/dist/chunks/{icons-DMITlfw5.js → icons-CYfcpSDS.js} +26 -10
- package/dist/component-names.d.ts +1 -1
- package/dist/components/AppShellContextNav.vue.d.ts +13 -0
- package/dist/components/AppShellContextNav.vue.js +4 -0
- package/dist/components/ChartRangeSelector.vue.d.ts +21 -0
- package/dist/components/ChartRangeSelector.vue.js +4 -0
- package/dist/components/ContextMenuItems.vue.js +1 -1
- package/dist/components/DropzoneLinkField.vue.d.ts +2 -2
- package/dist/components/DropzoneLinkField.vue.js +1 -1
- package/dist/components/FieldLeading.vue.d.ts +16 -0
- package/dist/components/FieldLeading.vue.js +4 -0
- package/dist/components/FieldWrapper.vue.js +1 -1
- package/dist/components/IconCloseButton.vue.d.ts +7 -0
- package/dist/components/IconCloseButton.vue.js +4 -0
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpActivityLine.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +125 -2
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.js +1 -1
- package/dist/components/LpBottomNav.vue.d.ts +5 -0
- package/dist/components/LpBottomNav.vue.js +1 -1
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +14 -2
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.d.ts +74 -2
- package/dist/components/LpChart.vue.js +326 -85
- package/dist/components/LpCheckbox.vue.d.ts +6 -2
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpCodeDisplay.vue.js +1 -1
- package/dist/components/LpCollapse.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +8 -0
- package/dist/components/LpCommandPalette.vue.js +107 -56
- package/dist/components/LpCommandScope.vue.js +1 -1
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.js +1 -1
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpContextSwitcher.vue.js +1 -1
- package/dist/components/LpCopyButton.vue.js +1 -1
- package/dist/components/LpCountdown.vue.js +1 -1
- package/dist/components/LpDataList.vue.d.ts +1 -1
- package/dist/components/LpDataList.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.d.ts +3 -15
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.d.ts +3 -13
- package/dist/components/LpDateRangePicker.vue.js +1 -1
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDock.vue.js +21 -18
- package/dist/components/LpDrawer.vue.d.ts +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +14 -11
- package/dist/components/LpEditable.vue.js +1 -1
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFieldNotice.vue.js +1 -1
- package/dist/components/LpFileTree.vue.d.ts +96 -6
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.d.ts +48 -0
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpFrame.vue.js +1 -1
- package/dist/components/LpGauge.vue.d.ts +1 -1
- package/dist/components/LpHintExample.vue.js +1 -1
- package/dist/components/LpHintLayer.vue.js +1 -1
- package/dist/components/LpHints.vue.js +1 -1
- package/dist/components/LpHoverCard.vue.js +1 -1
- package/dist/components/LpIcon.vue.d.ts +2 -0
- package/dist/components/LpIcon.vue.js +1 -1
- package/dist/components/LpIdenticon.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +7 -6
- package/dist/components/LpInfraNode.vue.d.ts +11 -2
- package/dist/components/LpInfraNode.vue.js +1 -1
- package/dist/components/LpInput.vue.d.ts +1 -1
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpInspector.vue.d.ts +87 -0
- package/dist/components/LpInspector.vue.js +4 -0
- package/dist/components/LpLaneNode.vue.d.ts +2 -3
- package/dist/components/LpLaneNode.vue.js +1 -1
- package/dist/components/LpLightbox.vue.d.ts +8 -0
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLoader.vue.d.ts +1 -1
- package/dist/components/LpLoader.vue.js +1 -1
- package/dist/components/LpLogInspector.vue.d.ts +18 -0
- package/dist/components/LpLogInspector.vue.js +4 -0
- package/dist/components/LpLogViewer.vue.d.ts +93 -0
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMasonry.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.d.ts +4 -2
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.d.ts +5 -5
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +2 -2
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.d.ts +13 -2
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPageScrollbar.vue.d.ts +10 -0
- package/dist/components/LpPageScrollbar.vue.js +4 -0
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPaletteFacts.vue.js +1 -1
- package/dist/components/LpPaletteHeader.vue.js +2 -2
- package/dist/components/LpPaletteTheme.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.d.ts +2 -2
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPipeline.vue.d.ts +4 -0
- package/dist/components/LpPipeline.vue.js +1 -1
- package/dist/components/LpPopover.vue.d.ts +7 -3
- package/dist/components/LpPopover.vue.js +1 -1
- package/dist/components/LpProgress.vue.d.ts +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpQuota.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeCalendar.vue.js +1 -1
- package/dist/components/LpRangeSlider.vue.js +1 -1
- package/dist/components/LpRelativeTime.vue.js +1 -1
- package/dist/components/LpRepeater.vue.js +1 -1
- package/dist/components/LpRevealOnScroll.vue.js +1 -1
- package/dist/components/LpScramble.vue.js +1 -1
- package/dist/components/LpScrollArea.vue.js +1 -1
- package/dist/components/LpSecretField.vue.js +1 -1
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpServiceNode.vue.d.ts +8 -2
- package/dist/components/LpServiceNode.vue.js +1 -1
- package/dist/components/LpShaderBackdrop.vue.js +1 -1
- package/dist/components/LpShift.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +1 -1
- package/dist/components/LpSidebar.vue.d.ts +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSidebarRow.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpSparkline.vue.d.ts +1 -1
- package/dist/components/LpSpotlight.vue.js +1 -1
- package/dist/components/LpStat.vue.d.ts +16 -0
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpStatus.vue.js +1 -1
- package/dist/components/LpStepMark.vue.js +1 -1
- package/dist/components/LpStepper.vue.js +1 -1
- package/dist/components/LpSwap.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTabFace.vue.js +1 -1
- package/dist/components/LpTable.vue.d.ts +132 -0
- package/dist/components/LpTable.vue.js +547 -135
- package/dist/components/LpTableFilter.vue.js +1 -1
- package/dist/components/LpTableOfContents.vue.js +1 -1
- package/dist/components/LpTabs.vue.js +6 -5
- package/dist/components/LpTagsInput.vue.js +1 -1
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.d.ts +2 -0
- package/dist/components/LpThemeProvider.vue.js +1 -1
- package/dist/components/LpThemeStudio.vue.js +1 -1
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTopologyCanvas.vue.d.ts +131 -5
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpTour.vue.js +1 -1
- package/dist/components/LpTruncate.vue.js +1 -1
- package/dist/components/LpTypewriter.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/LpVideoBackdrop.vue.js +1 -1
- package/dist/components/LpWindow.vue.d.ts +99 -0
- package/dist/components/LpWindow.vue.js +4 -0
- package/dist/components/LpWorkspaceSwitcher.vue.d.ts +18 -0
- package/dist/components/LpWorkspaceSwitcher.vue.js +4 -0
- package/dist/components/MenuTitle.vue.js +1 -1
- package/dist/components/OptionContent.vue.js +1 -1
- package/dist/components/PickerField.vue.d.ts +2 -2
- package/dist/components/PickerField.vue.js +1 -1
- package/dist/components/RevealToggle.vue.js +1 -1
- package/dist/components/SliderRail.vue.d.ts +3 -0
- package/dist/components/SliderRail.vue.js +4 -0
- package/dist/components/TourCard.vue.js +1 -1
- package/dist/components/address.js +2 -1
- package/dist/components/appShell.d.ts +36 -0
- package/dist/components/appShell.js +30 -0
- package/dist/components/calendar.d.ts +12 -3
- package/dist/components/calendar.js +2 -5
- package/dist/components/chart.d.ts +21 -0
- package/dist/components/chart.js +4 -3
- package/dist/components/chartWindow.d.ts +57 -0
- package/dist/components/chartWindow.js +136 -0
- package/dist/components/color.js +1 -1
- package/dist/components/commandPalette.d.ts +16 -2
- package/dist/components/commandPalette.js +39 -4
- package/dist/components/controlSize.d.ts +5 -0
- package/dist/components/controlSize.js +8 -0
- package/dist/components/dropdown.d.ts +5 -0
- package/dist/components/dropdown.js +2 -0
- package/dist/components/exportData.d.ts +6 -1
- package/dist/components/exportData.js +25 -18
- package/dist/components/fileTree.d.ts +67 -0
- package/dist/components/fileTree.js +141 -1
- package/dist/components/identicon.js +3 -2
- package/dist/components/logTools.d.ts +93 -0
- package/dist/components/logTools.js +210 -0
- package/dist/components/menuWhen.js +1 -1
- package/dist/components/meter.js +2 -1
- package/dist/components/pipeline.d.ts +2 -0
- package/dist/components/pipeline.js +5 -0
- package/dist/components/relativeTime.js +2 -1
- package/dist/components/reveal.js +2 -1
- package/dist/components/scrollbar.d.ts +34 -0
- package/dist/components/scrollbar.js +47 -0
- package/dist/components/select.d.ts +5 -0
- package/dist/components/select.js +10 -0
- package/dist/components/sidebar.d.ts +1 -0
- package/dist/components/tableFeatures.d.ts +58 -0
- package/dist/components/tableFeatures.js +87 -0
- package/dist/components/topologyGraph.d.ts +164 -0
- package/dist/components/topologyGraph.js +416 -0
- package/dist/components/tour.js +5 -3
- package/dist/components/videoBackdrop.js +7 -6
- package/dist/composables/clamp.d.ts +2 -0
- package/dist/composables/clamp.js +4 -0
- package/dist/composables/devWarn.d.ts +3 -0
- package/dist/composables/devWarn.js +14 -0
- package/dist/composables/easing.d.ts +13 -4
- package/dist/composables/easing.js +16 -10
- package/dist/composables/overlayHistory.js +14 -1
- package/dist/composables/pointerDrag.d.ts +15 -0
- package/dist/composables/pointerDrag.js +40 -0
- package/dist/composables/propCheck.d.ts +10 -0
- package/dist/composables/propCheck.js +89 -0
- package/dist/composables/realm.d.ts +6 -0
- package/dist/composables/realm.js +23 -0
- package/dist/composables/scroll.js +4 -2
- package/dist/composables/scrollMemory.d.ts +12 -1
- package/dist/composables/scrollMemory.js +36 -12
- package/dist/composables/spatialNav.js +2 -1
- package/dist/composables/textEntry.d.ts +17 -0
- package/dist/composables/textEntry.js +19 -0
- package/dist/composables/useAsyncTask.d.ts +6 -0
- package/dist/composables/useChartSync.d.ts +5 -0
- package/dist/composables/useChartSync.js +33 -0
- package/dist/composables/useCloseGuard.d.ts +3 -1
- package/dist/composables/useCloseGuard.js +6 -3
- package/dist/composables/useCommands.d.ts +50 -0
- package/dist/composables/useCommands.js +66 -0
- package/dist/composables/useField.d.ts +37 -0
- package/dist/composables/useField.js +27 -2
- package/dist/composables/useFitScale.js +4 -1
- package/dist/composables/useFormDraft.d.ts +13 -2
- package/dist/composables/useFormDraft.js +17 -9
- package/dist/composables/useHintSystem.js +2 -2
- package/dist/composables/useHotkeys.js +11 -32
- package/dist/composables/useInView.js +2 -1
- package/dist/composables/useInputFilter.js +7 -5
- package/dist/composables/useInputMask.js +5 -3
- package/dist/composables/useLinkedView.d.ts +97 -0
- package/dist/composables/useLinkedView.js +139 -0
- package/dist/composables/useOptimisticToggle.d.ts +3 -1
- package/dist/composables/useOptimisticToggle.js +2 -1
- package/dist/composables/useOverlayHistory.d.ts +7 -0
- package/dist/composables/useOverlayHistory.js +16 -2
- package/dist/composables/usePaintSelect.js +18 -14
- package/dist/composables/usePaletteCommands.d.ts +7 -0
- package/dist/composables/usePaletteCommands.js +16 -3
- package/dist/composables/usePersisted.d.ts +10 -0
- package/dist/composables/usePersisted.js +1 -1
- package/dist/composables/usePillTransition.d.ts +2 -2
- package/dist/composables/usePillTransition.js +5 -5
- package/dist/composables/useResizeObserver.js +2 -1
- package/dist/composables/useReveal.js +2 -1
- package/dist/composables/useRovingFocus.d.ts +2 -2
- package/dist/composables/useRovingFocus.js +9 -11
- package/dist/composables/useScramble.js +7 -1
- package/dist/composables/useShift.js +8 -6
- package/dist/composables/useTilt.js +2 -1
- package/dist/composables/useTween.js +9 -5
- package/dist/composables/useView.d.ts +1 -0
- package/dist/composables/useView.js +14 -2
- package/dist/composables/useZoomPan.js +2 -4
- package/dist/composables/windowing.d.ts +40 -0
- package/dist/composables/windowing.js +118 -0
- package/dist/icons-all.js +1 -1
- package/dist/icons.d.ts +4 -0
- package/dist/index.d.ts +31 -5
- package/dist/index.js +312 -264
- package/dist/layout/tree.js +19 -15
- package/dist/layout/useLayout.d.ts +2 -0
- package/dist/layout/useLayout.js +5 -1
- package/dist/layout/useWorkspaces.d.ts +52 -0
- package/dist/layout/useWorkspaces.js +140 -0
- package/dist/{chunks/backdrop-Dkf2vM81.js → theme/backdrop.js} +5 -5
- package/dist/theme/config.js +25 -0
- package/dist/theme/define.js +61 -0
- package/dist/{chunks/palette-Co8tSASo.js → theme/palette.js} +2 -2
- package/dist/theme/presets.js +239 -0
- package/dist/theme/studio.js +134 -0
- package/dist/theme/tokens.js +63 -0
- package/dist/theme/useTheme.d.ts +3 -2
- package/dist/theme/useTheme.js +325 -0
- package/package.json +8 -1
- package/src/component-names.ts +6 -1
- package/src/components/AppShellContextNav.vue +44 -0
- package/src/components/ChartRangeSelector.vue +120 -0
- package/src/components/FieldLeading.vue +17 -0
- package/src/components/IconCloseButton.vue +21 -0
- package/src/components/LayoutCanvas.vue +3 -0
- package/src/components/LayoutNode.vue +37 -38
- package/src/components/LpActivityLine.vue +4 -2
- package/src/components/LpAddressInput.vue +3 -3
- package/src/components/LpAppShell.vue +385 -24
- package/src/components/LpAutocomplete.vue +13 -30
- package/src/components/LpBottomNav.vue +9 -1
- package/src/components/LpBreadcrumbs.vue +2 -1
- package/src/components/LpButton.vue +11 -4
- package/src/components/LpChart.vue +336 -37
- package/src/components/LpCheckbox.vue +43 -9
- package/src/components/LpCodeBlock.vue +17 -23
- package/src/components/LpCollapse.vue +5 -4
- package/src/components/LpColorPicker.vue +8 -9
- package/src/components/LpCommandPalette.vue +133 -50
- package/src/components/LpContextMenu.vue +22 -13
- package/src/components/LpCountdown.vue +2 -1
- package/src/components/LpDataList.vue +13 -1
- package/src/components/LpDatePicker.vue +5 -17
- package/src/components/LpDateRangePicker.vue +5 -16
- package/src/components/LpDock.vue +25 -16
- package/src/components/LpDrawer.vue +70 -61
- package/src/components/LpDropdownMenu.vue +6 -10
- package/src/components/LpDropzone.vue +12 -7
- package/src/components/LpEditable.vue +6 -2
- package/src/components/LpFileTree.vue +507 -66
- package/src/components/LpFileTreeNode.vue +106 -5
- package/src/components/LpFormField.vue +1 -1
- package/src/components/LpFrame.vue +33 -13
- package/src/components/LpHoverCard.vue +37 -5
- package/src/components/LpIcon.vue +6 -2
- package/src/components/LpIdenticon.vue +2 -1
- package/src/components/LpImageEditor.vue +5 -2
- package/src/components/LpInfraNode.vue +4 -3
- package/src/components/LpInput.vue +19 -26
- package/src/components/LpInspector.vue +197 -0
- package/src/components/LpLaneNode.vue +5 -1
- package/src/components/LpLightbox.vue +74 -29
- package/src/components/LpLogInspector.vue +85 -0
- package/src/components/LpLogViewer.vue +239 -29
- package/src/components/LpMapPicker.vue +2 -1
- package/src/components/LpMasonry.vue +8 -5
- package/src/components/LpModal.vue +12 -12
- package/src/components/LpModalNav.vue +2 -21
- package/src/components/LpNumberField.vue +13 -16
- package/src/components/LpOtpInput.vue +2 -2
- package/src/components/LpPageScrollbar.vue +195 -0
- package/src/components/LpPagination.vue +5 -3
- package/src/components/LpPhoneInput.vue +8 -19
- package/src/components/LpPie.vue +25 -23
- package/src/components/LpPipeline.vue +14 -1
- package/src/components/LpPopover.vue +23 -2
- package/src/components/LpProgress.vue +2 -1
- package/src/components/LpRadioGroup.vue +2 -2
- package/src/components/LpRangeSlider.vue +5 -6
- package/src/components/LpRevealOnScroll.vue +1 -1
- package/src/components/LpScrollArea.vue +55 -34
- package/src/components/LpSecretField.vue +2 -1
- package/src/components/LpSegmented.vue +6 -8
- package/src/components/LpSelect.vue +54 -24
- package/src/components/LpServiceNode.vue +4 -2
- package/src/components/LpShaderBackdrop.vue +12 -9
- package/src/components/LpShift.vue +18 -15
- package/src/components/LpSidebar.vue +77 -74
- package/src/components/LpSidebarNav.vue +3 -2
- package/src/components/LpSidebarRow.vue +2 -1
- package/src/components/LpSlider.vue +5 -6
- package/src/components/LpSpotlight.vue +2 -1
- package/src/components/LpStat.vue +25 -5
- package/src/components/LpSwitch.vue +5 -3
- package/src/components/LpTable.vue +609 -93
- package/src/components/LpTableFilter.vue +1 -1
- package/src/components/LpTableOfContents.vue +16 -9
- package/src/components/LpTabs.vue +2 -1
- package/src/components/LpTagsInput.vue +12 -19
- package/src/components/LpTextarea.vue +5 -11
- package/src/components/LpThemeProvider.vue +29 -13
- package/src/components/LpTimeField.vue +4 -7
- package/src/components/LpToaster.vue +7 -6
- package/src/components/LpTooltip.vue +69 -18
- package/src/components/LpTopologyCanvas.vue +569 -42
- package/src/components/LpTour.vue +7 -5
- package/src/components/LpTruncate.vue +4 -3
- package/src/components/LpTypewriter.vue +2 -1
- package/src/components/LpVideoBackdrop.vue +11 -13
- package/src/components/LpWindow.vue +373 -0
- package/src/components/LpWorkspaceSwitcher.vue +116 -0
- package/src/components/PickerField.vue +2 -2
- package/src/components/SliderRail.vue +10 -0
- package/src/components/address.ts +3 -1
- package/src/components/appShell.ts +74 -0
- package/src/components/calendar.ts +12 -5
- package/src/components/chart.ts +27 -3
- package/src/components/chartWindow.ts +198 -0
- package/src/components/color.ts +2 -1
- package/src/components/commandPalette.ts +66 -5
- package/src/components/controlSize.ts +6 -0
- package/src/components/dropdown.ts +3 -0
- package/src/components/exportData.ts +33 -18
- package/src/components/fileTree.ts +215 -0
- package/src/components/identicon.ts +4 -2
- package/src/components/logTools.ts +298 -0
- package/src/components/meter.ts +2 -1
- package/src/components/pipeline.ts +6 -0
- package/src/components/relativeTime.ts +2 -1
- package/src/components/reveal.ts +3 -1
- package/src/components/scrollbar.ts +71 -0
- package/src/components/select.ts +14 -0
- package/src/components/sidebar.ts +1 -0
- package/src/components/tableFeatures.ts +149 -0
- package/src/components/topologyGraph.ts +655 -0
- package/src/components/tour.ts +5 -3
- package/src/components/videoBackdrop.ts +7 -6
- package/src/composables/clamp.ts +2 -0
- package/src/composables/devWarn.ts +19 -0
- package/src/composables/easing.ts +37 -12
- package/src/composables/overlayHistory.ts +24 -0
- package/src/composables/pointerDrag.ts +54 -0
- package/src/composables/propCheck.ts +105 -0
- package/src/composables/realm.ts +25 -0
- package/src/composables/scroll.ts +4 -2
- package/src/composables/scrollMemory.ts +56 -14
- package/src/composables/spatialNav.ts +2 -1
- package/src/composables/textEntry.ts +33 -0
- package/src/composables/useAsyncTask.ts +6 -0
- package/src/composables/useChartSync.ts +52 -0
- package/src/composables/useCloseGuard.ts +22 -10
- package/src/composables/useCommands.ts +123 -0
- package/src/composables/useField.ts +41 -2
- package/src/composables/useFitScale.ts +4 -1
- package/src/composables/useFormDraft.ts +38 -14
- package/src/composables/useHintSystem.ts +2 -2
- package/src/composables/useHotkeys.ts +19 -48
- package/src/composables/useInView.ts +2 -1
- package/src/composables/useInputFilter.ts +8 -5
- package/src/composables/useInputMask.ts +8 -3
- package/src/composables/useLinkedView.ts +222 -0
- package/src/composables/useOptimisticToggle.ts +7 -1
- package/src/composables/useOverlayHistory.ts +20 -0
- package/src/composables/usePaintSelect.ts +20 -14
- package/src/composables/usePaletteCommands.ts +18 -2
- package/src/composables/usePersisted.ts +10 -0
- package/src/composables/usePillTransition.ts +9 -6
- package/src/composables/useResizeObserver.ts +2 -1
- package/src/composables/useReveal.ts +2 -1
- package/src/composables/useRovingFocus.ts +11 -14
- package/src/composables/useScramble.ts +12 -1
- package/src/composables/useShift.ts +8 -6
- package/src/composables/useTilt.ts +2 -1
- package/src/composables/useTween.ts +10 -5
- package/src/composables/useView.ts +17 -3
- package/src/composables/useZoomPan.ts +2 -5
- package/src/composables/windowing.ts +190 -0
- package/src/icons.ts +27 -6
- package/src/index.ts +42 -4
- package/src/kit-icons.ts +1 -1
- package/src/layout/tree.ts +31 -16
- package/src/layout/useLayout.ts +6 -0
- package/src/layout/useWorkspaces.ts +234 -0
- package/src/nuxt.ts +9 -1
- package/src/runtime/propCheck.plugin.ts +10 -0
- package/src/theme/studio.ts +7 -5
- package/src/theme/useTheme.ts +89 -15
- package/src/tokens/tokens.css +8 -1
- package/dist/assets/LpTabs-CJ0rYQbx.css +0 -18
- package/dist/chunks/LpAppShell-Dgejipzz.js +0 -319
- package/dist/chunks/LpFileTree-CooHinsC.js +0 -425
- package/dist/chunks/LpThemeProvider-8MG6yqU9.js +0 -640
- package/dist/chunks/LpTopologyCanvas-B1gIbS0n.js +0 -298
|
@@ -23,11 +23,42 @@ import {
|
|
|
23
23
|
type Padding,
|
|
24
24
|
} from "@vue-flow/core"
|
|
25
25
|
import { MiniMap } from "@vue-flow/minimap"
|
|
26
|
-
import { computed, defineComponent, h, markRaw, ref, watch, type Component } from "vue"
|
|
26
|
+
import { computed, defineComponent, h, markRaw, nextTick, ref, shallowRef, watch, type Component } from "vue"
|
|
27
|
+
import { prefersReducedMotion } from "../composables/easing"
|
|
28
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
29
|
+
import { useResizeObserver } from "../composables/useResizeObserver"
|
|
30
|
+
import { isHTMLElement, nodeWindow } from "../composables/useView"
|
|
31
|
+
import { backgroundOf, resolveCssColor, svgToPng } from "./exportData"
|
|
32
|
+
import LpButton from "./LpButton.vue"
|
|
33
|
+
import LpIcon from "./LpIcon.vue"
|
|
27
34
|
import LpInfraNode, { type InfraNodeData } from "./LpInfraNode.vue"
|
|
35
|
+
import LpInput from "./LpInput.vue"
|
|
28
36
|
import LpLaneNode from "./LpLaneNode.vue"
|
|
29
37
|
import LpServiceNode, { type ServiceNodeData } from "./LpServiceNode.vue"
|
|
38
|
+
import LpSlider from "./LpSlider.vue"
|
|
30
39
|
import LpWaypointEdge from "./LpWaypointEdge.vue"
|
|
40
|
+
import {
|
|
41
|
+
absolutePositions,
|
|
42
|
+
clusterGraph,
|
|
43
|
+
collapseChildren,
|
|
44
|
+
detailAtZoom,
|
|
45
|
+
diffTopology,
|
|
46
|
+
edgeLabelText,
|
|
47
|
+
findNodes,
|
|
48
|
+
findPath,
|
|
49
|
+
frameSize,
|
|
50
|
+
groupBoxes,
|
|
51
|
+
layeredLayout,
|
|
52
|
+
nearestInDirection,
|
|
53
|
+
neighborhood,
|
|
54
|
+
nodeName,
|
|
55
|
+
topologyToSvg,
|
|
56
|
+
type DetailLevel,
|
|
57
|
+
type GraphDirection,
|
|
58
|
+
type GraphPath,
|
|
59
|
+
type LayoutOptions,
|
|
60
|
+
type TopologyDiff,
|
|
61
|
+
} from "./topologyGraph"
|
|
31
62
|
|
|
32
63
|
// Vue Flow base CSS + canvas chrome theming is shipped via the kit's
|
|
33
64
|
// "@leavepulse/ui/canvas.css" export (a plain CSS file). Component-local CSS
|
|
@@ -53,6 +84,11 @@ export interface TopologyNode {
|
|
|
53
84
|
width?: number
|
|
54
85
|
height?: number
|
|
55
86
|
label?: string
|
|
87
|
+
/**
|
|
88
|
+
* Free-form facts about the node, for `groupBy` ("region", "environment"):
|
|
89
|
+
* what the card does not show but a graph can be sorted by.
|
|
90
|
+
*/
|
|
91
|
+
meta?: Record<string, string | undefined>
|
|
56
92
|
}
|
|
57
93
|
|
|
58
94
|
/**
|
|
@@ -84,8 +120,32 @@ export interface TopologyEdge {
|
|
|
84
120
|
* the gaps are. Requires `edgeType: "waypoints"`; ignored otherwise.
|
|
85
121
|
*/
|
|
86
122
|
waypoints?: { x: number; y: number }[]
|
|
123
|
+
/** Measured round-trip time in ms, printed on the edge after its `kind`. */
|
|
124
|
+
rtt?: number
|
|
125
|
+
/** Measured packet loss in percent, printed on the edge after `rtt`. */
|
|
126
|
+
loss?: number
|
|
87
127
|
}
|
|
88
128
|
|
|
129
|
+
/** One version of the graph, for `timeline` and `compareTo`. */
|
|
130
|
+
export interface TopologySnapshot {
|
|
131
|
+
nodes: TopologyNode[]
|
|
132
|
+
edges: TopologyEdge[]
|
|
133
|
+
/** What the timeline shows for this version: a date, a deploy. */
|
|
134
|
+
label?: string
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** A button over a multi-selection; pressing it emits `selection-action`. */
|
|
138
|
+
export interface TopologySelectionAction {
|
|
139
|
+
id: string
|
|
140
|
+
label: string
|
|
141
|
+
icon?: string
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** The node facts a graph can be grouped by, or a function that picks one. */
|
|
145
|
+
export type TopologyGroupBy = string | ((node: TopologyNode) => string | undefined)
|
|
146
|
+
|
|
147
|
+
export type TopologyLayoutOptions = Omit<LayoutOptions, "groupOf">
|
|
148
|
+
|
|
89
149
|
/**
|
|
90
150
|
* A background swimlane: a sized zone (one per project / overlay network) drawn
|
|
91
151
|
* beneath the nodes. The consumer computes the geometry; the canvas renders it
|
|
@@ -103,6 +163,21 @@ export interface TopologyLane {
|
|
|
103
163
|
accent?: string
|
|
104
164
|
}
|
|
105
165
|
|
|
166
|
+
/** Wording of the canvas's own controls. `{n}` is a count. */
|
|
167
|
+
export type TopologyLabels = {
|
|
168
|
+
search: string
|
|
169
|
+
searchResults: string
|
|
170
|
+
selected: string
|
|
171
|
+
timeline: string
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const TOPOLOGY_LABELS: TopologyLabels = {
|
|
175
|
+
search: "Find a node",
|
|
176
|
+
searchResults: "{n} found",
|
|
177
|
+
selected: "{n} selected",
|
|
178
|
+
timeline: "Version",
|
|
179
|
+
}
|
|
180
|
+
|
|
106
181
|
/** Canvas viewport transform — for restoring a saved pan/zoom. */
|
|
107
182
|
export interface CanvasViewport {
|
|
108
183
|
x: number
|
|
@@ -155,6 +230,50 @@ const props = withDefaults(
|
|
|
155
230
|
*/
|
|
156
231
|
minimap?: boolean
|
|
157
232
|
controls?: boolean
|
|
233
|
+
/** Hide edges this returns false for, by kind, state or anything else. */
|
|
234
|
+
edgeFilter?: (edge: TopologyEdge) => boolean
|
|
235
|
+
/** Picking a node fades everything not directly connected to it. */
|
|
236
|
+
highlightNeighbors?: boolean
|
|
237
|
+
/**
|
|
238
|
+
* Two node ids: the shortest path between them stands out, the rest fades.
|
|
239
|
+
* The result arrives in `path-change`. Goes by `directedPath` and by what
|
|
240
|
+
* `edgeFilter` leaves visible.
|
|
241
|
+
*/
|
|
242
|
+
path?: [string, string]
|
|
243
|
+
/** Walk the path along edge direction only. Default: a wire goes both ways. */
|
|
244
|
+
directedPath?: boolean
|
|
245
|
+
/**
|
|
246
|
+
* Draw a lane around each group of nodes, keyed by a `meta` field, a `data`
|
|
247
|
+
* field or a function. `autoLayout` then keeps the groups apart.
|
|
248
|
+
*/
|
|
249
|
+
groupBy?: TopologyGroupBy
|
|
250
|
+
/** A search box that finds a node by name or id and brings it into view. */
|
|
251
|
+
search?: boolean
|
|
252
|
+
/** Arrow keys walk between nodes instead of nudging the focused one. */
|
|
253
|
+
keyboardNav?: boolean
|
|
254
|
+
/** Buttons shown while two or more nodes are selected. */
|
|
255
|
+
selectionActions?: TopologySelectionAction[]
|
|
256
|
+
/** An earlier version: what differs is marked on the graph drawn now. */
|
|
257
|
+
compareTo?: TopologySnapshot
|
|
258
|
+
/**
|
|
259
|
+
* Versions of the graph, oldest first: a slider steps through them and
|
|
260
|
+
* each is drawn with what changed since the one before. `nodes` and `edges`
|
|
261
|
+
* are not drawn meanwhile.
|
|
262
|
+
*/
|
|
263
|
+
timeline?: TopologySnapshot[]
|
|
264
|
+
/** Fit the graph again whenever the canvas changes size. Default "never": the view is the user's. */
|
|
265
|
+
fitOnResize?: "never" | "always"
|
|
266
|
+
/** Change it when a new dataset arrives, to fit the graph to it once. */
|
|
267
|
+
dataRevision?: string | number
|
|
268
|
+
/** Grow each group frame to hold its services, however the data sized it. */
|
|
269
|
+
autoSizeGroups?: boolean
|
|
270
|
+
/**
|
|
271
|
+
* How much to draw. "simple" leaves services out, with their edges moved to
|
|
272
|
+
* their hosts; "overview" folds each `groupBy` group into one node; "auto"
|
|
273
|
+
* picks by zoom. Default "full".
|
|
274
|
+
*/
|
|
275
|
+
detail?: DetailLevel | "auto"
|
|
276
|
+
labels?: Partial<TopologyLabels>
|
|
158
277
|
}>(),
|
|
159
278
|
{
|
|
160
279
|
connectable: false,
|
|
@@ -164,9 +283,23 @@ const props = withDefaults(
|
|
|
164
283
|
lanes: () => [],
|
|
165
284
|
minimap: true,
|
|
166
285
|
controls: true,
|
|
286
|
+
edgeFilter: undefined,
|
|
287
|
+
path: undefined,
|
|
288
|
+
groupBy: undefined,
|
|
289
|
+
selectionActions: undefined,
|
|
290
|
+
compareTo: undefined,
|
|
291
|
+
timeline: undefined,
|
|
292
|
+
fitOnResize: "never",
|
|
293
|
+
dataRevision: undefined,
|
|
294
|
+
detail: "full",
|
|
295
|
+
labels: undefined,
|
|
167
296
|
},
|
|
168
297
|
)
|
|
169
298
|
|
|
299
|
+
const text = useLabels("topology", TOPOLOGY_LABELS, () => props.labels)
|
|
300
|
+
/** Index into `timeline`; unset follows the newest version. */
|
|
301
|
+
const timelineIndex = defineModel<number | undefined>("timelineIndex", { default: undefined })
|
|
302
|
+
|
|
170
303
|
const emit = defineEmits<{
|
|
171
304
|
(e: "connect", value: Connection): void
|
|
172
305
|
(e: "node-select", id: string): void
|
|
@@ -183,6 +316,10 @@ const emit = defineEmits<{
|
|
|
183
316
|
e: "node-drag-stop",
|
|
184
317
|
value: { id: string; position: { x: number; y: number } },
|
|
185
318
|
): void
|
|
319
|
+
/** The path asked for by `path`: null when the two are not connected. */
|
|
320
|
+
(e: "path-change", value: GraphPath | null): void
|
|
321
|
+
/** A `selectionActions` button was pressed for these nodes. */
|
|
322
|
+
(e: "selection-action", value: { action: string; ids: string[] }): void
|
|
186
323
|
/** Pan/zoom settled — the new viewport (for per-user persistence). */
|
|
187
324
|
(
|
|
188
325
|
e: "viewport-change",
|
|
@@ -214,11 +351,159 @@ const nodeTypes: NodeTypesObject = {
|
|
|
214
351
|
// where the gaps are; with none it falls back to the plain bezier.
|
|
215
352
|
const edgeTypes = { waypoints: markRaw(LpWaypointEdge) as Component }
|
|
216
353
|
|
|
354
|
+
const {
|
|
355
|
+
onNodeClick,
|
|
356
|
+
onNodeDoubleClick,
|
|
357
|
+
onNodeContextMenu,
|
|
358
|
+
onEdgeContextMenu,
|
|
359
|
+
onPaneClick,
|
|
360
|
+
onConnect,
|
|
361
|
+
onNodeDragStop,
|
|
362
|
+
onMoveEnd,
|
|
363
|
+
getSelectedNodes,
|
|
364
|
+
fitView,
|
|
365
|
+
setViewport,
|
|
366
|
+
onNodesInitialized,
|
|
367
|
+
vueFlowRef,
|
|
368
|
+
viewport,
|
|
369
|
+
findNode,
|
|
370
|
+
getNodes,
|
|
371
|
+
addSelectedNodes,
|
|
372
|
+
removeSelectedNodes,
|
|
373
|
+
} = useVueFlow()
|
|
374
|
+
|
|
375
|
+
// What is drawn: the chosen version from the timeline, or the graph as given.
|
|
376
|
+
const versionIndex = computed(() => {
|
|
377
|
+
const last = (props.timeline?.length ?? 0) - 1
|
|
378
|
+
return Math.max(0, Math.min(timelineIndex.value ?? last, last))
|
|
379
|
+
})
|
|
380
|
+
const current = computed<TopologySnapshot>(
|
|
381
|
+
() => props.timeline?.[versionIndex.value] ?? { nodes: props.nodes, edges: props.edges },
|
|
382
|
+
)
|
|
383
|
+
const baseline = computed(() => (props.timeline ? props.timeline[versionIndex.value - 1] : props.compareTo))
|
|
384
|
+
const diff = computed<TopologyDiff | null>(() => (baseline.value ? diffTopology(baseline.value, current.value) : null))
|
|
385
|
+
|
|
386
|
+
/*
|
|
387
|
+
* A redraw hands Vue Flow every node again, position included, and highlighting
|
|
388
|
+
* redraws on a click. Where the user dragged a node stands until the host
|
|
389
|
+
* sends new data, so a host that does not persist positions keeps them too.
|
|
390
|
+
*/
|
|
391
|
+
const moved = shallowRef(new Map<string, { x: number; y: number }>())
|
|
392
|
+
watch(() => current.value.nodes, () => (moved.value = new Map()))
|
|
393
|
+
const placed = computed(() =>
|
|
394
|
+
moved.value.size
|
|
395
|
+
? current.value.nodes.map((n) => (moved.value.has(n.id) ? { ...n, position: moved.value.get(n.id)! } : n))
|
|
396
|
+
: current.value.nodes,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
function groupOf(node: TopologyNode): string | undefined {
|
|
400
|
+
const by = props.groupBy
|
|
401
|
+
if (typeof by === "function") return by(node)
|
|
402
|
+
if (!by) return undefined
|
|
403
|
+
const value = node.meta?.[by] ?? (node.data as unknown as Record<string, unknown>)[by]
|
|
404
|
+
return typeof value === "string" && value ? value : undefined
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// With the removed parts of `diff` put back, so what went is seen going.
|
|
408
|
+
const versioned = computed(() => {
|
|
409
|
+
const edges = props.edgeFilter ? current.value.edges.filter(props.edgeFilter) : current.value.edges
|
|
410
|
+
const was = baseline.value
|
|
411
|
+
if (!diff.value || !was) return { nodes: placed.value, edges }
|
|
412
|
+
const goneNodes = new Set(diff.value.removedNodes)
|
|
413
|
+
const goneEdges = new Set(diff.value.removedEdges)
|
|
414
|
+
return {
|
|
415
|
+
nodes: [...placed.value, ...was.nodes.filter((n) => goneNodes.has(n.id))],
|
|
416
|
+
edges: [...edges, ...was.edges.filter((e) => goneEdges.has(e.id) && (props.edgeFilter?.(e) ?? true))],
|
|
417
|
+
}
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
const detailLevel = computed<DetailLevel>(() => {
|
|
421
|
+
if (props.detail === "auto") return detailAtZoom(viewport.value.zoom, !!props.groupBy)
|
|
422
|
+
return props.detail === "overview" && !props.groupBy ? "simple" : props.detail
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
// Merged edges stand in for the ones they fold: the first lends its look.
|
|
426
|
+
function foldedEdges(merged: ReturnType<typeof collapseChildren>["edges"], all: TopologyEdge[]): TopologyEdge[] {
|
|
427
|
+
const byId = new Map(all.map((e) => [e.id, e]))
|
|
428
|
+
return merged.map((m) => {
|
|
429
|
+
const first = byId.get(m.members[0]!)!
|
|
430
|
+
const kind = m.members.length > 1 ? `${first.kind ?? ""} ×${m.members.length}`.trim() : first.kind
|
|
431
|
+
return { ...first, id: m.id, source: m.source, target: m.target, kind, waypoints: undefined }
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const shown = computed<{ nodes: TopologyNode[]; edges: TopologyEdge[] }>(() => {
|
|
436
|
+
const { nodes, edges } = versioned.value
|
|
437
|
+
if (detailLevel.value === "full") return { nodes, edges }
|
|
438
|
+
if (detailLevel.value === "simple") {
|
|
439
|
+
const folded = collapseChildren(nodes, edges)
|
|
440
|
+
return { nodes: folded.nodes, edges: foldedEdges(folded.edges, edges) }
|
|
441
|
+
}
|
|
442
|
+
const graph = clusterGraph(nodes, edges, groupOf)
|
|
443
|
+
const clusters = graph.clusters.map((c): TopologyNode => ({
|
|
444
|
+
id: c.id,
|
|
445
|
+
position: c.position,
|
|
446
|
+
data: { name: `${c.key} (${c.members.length})`, role: "cluster" },
|
|
447
|
+
}))
|
|
448
|
+
return { nodes: [...graph.nodes, ...clusters], edges: foldedEdges(graph.edges, edges) }
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
const query = ref("")
|
|
452
|
+
const matches = computed(() => findNodes(shown.value.nodes, query.value))
|
|
453
|
+
const focused = ref<string | null>(null)
|
|
454
|
+
|
|
455
|
+
const pathFound = computed(() => {
|
|
456
|
+
if (!props.path) return null
|
|
457
|
+
return findPath(shown.value.edges, props.path[0], props.path[1], { directed: props.directedPath })
|
|
458
|
+
})
|
|
459
|
+
// By route, not by object: `:path="[a, b]"` in a template is a new array on each
|
|
460
|
+
// render, and an answer per render would loop if the host renders what it hears.
|
|
461
|
+
watch(() => JSON.stringify(pathFound.value), () => emit("path-change", pathFound.value), { immediate: !!props.path })
|
|
462
|
+
|
|
463
|
+
// What stands out, when something does. Nothing here means the graph as it is.
|
|
464
|
+
const emphasis = computed(() => {
|
|
465
|
+
const picked: GraphPath | null =
|
|
466
|
+
pathFound.value ??
|
|
467
|
+
(matches.value.length ? { nodes: matches.value.map((n) => n.id), edges: [] } : null) ??
|
|
468
|
+
(props.highlightNeighbors && focused.value ? neighborhood(shown.value.edges, focused.value) : null)
|
|
469
|
+
if (!picked) return null
|
|
470
|
+
const lit = new Set(picked.nodes)
|
|
471
|
+
// A frame stays lit around a service that is, or the service fades with it.
|
|
472
|
+
for (const n of shown.value.nodes) if (n.parent && lit.has(n.id)) lit.add(n.parent)
|
|
473
|
+
return { nodes: lit, edges: new Set(picked.edges), path: !!pathFound.value }
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
function diffState(id: string, added: string[] | undefined, changed: string[] | undefined, removed: string[] | undefined) {
|
|
477
|
+
if (added?.includes(id)) return "added"
|
|
478
|
+
if (removed?.includes(id)) return "removed"
|
|
479
|
+
return changed?.includes(id) ? "changed" : null
|
|
480
|
+
}
|
|
481
|
+
const GROUP_WIDTH = 260
|
|
482
|
+
const GROUP_HEIGHT = 160
|
|
483
|
+
const PATH_EXTRA_WIDTH = 1.5
|
|
484
|
+
const REMOVED_DASH = "3 5"
|
|
485
|
+
const DIFF_COLOR = {
|
|
486
|
+
added: "var(--color-action)",
|
|
487
|
+
changed: "var(--color-accent)",
|
|
488
|
+
removed: "var(--color-danger)",
|
|
489
|
+
} as const
|
|
490
|
+
|
|
217
491
|
// Lanes render as non-interactive Vue Flow nodes BENEATH the graph: prepended
|
|
218
492
|
// so they paint first, with selecting/dragging/connecting off and a low z so
|
|
219
493
|
// real nodes always sit on top. They pan/zoom with the viewport for free.
|
|
494
|
+
// Measured where it has been drawn: a card's size is the card's own business.
|
|
495
|
+
function sized(node: TopologyNode): TopologyNode {
|
|
496
|
+
const measured = findNode(node.id)?.dimensions
|
|
497
|
+
return { ...node, width: node.width ?? (measured?.width || undefined), height: node.height ?? (measured?.height || undefined) }
|
|
498
|
+
}
|
|
499
|
+
const allLanes = computed<TopologyLane[]>(() => {
|
|
500
|
+
if (!props.groupBy || detailLevel.value === "overview") return props.lanes
|
|
501
|
+
const groups = groupBoxes(shown.value.nodes.map(sized), (n) => groupOf(n as TopologyNode))
|
|
502
|
+
return [...props.lanes, ...groups.map(({ key, box }) => ({ id: `group-${key}`, label: key, ...box }))]
|
|
503
|
+
})
|
|
504
|
+
|
|
220
505
|
const laneNodes = computed<Node[]>(() =>
|
|
221
|
-
|
|
506
|
+
allLanes.value.map(
|
|
222
507
|
(l): Node => ({
|
|
223
508
|
id: `lane-${l.id}`,
|
|
224
509
|
type: "lane",
|
|
@@ -234,25 +519,37 @@ const laneNodes = computed<Node[]>(() =>
|
|
|
234
519
|
)
|
|
235
520
|
|
|
236
521
|
const graphNodes = computed<Node[]>(() =>
|
|
237
|
-
|
|
522
|
+
shown.value.nodes.map((n): Node => {
|
|
238
523
|
const type = n.type ?? "infra"
|
|
524
|
+
const faded = !!emphasis.value && !emphasis.value.nodes.has(n.id)
|
|
525
|
+
const state = diffState(n.id, diff.value?.addedNodes, diff.value?.changedNodes, diff.value?.removedNodes)
|
|
239
526
|
const node: Node = {
|
|
240
527
|
id: n.id,
|
|
241
528
|
type,
|
|
242
529
|
position: n.position,
|
|
243
|
-
data: n.data,
|
|
530
|
+
data: faded ? { ...n.data, dimmed: true } : n.data,
|
|
244
531
|
zIndex: 1,
|
|
532
|
+
ariaLabel: props.keyboardNav ? nodeName(n) : undefined,
|
|
245
533
|
...(n.parent ? { parentNode: n.parent } : {}),
|
|
534
|
+
...(n.parent && props.autoSizeGroups ? { expandParent: true } : {}),
|
|
535
|
+
}
|
|
536
|
+
if (state) {
|
|
537
|
+
node.style = { outline: `2px solid ${DIFF_COLOR[state]}`, outlineOffset: "2px", borderRadius: "var(--radius-card)" }
|
|
246
538
|
}
|
|
247
539
|
if (type === "group") {
|
|
248
540
|
// A host frame: a Vue Flow group node sized to hold its services.
|
|
541
|
+
const given = { width: n.width ?? GROUP_WIDTH, height: n.height ?? GROUP_HEIGHT }
|
|
542
|
+
const frame = props.autoSizeGroups
|
|
543
|
+
? frameSize(shown.value.nodes.filter((child) => child.parent === n.id).map(sized), { minWidth: given.width, minHeight: given.height })
|
|
544
|
+
: given
|
|
249
545
|
node.style = {
|
|
250
|
-
|
|
251
|
-
|
|
546
|
+
...node.style,
|
|
547
|
+
width: `${frame.width}px`,
|
|
548
|
+
height: `${frame.height}px`,
|
|
252
549
|
background: "color-mix(in srgb, var(--color-surface-soft) 60%, transparent)",
|
|
253
550
|
border: "1px solid var(--color-line)",
|
|
254
551
|
borderRadius: "var(--radius-card)",
|
|
255
|
-
opacity: (n.data as { dimmed?: boolean }).dimmed ? "0.35" : "1",
|
|
552
|
+
opacity: faded || (n.data as { dimmed?: boolean }).dimmed ? "0.35" : "1",
|
|
256
553
|
transition: "opacity var(--duration-fast)",
|
|
257
554
|
}
|
|
258
555
|
node.label = n.label ?? n.data.name
|
|
@@ -288,54 +585,49 @@ function edgeDash(cat: EdgeCategory, o: EdgeObserved): string {
|
|
|
288
585
|
}
|
|
289
586
|
|
|
290
587
|
const flowEdges = computed(() =>
|
|
291
|
-
|
|
588
|
+
shown.value.edges.map((e) => {
|
|
292
589
|
const obs = e.observed ?? "applied"
|
|
293
590
|
const cat = edgeCategory(e)
|
|
294
|
-
const
|
|
591
|
+
const state = diffState(e.id, diff.value?.addedEdges, diff.value?.changedEdges, diff.value?.removedEdges)
|
|
592
|
+
const color = state ? DIFF_COLOR[state] : edgeColor(cat, obs)
|
|
593
|
+
const lit = emphasis.value?.edges.has(e.id) ?? false
|
|
594
|
+
const faded = e.dimmed || (!!emphasis.value && !lit)
|
|
295
595
|
return {
|
|
296
596
|
id: e.id,
|
|
297
597
|
source: e.source,
|
|
298
598
|
target: e.target,
|
|
299
599
|
type: props.edgeType ?? "default",
|
|
300
600
|
data: { waypoints: e.waypoints },
|
|
301
|
-
animated: obs !== "applied" && !
|
|
302
|
-
label: e
|
|
601
|
+
animated: obs !== "applied" && !faded,
|
|
602
|
+
label: edgeLabelText(e),
|
|
303
603
|
labelBgStyle: { fill: "var(--color-surface)" },
|
|
304
604
|
labelStyle: { fill: "var(--color-muted-strong)", fontSize: "10px" },
|
|
305
605
|
style: {
|
|
306
606
|
stroke: color,
|
|
307
|
-
strokeWidth: cat === "structural" ? 1.5 : 2,
|
|
308
|
-
strokeDasharray: edgeDash(cat, obs),
|
|
607
|
+
strokeWidth: (cat === "structural" ? 1.5 : 2) + (lit && emphasis.value?.path ? PATH_EXTRA_WIDTH : 0),
|
|
608
|
+
strokeDasharray: state === "removed" ? REMOVED_DASH : edgeDash(cat, obs),
|
|
309
609
|
strokeLinecap: "round" as const,
|
|
310
|
-
opacity:
|
|
610
|
+
opacity: faded ? 0.12 : 1,
|
|
311
611
|
},
|
|
312
612
|
markerEnd: { type: MarkerType.ArrowClosed, color },
|
|
313
613
|
}
|
|
314
614
|
}),
|
|
315
615
|
)
|
|
316
616
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
onEdgeContextMenu,
|
|
322
|
-
onPaneClick,
|
|
323
|
-
onConnect,
|
|
324
|
-
onNodeDragStop,
|
|
325
|
-
onMoveEnd,
|
|
326
|
-
getSelectedNodes,
|
|
327
|
-
fitView,
|
|
328
|
-
setViewport,
|
|
329
|
-
onNodesInitialized,
|
|
330
|
-
} = useVueFlow()
|
|
331
|
-
onNodeClick(({ node }) => emit("node-select", node.id))
|
|
617
|
+
onNodeClick(({ node }) => {
|
|
618
|
+
focused.value = node.id
|
|
619
|
+
emit("node-select", node.id)
|
|
620
|
+
})
|
|
332
621
|
// A node usually has two levels of interest — pick it out, or open it — and a
|
|
333
622
|
// single click cannot carry both. Hosts that only need one can ignore this.
|
|
334
623
|
onNodeDoubleClick(({ node }) => emit("node-activate", node.id))
|
|
335
624
|
// Drag settled → surface the node's final position so the host can persist it.
|
|
336
|
-
onNodeDragStop(({ node }) =>
|
|
337
|
-
|
|
338
|
-
)
|
|
625
|
+
onNodeDragStop(({ node, nodes }) => {
|
|
626
|
+
const next = new Map(moved.value)
|
|
627
|
+
for (const dragged of nodes.length ? nodes : [node]) next.set(dragged.id, { ...dragged.position })
|
|
628
|
+
moved.value = next
|
|
629
|
+
emit("node-drag-stop", { id: node.id, position: { ...node.position } })
|
|
630
|
+
})
|
|
339
631
|
// Pan/zoom settled → surface the viewport so the host can persist it per user.
|
|
340
632
|
onMoveEnd(({ flowTransform }) => {
|
|
341
633
|
if (flowTransform)
|
|
@@ -388,21 +680,27 @@ onEdgeContextMenu(({ edge, event }) => {
|
|
|
388
680
|
e.preventDefault?.()
|
|
389
681
|
emit("edge-contextmenu", { id: edge.id, x: e.clientX, y: e.clientY })
|
|
390
682
|
})
|
|
391
|
-
onPaneClick(() =>
|
|
683
|
+
onPaneClick(() => {
|
|
684
|
+
focused.value = null
|
|
685
|
+
emit("pane-click")
|
|
686
|
+
})
|
|
392
687
|
onConnect((conn) => emit("connect", conn))
|
|
393
688
|
|
|
394
689
|
// The legend and the minimap float over the canvas, so a plain fit puts the
|
|
395
690
|
// outermost nodes under them. Fitting reserves their corner instead: the side
|
|
396
691
|
// they sit on gets their height on top of the usual margin. On a phone both
|
|
397
|
-
// are hidden and only the margin is left.
|
|
692
|
+
// are hidden and only the margin is left. What is reserved is what is on
|
|
693
|
+
// screen, and they go by the canvas's own width: a canvas in a narrow panel of
|
|
694
|
+
// a wide window is a phone-sized canvas too.
|
|
398
695
|
const legendBox = ref<HTMLElement | null>(null)
|
|
399
696
|
const MINIMAP_HEIGHT = 165
|
|
400
697
|
const FRAME = 24
|
|
401
698
|
function framePadding(): Padding {
|
|
402
|
-
const
|
|
403
|
-
const legendHeight =
|
|
699
|
+
const legendShown = legendBox.value?.offsetHeight ?? 0
|
|
700
|
+
const legendHeight = legendShown ? legendShown + 15 : 0
|
|
701
|
+
const minimap = vueFlowRef.value?.querySelector<HTMLElement>(".vue-flow__minimap")
|
|
404
702
|
const legendTop = props.legendPosition.startsWith("top")
|
|
405
|
-
const bottom = Math.max(legendTop ? 0 : legendHeight,
|
|
703
|
+
const bottom = Math.max(legendTop ? 0 : legendHeight, minimap?.offsetHeight ? MINIMAP_HEIGHT : 0)
|
|
406
704
|
return {
|
|
407
705
|
top: `${FRAME + (legendTop ? legendHeight : 0)}px`,
|
|
408
706
|
bottom: `${FRAME + bottom}px`,
|
|
@@ -413,6 +711,15 @@ function framePadding(): Padding {
|
|
|
413
711
|
function fitFramed(params: Parameters<typeof fitView>[0] = {}) {
|
|
414
712
|
return fitView({ padding: framePadding(), ...params })
|
|
415
713
|
}
|
|
714
|
+
// Read when the button is pressed: the panels do not exist yet at first render.
|
|
715
|
+
const controlsFit = {
|
|
716
|
+
get padding() {
|
|
717
|
+
return framePadding()
|
|
718
|
+
},
|
|
719
|
+
}
|
|
720
|
+
// The canvas fits itself once, on the first nodes: after that the view is the
|
|
721
|
+
// user's. A host whose data arrives in stages, or whose box changed size, calls
|
|
722
|
+
// the exposed `fitView` when it wants the whole graph back in frame.
|
|
416
723
|
let fitted = false
|
|
417
724
|
onNodesInitialized(() => {
|
|
418
725
|
if (fitted) return
|
|
@@ -426,7 +733,178 @@ async function applyViewport(v: CanvasViewport): Promise<void> {
|
|
|
426
733
|
await setViewport({ x: v.x, y: v.y, zoom: v.zoom })
|
|
427
734
|
}
|
|
428
735
|
|
|
429
|
-
|
|
736
|
+
function refit() {
|
|
737
|
+
void nextTick(() => {
|
|
738
|
+
const root = vueFlowRef.value
|
|
739
|
+
if (root) nodeWindow(root).requestAnimationFrame(() => void fitFramed())
|
|
740
|
+
})
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// A new dataset is the one time the canvas takes the view back. The flag also
|
|
744
|
+
// covers data that lands a moment later and has to be measured first.
|
|
745
|
+
watch(() => props.dataRevision, () => {
|
|
746
|
+
fitted = false
|
|
747
|
+
refit()
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
let resizeFrame = 0
|
|
751
|
+
useResizeObserver(vueFlowRef, (_, el) => {
|
|
752
|
+
if (props.fitOnResize !== "always" || !fitted || resizeFrame) return
|
|
753
|
+
resizeFrame = nodeWindow(el).requestAnimationFrame(() => {
|
|
754
|
+
resizeFrame = 0
|
|
755
|
+
void fitFramed()
|
|
756
|
+
})
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
const FOCUS_MAX_ZOOM = 1.2
|
|
760
|
+
const FOCUS_DURATION_MS = 300
|
|
761
|
+
|
|
762
|
+
function nodeElement(id: string): HTMLElement | null {
|
|
763
|
+
const root = vueFlowRef.value
|
|
764
|
+
if (!root) return null
|
|
765
|
+
return root.querySelector<HTMLElement>(`.vue-flow__node[data-id="${nodeWindow(root).CSS.escape(id)}"]`)
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** Bring a node into view and pick it. False when it is not on the canvas. */
|
|
769
|
+
async function focusNode(id: string, options: { keepFocus?: boolean } = {}): Promise<boolean> {
|
|
770
|
+
if (!findNode(id)) return false
|
|
771
|
+
focused.value = id
|
|
772
|
+
emit("node-select", id)
|
|
773
|
+
await fitView({
|
|
774
|
+
nodes: [id],
|
|
775
|
+
maxZoom: FOCUS_MAX_ZOOM,
|
|
776
|
+
padding: framePadding(),
|
|
777
|
+
duration: prefersReducedMotion() ? 0 : FOCUS_DURATION_MS,
|
|
778
|
+
})
|
|
779
|
+
if (!options.keepFocus) nodeElement(id)?.focus()
|
|
780
|
+
return true
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
let matchCursor = 0
|
|
784
|
+
watch(query, () => (matchCursor = 0))
|
|
785
|
+
function nextMatch() {
|
|
786
|
+
const hits = matches.value
|
|
787
|
+
if (!hits.length) return
|
|
788
|
+
void focusNode(hits[matchCursor++ % hits.length]!.id, { keepFocus: true })
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const selectedIds = computed(() => getSelectedNodes.value.map((n) => n.id))
|
|
792
|
+
|
|
793
|
+
const ARROWS: Record<string, GraphDirection> = {
|
|
794
|
+
ArrowUp: "up",
|
|
795
|
+
ArrowDown: "down",
|
|
796
|
+
ArrowLeft: "left",
|
|
797
|
+
ArrowRight: "right",
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// In the capture phase: Vue Flow's own arrow keys nudge the focused node.
|
|
801
|
+
function onKeydown(event: KeyboardEvent) {
|
|
802
|
+
if (!props.keyboardNav || !isHTMLElement(event.target)) return
|
|
803
|
+
const id = event.target.closest(".vue-flow__node")?.getAttribute("data-id")
|
|
804
|
+
if (!id) return
|
|
805
|
+
const direction = ARROWS[event.key]
|
|
806
|
+
if (direction) {
|
|
807
|
+
const next = nearestInDirection(absolutePositions(shown.value.nodes), id, direction)
|
|
808
|
+
if (next) void focusNode(next)
|
|
809
|
+
} else if (event.key === "Enter") emit("node-activate", id)
|
|
810
|
+
else if (event.key === " ") {
|
|
811
|
+
focused.value = id
|
|
812
|
+
emit("node-select", id)
|
|
813
|
+
} else return
|
|
814
|
+
event.preventDefault()
|
|
815
|
+
event.stopPropagation()
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/* Wider than a lane's padding and label together, so group boxes stay apart. */
|
|
819
|
+
const LANE_BAND_GAP = 104
|
|
820
|
+
const OFFSCREEN = "position:absolute;left:-99999px;top:0;visibility:hidden"
|
|
821
|
+
const EXPORT_NODE_WIDTH = 220
|
|
822
|
+
const EXPORT_NODE_HEIGHT = 96
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Lay the graph out in layers, group by group under `groupBy`. The canvas moves
|
|
826
|
+
* at once; `update:nodes` carries the new positions for a host to keep.
|
|
827
|
+
*/
|
|
828
|
+
function autoLayout(options: TopologyLayoutOptions = {}) {
|
|
829
|
+
const positions = layeredLayout(placed.value.map(sized), current.value.edges, {
|
|
830
|
+
groupGap: LANE_BAND_GAP,
|
|
831
|
+
...options,
|
|
832
|
+
groupOf: props.groupBy ? (n) => groupOf(n as TopologyNode) : undefined,
|
|
833
|
+
})
|
|
834
|
+
moved.value = new Map([...moved.value, ...positions])
|
|
835
|
+
emit("update:nodes", current.value.nodes.map((n) => {
|
|
836
|
+
const position = positions.get(n.id)
|
|
837
|
+
return position ? { ...n, position } : n
|
|
838
|
+
}))
|
|
839
|
+
refit()
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/** The graph as a clean SVG: boxes, wires and names, not a capture of the cards. */
|
|
843
|
+
function exportSvg(): string | null {
|
|
844
|
+
const root = vueFlowRef.value
|
|
845
|
+
if (!root) return null
|
|
846
|
+
const places = absolutePositions(shown.value.nodes)
|
|
847
|
+
const color = (token: string) => resolveCssColor(`var(${token})`, root)
|
|
848
|
+
return topologyToSvg(
|
|
849
|
+
shown.value.nodes.map((n) => {
|
|
850
|
+
const size = sized(n)
|
|
851
|
+
return {
|
|
852
|
+
...places.get(n.id)!,
|
|
853
|
+
id: n.id,
|
|
854
|
+
label: nodeName(n),
|
|
855
|
+
frame: n.type === "group",
|
|
856
|
+
width: size.width ?? EXPORT_NODE_WIDTH,
|
|
857
|
+
height: size.height ?? EXPORT_NODE_HEIGHT,
|
|
858
|
+
}
|
|
859
|
+
}),
|
|
860
|
+
shown.value.edges.map((e) => {
|
|
861
|
+
const obs = e.observed ?? "applied"
|
|
862
|
+
const cat = edgeCategory(e)
|
|
863
|
+
return { ...e, label: edgeLabelText(e), dash: edgeDash(cat, obs), color: resolveCssColor(edgeColor(cat, obs), root) }
|
|
864
|
+
}),
|
|
865
|
+
{
|
|
866
|
+
background: backgroundOf(root) ?? color("--color-surface"),
|
|
867
|
+
surface: color("--color-surface-raised"),
|
|
868
|
+
line: color("--color-line-strong"),
|
|
869
|
+
text: color("--color-ink"),
|
|
870
|
+
edge: color("--color-brand"),
|
|
871
|
+
},
|
|
872
|
+
)
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/** The same drawing as a PNG. */
|
|
876
|
+
async function exportPng(): Promise<Blob | null> {
|
|
877
|
+
const svg = exportSvg()
|
|
878
|
+
const root = vueFlowRef.value
|
|
879
|
+
if (!svg || !root) return null
|
|
880
|
+
const parsed = new (nodeWindow(root).DOMParser)().parseFromString(svg, "image/svg+xml")
|
|
881
|
+
const drawing = root.ownerDocument.importNode(parsed.documentElement, true) as unknown as SVGSVGElement
|
|
882
|
+
// In the page for as long as it takes: the rasteriser measures what it draws.
|
|
883
|
+
drawing.style.cssText = OFFSCREEN
|
|
884
|
+
root.append(drawing)
|
|
885
|
+
try {
|
|
886
|
+
return await svgToPng(drawing)
|
|
887
|
+
} finally {
|
|
888
|
+
drawing.remove()
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/** Replace the marquee selection, for a host driving it from a list. */
|
|
893
|
+
function selectNodes(ids: string[]) {
|
|
894
|
+
removeSelectedNodes(getSelectedNodes.value)
|
|
895
|
+
addSelectedNodes(getNodes.value.filter((n) => ids.includes(n.id)))
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
defineExpose({
|
|
899
|
+
fitView: fitFramed,
|
|
900
|
+
setViewport: applyViewport,
|
|
901
|
+
focusNode,
|
|
902
|
+
searchNodes: (query: string) => findNodes(shown.value.nodes, query).map((n) => n.id),
|
|
903
|
+
autoLayout,
|
|
904
|
+
exportSvg,
|
|
905
|
+
exportPng,
|
|
906
|
+
selectNodes,
|
|
907
|
+
})
|
|
430
908
|
</script>
|
|
431
909
|
|
|
432
910
|
<template>
|
|
@@ -441,10 +919,11 @@ defineExpose({ fitView: fitFramed, setViewport: applyViewport })
|
|
|
441
919
|
:selection-key-code="selectable ? true : null"
|
|
442
920
|
multi-selection-key-code="Shift"
|
|
443
921
|
:selection-mode="SelectionMode.Partial"
|
|
922
|
+
@keydown.capture="onKeydown"
|
|
444
923
|
:default-viewport="{ zoom: 0.9 }"
|
|
445
924
|
:min-zoom="0.3"
|
|
446
925
|
:max-zoom="2"
|
|
447
|
-
class="lp-topology"
|
|
926
|
+
class="lp-topology @container"
|
|
448
927
|
>
|
|
449
928
|
<Background :gap="22" :size="1.4" pattern-color="var(--color-line-strong)" />
|
|
450
929
|
<!-- On a phone the minimap, the zoom controls and the legend would all
|
|
@@ -452,18 +931,66 @@ defineExpose({ fitView: fitFramed, setViewport: applyViewport })
|
|
|
452
931
|
other and the graph; pinch-zoom and panning do the minimap's job there. -->
|
|
453
932
|
<MiniMap
|
|
454
933
|
v-if="minimap"
|
|
455
|
-
class="max-
|
|
934
|
+
class="@max-[40rem]:!hidden"
|
|
456
935
|
pannable
|
|
457
936
|
zoomable
|
|
458
937
|
node-color="var(--color-surface-soft)"
|
|
459
938
|
mask-color="rgba(0,0,0,0.55)"
|
|
460
939
|
/>
|
|
461
|
-
<Controls v-if="controls" :fit-view-params="
|
|
940
|
+
<Controls v-if="controls" :fit-view-params="controlsFit" />
|
|
941
|
+
|
|
942
|
+
<Panel v-if="search" position="top-left" class="nodrag nopan w-56">
|
|
943
|
+
<LpInput
|
|
944
|
+
v-model="query"
|
|
945
|
+
type="search"
|
|
946
|
+
:aria-label="text.search"
|
|
947
|
+
:placeholder="text.search"
|
|
948
|
+
@keydown.enter.prevent="nextMatch"
|
|
949
|
+
/>
|
|
950
|
+
<p v-if="query.trim()" class="mt-1 text-xs text-muted" role="status">
|
|
951
|
+
{{ text.searchResults.replace("{n}", String(matches.length)) }}
|
|
952
|
+
</p>
|
|
953
|
+
</Panel>
|
|
954
|
+
|
|
955
|
+
<Panel
|
|
956
|
+
v-if="selectionActions?.length && selectedIds.length > 1"
|
|
957
|
+
position="top-right"
|
|
958
|
+
class="nodrag nopan flex items-center gap-2 rounded-card border border-line bg-surface-overlay/90 px-3 py-2 text-xs text-muted backdrop-blur"
|
|
959
|
+
>
|
|
960
|
+
<span>{{ text.selected.replace("{n}", String(selectedIds.length)) }}</span>
|
|
961
|
+
<LpButton
|
|
962
|
+
v-for="action in selectionActions"
|
|
963
|
+
:key="action.id"
|
|
964
|
+
size="sm"
|
|
965
|
+
variant="outline"
|
|
966
|
+
@click="emit('selection-action', { action: action.id, ids: selectedIds })"
|
|
967
|
+
>
|
|
968
|
+
<!-- LpButton has no icon prop: an :icon here fell through and drew nothing. -->
|
|
969
|
+
<LpIcon v-if="action.icon" :name="action.icon" :size="14" />
|
|
970
|
+
{{ action.label }}
|
|
971
|
+
</LpButton>
|
|
972
|
+
</Panel>
|
|
973
|
+
|
|
974
|
+
<Panel
|
|
975
|
+
v-if="(timeline?.length ?? 0) > 1"
|
|
976
|
+
position="bottom-center"
|
|
977
|
+
class="nodrag nopan flex w-72 max-w-[60%] flex-col gap-1 rounded-card border border-line bg-surface-overlay/90 px-3 py-2 text-xs text-muted backdrop-blur"
|
|
978
|
+
>
|
|
979
|
+
<span>{{ timeline?.[versionIndex]?.label ?? `${text.timeline} ${versionIndex + 1}` }}</span>
|
|
980
|
+
<LpSlider
|
|
981
|
+
:model-value="versionIndex"
|
|
982
|
+
:min="0"
|
|
983
|
+
:max="(timeline?.length ?? 1) - 1"
|
|
984
|
+
:step="1"
|
|
985
|
+
:aria-label="text.timeline"
|
|
986
|
+
@update:model-value="timelineIndex = $event"
|
|
987
|
+
/>
|
|
988
|
+
</Panel>
|
|
462
989
|
|
|
463
990
|
<!-- Edge-type legend: makes the colour/dash encoding self-explanatory. -->
|
|
464
991
|
<!-- On a phone the legend would sit on the graph itself; the edge styles
|
|
465
992
|
stay readable without it there. -->
|
|
466
|
-
<Panel v-if="legend" :position="legendPosition" :class="legendOffset" class="max-
|
|
993
|
+
<Panel v-if="legend" :position="legendPosition" :class="legendOffset" class="@max-[40rem]:hidden">
|
|
467
994
|
<div
|
|
468
995
|
ref="legendBox"
|
|
469
996
|
class="flex flex-col gap-1.5 rounded-card border border-line bg-surface-overlay/90 px-3 py-2 text-[11px] backdrop-blur"
|