@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
@@ -0,0 +1,655 @@
1
+ /*
2
+ * The graph logic behind LpTopologyCanvas: paths, neighbours, version diffs,
3
+ * layout and clustering. Plain functions over plain data, with no Vue Flow and
4
+ * no DOM, so each can be tested alone and an app can run them on its own data.
5
+ */
6
+
7
+ export interface GraphPoint {
8
+ x: number
9
+ y: number
10
+ }
11
+
12
+ export interface GraphNode {
13
+ id: string
14
+ position: GraphPoint
15
+ parent?: string
16
+ width?: number
17
+ height?: number
18
+ type?: string
19
+ label?: string
20
+ data?: unknown
21
+ }
22
+
23
+ export interface GraphEdge {
24
+ id: string
25
+ source: string
26
+ target: string
27
+ }
28
+
29
+ export interface GraphPath {
30
+ nodes: string[]
31
+ edges: string[]
32
+ }
33
+
34
+ export interface PathOptions {
35
+ /** Follow edges only from source to target. Default false: a wire goes both ways. */
36
+ directed?: boolean
37
+ /** Leave out edges that may not be walked (a drifted link, a hidden kind). */
38
+ allow?: (edge: GraphEdge) => boolean
39
+ }
40
+
41
+ /** What touches a node: itself, the nodes one edge away, and those edges. */
42
+ export function neighborhood(edges: readonly GraphEdge[], id: string): GraphPath {
43
+ const nodes = new Set([id])
44
+ const touching: string[] = []
45
+ for (const edge of edges) {
46
+ if (edge.source !== id && edge.target !== id) continue
47
+ nodes.add(edge.source)
48
+ nodes.add(edge.target)
49
+ touching.push(edge.id)
50
+ }
51
+ return { nodes: [...nodes], edges: touching }
52
+ }
53
+
54
+ /** The shortest path by hop count, or null when the two are not connected. */
55
+ export function findPath(
56
+ edges: readonly GraphEdge[],
57
+ from: string,
58
+ to: string,
59
+ options: PathOptions = {},
60
+ ): GraphPath | null {
61
+ const links = new Map<string, { next: string; edge: string }[]>()
62
+ const link = (a: string, b: string, edge: string) => {
63
+ const list = links.get(a)
64
+ if (list) list.push({ next: b, edge })
65
+ else links.set(a, [{ next: b, edge }])
66
+ }
67
+ for (const edge of edges) {
68
+ if (options.allow && !options.allow(edge)) continue
69
+ link(edge.source, edge.target, edge.id)
70
+ if (!options.directed) link(edge.target, edge.source, edge.id)
71
+ }
72
+
73
+ const cameBy = new Map<string, { from: string; edge: string } | null>([[from, null]])
74
+ const queue = [from]
75
+ for (let head = 0; head < queue.length; head++) {
76
+ const at = queue[head]!
77
+ if (at === to) break
78
+ for (const { next, edge } of links.get(at) ?? []) {
79
+ if (cameBy.has(next)) continue
80
+ cameBy.set(next, { from: at, edge })
81
+ queue.push(next)
82
+ }
83
+ }
84
+ if (!cameBy.has(to)) return null
85
+
86
+ const path: GraphPath = { nodes: [to], edges: [] }
87
+ for (let step = cameBy.get(to); step; step = cameBy.get(step.from)) {
88
+ path.nodes.unshift(step.from)
89
+ path.edges.unshift(step.edge)
90
+ }
91
+ return path
92
+ }
93
+
94
+ export interface TopologyDiff {
95
+ addedNodes: string[]
96
+ removedNodes: string[]
97
+ changedNodes: string[]
98
+ addedEdges: string[]
99
+ removedEdges: string[]
100
+ changedEdges: string[]
101
+ }
102
+
103
+ /*
104
+ * Position is left out of a node's comparison: dragging a card changes nothing
105
+ * about the infrastructure. `dimmed` is display state and goes the same way.
106
+ */
107
+ function fingerprint(item: object): string {
108
+ const { position: _position, dimmed: _dimmed, data, ...rest } = item as Record<string, unknown>
109
+ const { dimmed: _dataDimmed, ...own } = (data ?? {}) as Record<string, unknown>
110
+ return JSON.stringify([rest, own], (_key, value) =>
111
+ value && typeof value === "object" && !Array.isArray(value)
112
+ ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => (a < b ? -1 : 1)))
113
+ : value,
114
+ )
115
+ }
116
+
117
+ function compare<T extends { id: string }>(before: readonly T[], after: readonly T[]) {
118
+ const old = new Map(before.map((item) => [item.id, item]))
119
+ const fresh = new Set(after.map((item) => item.id))
120
+ return {
121
+ added: after.filter((item) => !old.has(item.id)).map((item) => item.id),
122
+ removed: before.filter((item) => !fresh.has(item.id)).map((item) => item.id),
123
+ changed: after
124
+ .filter((item) => old.has(item.id) && fingerprint(old.get(item.id)!) !== fingerprint(item))
125
+ .map((item) => item.id),
126
+ }
127
+ }
128
+
129
+ /** What changed between two versions of a graph, by id. */
130
+ export function diffTopology(
131
+ before: { nodes: readonly GraphNode[]; edges: readonly GraphEdge[] },
132
+ after: { nodes: readonly GraphNode[]; edges: readonly GraphEdge[] },
133
+ ): TopologyDiff {
134
+ const nodes = compare(before.nodes, after.nodes)
135
+ const edges = compare(before.edges, after.edges)
136
+ return {
137
+ addedNodes: nodes.added,
138
+ removedNodes: nodes.removed,
139
+ changedNodes: nodes.changed,
140
+ addedEdges: edges.added,
141
+ removedEdges: edges.removed,
142
+ changedEdges: edges.changed,
143
+ }
144
+ }
145
+
146
+ export interface LayoutOptions {
147
+ /** Which way the layers run. Default "LR": columns, left to right. */
148
+ direction?: "LR" | "TB"
149
+ /** Space between layers. */
150
+ layerGap?: number
151
+ /** Space between nodes of one layer. */
152
+ nodeGap?: number
153
+ /** Size of a node that carries none. */
154
+ nodeWidth?: number
155
+ nodeHeight?: number
156
+ /** Lay each group out on its own, one band after another. */
157
+ groupOf?: (node: GraphNode) => string | undefined
158
+ /** Space between group bands. */
159
+ groupGap?: number
160
+ }
161
+
162
+ const DEFAULT_NODE_WIDTH = 220
163
+ const DEFAULT_NODE_HEIGHT = 96
164
+ const DEFAULT_LAYER_GAP = 96
165
+ const DEFAULT_NODE_GAP = 32
166
+ const DEFAULT_GROUP_GAP = 72
167
+ /* Down and up again twice settles a small graph's crossings; more buys little. */
168
+ const ORDER_SWEEPS = 4
169
+
170
+ /** The outermost node each node sits in: edges between children count for it. */
171
+ function topLevelOf(nodes: readonly GraphNode[]): Map<string, string> {
172
+ const parents = new Map(nodes.map((node) => [node.id, node.parent]))
173
+ const top = new Map<string, string>()
174
+ for (const node of nodes) {
175
+ let at = node.id
176
+ const seen = new Set([at])
177
+ for (let up = parents.get(at); up && parents.has(up) && !seen.has(up); up = parents.get(at)) {
178
+ at = up
179
+ seen.add(at)
180
+ }
181
+ top.set(node.id, at)
182
+ }
183
+ return top
184
+ }
185
+
186
+ /** Links between distinct top-level nodes, each pair once, with cycles broken. */
187
+ function acyclicLinks(ids: readonly string[], links: readonly [string, string][]): [string, string][] {
188
+ const out = new Map(ids.map((id): [string, string[]] => [id, []]))
189
+ for (const [a, b] of links) {
190
+ const list = out.get(a)
191
+ if (list && out.has(b) && a !== b && !list.includes(b)) list.push(b)
192
+ }
193
+ const state = new Map<string, "open" | "done">()
194
+ const kept: [string, string][] = []
195
+ // Walked with its own stack: a long chain must not exhaust the call stack.
196
+ for (const root of ids) {
197
+ if (state.has(root)) continue
198
+ state.set(root, "open")
199
+ const stack: { id: string; next: number }[] = [{ id: root, next: 0 }]
200
+ while (stack.length) {
201
+ const top = stack[stack.length - 1]!
202
+ const target = out.get(top.id)![top.next++]
203
+ if (target === undefined) {
204
+ state.set(top.id, "done")
205
+ stack.pop()
206
+ } else if (state.get(target) !== "open") {
207
+ kept.push([top.id, target])
208
+ if (!state.has(target)) {
209
+ state.set(target, "open")
210
+ stack.push({ id: target, next: 0 })
211
+ }
212
+ }
213
+ }
214
+ }
215
+ return kept
216
+ }
217
+
218
+ /** Each id's layer: one past the deepest node leading to it. */
219
+ function rankOf(ids: readonly string[], links: readonly [string, string][]): Map<string, number> {
220
+ const rank = new Map(ids.map((id) => [id, 0]))
221
+ const waiting = new Map(ids.map((id) => [id, 0]))
222
+ const out = new Map(ids.map((id): [string, string[]] => [id, []]))
223
+ for (const [a, b] of links) {
224
+ out.get(a)!.push(b)
225
+ waiting.set(b, waiting.get(b)! + 1)
226
+ }
227
+ const queue = ids.filter((id) => waiting.get(id) === 0)
228
+ for (let head = 0; head < queue.length; head++) {
229
+ const at = queue[head]!
230
+ for (const next of out.get(at)!) {
231
+ rank.set(next, Math.max(rank.get(next)!, rank.get(at)! + 1))
232
+ waiting.set(next, waiting.get(next)! - 1)
233
+ if (waiting.get(next) === 0) queue.push(next)
234
+ }
235
+ }
236
+ return rank
237
+ }
238
+
239
+ /** Orders each layer by the mean place of its neighbours, to cut crossings. */
240
+ function orderLayers(layers: string[][], links: readonly [string, string][]): void {
241
+ const around = new Map<string, string[]>()
242
+ const add = (a: string, b: string) => around.set(a, [...(around.get(a) ?? []), b])
243
+ for (const [a, b] of links) {
244
+ add(a, b)
245
+ add(b, a)
246
+ }
247
+ for (let sweep = 0; sweep < ORDER_SWEEPS; sweep++) {
248
+ const down = sweep % 2 === 0
249
+ const place = new Map(layers.flatMap((layer) => layer.map((id, i): [string, number] => [id, i])))
250
+ for (let i = 0; i < layers.length; i++) {
251
+ const at = down ? i : layers.length - 1 - i
252
+ const weight = (id: string, fallback: number) => {
253
+ const known = (around.get(id) ?? []).map((other) => place.get(other)!).filter((n) => n !== undefined)
254
+ return known.length ? known.reduce((sum, n) => sum + n, 0) / known.length : fallback
255
+ }
256
+ const ranked = layers[at]!.map((id, index) => ({ id, index, weight: weight(id, index) }))
257
+ ranked.sort((a, b) => a.weight - b.weight || a.index - b.index)
258
+ layers[at] = ranked.map((entry) => entry.id)
259
+ layers[at]!.forEach((id, index) => place.set(id, index))
260
+ }
261
+ }
262
+ }
263
+
264
+ /*
265
+ * A simple layered drawing: edges run one way across the layers, cycles broken
266
+ * at their back edge. A node inside another keeps its place in it, so only
267
+ * top-level nodes get a position; their children's edges count for them.
268
+ */
269
+ export function layeredLayout(
270
+ nodes: readonly GraphNode[],
271
+ edges: readonly GraphEdge[],
272
+ options: LayoutOptions = {},
273
+ ): Map<string, GraphPoint> {
274
+ const vertical = options.direction === "TB"
275
+ const layerGap = options.layerGap ?? DEFAULT_LAYER_GAP
276
+ const nodeGap = options.nodeGap ?? DEFAULT_NODE_GAP
277
+ const groupGap = options.groupGap ?? DEFAULT_GROUP_GAP
278
+ const width = (node: GraphNode) => node.width ?? options.nodeWidth ?? DEFAULT_NODE_WIDTH
279
+ const height = (node: GraphNode) => node.height ?? options.nodeHeight ?? DEFAULT_NODE_HEIGHT
280
+ const along = (node: GraphNode) => (vertical ? height(node) : width(node))
281
+ const across = (node: GraphNode) => (vertical ? width(node) : height(node))
282
+
283
+ const top = topLevelOf(nodes)
284
+ const byId = new Map(nodes.map((node) => [node.id, node]))
285
+ const roots = nodes.filter((node) => top.get(node.id) === node.id)
286
+ const links = edges.flatMap((edge): [string, string][] => {
287
+ const a = top.get(edge.source)
288
+ const b = top.get(edge.target)
289
+ return a && b ? [[a, b]] : []
290
+ })
291
+
292
+ const bands = new Map<string, GraphNode[]>()
293
+ for (const node of roots) {
294
+ const key = options.groupOf?.(node) ?? ""
295
+ const band = bands.get(key)
296
+ if (band) band.push(node)
297
+ else bands.set(key, [node])
298
+ }
299
+
300
+ const positions = new Map<string, GraphPoint>()
301
+ let bandStart = 0
302
+ for (const band of bands.values()) {
303
+ const ids = band.map((node) => node.id)
304
+ const acyclic = acyclicLinks(ids, links)
305
+ const rank = rankOf(ids, acyclic)
306
+ const layers: string[][] = []
307
+ for (const id of ids) (layers[rank.get(id)!] ??= []).push(id)
308
+ orderLayers(layers, acyclic)
309
+
310
+ const extent = (layer: string[]) =>
311
+ layer.reduce((sum, id) => sum + across(byId.get(id)!) + nodeGap, -nodeGap)
312
+ const bandSize = Math.max(...layers.map(extent))
313
+ let main = 0
314
+ for (const layer of layers) {
315
+ let cross = bandStart + (bandSize - extent(layer)) / 2
316
+ for (const id of layer) {
317
+ positions.set(id, vertical ? { x: cross, y: main } : { x: main, y: cross })
318
+ cross += across(byId.get(id)!) + nodeGap
319
+ }
320
+ main += Math.max(...layer.map((id) => along(byId.get(id)!))) + layerGap
321
+ }
322
+ bandStart += bandSize + groupGap
323
+ }
324
+ return positions
325
+ }
326
+
327
+ export interface GraphBox extends GraphPoint {
328
+ width: number
329
+ height: number
330
+ }
331
+
332
+ export interface LaneOptions {
333
+ padding?: number
334
+ /** Room over the nodes for the lane's label. */
335
+ header?: number
336
+ nodeWidth?: number
337
+ nodeHeight?: number
338
+ }
339
+
340
+ const LANE_PADDING = 24
341
+ const LANE_HEADER = 28
342
+
343
+ /** One box per group, around its top-level nodes where they stand now. */
344
+ export function groupBoxes(
345
+ nodes: readonly GraphNode[],
346
+ keyOf: (node: GraphNode) => string | undefined,
347
+ options: LaneOptions = {},
348
+ ): { key: string; box: GraphBox }[] {
349
+ const padding = options.padding ?? LANE_PADDING
350
+ const header = options.header ?? LANE_HEADER
351
+ const edges = new Map<string, { left: number; top: number; right: number; bottom: number }>()
352
+ for (const node of nodes) {
353
+ const key = node.parent ? undefined : keyOf(node)
354
+ if (key === undefined) continue
355
+ const { x, y } = node.position
356
+ const right = x + (node.width ?? options.nodeWidth ?? DEFAULT_NODE_WIDTH)
357
+ const bottom = y + (node.height ?? options.nodeHeight ?? DEFAULT_NODE_HEIGHT)
358
+ const seen = edges.get(key)
359
+ edges.set(key, seen
360
+ ? { left: Math.min(seen.left, x), top: Math.min(seen.top, y), right: Math.max(seen.right, right), bottom: Math.max(seen.bottom, bottom) }
361
+ : { left: x, top: y, right, bottom })
362
+ }
363
+ return [...edges].map(([key, e]) => ({
364
+ key,
365
+ box: {
366
+ x: e.left - padding,
367
+ y: e.top - padding - header,
368
+ width: e.right - e.left + padding * 2,
369
+ height: e.bottom - e.top + padding * 2 + header,
370
+ },
371
+ }))
372
+ }
373
+
374
+ export interface FrameOptions {
375
+ padding?: number
376
+ minWidth?: number
377
+ minHeight?: number
378
+ childWidth?: number
379
+ childHeight?: number
380
+ }
381
+
382
+ const CHILD_WIDTH = 170
383
+ const CHILD_HEIGHT = 56
384
+ const FRAME_PADDING = 16
385
+
386
+ /** The frame that holds these children, whose positions are relative to it. */
387
+ export function frameSize(children: readonly GraphNode[], options: FrameOptions = {}): { width: number; height: number } {
388
+ const padding = options.padding ?? FRAME_PADDING
389
+ let width = options.minWidth ?? 0
390
+ let height = options.minHeight ?? 0
391
+ for (const child of children) {
392
+ width = Math.max(width, child.position.x + (child.width ?? options.childWidth ?? CHILD_WIDTH) + padding)
393
+ height = Math.max(height, child.position.y + (child.height ?? options.childHeight ?? CHILD_HEIGHT) + padding)
394
+ }
395
+ return { width, height }
396
+ }
397
+
398
+ export interface MergedEdge extends GraphEdge {
399
+ /** The original edges this one stands for. */
400
+ members: string[]
401
+ }
402
+
403
+ /**
404
+ * Edges redrawn between what their ends stand for now: inner ones go, and
405
+ * parallel ones become one, keeping which they were.
406
+ */
407
+ export function mergeEdges(edges: readonly GraphEdge[], mapId: (id: string) => string | undefined): MergedEdge[] {
408
+ const merged = new Map<string, MergedEdge>()
409
+ for (const edge of edges) {
410
+ const source = mapId(edge.source)
411
+ const target = mapId(edge.target)
412
+ if (source === undefined || target === undefined || source === target) continue
413
+ const id = `${source}\u0000${target}`
414
+ const seen = merged.get(id)
415
+ if (seen) seen.members.push(edge.id)
416
+ else merged.set(id, { id, source, target, members: [edge.id] })
417
+ }
418
+ return [...merged.values()]
419
+ }
420
+
421
+ export interface GraphCluster {
422
+ id: string
423
+ key: string
424
+ position: GraphPoint
425
+ members: string[]
426
+ }
427
+
428
+ export interface ClusteredGraph<N extends GraphNode> {
429
+ nodes: N[]
430
+ clusters: GraphCluster[]
431
+ edges: MergedEdge[]
432
+ }
433
+
434
+ const CLUSTER_PREFIX = "lp-cluster:"
435
+
436
+ /**
437
+ * Collapses each group into one cluster at the middle of its top-level nodes.
438
+ * A node with no group stays; a child goes with whatever holds it.
439
+ */
440
+ export function clusterGraph<N extends GraphNode>(
441
+ nodes: readonly N[],
442
+ edges: readonly GraphEdge[],
443
+ keyOf: (node: N) => string | undefined,
444
+ ): ClusteredGraph<N> {
445
+ const top = topLevelOf(nodes)
446
+ const byId = new Map(nodes.map((node) => [node.id, node]))
447
+ const groups = new Map<string, N[]>()
448
+ const standsFor = new Map<string, string>()
449
+ for (const node of nodes) {
450
+ const root = byId.get(top.get(node.id)!)!
451
+ const key = keyOf(root)
452
+ if (key === undefined) {
453
+ standsFor.set(node.id, node.id)
454
+ continue
455
+ }
456
+ standsFor.set(node.id, CLUSTER_PREFIX + key)
457
+ if (node !== root) continue
458
+ const group = groups.get(key)
459
+ if (group) group.push(node)
460
+ else groups.set(key, [node])
461
+ }
462
+ const clusters = [...groups].map(([key, members]): GraphCluster => ({
463
+ id: CLUSTER_PREFIX + key,
464
+ key,
465
+ position: {
466
+ x: members.reduce((sum, node) => sum + node.position.x, 0) / members.length,
467
+ y: members.reduce((sum, node) => sum + node.position.y, 0) / members.length,
468
+ },
469
+ members: nodes.filter((node) => standsFor.get(node.id) === CLUSTER_PREFIX + key).map((node) => node.id),
470
+ }))
471
+ return {
472
+ nodes: nodes.filter((node) => standsFor.get(node.id) === node.id),
473
+ clusters,
474
+ edges: mergeEdges(edges, (id) => standsFor.get(id)),
475
+ }
476
+ }
477
+
478
+ /** Drops children, with their edges moved up to whatever holds them. */
479
+ export function collapseChildren<N extends GraphNode>(
480
+ nodes: readonly N[],
481
+ edges: readonly GraphEdge[],
482
+ ): { nodes: N[]; edges: MergedEdge[] } {
483
+ const top = topLevelOf(nodes)
484
+ return { nodes: nodes.filter((node) => top.get(node.id) === node.id), edges: mergeEdges(edges, (id) => top.get(id)) }
485
+ }
486
+
487
+ export type DetailLevel = "full" | "simple" | "overview"
488
+
489
+ export const SIMPLE_BELOW_ZOOM = 0.7
490
+ export const OVERVIEW_BELOW_ZOOM = 0.45
491
+
492
+ /** How much of the graph to draw at a zoom. Clusters need groups to exist. */
493
+ export function detailAtZoom(zoom: number, grouped: boolean): DetailLevel {
494
+ if (zoom < OVERVIEW_BELOW_ZOOM && grouped) return "overview"
495
+ return zoom < SIMPLE_BELOW_ZOOM ? "simple" : "full"
496
+ }
497
+
498
+ /** Where every node really is: a child's position is relative to its parent. */
499
+ export function absolutePositions(nodes: readonly GraphNode[]): Map<string, GraphPoint> {
500
+ const byId = new Map(nodes.map((node) => [node.id, node]))
501
+ const resolved = new Map<string, GraphPoint>()
502
+ const resolve = (node: GraphNode, seen: Set<string>): GraphPoint => {
503
+ const known = resolved.get(node.id)
504
+ if (known) return known
505
+ const parent = node.parent ? byId.get(node.parent) : undefined
506
+ const base = parent && !seen.has(parent.id) ? resolve(parent, seen.add(node.id)) : { x: 0, y: 0 }
507
+ const point = { x: base.x + node.position.x, y: base.y + node.position.y }
508
+ resolved.set(node.id, point)
509
+ return point
510
+ }
511
+ for (const node of nodes) resolve(node, new Set())
512
+ return resolved
513
+ }
514
+
515
+ export function nodeName(node: GraphNode): string {
516
+ const name = (node.data as { name?: unknown } | undefined)?.name
517
+ return node.label ?? (typeof name === "string" ? name : node.id)
518
+ }
519
+
520
+ /** Nodes matching a query, by name or id: names first, then earlier matches. */
521
+ export function findNodes<N extends GraphNode>(nodes: readonly N[], query: string): N[] {
522
+ const needle = query.trim().toLowerCase()
523
+ if (!needle) return []
524
+ const score = (node: N) => {
525
+ const at = nodeName(node).toLowerCase().indexOf(needle)
526
+ if (at === 0) return 0
527
+ if (at > 0) return 1
528
+ return node.id.toLowerCase().includes(needle) ? 2 : -1
529
+ }
530
+ return nodes
531
+ .map((node) => ({ node, rank: score(node) }))
532
+ .filter((entry) => entry.rank >= 0)
533
+ .sort((a, b) => a.rank - b.rank)
534
+ .map((entry) => entry.node)
535
+ }
536
+
537
+ export type GraphDirection = "up" | "down" | "left" | "right"
538
+
539
+ /* A step sideways costs this much more than a step the way the arrow points. */
540
+ const OFF_AXIS_PENALTY = 2
541
+
542
+ /** The closest node the way an arrow key points, or null at the edge of the graph. */
543
+ export function nearestInDirection(
544
+ positions: ReadonlyMap<string, GraphPoint>,
545
+ from: string,
546
+ direction: GraphDirection,
547
+ ): string | null {
548
+ const origin = positions.get(from)
549
+ if (!origin) return null
550
+ let best: string | null = null
551
+ let bestCost = Infinity
552
+ for (const [id, point] of positions) {
553
+ if (id === from) continue
554
+ const dx = point.x - origin.x
555
+ const dy = point.y - origin.y
556
+ const main = { up: -dy, down: dy, left: -dx, right: dx }[direction]
557
+ if (main <= 0) continue
558
+ const side = direction === "up" || direction === "down" ? dx : dy
559
+ const cost = main + Math.abs(side) * OFF_AXIS_PENALTY
560
+ if (cost < bestCost) {
561
+ bestCost = cost
562
+ best = id
563
+ }
564
+ }
565
+ return best
566
+ }
567
+
568
+ export interface EdgeReading {
569
+ kind?: string
570
+ /** Round-trip time, in milliseconds. */
571
+ rtt?: number
572
+ /** Packet loss, in percent. */
573
+ loss?: number
574
+ }
575
+
576
+ /** The edge's label: its kind, then whatever was measured on it. */
577
+ export function edgeLabelText(edge: EdgeReading): string | undefined {
578
+ const parts = [
579
+ edge.kind,
580
+ edge.rtt === undefined ? undefined : `${Math.round(edge.rtt)} ms`,
581
+ edge.loss === undefined ? undefined : `${edge.loss}% loss`,
582
+ ].filter((part) => part !== undefined)
583
+ return parts.length ? parts.join(" · ") : undefined
584
+ }
585
+
586
+ export interface SvgTheme {
587
+ background: string
588
+ surface: string
589
+ line: string
590
+ text: string
591
+ edge: string
592
+ }
593
+
594
+ export interface SvgNode extends GraphBox {
595
+ id: string
596
+ label: string
597
+ /** A frame holding other nodes: drawn behind them, label at the top. */
598
+ frame?: boolean
599
+ }
600
+
601
+ export interface SvgEdge extends GraphEdge {
602
+ label?: string
603
+ color?: string
604
+ dash?: string
605
+ }
606
+
607
+ const SVG_MARGIN = 24
608
+ const SVG_RADIUS = 10
609
+ const SVG_FONT_SIZE = 12
610
+ const SVG_FRAME_LABEL_Y = 18
611
+
612
+ const escapeXml = (text: string) =>
613
+ text.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`)
614
+
615
+ /*
616
+ * A clean drawing of the graph rather than a capture of the screen: the cards
617
+ * are HTML, which only goes into an SVG as foreignObject, and that taints the
618
+ * canvas a PNG is drawn on. Boxes, curves and names survive; card detail does not.
619
+ */
620
+ export function topologyToSvg(nodes: readonly SvgNode[], edges: readonly SvgEdge[], theme: SvgTheme): string {
621
+ const left = Math.min(0, ...nodes.map((n) => n.x)) - SVG_MARGIN
622
+ const top = Math.min(0, ...nodes.map((n) => n.y)) - SVG_MARGIN
623
+ const width = Math.max(0, ...nodes.map((n) => n.x + n.width)) + SVG_MARGIN - left
624
+ const height = Math.max(0, ...nodes.map((n) => n.y + n.height)) + SVG_MARGIN - top
625
+ const byId = new Map(nodes.map((node) => [node.id, node]))
626
+ const middle = (n: SvgNode) => ({ x: n.x + n.width / 2, y: n.y + n.height / 2 })
627
+
628
+ const wires = edges.flatMap((edge) => {
629
+ const from = byId.get(edge.source)
630
+ const to = byId.get(edge.target)
631
+ if (!from || !to) return []
632
+ const a = middle(from)
633
+ const b = middle(to)
634
+ const dash = edge.dash && edge.dash !== "0" ? ` stroke-dasharray="${escapeXml(edge.dash)}"` : ""
635
+ const line = `<line x1="${a.x}" y1="${a.y}" x2="${b.x}" y2="${b.y}" stroke="${escapeXml(edge.color ?? theme.edge)}" stroke-width="2"${dash}/>`
636
+ const label = edge.label
637
+ ? `<text x="${(a.x + b.x) / 2}" y="${(a.y + b.y) / 2}" text-anchor="middle" fill="${escapeXml(theme.text)}">${escapeXml(edge.label)}</text>`
638
+ : ""
639
+ return [line + label]
640
+ })
641
+
642
+ const box = (n: SvgNode) => {
643
+ const textX = n.x + n.width / 2
644
+ const textY = n.frame ? n.y + SVG_FRAME_LABEL_Y : n.y + n.height / 2
645
+ return `<rect x="${n.x}" y="${n.y}" width="${n.width}" height="${n.height}" rx="${SVG_RADIUS}" fill="${escapeXml(theme.surface)}" stroke="${escapeXml(theme.line)}"/>`
646
+ + `<text x="${textX}" y="${textY}" text-anchor="middle" dominant-baseline="middle" fill="${escapeXml(theme.text)}">${escapeXml(n.label)}</text>`
647
+ }
648
+ // Frames go down first, or they would cover the wires and nodes inside them.
649
+ const frames = nodes.filter((n) => n.frame).map(box)
650
+ const cards = nodes.filter((n) => !n.frame).map(box)
651
+
652
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${left} ${top} ${width} ${height}" font-family="sans-serif" font-size="${SVG_FONT_SIZE}">`
653
+ + `<rect x="${left}" y="${top}" width="${width}" height="${height}" fill="${escapeXml(theme.background)}"/>`
654
+ + frames.join("") + wires.join("") + cards.join("") + "</svg>"
655
+ }
@@ -1,3 +1,4 @@
1
+ import { nodeWindow } from "../composables/realm"
1
2
  /*
2
3
  * Geometry for LpTour, kept out of the component so it can be tested without a
3
4
  * DOM: where the spotlight hole goes and what the dim panels around it cover.
@@ -71,8 +72,9 @@ export function holeClip(hole: Box | null, viewport: Size, radius: number): stri
71
72
  */
72
73
  export function openContextMenu(el: Element): () => void {
73
74
  const box = el.getBoundingClientRect()
75
+ const win = nodeWindow(el)
74
76
  el.dispatchEvent(
75
- new MouseEvent("contextmenu", {
77
+ new win.MouseEvent("contextmenu", {
76
78
  bubbles: true,
77
79
  cancelable: true,
78
80
  button: 2,
@@ -83,7 +85,7 @@ export function openContextMenu(el: Element): () => void {
83
85
  return () => {
84
86
  const outside = { bubbles: true, cancelable: true, clientX: 0, clientY: 0 }
85
87
  const body = el.ownerDocument.body
86
- body.dispatchEvent(new PointerEvent("pointerdown", outside))
87
- body.dispatchEvent(new PointerEvent("pointerup", outside))
88
+ body.dispatchEvent(new win.PointerEvent("pointerdown", outside))
89
+ body.dispatchEvent(new win.PointerEvent("pointerup", outside))
88
90
  }
89
91
  }