@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.
Files changed (621) hide show
  1. package/README.md +68 -0
  2. package/dist/assets/{LpCommandPalette-DOIZHw-g.css → LpCommandPalette-DDJKICKh.css} +19 -19
  3. package/dist/assets/{LpDock-Cbh0wQUv.css → LpDock-Dw82tWVD.css} +3 -3
  4. package/dist/assets/{LpDropzone-B_qRq1-R.css → LpDropzone-DNbh_NFJ.css} +10 -10
  5. package/dist/assets/{LpImageEditor-XM5bAziW.css → LpImageEditor-Bgu20XLs.css} +6 -6
  6. package/dist/assets/{LpTable-Bv-EcQs7.css → LpTable-CCk0ZHo7.css} +19 -19
  7. package/dist/assets/LpTabs-Bh7a-Bdl.css +18 -0
  8. package/dist/chunks/AppShellContextNav-C6Ho2mof.js +49 -0
  9. package/dist/chunks/ChartRangeSelector-HO0sMMz6.js +129 -0
  10. package/dist/chunks/{ContextMenuItems-BXumicbh.js → ContextMenuItems-DmaKSJVv.js} +1 -1
  11. package/dist/chunks/{DropzoneLinkField-C-9BCGJn.js → DropzoneLinkField-CLLhd3WB.js} +2 -2
  12. package/dist/chunks/FieldLeading-mr81aEAP.js +27 -0
  13. package/dist/chunks/{FieldWrapper-CORPzo9s.js → FieldWrapper-DnuMTrqW.js} +1 -1
  14. package/dist/chunks/IconCloseButton-COZEsa9u.js +29 -0
  15. package/dist/chunks/{LayoutCanvas-CNOAUk7D.js → LayoutCanvas-CZKOltfA.js} +1 -1
  16. package/dist/chunks/{LayoutNode-DF5BMN0J.js → LayoutNode-D2f7v683.js} +45 -45
  17. package/dist/chunks/{LpActionBar-BP1ld7-S.js → LpActionBar-BGMk5KoG.js} +3 -3
  18. package/dist/chunks/{LpActivityLine-CJvzrNpj.js → LpActivityLine-Ea-t5Vza.js} +4 -2
  19. package/dist/chunks/{LpAddressInput-E8KtwU7-.js → LpAddressInput-BSS49jpm.js} +12 -11
  20. package/dist/chunks/{LpAlert-n5XPAZfv.js → LpAlert-CjvTZIKF.js} +3 -3
  21. package/dist/chunks/LpAppShell-BKbYCHkx.js +601 -0
  22. package/dist/chunks/{LpAutocomplete-C-SpPfBG.js → LpAutocomplete-DkY6CSyD.js} +24 -42
  23. package/dist/chunks/{LpAvatar-Cg6Az3wz.js → LpAvatar-cQVsNk6F.js} +2 -2
  24. package/dist/chunks/{LpBadge-wUYLN4U9.js → LpBadge-D5QQdDQ_.js} +2 -2
  25. package/dist/chunks/{LpBottomNav-CbNQQPuL.js → LpBottomNav-zxLGGbjm.js} +9 -2
  26. package/dist/chunks/{LpBreadcrumbs-DPdbth-c.js → LpBreadcrumbs-CIIBsygI.js} +4 -3
  27. package/dist/chunks/{LpButton-CFb44IjC.js → LpButton-DSHioIfD.js} +5 -4
  28. package/dist/chunks/{LpCalendar-CuoWks-0.js → LpCalendar-D4-LVjfF.js} +1 -1
  29. package/dist/chunks/{LpCard-r13WRqox.js → LpCard-BPtYXwnT.js} +1 -1
  30. package/dist/chunks/{LpCheckbox-Cs4SHaHX.js → LpCheckbox-D6RuDW4X.js} +38 -16
  31. package/dist/chunks/{LpCodeBlock-DRC8VbXW.js → LpCodeBlock-DBqy4RLd.js} +19 -41
  32. package/dist/chunks/{LpCodeDisplay-CE9TxCA4.js → LpCodeDisplay-D3BUAhV1.js} +5 -5
  33. package/dist/chunks/{LpCollapse-CWS9M_lm.js → LpCollapse-DrAw5DBZ.js} +5 -4
  34. package/dist/chunks/{LpColorPicker-1y1QJHZR.js → LpColorPicker-COHzV68R.js} +11 -12
  35. package/dist/chunks/{LpCommandScope-CtUhJe8T.js → LpCommandScope-DRUvHiX7.js} +1 -1
  36. package/dist/chunks/{LpConfirmDialog-DlD45Bi_.js → LpConfirmDialog-CnrmzkuN.js} +3 -3
  37. package/dist/chunks/{LpConfirmHost-BIWjBLmI.js → LpConfirmHost-CwbObVGH.js} +1 -1
  38. package/dist/chunks/{LpContextMenu-rx2tCjwk.js → LpContextMenu-Bx_IhQDS.js} +17 -19
  39. package/dist/chunks/{LpContextSwitcher-Dwh6dyID.js → LpContextSwitcher-1C1tExdl.js} +2 -2
  40. package/dist/chunks/{LpCopyButton-xF7wFVoU.js → LpCopyButton-sAMvWeLP.js} +3 -3
  41. package/dist/chunks/{LpCountdown-DgeY3cLG.js → LpCountdown-DVl7vXI1.js} +4 -3
  42. package/dist/chunks/{LpDataList-Jxyl9H5w.js → LpDataList-CaVfsozH.js} +13 -4
  43. package/dist/chunks/{LpDatePicker-CA0QTfBC.js → LpDatePicker-DwSDY0mt.js} +12 -18
  44. package/dist/chunks/{LpDateRangePicker-u3J51xXk.js → LpDateRangePicker-CNQXdTVv.js} +10 -16
  45. package/dist/chunks/{LpDisclosure-Cpa7Hc5T.js → LpDisclosure-DG7JCo-d.js} +2 -2
  46. package/dist/chunks/{LpDrawer-CrG8t_KP.js → LpDrawer-DXNQFpAA.js} +61 -61
  47. package/dist/chunks/{LpDropdownMenu-Dl7Z4ZhS.js → LpDropdownMenu-CLzxIOgM.js} +9 -17
  48. package/dist/chunks/{LpEditable-Cy6GLbFx.js → LpEditable-DVT0Rh8F.js} +9 -6
  49. package/dist/chunks/{LpEmptyState-EQHiRYhG.js → LpEmptyState-DurPF001.js} +1 -1
  50. package/dist/chunks/{LpFieldNotice-CzQ2_5rQ.js → LpFieldNotice-DJ10cYIo.js} +1 -1
  51. package/dist/chunks/LpFileTree-C-rJKzEQ.js +735 -0
  52. package/dist/chunks/{LpFileTreeNode-cT6BIWvz.js → LpFileTreeNode-BgYn4U1J.js} +116 -34
  53. package/dist/chunks/{LpFormField-Ch69Jd-X.js → LpFormField-BKYn9dyP.js} +3 -3
  54. package/dist/chunks/{LpFrame-BVQCQLDI.js → LpFrame-Bv5Fphte.js} +25 -11
  55. package/dist/chunks/{LpHintExample-9Py0wc-L.js → LpHintExample-YzCXiSUp.js} +1 -1
  56. package/dist/chunks/{LpHintLayer-CgObdUsM.js → LpHintLayer-BeKRsRon.js} +2 -2
  57. package/dist/chunks/{LpHints-Bz-BlaSl.js → LpHints-qf3xYsza.js} +3 -3
  58. package/dist/chunks/{LpHoverCard-spGxyw7T.js → LpHoverCard-CHE9jAJG.js} +29 -11
  59. package/dist/chunks/{LpIcon--5WSb3jJ.js → LpIcon-C6JpQ5td.js} +6 -3
  60. package/dist/chunks/{LpIdenticon-BzdC_qz9.js → LpIdenticon-Dbbr4fvt.js} +2 -1
  61. package/dist/chunks/{LpInfraNode-46XOCkfq.js → LpInfraNode-Bb5nbuFY.js} +19 -2
  62. package/dist/chunks/{LpInput-Tw-yxT4I.js → LpInput-D0O6dpuA.js} +32 -47
  63. package/dist/chunks/LpInspector-DO3fTCQ1.js +209 -0
  64. package/dist/chunks/{LpLaneNode-HtnQf2FF.js → LpLaneNode-Bqcdm8Rf.js} +19 -1
  65. package/dist/chunks/{LpLightbox-B95yu22B.js → LpLightbox-CtDTQV01.js} +57 -32
  66. package/dist/chunks/{LpLoader-DVwkOmt9.js → LpLoader-Bf1yZwaU.js} +1 -1
  67. package/dist/chunks/LpLogInspector-BdQlX6S-.js +119 -0
  68. package/dist/chunks/{LpLogViewer-ngqkns4m.js → LpLogViewer-umCZBapN.js} +234 -53
  69. package/dist/chunks/{LpMapPicker-DRGgh89B.js → LpMapPicker-D5_YLLBC.js} +4 -3
  70. package/dist/chunks/{LpMasonry--k0571qG.js → LpMasonry-DPYekFxs.js} +8 -5
  71. package/dist/chunks/{LpMediaGallery-Bkram-MO.js → LpMediaGallery-BYEwySoF.js} +3 -3
  72. package/dist/chunks/{LpModal-CoGhLTyH.js → LpModal-DDvrSAnf.js} +11 -13
  73. package/dist/chunks/{LpModalNav-BGvs2y5Q.js → LpModalNav-gOi1Giz1.js} +11 -37
  74. package/dist/chunks/{LpNotificationBell-B0034Lk-.js → LpNotificationBell-DUbVyK6R.js} +9 -9
  75. package/dist/chunks/{LpNumberField-e0IBkWU1.js → LpNumberField-Csg-1Zh_.js} +25 -20
  76. package/dist/chunks/{LpOtpInput-CeBPsM7D.js → LpOtpInput-BRpLBbOD.js} +2 -2
  77. package/dist/chunks/LpPageScrollbar-D2O0xhJw.js +164 -0
  78. package/dist/chunks/{LpPagination-BIIiYHXR.js → LpPagination-snQ020xQ.js} +6 -4
  79. package/dist/chunks/{LpPaletteTheme-DiLYKQiZ.js → LpPaletteTheme-BIXpZLGe.js} +4 -4
  80. package/dist/chunks/{LpPasswordInput-BkHKQDhM.js → LpPasswordInput-D4HPhqfV.js} +2 -2
  81. package/dist/chunks/{LpPhoneInput-CETEp5W7.js → LpPhoneInput-BSL7NX4z.js} +15 -26
  82. package/dist/chunks/{LpPie-DrZepi0p.js → LpPie-oVNHjsKD.js} +22 -21
  83. package/dist/chunks/{LpPipeline-B3aOVOkV.js → LpPipeline-BSXT6oUJ.js} +16 -7
  84. package/dist/chunks/{LpPopover-DmZ3wUtP.js → LpPopover-BQSuxtai.js} +10 -1
  85. package/dist/chunks/{LpProgress-Yr6ONG5t.js → LpProgress-D_MSjowb.js} +2 -1
  86. package/dist/chunks/{LpQuota-Dk2MyI2Z.js → LpQuota-CgWPgdVt.js} +1 -1
  87. package/dist/chunks/{LpRadioGroup-ggaPL92T.js → LpRadioGroup-BrazjLY_.js} +4 -4
  88. package/dist/chunks/{LpRangeCalendar-CYYUYqJ7.js → LpRangeCalendar-BrqlGZzE.js} +1 -1
  89. package/dist/chunks/{LpRangeSlider-CM-0ZITZ.js → LpRangeSlider-fz61tzc-.js} +5 -9
  90. package/dist/chunks/{LpRelativeTime-DwgCb5-8.js → LpRelativeTime-0zI-44ve.js} +1 -1
  91. package/dist/chunks/{LpRepeater-D7ZmnIYd.js → LpRepeater-BkJHSPI1.js} +3 -3
  92. package/dist/chunks/{LpRevealOnScroll-DcQjexLy.js → LpRevealOnScroll-CPwyiQAr.js} +1 -1
  93. package/dist/chunks/{LpScramble-Bn9OoOtC.js → LpScramble-DsE77iVp.js} +1 -1
  94. package/dist/chunks/{LpScrollArea-MyEnVB_5.js → LpScrollArea-CqvwfZs2.js} +31 -22
  95. package/dist/chunks/{LpSecretField-BbmqM9ye.js → LpSecretField-B2roc4Iz.js} +10 -9
  96. package/dist/chunks/{LpSection-DFK_uoJE.js → LpSection-BfteipxU.js} +1 -1
  97. package/dist/chunks/{LpSegmented-CeACsMMA.js → LpSegmented-DbEJhbv-.js} +8 -10
  98. package/dist/chunks/{LpSelect-Cxq4yBGd.js → LpSelect-Y-iuSymW.js} +47 -30
  99. package/dist/chunks/{LpServiceNode-D8uJ8sGk.js → LpServiceNode-Cln2ntdw.js} +17 -0
  100. package/dist/chunks/{LpShaderBackdrop-Bote0iuy.js → LpShaderBackdrop-wQygYb5h.js} +13 -10
  101. package/dist/chunks/{LpShift-CCq7dofl.js → LpShift-DAgS_d2x.js} +18 -15
  102. package/dist/chunks/{LpSidebar-DzE1783C.js → LpSidebar-DDNJzb69.js} +74 -87
  103. package/dist/chunks/{LpSidebarNav-DE3PhXVP.js → LpSidebarNav-CE0jQS8i.js} +8 -6
  104. package/dist/chunks/{LpSidebarRow-BRYhNDSo.js → LpSidebarRow-DOTg1txp.js} +6 -5
  105. package/dist/chunks/{LpSlider-DxYoCJJO.js → LpSlider-A4ixovgS.js} +5 -9
  106. package/dist/chunks/{LpSpotlight-4F3NeUMY.js → LpSpotlight-DwxkhFgR.js} +2 -1
  107. package/dist/chunks/{LpStat-f2LfDCJ4.js → LpStat-DPjc6_fX.js} +19 -6
  108. package/dist/chunks/{LpStatus-DglxttQ7.js → LpStatus-DGJFwk5F.js} +2 -2
  109. package/dist/chunks/{LpStepMark-DpzKVyh8.js → LpStepMark-Dw2MRTrt.js} +2 -2
  110. package/dist/chunks/{LpStepper-DpsocBNJ.js → LpStepper-DNnh7lCu.js} +1 -1
  111. package/dist/chunks/{LpSwap-C96c0LWh.js → LpSwap-M7nFeW3C.js} +1 -1
  112. package/dist/chunks/{LpSwitch-BnXd1llc.js → LpSwitch-DnC2-0aV.js} +3 -3
  113. package/dist/chunks/{LpTabFace-DPCwKkH2.js → LpTabFace-DQoV8mQG.js} +2 -2
  114. package/dist/chunks/{LpTableFilter-CiVBpaq0.js → LpTableFilter-B_XMOeAh.js} +4 -4
  115. package/dist/chunks/{LpTableOfContents-BN5zvpnx.js → LpTableOfContents-CKkWEK6R.js} +14 -9
  116. package/dist/chunks/{LpTagsInput-C-mDG648.js → LpTagsInput-Dt0mteSj.js} +19 -27
  117. package/dist/chunks/{LpTextarea-TE-yZv2U.js → LpTextarea-Co0b9PVx.js} +8 -16
  118. package/dist/chunks/LpThemeProvider-XK6ZeWAE.js +81 -0
  119. package/dist/chunks/{LpThemeStudio-BpORDvH6.js → LpThemeStudio-ChU0_Ga8.js} +12 -195
  120. package/dist/chunks/{LpThemeSwitcher-BnEn_mvf.js → LpThemeSwitcher-DmCZ21P_.js} +8 -6
  121. package/dist/chunks/{LpTimeField-BawSVsmt.js → LpTimeField-DeOcKWJq.js} +6 -9
  122. package/dist/chunks/{LpToaster-IwTXjbbU.js → LpToaster-Ct_ZOhCh.js} +10 -8
  123. package/dist/chunks/{LpTooltip-BcBvIx4z.js → LpTooltip-Bz_rrq5S.js} +56 -24
  124. package/dist/chunks/LpTopologyCanvas-jqz1KCq2.js +678 -0
  125. package/dist/chunks/{LpTour-BZq-mUMv.js → LpTour-ClKYhx4h.js} +7 -5
  126. package/dist/chunks/{LpTruncate-CflAIwiJ.js → LpTruncate-ellq9RYM.js} +6 -5
  127. package/dist/chunks/{LpTypewriter-BCL_a6wE.js → LpTypewriter-CZIzpdbe.js} +2 -1
  128. package/dist/chunks/{LpUptimeBar-DqNvvMBK.js → LpUptimeBar-BoxcCDVY.js} +3 -3
  129. package/dist/chunks/{LpVideoBackdrop-DS1wGv-X.js → LpVideoBackdrop-Ct4St4ZL.js} +10 -13
  130. package/dist/chunks/LpWindow-B8YSRVTH.js +297 -0
  131. package/dist/chunks/LpWorkspaceSwitcher-BeNHCLd5.js +139 -0
  132. package/dist/chunks/{MenuTitle-BmCNNTKH.js → MenuTitle-Bh5dAdV1.js} +1 -1
  133. package/dist/chunks/{OptionContent-22UT8zLn.js → OptionContent-CGb8QlHA.js} +1 -1
  134. package/dist/chunks/{PickerField-Bfh8id3d.js → PickerField-BM1dyafh.js} +4 -4
  135. package/dist/chunks/{RevealToggle-BEit1n6Z.js → RevealToggle-D_iyrB8d.js} +3 -3
  136. package/dist/chunks/SliderRail-DhPREa_R.js +18 -0
  137. package/dist/chunks/{TourCard-DNclH3u_.js → TourCard-DAzTYfav.js} +4 -4
  138. package/dist/chunks/{icons-DMITlfw5.js → icons-CYfcpSDS.js} +26 -10
  139. package/dist/component-names.d.ts +1 -1
  140. package/dist/components/AppShellContextNav.vue.d.ts +13 -0
  141. package/dist/components/AppShellContextNav.vue.js +4 -0
  142. package/dist/components/ChartRangeSelector.vue.d.ts +21 -0
  143. package/dist/components/ChartRangeSelector.vue.js +4 -0
  144. package/dist/components/ContextMenuItems.vue.js +1 -1
  145. package/dist/components/DropzoneLinkField.vue.d.ts +2 -2
  146. package/dist/components/DropzoneLinkField.vue.js +1 -1
  147. package/dist/components/FieldLeading.vue.d.ts +16 -0
  148. package/dist/components/FieldLeading.vue.js +4 -0
  149. package/dist/components/FieldWrapper.vue.js +1 -1
  150. package/dist/components/IconCloseButton.vue.d.ts +7 -0
  151. package/dist/components/IconCloseButton.vue.js +4 -0
  152. package/dist/components/LayoutCanvas.vue.js +1 -1
  153. package/dist/components/LayoutNode.vue.js +1 -1
  154. package/dist/components/LpActionBar.vue.js +1 -1
  155. package/dist/components/LpActivityLine.vue.js +1 -1
  156. package/dist/components/LpAddressInput.vue.js +1 -1
  157. package/dist/components/LpAlert.vue.js +1 -1
  158. package/dist/components/LpAppShell.vue.d.ts +125 -2
  159. package/dist/components/LpAppShell.vue.js +1 -1
  160. package/dist/components/LpAutocomplete.vue.js +1 -1
  161. package/dist/components/LpAvatar.vue.js +1 -1
  162. package/dist/components/LpBadge.vue.js +1 -1
  163. package/dist/components/LpBottomNav.vue.d.ts +5 -0
  164. package/dist/components/LpBottomNav.vue.js +1 -1
  165. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  166. package/dist/components/LpButton.vue.d.ts +14 -2
  167. package/dist/components/LpButton.vue.js +1 -1
  168. package/dist/components/LpCalendar.vue.js +1 -1
  169. package/dist/components/LpCard.vue.js +1 -1
  170. package/dist/components/LpChart.vue.d.ts +74 -2
  171. package/dist/components/LpChart.vue.js +326 -85
  172. package/dist/components/LpCheckbox.vue.d.ts +6 -2
  173. package/dist/components/LpCheckbox.vue.js +1 -1
  174. package/dist/components/LpCodeBlock.vue.js +1 -1
  175. package/dist/components/LpCodeDisplay.vue.js +1 -1
  176. package/dist/components/LpCollapse.vue.js +1 -1
  177. package/dist/components/LpColorPicker.vue.js +1 -1
  178. package/dist/components/LpCommandPalette.vue.d.ts +8 -0
  179. package/dist/components/LpCommandPalette.vue.js +107 -56
  180. package/dist/components/LpCommandScope.vue.js +1 -1
  181. package/dist/components/LpConfirmDialog.vue.js +1 -1
  182. package/dist/components/LpConfirmHost.vue.js +1 -1
  183. package/dist/components/LpContextMenu.vue.js +1 -1
  184. package/dist/components/LpContextSwitcher.vue.js +1 -1
  185. package/dist/components/LpCopyButton.vue.js +1 -1
  186. package/dist/components/LpCountdown.vue.js +1 -1
  187. package/dist/components/LpDataList.vue.d.ts +1 -1
  188. package/dist/components/LpDataList.vue.js +1 -1
  189. package/dist/components/LpDatePicker.vue.d.ts +3 -15
  190. package/dist/components/LpDatePicker.vue.js +1 -1
  191. package/dist/components/LpDateRangePicker.vue.d.ts +3 -13
  192. package/dist/components/LpDateRangePicker.vue.js +1 -1
  193. package/dist/components/LpDisclosure.vue.js +1 -1
  194. package/dist/components/LpDock.vue.js +21 -18
  195. package/dist/components/LpDrawer.vue.d.ts +1 -1
  196. package/dist/components/LpDrawer.vue.js +1 -1
  197. package/dist/components/LpDropdownMenu.vue.js +1 -1
  198. package/dist/components/LpDropzone.vue.js +14 -11
  199. package/dist/components/LpEditable.vue.js +1 -1
  200. package/dist/components/LpEmptyState.vue.js +1 -1
  201. package/dist/components/LpFieldNotice.vue.js +1 -1
  202. package/dist/components/LpFileTree.vue.d.ts +96 -6
  203. package/dist/components/LpFileTree.vue.js +1 -1
  204. package/dist/components/LpFileTreeNode.vue.d.ts +48 -0
  205. package/dist/components/LpFileTreeNode.vue.js +1 -1
  206. package/dist/components/LpFormField.vue.js +1 -1
  207. package/dist/components/LpFrame.vue.js +1 -1
  208. package/dist/components/LpGauge.vue.d.ts +1 -1
  209. package/dist/components/LpHintExample.vue.js +1 -1
  210. package/dist/components/LpHintLayer.vue.js +1 -1
  211. package/dist/components/LpHints.vue.js +1 -1
  212. package/dist/components/LpHoverCard.vue.js +1 -1
  213. package/dist/components/LpIcon.vue.d.ts +2 -0
  214. package/dist/components/LpIcon.vue.js +1 -1
  215. package/dist/components/LpIdenticon.vue.js +1 -1
  216. package/dist/components/LpImageEditor.vue.js +7 -6
  217. package/dist/components/LpInfraNode.vue.d.ts +11 -2
  218. package/dist/components/LpInfraNode.vue.js +1 -1
  219. package/dist/components/LpInput.vue.d.ts +1 -1
  220. package/dist/components/LpInput.vue.js +1 -1
  221. package/dist/components/LpInspector.vue.d.ts +87 -0
  222. package/dist/components/LpInspector.vue.js +4 -0
  223. package/dist/components/LpLaneNode.vue.d.ts +2 -3
  224. package/dist/components/LpLaneNode.vue.js +1 -1
  225. package/dist/components/LpLightbox.vue.d.ts +8 -0
  226. package/dist/components/LpLightbox.vue.js +1 -1
  227. package/dist/components/LpLoader.vue.d.ts +1 -1
  228. package/dist/components/LpLoader.vue.js +1 -1
  229. package/dist/components/LpLogInspector.vue.d.ts +18 -0
  230. package/dist/components/LpLogInspector.vue.js +4 -0
  231. package/dist/components/LpLogViewer.vue.d.ts +93 -0
  232. package/dist/components/LpLogViewer.vue.js +1 -1
  233. package/dist/components/LpMapPicker.vue.js +1 -1
  234. package/dist/components/LpMasonry.vue.js +1 -1
  235. package/dist/components/LpMediaGallery.vue.js +1 -1
  236. package/dist/components/LpModal.vue.d.ts +4 -2
  237. package/dist/components/LpModal.vue.js +1 -1
  238. package/dist/components/LpModalNav.vue.d.ts +5 -5
  239. package/dist/components/LpModalNav.vue.js +1 -1
  240. package/dist/components/LpNotificationBell.vue.d.ts +2 -2
  241. package/dist/components/LpNotificationBell.vue.js +1 -1
  242. package/dist/components/LpNumberField.vue.d.ts +13 -2
  243. package/dist/components/LpNumberField.vue.js +1 -1
  244. package/dist/components/LpOtpInput.vue.js +1 -1
  245. package/dist/components/LpPageScrollbar.vue.d.ts +10 -0
  246. package/dist/components/LpPageScrollbar.vue.js +4 -0
  247. package/dist/components/LpPagination.vue.js +1 -1
  248. package/dist/components/LpPaletteFacts.vue.js +1 -1
  249. package/dist/components/LpPaletteHeader.vue.js +2 -2
  250. package/dist/components/LpPaletteTheme.vue.js +1 -1
  251. package/dist/components/LpPasswordInput.vue.js +1 -1
  252. package/dist/components/LpPhoneInput.vue.js +1 -1
  253. package/dist/components/LpPie.vue.d.ts +2 -2
  254. package/dist/components/LpPie.vue.js +1 -1
  255. package/dist/components/LpPipeline.vue.d.ts +4 -0
  256. package/dist/components/LpPipeline.vue.js +1 -1
  257. package/dist/components/LpPopover.vue.d.ts +7 -3
  258. package/dist/components/LpPopover.vue.js +1 -1
  259. package/dist/components/LpProgress.vue.d.ts +1 -1
  260. package/dist/components/LpProgress.vue.js +1 -1
  261. package/dist/components/LpQuota.vue.js +1 -1
  262. package/dist/components/LpRadioGroup.vue.js +1 -1
  263. package/dist/components/LpRangeCalendar.vue.js +1 -1
  264. package/dist/components/LpRangeSlider.vue.js +1 -1
  265. package/dist/components/LpRelativeTime.vue.js +1 -1
  266. package/dist/components/LpRepeater.vue.js +1 -1
  267. package/dist/components/LpRevealOnScroll.vue.js +1 -1
  268. package/dist/components/LpScramble.vue.js +1 -1
  269. package/dist/components/LpScrollArea.vue.js +1 -1
  270. package/dist/components/LpSecretField.vue.js +1 -1
  271. package/dist/components/LpSection.vue.js +1 -1
  272. package/dist/components/LpSegmented.vue.js +1 -1
  273. package/dist/components/LpSelect.vue.js +1 -1
  274. package/dist/components/LpServiceNode.vue.d.ts +8 -2
  275. package/dist/components/LpServiceNode.vue.js +1 -1
  276. package/dist/components/LpShaderBackdrop.vue.js +1 -1
  277. package/dist/components/LpShift.vue.js +1 -1
  278. package/dist/components/LpShortcutsDialog.vue.js +1 -1
  279. package/dist/components/LpSidebar.vue.d.ts +1 -1
  280. package/dist/components/LpSidebar.vue.js +1 -1
  281. package/dist/components/LpSidebarNav.vue.js +1 -1
  282. package/dist/components/LpSidebarRow.vue.js +1 -1
  283. package/dist/components/LpSlider.vue.js +1 -1
  284. package/dist/components/LpSparkline.vue.d.ts +1 -1
  285. package/dist/components/LpSpotlight.vue.js +1 -1
  286. package/dist/components/LpStat.vue.d.ts +16 -0
  287. package/dist/components/LpStat.vue.js +1 -1
  288. package/dist/components/LpStatus.vue.js +1 -1
  289. package/dist/components/LpStepMark.vue.js +1 -1
  290. package/dist/components/LpStepper.vue.js +1 -1
  291. package/dist/components/LpSwap.vue.js +1 -1
  292. package/dist/components/LpSwitch.vue.js +1 -1
  293. package/dist/components/LpTabFace.vue.js +1 -1
  294. package/dist/components/LpTable.vue.d.ts +132 -0
  295. package/dist/components/LpTable.vue.js +547 -135
  296. package/dist/components/LpTableFilter.vue.js +1 -1
  297. package/dist/components/LpTableOfContents.vue.js +1 -1
  298. package/dist/components/LpTabs.vue.js +6 -5
  299. package/dist/components/LpTagsInput.vue.js +1 -1
  300. package/dist/components/LpTextarea.vue.js +1 -1
  301. package/dist/components/LpThemeProvider.vue.d.ts +2 -0
  302. package/dist/components/LpThemeProvider.vue.js +1 -1
  303. package/dist/components/LpThemeStudio.vue.js +1 -1
  304. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  305. package/dist/components/LpTimeField.vue.js +1 -1
  306. package/dist/components/LpToaster.vue.js +1 -1
  307. package/dist/components/LpTooltip.vue.js +1 -1
  308. package/dist/components/LpTopologyCanvas.vue.d.ts +131 -5
  309. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  310. package/dist/components/LpTour.vue.js +1 -1
  311. package/dist/components/LpTruncate.vue.js +1 -1
  312. package/dist/components/LpTypewriter.vue.js +1 -1
  313. package/dist/components/LpUptimeBar.vue.js +1 -1
  314. package/dist/components/LpVideoBackdrop.vue.js +1 -1
  315. package/dist/components/LpWindow.vue.d.ts +99 -0
  316. package/dist/components/LpWindow.vue.js +4 -0
  317. package/dist/components/LpWorkspaceSwitcher.vue.d.ts +18 -0
  318. package/dist/components/LpWorkspaceSwitcher.vue.js +4 -0
  319. package/dist/components/MenuTitle.vue.js +1 -1
  320. package/dist/components/OptionContent.vue.js +1 -1
  321. package/dist/components/PickerField.vue.d.ts +2 -2
  322. package/dist/components/PickerField.vue.js +1 -1
  323. package/dist/components/RevealToggle.vue.js +1 -1
  324. package/dist/components/SliderRail.vue.d.ts +3 -0
  325. package/dist/components/SliderRail.vue.js +4 -0
  326. package/dist/components/TourCard.vue.js +1 -1
  327. package/dist/components/address.js +2 -1
  328. package/dist/components/appShell.d.ts +36 -0
  329. package/dist/components/appShell.js +30 -0
  330. package/dist/components/calendar.d.ts +12 -3
  331. package/dist/components/calendar.js +2 -5
  332. package/dist/components/chart.d.ts +21 -0
  333. package/dist/components/chart.js +4 -3
  334. package/dist/components/chartWindow.d.ts +57 -0
  335. package/dist/components/chartWindow.js +136 -0
  336. package/dist/components/color.js +1 -1
  337. package/dist/components/commandPalette.d.ts +16 -2
  338. package/dist/components/commandPalette.js +39 -4
  339. package/dist/components/controlSize.d.ts +5 -0
  340. package/dist/components/controlSize.js +8 -0
  341. package/dist/components/dropdown.d.ts +5 -0
  342. package/dist/components/dropdown.js +2 -0
  343. package/dist/components/exportData.d.ts +6 -1
  344. package/dist/components/exportData.js +25 -18
  345. package/dist/components/fileTree.d.ts +67 -0
  346. package/dist/components/fileTree.js +141 -1
  347. package/dist/components/identicon.js +3 -2
  348. package/dist/components/logTools.d.ts +93 -0
  349. package/dist/components/logTools.js +210 -0
  350. package/dist/components/menuWhen.js +1 -1
  351. package/dist/components/meter.js +2 -1
  352. package/dist/components/pipeline.d.ts +2 -0
  353. package/dist/components/pipeline.js +5 -0
  354. package/dist/components/relativeTime.js +2 -1
  355. package/dist/components/reveal.js +2 -1
  356. package/dist/components/scrollbar.d.ts +34 -0
  357. package/dist/components/scrollbar.js +47 -0
  358. package/dist/components/select.d.ts +5 -0
  359. package/dist/components/select.js +10 -0
  360. package/dist/components/sidebar.d.ts +1 -0
  361. package/dist/components/tableFeatures.d.ts +58 -0
  362. package/dist/components/tableFeatures.js +87 -0
  363. package/dist/components/topologyGraph.d.ts +164 -0
  364. package/dist/components/topologyGraph.js +416 -0
  365. package/dist/components/tour.js +5 -3
  366. package/dist/components/videoBackdrop.js +7 -6
  367. package/dist/composables/clamp.d.ts +2 -0
  368. package/dist/composables/clamp.js +4 -0
  369. package/dist/composables/devWarn.d.ts +3 -0
  370. package/dist/composables/devWarn.js +14 -0
  371. package/dist/composables/easing.d.ts +13 -4
  372. package/dist/composables/easing.js +16 -10
  373. package/dist/composables/overlayHistory.js +14 -1
  374. package/dist/composables/pointerDrag.d.ts +15 -0
  375. package/dist/composables/pointerDrag.js +40 -0
  376. package/dist/composables/propCheck.d.ts +10 -0
  377. package/dist/composables/propCheck.js +89 -0
  378. package/dist/composables/realm.d.ts +6 -0
  379. package/dist/composables/realm.js +23 -0
  380. package/dist/composables/scroll.js +4 -2
  381. package/dist/composables/scrollMemory.d.ts +12 -1
  382. package/dist/composables/scrollMemory.js +36 -12
  383. package/dist/composables/spatialNav.js +2 -1
  384. package/dist/composables/textEntry.d.ts +17 -0
  385. package/dist/composables/textEntry.js +19 -0
  386. package/dist/composables/useAsyncTask.d.ts +6 -0
  387. package/dist/composables/useChartSync.d.ts +5 -0
  388. package/dist/composables/useChartSync.js +33 -0
  389. package/dist/composables/useCloseGuard.d.ts +3 -1
  390. package/dist/composables/useCloseGuard.js +6 -3
  391. package/dist/composables/useCommands.d.ts +50 -0
  392. package/dist/composables/useCommands.js +66 -0
  393. package/dist/composables/useField.d.ts +37 -0
  394. package/dist/composables/useField.js +27 -2
  395. package/dist/composables/useFitScale.js +4 -1
  396. package/dist/composables/useFormDraft.d.ts +13 -2
  397. package/dist/composables/useFormDraft.js +17 -9
  398. package/dist/composables/useHintSystem.js +2 -2
  399. package/dist/composables/useHotkeys.js +11 -32
  400. package/dist/composables/useInView.js +2 -1
  401. package/dist/composables/useInputFilter.js +7 -5
  402. package/dist/composables/useInputMask.js +5 -3
  403. package/dist/composables/useLinkedView.d.ts +97 -0
  404. package/dist/composables/useLinkedView.js +139 -0
  405. package/dist/composables/useOptimisticToggle.d.ts +3 -1
  406. package/dist/composables/useOptimisticToggle.js +2 -1
  407. package/dist/composables/useOverlayHistory.d.ts +7 -0
  408. package/dist/composables/useOverlayHistory.js +16 -2
  409. package/dist/composables/usePaintSelect.js +18 -14
  410. package/dist/composables/usePaletteCommands.d.ts +7 -0
  411. package/dist/composables/usePaletteCommands.js +16 -3
  412. package/dist/composables/usePersisted.d.ts +10 -0
  413. package/dist/composables/usePersisted.js +1 -1
  414. package/dist/composables/usePillTransition.d.ts +2 -2
  415. package/dist/composables/usePillTransition.js +5 -5
  416. package/dist/composables/useResizeObserver.js +2 -1
  417. package/dist/composables/useReveal.js +2 -1
  418. package/dist/composables/useRovingFocus.d.ts +2 -2
  419. package/dist/composables/useRovingFocus.js +9 -11
  420. package/dist/composables/useScramble.js +7 -1
  421. package/dist/composables/useShift.js +8 -6
  422. package/dist/composables/useTilt.js +2 -1
  423. package/dist/composables/useTween.js +9 -5
  424. package/dist/composables/useView.d.ts +1 -0
  425. package/dist/composables/useView.js +14 -2
  426. package/dist/composables/useZoomPan.js +2 -4
  427. package/dist/composables/windowing.d.ts +40 -0
  428. package/dist/composables/windowing.js +118 -0
  429. package/dist/icons-all.js +1 -1
  430. package/dist/icons.d.ts +4 -0
  431. package/dist/index.d.ts +31 -5
  432. package/dist/index.js +312 -264
  433. package/dist/layout/tree.js +19 -15
  434. package/dist/layout/useLayout.d.ts +2 -0
  435. package/dist/layout/useLayout.js +5 -1
  436. package/dist/layout/useWorkspaces.d.ts +52 -0
  437. package/dist/layout/useWorkspaces.js +140 -0
  438. package/dist/{chunks/backdrop-Dkf2vM81.js → theme/backdrop.js} +5 -5
  439. package/dist/theme/config.js +25 -0
  440. package/dist/theme/define.js +61 -0
  441. package/dist/{chunks/palette-Co8tSASo.js → theme/palette.js} +2 -2
  442. package/dist/theme/presets.js +239 -0
  443. package/dist/theme/studio.js +134 -0
  444. package/dist/theme/tokens.js +63 -0
  445. package/dist/theme/useTheme.d.ts +3 -2
  446. package/dist/theme/useTheme.js +325 -0
  447. package/package.json +8 -1
  448. package/src/component-names.ts +6 -1
  449. package/src/components/AppShellContextNav.vue +44 -0
  450. package/src/components/ChartRangeSelector.vue +120 -0
  451. package/src/components/FieldLeading.vue +17 -0
  452. package/src/components/IconCloseButton.vue +21 -0
  453. package/src/components/LayoutCanvas.vue +3 -0
  454. package/src/components/LayoutNode.vue +37 -38
  455. package/src/components/LpActivityLine.vue +4 -2
  456. package/src/components/LpAddressInput.vue +3 -3
  457. package/src/components/LpAppShell.vue +385 -24
  458. package/src/components/LpAutocomplete.vue +13 -30
  459. package/src/components/LpBottomNav.vue +9 -1
  460. package/src/components/LpBreadcrumbs.vue +2 -1
  461. package/src/components/LpButton.vue +11 -4
  462. package/src/components/LpChart.vue +336 -37
  463. package/src/components/LpCheckbox.vue +43 -9
  464. package/src/components/LpCodeBlock.vue +17 -23
  465. package/src/components/LpCollapse.vue +5 -4
  466. package/src/components/LpColorPicker.vue +8 -9
  467. package/src/components/LpCommandPalette.vue +133 -50
  468. package/src/components/LpContextMenu.vue +22 -13
  469. package/src/components/LpCountdown.vue +2 -1
  470. package/src/components/LpDataList.vue +13 -1
  471. package/src/components/LpDatePicker.vue +5 -17
  472. package/src/components/LpDateRangePicker.vue +5 -16
  473. package/src/components/LpDock.vue +25 -16
  474. package/src/components/LpDrawer.vue +70 -61
  475. package/src/components/LpDropdownMenu.vue +6 -10
  476. package/src/components/LpDropzone.vue +12 -7
  477. package/src/components/LpEditable.vue +6 -2
  478. package/src/components/LpFileTree.vue +507 -66
  479. package/src/components/LpFileTreeNode.vue +106 -5
  480. package/src/components/LpFormField.vue +1 -1
  481. package/src/components/LpFrame.vue +33 -13
  482. package/src/components/LpHoverCard.vue +37 -5
  483. package/src/components/LpIcon.vue +6 -2
  484. package/src/components/LpIdenticon.vue +2 -1
  485. package/src/components/LpImageEditor.vue +5 -2
  486. package/src/components/LpInfraNode.vue +4 -3
  487. package/src/components/LpInput.vue +19 -26
  488. package/src/components/LpInspector.vue +197 -0
  489. package/src/components/LpLaneNode.vue +5 -1
  490. package/src/components/LpLightbox.vue +74 -29
  491. package/src/components/LpLogInspector.vue +85 -0
  492. package/src/components/LpLogViewer.vue +239 -29
  493. package/src/components/LpMapPicker.vue +2 -1
  494. package/src/components/LpMasonry.vue +8 -5
  495. package/src/components/LpModal.vue +12 -12
  496. package/src/components/LpModalNav.vue +2 -21
  497. package/src/components/LpNumberField.vue +13 -16
  498. package/src/components/LpOtpInput.vue +2 -2
  499. package/src/components/LpPageScrollbar.vue +195 -0
  500. package/src/components/LpPagination.vue +5 -3
  501. package/src/components/LpPhoneInput.vue +8 -19
  502. package/src/components/LpPie.vue +25 -23
  503. package/src/components/LpPipeline.vue +14 -1
  504. package/src/components/LpPopover.vue +23 -2
  505. package/src/components/LpProgress.vue +2 -1
  506. package/src/components/LpRadioGroup.vue +2 -2
  507. package/src/components/LpRangeSlider.vue +5 -6
  508. package/src/components/LpRevealOnScroll.vue +1 -1
  509. package/src/components/LpScrollArea.vue +55 -34
  510. package/src/components/LpSecretField.vue +2 -1
  511. package/src/components/LpSegmented.vue +6 -8
  512. package/src/components/LpSelect.vue +54 -24
  513. package/src/components/LpServiceNode.vue +4 -2
  514. package/src/components/LpShaderBackdrop.vue +12 -9
  515. package/src/components/LpShift.vue +18 -15
  516. package/src/components/LpSidebar.vue +77 -74
  517. package/src/components/LpSidebarNav.vue +3 -2
  518. package/src/components/LpSidebarRow.vue +2 -1
  519. package/src/components/LpSlider.vue +5 -6
  520. package/src/components/LpSpotlight.vue +2 -1
  521. package/src/components/LpStat.vue +25 -5
  522. package/src/components/LpSwitch.vue +5 -3
  523. package/src/components/LpTable.vue +609 -93
  524. package/src/components/LpTableFilter.vue +1 -1
  525. package/src/components/LpTableOfContents.vue +16 -9
  526. package/src/components/LpTabs.vue +2 -1
  527. package/src/components/LpTagsInput.vue +12 -19
  528. package/src/components/LpTextarea.vue +5 -11
  529. package/src/components/LpThemeProvider.vue +29 -13
  530. package/src/components/LpTimeField.vue +4 -7
  531. package/src/components/LpToaster.vue +7 -6
  532. package/src/components/LpTooltip.vue +69 -18
  533. package/src/components/LpTopologyCanvas.vue +569 -42
  534. package/src/components/LpTour.vue +7 -5
  535. package/src/components/LpTruncate.vue +4 -3
  536. package/src/components/LpTypewriter.vue +2 -1
  537. package/src/components/LpVideoBackdrop.vue +11 -13
  538. package/src/components/LpWindow.vue +373 -0
  539. package/src/components/LpWorkspaceSwitcher.vue +116 -0
  540. package/src/components/PickerField.vue +2 -2
  541. package/src/components/SliderRail.vue +10 -0
  542. package/src/components/address.ts +3 -1
  543. package/src/components/appShell.ts +74 -0
  544. package/src/components/calendar.ts +12 -5
  545. package/src/components/chart.ts +27 -3
  546. package/src/components/chartWindow.ts +198 -0
  547. package/src/components/color.ts +2 -1
  548. package/src/components/commandPalette.ts +66 -5
  549. package/src/components/controlSize.ts +6 -0
  550. package/src/components/dropdown.ts +3 -0
  551. package/src/components/exportData.ts +33 -18
  552. package/src/components/fileTree.ts +215 -0
  553. package/src/components/identicon.ts +4 -2
  554. package/src/components/logTools.ts +298 -0
  555. package/src/components/meter.ts +2 -1
  556. package/src/components/pipeline.ts +6 -0
  557. package/src/components/relativeTime.ts +2 -1
  558. package/src/components/reveal.ts +3 -1
  559. package/src/components/scrollbar.ts +71 -0
  560. package/src/components/select.ts +14 -0
  561. package/src/components/sidebar.ts +1 -0
  562. package/src/components/tableFeatures.ts +149 -0
  563. package/src/components/topologyGraph.ts +655 -0
  564. package/src/components/tour.ts +5 -3
  565. package/src/components/videoBackdrop.ts +7 -6
  566. package/src/composables/clamp.ts +2 -0
  567. package/src/composables/devWarn.ts +19 -0
  568. package/src/composables/easing.ts +37 -12
  569. package/src/composables/overlayHistory.ts +24 -0
  570. package/src/composables/pointerDrag.ts +54 -0
  571. package/src/composables/propCheck.ts +105 -0
  572. package/src/composables/realm.ts +25 -0
  573. package/src/composables/scroll.ts +4 -2
  574. package/src/composables/scrollMemory.ts +56 -14
  575. package/src/composables/spatialNav.ts +2 -1
  576. package/src/composables/textEntry.ts +33 -0
  577. package/src/composables/useAsyncTask.ts +6 -0
  578. package/src/composables/useChartSync.ts +52 -0
  579. package/src/composables/useCloseGuard.ts +22 -10
  580. package/src/composables/useCommands.ts +123 -0
  581. package/src/composables/useField.ts +41 -2
  582. package/src/composables/useFitScale.ts +4 -1
  583. package/src/composables/useFormDraft.ts +38 -14
  584. package/src/composables/useHintSystem.ts +2 -2
  585. package/src/composables/useHotkeys.ts +19 -48
  586. package/src/composables/useInView.ts +2 -1
  587. package/src/composables/useInputFilter.ts +8 -5
  588. package/src/composables/useInputMask.ts +8 -3
  589. package/src/composables/useLinkedView.ts +222 -0
  590. package/src/composables/useOptimisticToggle.ts +7 -1
  591. package/src/composables/useOverlayHistory.ts +20 -0
  592. package/src/composables/usePaintSelect.ts +20 -14
  593. package/src/composables/usePaletteCommands.ts +18 -2
  594. package/src/composables/usePersisted.ts +10 -0
  595. package/src/composables/usePillTransition.ts +9 -6
  596. package/src/composables/useResizeObserver.ts +2 -1
  597. package/src/composables/useReveal.ts +2 -1
  598. package/src/composables/useRovingFocus.ts +11 -14
  599. package/src/composables/useScramble.ts +12 -1
  600. package/src/composables/useShift.ts +8 -6
  601. package/src/composables/useTilt.ts +2 -1
  602. package/src/composables/useTween.ts +10 -5
  603. package/src/composables/useView.ts +17 -3
  604. package/src/composables/useZoomPan.ts +2 -5
  605. package/src/composables/windowing.ts +190 -0
  606. package/src/icons.ts +27 -6
  607. package/src/index.ts +42 -4
  608. package/src/kit-icons.ts +1 -1
  609. package/src/layout/tree.ts +31 -16
  610. package/src/layout/useLayout.ts +6 -0
  611. package/src/layout/useWorkspaces.ts +234 -0
  612. package/src/nuxt.ts +9 -1
  613. package/src/runtime/propCheck.plugin.ts +10 -0
  614. package/src/theme/studio.ts +7 -5
  615. package/src/theme/useTheme.ts +89 -15
  616. package/src/tokens/tokens.css +8 -1
  617. package/dist/assets/LpTabs-CJ0rYQbx.css +0 -18
  618. package/dist/chunks/LpAppShell-Dgejipzz.js +0 -319
  619. package/dist/chunks/LpFileTree-CooHinsC.js +0 -425
  620. package/dist/chunks/LpThemeProvider-8MG6yqU9.js +0 -640
  621. 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
- props.lanes.map(
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
- props.nodes.map((n): Node => {
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
- width: `${n.width ?? 260}px`,
251
- height: `${n.height ?? 160}px`,
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
- props.edges.map((e) => {
588
+ shown.value.edges.map((e) => {
292
589
  const obs = e.observed ?? "applied"
293
590
  const cat = edgeCategory(e)
294
- const color = edgeColor(cat, obs)
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" && !e.dimmed,
302
- label: e.kind,
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: e.dimmed ? 0.12 : 1,
610
+ opacity: faded ? 0.12 : 1,
311
611
  },
312
612
  markerEnd: { type: MarkerType.ArrowClosed, color },
313
613
  }
314
614
  }),
315
615
  )
316
616
 
317
- const {
318
- onNodeClick,
319
- onNodeDoubleClick,
320
- onNodeContextMenu,
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
- emit("node-drag-stop", { id: node.id, position: { ...node.position } }),
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(() => emit("pane-click"))
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 wide = typeof window === "undefined" || window.matchMedia("(min-width: 640px)").matches
403
- const legendHeight = wide && props.legend ? (legendBox.value?.offsetHeight ?? 0) + 15 : 0
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, wide && props.minimap ? MINIMAP_HEIGHT : 0)
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
- defineExpose({ fitView: fitFramed, setViewport: applyViewport })
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-sm:!hidden"
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="{ padding: framePadding() }" />
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-sm:hidden">
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"