@leavepulse/ui 0.77.2 → 0.78.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (597) 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-DXIr0eFz.js} +2 -2
  12. package/dist/chunks/{FieldWrapper-CORPzo9s.js → FieldWrapper-DnuMTrqW.js} +1 -1
  13. package/dist/chunks/IconCloseButton-COZEsa9u.js +29 -0
  14. package/dist/chunks/{LayoutCanvas-CNOAUk7D.js → LayoutCanvas-CZKOltfA.js} +1 -1
  15. package/dist/chunks/{LayoutNode-DF5BMN0J.js → LayoutNode-D2f7v683.js} +45 -45
  16. package/dist/chunks/{LpActionBar-BP1ld7-S.js → LpActionBar-BGMk5KoG.js} +3 -3
  17. package/dist/chunks/{LpActivityLine-CJvzrNpj.js → LpActivityLine-Ea-t5Vza.js} +4 -2
  18. package/dist/chunks/{LpAddressInput-sYMvYzcY.js → LpAddressInput-ZqoZ-N2Y.js} +12 -11
  19. package/dist/chunks/{LpAlert-n5XPAZfv.js → LpAlert-CjvTZIKF.js} +3 -3
  20. package/dist/chunks/LpAppShell-BNAVTQsY.js +582 -0
  21. package/dist/chunks/{LpAutocomplete-DOu7FN5P.js → LpAutocomplete-DkY6CSyD.js} +26 -42
  22. package/dist/chunks/{LpAvatar-Cg6Az3wz.js → LpAvatar-cQVsNk6F.js} +2 -2
  23. package/dist/chunks/{LpBadge-wUYLN4U9.js → LpBadge-D5QQdDQ_.js} +2 -2
  24. package/dist/chunks/{LpBottomNav-CbNQQPuL.js → LpBottomNav-zxLGGbjm.js} +9 -2
  25. package/dist/chunks/{LpBreadcrumbs-DPdbth-c.js → LpBreadcrumbs-CIIBsygI.js} +4 -3
  26. package/dist/chunks/{LpButton-CFb44IjC.js → LpButton-DSHioIfD.js} +5 -4
  27. package/dist/chunks/{LpCalendar-CuoWks-0.js → LpCalendar-D4-LVjfF.js} +1 -1
  28. package/dist/chunks/{LpCard-r13WRqox.js → LpCard-BPtYXwnT.js} +1 -1
  29. package/dist/chunks/{LpCheckbox-Cs4SHaHX.js → LpCheckbox-D6RuDW4X.js} +38 -16
  30. package/dist/chunks/{LpCodeBlock-DRC8VbXW.js → LpCodeBlock-DuzmnIHl.js} +19 -41
  31. package/dist/chunks/{LpCodeDisplay-CE9TxCA4.js → LpCodeDisplay-CO8fngxa.js} +5 -5
  32. package/dist/chunks/{LpCollapse-CWS9M_lm.js → LpCollapse-DrAw5DBZ.js} +5 -4
  33. package/dist/chunks/{LpColorPicker-1y1QJHZR.js → LpColorPicker-BxAHEnR5.js} +11 -12
  34. package/dist/chunks/{LpCommandScope-CtUhJe8T.js → LpCommandScope-DRUvHiX7.js} +1 -1
  35. package/dist/chunks/{LpConfirmDialog-DlD45Bi_.js → LpConfirmDialog-6oCIJTDa.js} +3 -3
  36. package/dist/chunks/{LpConfirmHost-BIWjBLmI.js → LpConfirmHost-DgHvfT6s.js} +1 -1
  37. package/dist/chunks/{LpContextMenu-rx2tCjwk.js → LpContextMenu--eMGIql6.js} +13 -15
  38. package/dist/chunks/{LpContextSwitcher-Dwh6dyID.js → LpContextSwitcher-1C1tExdl.js} +2 -2
  39. package/dist/chunks/{LpCopyButton-xF7wFVoU.js → LpCopyButton-7EOSIuwa.js} +3 -3
  40. package/dist/chunks/{LpCountdown-DgeY3cLG.js → LpCountdown-DVl7vXI1.js} +4 -3
  41. package/dist/chunks/{LpDataList-Jxyl9H5w.js → LpDataList-DgBeM01y.js} +13 -4
  42. package/dist/chunks/{LpDatePicker-CA0QTfBC.js → LpDatePicker-DwSDY0mt.js} +12 -18
  43. package/dist/chunks/{LpDateRangePicker-u3J51xXk.js → LpDateRangePicker-CNQXdTVv.js} +10 -16
  44. package/dist/chunks/{LpDisclosure-Cpa7Hc5T.js → LpDisclosure-DG7JCo-d.js} +2 -2
  45. package/dist/chunks/{LpDrawer-CrG8t_KP.js → LpDrawer-Bt9UUqt_.js} +61 -61
  46. package/dist/chunks/{LpDropdownMenu-Dl7Z4ZhS.js → LpDropdownMenu-CLzxIOgM.js} +9 -17
  47. package/dist/chunks/{LpEditable-Cy6GLbFx.js → LpEditable-DVT0Rh8F.js} +9 -6
  48. package/dist/chunks/{LpEmptyState-EQHiRYhG.js → LpEmptyState-DurPF001.js} +1 -1
  49. package/dist/chunks/{LpFieldNotice-CzQ2_5rQ.js → LpFieldNotice-DJ10cYIo.js} +1 -1
  50. package/dist/chunks/LpFileTree-BSx1kBoV.js +735 -0
  51. package/dist/chunks/{LpFileTreeNode-cT6BIWvz.js → LpFileTreeNode-4dVmFHb3.js} +116 -34
  52. package/dist/chunks/{LpFormField-Ch69Jd-X.js → LpFormField-BKYn9dyP.js} +3 -3
  53. package/dist/chunks/{LpFrame-BVQCQLDI.js → LpFrame-Bv5Fphte.js} +25 -11
  54. package/dist/chunks/{LpHintExample-9Py0wc-L.js → LpHintExample-YzCXiSUp.js} +1 -1
  55. package/dist/chunks/{LpHintLayer-CgObdUsM.js → LpHintLayer-BeKRsRon.js} +2 -2
  56. package/dist/chunks/{LpHints-Bz-BlaSl.js → LpHints-qf3xYsza.js} +3 -3
  57. package/dist/chunks/{LpHoverCard-spGxyw7T.js → LpHoverCard-DanH3Nz1.js} +23 -5
  58. package/dist/chunks/{LpIcon--5WSb3jJ.js → LpIcon-C6JpQ5td.js} +6 -3
  59. package/dist/chunks/{LpIdenticon-BzdC_qz9.js → LpIdenticon-Dbbr4fvt.js} +2 -1
  60. package/dist/chunks/{LpInfraNode-46XOCkfq.js → LpInfraNode-BAU40Km2.js} +2 -2
  61. package/dist/chunks/{LpInput-Tw-yxT4I.js → LpInput-DL6HEJdi.js} +16 -28
  62. package/dist/chunks/LpInspector-CuoMAhiw.js +209 -0
  63. package/dist/chunks/{LpLightbox-B95yu22B.js → LpLightbox-xvpQ64MM.js} +57 -32
  64. package/dist/chunks/{LpLoader-DVwkOmt9.js → LpLoader-Bf1yZwaU.js} +1 -1
  65. package/dist/chunks/LpLogInspector-6cNnXqp4.js +119 -0
  66. package/dist/chunks/{LpLogViewer-ngqkns4m.js → LpLogViewer-Dvb1E9_G.js} +234 -53
  67. package/dist/chunks/{LpMapPicker-DRGgh89B.js → LpMapPicker-D5_YLLBC.js} +4 -3
  68. package/dist/chunks/{LpMasonry--k0571qG.js → LpMasonry-DPYekFxs.js} +8 -5
  69. package/dist/chunks/{LpMediaGallery-Bkram-MO.js → LpMediaGallery-Df-tiWeq.js} +3 -3
  70. package/dist/chunks/{LpModal-CoGhLTyH.js → LpModal-DrtQ_1A2.js} +11 -13
  71. package/dist/chunks/{LpModalNav-BGvs2y5Q.js → LpModalNav-DkmGCnZE.js} +11 -37
  72. package/dist/chunks/{LpNotificationBell-B0034Lk-.js → LpNotificationBell-D_wklO1m.js} +9 -9
  73. package/dist/chunks/{LpNumberField-e0IBkWU1.js → LpNumberField-CNHsmSDZ.js} +7 -16
  74. package/dist/chunks/{LpOtpInput-CeBPsM7D.js → LpOtpInput-BRpLBbOD.js} +2 -2
  75. package/dist/chunks/{LpPagination-BIIiYHXR.js → LpPagination-snQ020xQ.js} +6 -4
  76. package/dist/chunks/{LpPaletteTheme-DiLYKQiZ.js → LpPaletteTheme-dVtxHx-F.js} +4 -4
  77. package/dist/chunks/{LpPasswordInput-BkHKQDhM.js → LpPasswordInput-B-zD6tUS.js} +2 -2
  78. package/dist/chunks/{LpPhoneInput-CETEp5W7.js → LpPhoneInput-BSL7NX4z.js} +15 -26
  79. package/dist/chunks/{LpPie-DrZepi0p.js → LpPie-9t_guO49.js} +22 -21
  80. package/dist/chunks/{LpPipeline-B3aOVOkV.js → LpPipeline-rjXvEDRJ.js} +16 -7
  81. package/dist/chunks/{LpPopover-DmZ3wUtP.js → LpPopover-BQSuxtai.js} +10 -1
  82. package/dist/chunks/{LpProgress-Yr6ONG5t.js → LpProgress-D_MSjowb.js} +2 -1
  83. package/dist/chunks/{LpQuota-Dk2MyI2Z.js → LpQuota-CgWPgdVt.js} +1 -1
  84. package/dist/chunks/{LpRadioGroup-ggaPL92T.js → LpRadioGroup-BrazjLY_.js} +4 -4
  85. package/dist/chunks/{LpRangeCalendar-CYYUYqJ7.js → LpRangeCalendar-BrqlGZzE.js} +1 -1
  86. package/dist/chunks/{LpRangeSlider-CM-0ZITZ.js → LpRangeSlider-fz61tzc-.js} +5 -9
  87. package/dist/chunks/{LpRelativeTime-DwgCb5-8.js → LpRelativeTime-BovUk8MP.js} +1 -1
  88. package/dist/chunks/{LpRepeater-D7ZmnIYd.js → LpRepeater-BkJHSPI1.js} +3 -3
  89. package/dist/chunks/{LpRevealOnScroll-DcQjexLy.js → LpRevealOnScroll-CPwyiQAr.js} +1 -1
  90. package/dist/chunks/{LpScramble-Bn9OoOtC.js → LpScramble-DsE77iVp.js} +1 -1
  91. package/dist/chunks/{LpScrollArea-MyEnVB_5.js → LpScrollArea-ay4Xo_hg.js} +22 -10
  92. package/dist/chunks/{LpSecretField-BbmqM9ye.js → LpSecretField-DBvU6iAw.js} +10 -9
  93. package/dist/chunks/{LpSection-DFK_uoJE.js → LpSection-BfteipxU.js} +1 -1
  94. package/dist/chunks/{LpSegmented-CeACsMMA.js → LpSegmented-DbEJhbv-.js} +8 -10
  95. package/dist/chunks/{LpSelect-Cxq4yBGd.js → LpSelect-Y-iuSymW.js} +47 -30
  96. package/dist/chunks/{LpShaderBackdrop-Bote0iuy.js → LpShaderBackdrop-wQygYb5h.js} +13 -10
  97. package/dist/chunks/{LpShift-CCq7dofl.js → LpShift-DAgS_d2x.js} +18 -15
  98. package/dist/chunks/{LpSidebar-DzE1783C.js → LpSidebar-kSkpPh3-.js} +74 -87
  99. package/dist/chunks/{LpSidebarNav-DE3PhXVP.js → LpSidebarNav-C2mvqrY2.js} +8 -6
  100. package/dist/chunks/{LpSidebarRow-BRYhNDSo.js → LpSidebarRow-eRP4Q31q.js} +6 -5
  101. package/dist/chunks/{LpSlider-DxYoCJJO.js → LpSlider-A4ixovgS.js} +5 -9
  102. package/dist/chunks/{LpSpotlight-4F3NeUMY.js → LpSpotlight-DwxkhFgR.js} +2 -1
  103. package/dist/chunks/{LpStat-f2LfDCJ4.js → LpStat-pMNZgFcq.js} +2 -2
  104. package/dist/chunks/{LpStatus-DglxttQ7.js → LpStatus-DGJFwk5F.js} +2 -2
  105. package/dist/chunks/{LpStepMark-DpzKVyh8.js → LpStepMark-Dw2MRTrt.js} +2 -2
  106. package/dist/chunks/{LpStepper-DpsocBNJ.js → LpStepper-DNnh7lCu.js} +1 -1
  107. package/dist/chunks/{LpSwap-C96c0LWh.js → LpSwap-M7nFeW3C.js} +1 -1
  108. package/dist/chunks/{LpSwitch-BnXd1llc.js → LpSwitch-DnC2-0aV.js} +3 -3
  109. package/dist/chunks/{LpTabFace-DPCwKkH2.js → LpTabFace-DQoV8mQG.js} +2 -2
  110. package/dist/chunks/{LpTableFilter-CiVBpaq0.js → LpTableFilter-DgARTsA1.js} +4 -4
  111. package/dist/chunks/{LpTableOfContents-BN5zvpnx.js → LpTableOfContents-CKkWEK6R.js} +14 -9
  112. package/dist/chunks/{LpTagsInput-C-mDG648.js → LpTagsInput-Dt0mteSj.js} +19 -27
  113. package/dist/chunks/{LpTextarea-TE-yZv2U.js → LpTextarea-Co0b9PVx.js} +8 -16
  114. package/dist/chunks/LpThemeProvider-XK6ZeWAE.js +81 -0
  115. package/dist/chunks/{LpThemeStudio-BpORDvH6.js → LpThemeStudio-DzYfE-5k.js} +12 -195
  116. package/dist/chunks/{LpThemeSwitcher-BnEn_mvf.js → LpThemeSwitcher-CFLkdbBL.js} +8 -6
  117. package/dist/chunks/{LpTimeField-BawSVsmt.js → LpTimeField-DeOcKWJq.js} +6 -9
  118. package/dist/chunks/{LpToaster-IwTXjbbU.js → LpToaster-QFj0P-5m.js} +10 -8
  119. package/dist/chunks/{LpTooltip-BcBvIx4z.js → LpTooltip-BJf3lZMq.js} +51 -19
  120. package/dist/chunks/LpTopologyCanvas-BOHHHZ_6.js +673 -0
  121. package/dist/chunks/{LpTour-BZq-mUMv.js → LpTour-ClKYhx4h.js} +7 -5
  122. package/dist/chunks/{LpTruncate-CflAIwiJ.js → LpTruncate-C6u3gjc6.js} +6 -5
  123. package/dist/chunks/{LpTypewriter-BCL_a6wE.js → LpTypewriter-CZIzpdbe.js} +2 -1
  124. package/dist/chunks/{LpUptimeBar-DqNvvMBK.js → LpUptimeBar-BnYmR2rj.js} +3 -3
  125. package/dist/chunks/{LpVideoBackdrop-DS1wGv-X.js → LpVideoBackdrop-Ct4St4ZL.js} +10 -13
  126. package/dist/chunks/LpWindow-B8YSRVTH.js +297 -0
  127. package/dist/chunks/LpWorkspaceSwitcher-D0e9upAy.js +139 -0
  128. package/dist/chunks/{MenuTitle-BmCNNTKH.js → MenuTitle-Bh5dAdV1.js} +1 -1
  129. package/dist/chunks/{OptionContent-22UT8zLn.js → OptionContent-CGb8QlHA.js} +1 -1
  130. package/dist/chunks/{PickerField-Bfh8id3d.js → PickerField-BM1dyafh.js} +4 -4
  131. package/dist/chunks/{RevealToggle-BEit1n6Z.js → RevealToggle-CCPDE8Mv.js} +3 -3
  132. package/dist/chunks/SliderRail-DhPREa_R.js +18 -0
  133. package/dist/chunks/{TourCard-DNclH3u_.js → TourCard-DAzTYfav.js} +4 -4
  134. package/dist/chunks/{icons-DMITlfw5.js → icons-CYfcpSDS.js} +26 -10
  135. package/dist/component-names.d.ts +1 -1
  136. package/dist/components/AppShellContextNav.vue.d.ts +13 -0
  137. package/dist/components/AppShellContextNav.vue.js +4 -0
  138. package/dist/components/ChartRangeSelector.vue.d.ts +21 -0
  139. package/dist/components/ChartRangeSelector.vue.js +4 -0
  140. package/dist/components/ContextMenuItems.vue.js +1 -1
  141. package/dist/components/DropzoneLinkField.vue.d.ts +2 -2
  142. package/dist/components/DropzoneLinkField.vue.js +1 -1
  143. package/dist/components/FieldWrapper.vue.js +1 -1
  144. package/dist/components/IconCloseButton.vue.d.ts +7 -0
  145. package/dist/components/IconCloseButton.vue.js +4 -0
  146. package/dist/components/LayoutCanvas.vue.js +1 -1
  147. package/dist/components/LayoutNode.vue.js +1 -1
  148. package/dist/components/LpActionBar.vue.js +1 -1
  149. package/dist/components/LpActivityLine.vue.js +1 -1
  150. package/dist/components/LpAddressInput.vue.js +1 -1
  151. package/dist/components/LpAlert.vue.js +1 -1
  152. package/dist/components/LpAppShell.vue.d.ts +110 -2
  153. package/dist/components/LpAppShell.vue.js +1 -1
  154. package/dist/components/LpAutocomplete.vue.js +1 -1
  155. package/dist/components/LpAvatar.vue.js +1 -1
  156. package/dist/components/LpBadge.vue.js +1 -1
  157. package/dist/components/LpBottomNav.vue.d.ts +5 -0
  158. package/dist/components/LpBottomNav.vue.js +1 -1
  159. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  160. package/dist/components/LpButton.vue.d.ts +14 -2
  161. package/dist/components/LpButton.vue.js +1 -1
  162. package/dist/components/LpCalendar.vue.js +1 -1
  163. package/dist/components/LpCard.vue.js +1 -1
  164. package/dist/components/LpChart.vue.d.ts +74 -2
  165. package/dist/components/LpChart.vue.js +326 -85
  166. package/dist/components/LpCheckbox.vue.d.ts +6 -2
  167. package/dist/components/LpCheckbox.vue.js +1 -1
  168. package/dist/components/LpCodeBlock.vue.js +1 -1
  169. package/dist/components/LpCodeDisplay.vue.js +1 -1
  170. package/dist/components/LpCollapse.vue.js +1 -1
  171. package/dist/components/LpColorPicker.vue.js +1 -1
  172. package/dist/components/LpCommandPalette.vue.d.ts +8 -0
  173. package/dist/components/LpCommandPalette.vue.js +107 -56
  174. package/dist/components/LpCommandScope.vue.js +1 -1
  175. package/dist/components/LpConfirmDialog.vue.js +1 -1
  176. package/dist/components/LpConfirmHost.vue.js +1 -1
  177. package/dist/components/LpContextMenu.vue.js +1 -1
  178. package/dist/components/LpContextSwitcher.vue.js +1 -1
  179. package/dist/components/LpCopyButton.vue.js +1 -1
  180. package/dist/components/LpCountdown.vue.js +1 -1
  181. package/dist/components/LpDataList.vue.d.ts +1 -1
  182. package/dist/components/LpDataList.vue.js +1 -1
  183. package/dist/components/LpDatePicker.vue.d.ts +3 -15
  184. package/dist/components/LpDatePicker.vue.js +1 -1
  185. package/dist/components/LpDateRangePicker.vue.d.ts +3 -13
  186. package/dist/components/LpDateRangePicker.vue.js +1 -1
  187. package/dist/components/LpDisclosure.vue.js +1 -1
  188. package/dist/components/LpDock.vue.js +21 -18
  189. package/dist/components/LpDrawer.vue.d.ts +1 -1
  190. package/dist/components/LpDrawer.vue.js +1 -1
  191. package/dist/components/LpDropdownMenu.vue.js +1 -1
  192. package/dist/components/LpDropzone.vue.js +14 -11
  193. package/dist/components/LpEditable.vue.js +1 -1
  194. package/dist/components/LpEmptyState.vue.js +1 -1
  195. package/dist/components/LpFieldNotice.vue.js +1 -1
  196. package/dist/components/LpFileTree.vue.d.ts +96 -6
  197. package/dist/components/LpFileTree.vue.js +1 -1
  198. package/dist/components/LpFileTreeNode.vue.d.ts +48 -0
  199. package/dist/components/LpFileTreeNode.vue.js +1 -1
  200. package/dist/components/LpFormField.vue.js +1 -1
  201. package/dist/components/LpFrame.vue.js +1 -1
  202. package/dist/components/LpGauge.vue.d.ts +1 -1
  203. package/dist/components/LpHintExample.vue.js +1 -1
  204. package/dist/components/LpHintLayer.vue.js +1 -1
  205. package/dist/components/LpHints.vue.js +1 -1
  206. package/dist/components/LpHoverCard.vue.js +1 -1
  207. package/dist/components/LpIcon.vue.d.ts +2 -0
  208. package/dist/components/LpIcon.vue.js +1 -1
  209. package/dist/components/LpIdenticon.vue.js +1 -1
  210. package/dist/components/LpImageEditor.vue.js +7 -6
  211. package/dist/components/LpInfraNode.vue.js +1 -1
  212. package/dist/components/LpInput.vue.d.ts +1 -1
  213. package/dist/components/LpInput.vue.js +1 -1
  214. package/dist/components/LpInspector.vue.d.ts +87 -0
  215. package/dist/components/LpInspector.vue.js +4 -0
  216. package/dist/components/LpLightbox.vue.d.ts +8 -0
  217. package/dist/components/LpLightbox.vue.js +1 -1
  218. package/dist/components/LpLoader.vue.d.ts +1 -1
  219. package/dist/components/LpLoader.vue.js +1 -1
  220. package/dist/components/LpLogInspector.vue.d.ts +18 -0
  221. package/dist/components/LpLogInspector.vue.js +4 -0
  222. package/dist/components/LpLogViewer.vue.d.ts +93 -0
  223. package/dist/components/LpLogViewer.vue.js +1 -1
  224. package/dist/components/LpMapPicker.vue.js +1 -1
  225. package/dist/components/LpMasonry.vue.js +1 -1
  226. package/dist/components/LpMediaGallery.vue.js +1 -1
  227. package/dist/components/LpModal.vue.d.ts +4 -2
  228. package/dist/components/LpModal.vue.js +1 -1
  229. package/dist/components/LpModalNav.vue.d.ts +5 -5
  230. package/dist/components/LpModalNav.vue.js +1 -1
  231. package/dist/components/LpNotificationBell.vue.d.ts +2 -2
  232. package/dist/components/LpNotificationBell.vue.js +1 -1
  233. package/dist/components/LpNumberField.vue.js +1 -1
  234. package/dist/components/LpOtpInput.vue.js +1 -1
  235. package/dist/components/LpPagination.vue.js +1 -1
  236. package/dist/components/LpPaletteFacts.vue.js +1 -1
  237. package/dist/components/LpPaletteHeader.vue.js +2 -2
  238. package/dist/components/LpPaletteTheme.vue.js +1 -1
  239. package/dist/components/LpPasswordInput.vue.js +1 -1
  240. package/dist/components/LpPhoneInput.vue.js +1 -1
  241. package/dist/components/LpPie.vue.d.ts +2 -2
  242. package/dist/components/LpPie.vue.js +1 -1
  243. package/dist/components/LpPipeline.vue.d.ts +4 -0
  244. package/dist/components/LpPipeline.vue.js +1 -1
  245. package/dist/components/LpPopover.vue.d.ts +7 -3
  246. package/dist/components/LpPopover.vue.js +1 -1
  247. package/dist/components/LpProgress.vue.d.ts +1 -1
  248. package/dist/components/LpProgress.vue.js +1 -1
  249. package/dist/components/LpQuota.vue.js +1 -1
  250. package/dist/components/LpRadioGroup.vue.js +1 -1
  251. package/dist/components/LpRangeCalendar.vue.js +1 -1
  252. package/dist/components/LpRangeSlider.vue.js +1 -1
  253. package/dist/components/LpRelativeTime.vue.js +1 -1
  254. package/dist/components/LpRepeater.vue.js +1 -1
  255. package/dist/components/LpRevealOnScroll.vue.js +1 -1
  256. package/dist/components/LpScramble.vue.js +1 -1
  257. package/dist/components/LpScrollArea.vue.js +1 -1
  258. package/dist/components/LpSecretField.vue.js +1 -1
  259. package/dist/components/LpSection.vue.js +1 -1
  260. package/dist/components/LpSegmented.vue.js +1 -1
  261. package/dist/components/LpSelect.vue.js +1 -1
  262. package/dist/components/LpShaderBackdrop.vue.js +1 -1
  263. package/dist/components/LpShift.vue.js +1 -1
  264. package/dist/components/LpShortcutsDialog.vue.js +1 -1
  265. package/dist/components/LpSidebar.vue.d.ts +1 -1
  266. package/dist/components/LpSidebar.vue.js +1 -1
  267. package/dist/components/LpSidebarNav.vue.js +1 -1
  268. package/dist/components/LpSidebarRow.vue.js +1 -1
  269. package/dist/components/LpSlider.vue.js +1 -1
  270. package/dist/components/LpSparkline.vue.d.ts +1 -1
  271. package/dist/components/LpSpotlight.vue.js +1 -1
  272. package/dist/components/LpStat.vue.js +1 -1
  273. package/dist/components/LpStatus.vue.js +1 -1
  274. package/dist/components/LpStepMark.vue.js +1 -1
  275. package/dist/components/LpStepper.vue.js +1 -1
  276. package/dist/components/LpSwap.vue.js +1 -1
  277. package/dist/components/LpSwitch.vue.js +1 -1
  278. package/dist/components/LpTabFace.vue.js +1 -1
  279. package/dist/components/LpTable.vue.d.ts +132 -0
  280. package/dist/components/LpTable.vue.js +547 -135
  281. package/dist/components/LpTableFilter.vue.js +1 -1
  282. package/dist/components/LpTableOfContents.vue.js +1 -1
  283. package/dist/components/LpTabs.vue.js +6 -5
  284. package/dist/components/LpTagsInput.vue.js +1 -1
  285. package/dist/components/LpTextarea.vue.js +1 -1
  286. package/dist/components/LpThemeProvider.vue.d.ts +2 -0
  287. package/dist/components/LpThemeProvider.vue.js +1 -1
  288. package/dist/components/LpThemeStudio.vue.js +1 -1
  289. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  290. package/dist/components/LpTimeField.vue.js +1 -1
  291. package/dist/components/LpToaster.vue.js +1 -1
  292. package/dist/components/LpTooltip.vue.js +1 -1
  293. package/dist/components/LpTopologyCanvas.vue.d.ts +131 -5
  294. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  295. package/dist/components/LpTour.vue.js +1 -1
  296. package/dist/components/LpTruncate.vue.js +1 -1
  297. package/dist/components/LpTypewriter.vue.js +1 -1
  298. package/dist/components/LpUptimeBar.vue.js +1 -1
  299. package/dist/components/LpVideoBackdrop.vue.js +1 -1
  300. package/dist/components/LpWindow.vue.d.ts +99 -0
  301. package/dist/components/LpWindow.vue.js +4 -0
  302. package/dist/components/LpWorkspaceSwitcher.vue.d.ts +18 -0
  303. package/dist/components/LpWorkspaceSwitcher.vue.js +4 -0
  304. package/dist/components/MenuTitle.vue.js +1 -1
  305. package/dist/components/OptionContent.vue.js +1 -1
  306. package/dist/components/PickerField.vue.d.ts +2 -2
  307. package/dist/components/PickerField.vue.js +1 -1
  308. package/dist/components/RevealToggle.vue.js +1 -1
  309. package/dist/components/SliderRail.vue.d.ts +3 -0
  310. package/dist/components/SliderRail.vue.js +4 -0
  311. package/dist/components/TourCard.vue.js +1 -1
  312. package/dist/components/address.js +2 -1
  313. package/dist/components/appShell.d.ts +36 -0
  314. package/dist/components/appShell.js +30 -0
  315. package/dist/components/calendar.d.ts +12 -3
  316. package/dist/components/calendar.js +2 -5
  317. package/dist/components/chart.d.ts +21 -0
  318. package/dist/components/chart.js +4 -3
  319. package/dist/components/chartWindow.d.ts +57 -0
  320. package/dist/components/chartWindow.js +136 -0
  321. package/dist/components/color.js +1 -1
  322. package/dist/components/commandPalette.d.ts +16 -2
  323. package/dist/components/commandPalette.js +39 -4
  324. package/dist/components/controlSize.d.ts +5 -0
  325. package/dist/components/controlSize.js +8 -0
  326. package/dist/components/dropdown.d.ts +5 -0
  327. package/dist/components/dropdown.js +2 -0
  328. package/dist/components/exportData.d.ts +6 -1
  329. package/dist/components/exportData.js +25 -18
  330. package/dist/components/fileTree.d.ts +67 -0
  331. package/dist/components/fileTree.js +141 -1
  332. package/dist/components/identicon.js +3 -2
  333. package/dist/components/logTools.d.ts +93 -0
  334. package/dist/components/logTools.js +210 -0
  335. package/dist/components/menuWhen.js +1 -1
  336. package/dist/components/meter.js +2 -1
  337. package/dist/components/pipeline.d.ts +2 -0
  338. package/dist/components/pipeline.js +5 -0
  339. package/dist/components/relativeTime.js +2 -1
  340. package/dist/components/reveal.js +2 -1
  341. package/dist/components/select.d.ts +5 -0
  342. package/dist/components/select.js +10 -0
  343. package/dist/components/sidebar.d.ts +1 -0
  344. package/dist/components/tableFeatures.d.ts +58 -0
  345. package/dist/components/tableFeatures.js +87 -0
  346. package/dist/components/topologyGraph.d.ts +164 -0
  347. package/dist/components/topologyGraph.js +416 -0
  348. package/dist/components/tour.js +5 -3
  349. package/dist/components/videoBackdrop.js +7 -6
  350. package/dist/composables/clamp.d.ts +2 -0
  351. package/dist/composables/clamp.js +4 -0
  352. package/dist/composables/devWarn.d.ts +3 -0
  353. package/dist/composables/devWarn.js +14 -0
  354. package/dist/composables/easing.d.ts +13 -4
  355. package/dist/composables/easing.js +16 -10
  356. package/dist/composables/overlayHistory.js +14 -1
  357. package/dist/composables/pointerDrag.d.ts +15 -0
  358. package/dist/composables/pointerDrag.js +40 -0
  359. package/dist/composables/propCheck.d.ts +10 -0
  360. package/dist/composables/propCheck.js +78 -0
  361. package/dist/composables/realm.d.ts +6 -0
  362. package/dist/composables/realm.js +23 -0
  363. package/dist/composables/scroll.js +4 -2
  364. package/dist/composables/scrollMemory.d.ts +12 -1
  365. package/dist/composables/scrollMemory.js +36 -12
  366. package/dist/composables/spatialNav.js +2 -1
  367. package/dist/composables/textEntry.d.ts +17 -0
  368. package/dist/composables/textEntry.js +19 -0
  369. package/dist/composables/useAsyncTask.d.ts +6 -0
  370. package/dist/composables/useChartSync.d.ts +5 -0
  371. package/dist/composables/useChartSync.js +33 -0
  372. package/dist/composables/useCloseGuard.d.ts +3 -1
  373. package/dist/composables/useCloseGuard.js +6 -3
  374. package/dist/composables/useCommands.d.ts +50 -0
  375. package/dist/composables/useCommands.js +66 -0
  376. package/dist/composables/useField.d.ts +37 -0
  377. package/dist/composables/useField.js +27 -2
  378. package/dist/composables/useFitScale.js +4 -1
  379. package/dist/composables/useFormDraft.d.ts +13 -2
  380. package/dist/composables/useFormDraft.js +17 -9
  381. package/dist/composables/useHintSystem.js +2 -2
  382. package/dist/composables/useHotkeys.js +11 -32
  383. package/dist/composables/useInView.js +2 -1
  384. package/dist/composables/useInputFilter.js +7 -5
  385. package/dist/composables/useInputMask.js +5 -3
  386. package/dist/composables/useLinkedView.d.ts +97 -0
  387. package/dist/composables/useLinkedView.js +139 -0
  388. package/dist/composables/useOptimisticToggle.d.ts +3 -1
  389. package/dist/composables/useOptimisticToggle.js +2 -1
  390. package/dist/composables/useOverlayHistory.d.ts +7 -0
  391. package/dist/composables/useOverlayHistory.js +16 -2
  392. package/dist/composables/usePaintSelect.js +18 -14
  393. package/dist/composables/usePaletteCommands.d.ts +7 -0
  394. package/dist/composables/usePaletteCommands.js +12 -1
  395. package/dist/composables/usePersisted.d.ts +10 -0
  396. package/dist/composables/usePersisted.js +1 -1
  397. package/dist/composables/usePillTransition.d.ts +2 -2
  398. package/dist/composables/usePillTransition.js +5 -5
  399. package/dist/composables/useResizeObserver.js +2 -1
  400. package/dist/composables/useReveal.js +2 -1
  401. package/dist/composables/useRovingFocus.d.ts +2 -2
  402. package/dist/composables/useRovingFocus.js +9 -11
  403. package/dist/composables/useScramble.js +7 -1
  404. package/dist/composables/useShift.js +8 -6
  405. package/dist/composables/useTilt.js +2 -1
  406. package/dist/composables/useTween.js +9 -5
  407. package/dist/composables/useView.d.ts +1 -0
  408. package/dist/composables/useView.js +14 -2
  409. package/dist/composables/useZoomPan.js +2 -4
  410. package/dist/composables/windowing.d.ts +40 -0
  411. package/dist/composables/windowing.js +118 -0
  412. package/dist/icons-all.js +1 -1
  413. package/dist/icons.d.ts +4 -0
  414. package/dist/index.d.ts +30 -5
  415. package/dist/index.js +310 -264
  416. package/dist/layout/tree.js +19 -15
  417. package/dist/layout/useLayout.d.ts +2 -0
  418. package/dist/layout/useLayout.js +5 -1
  419. package/dist/layout/useWorkspaces.d.ts +52 -0
  420. package/dist/layout/useWorkspaces.js +140 -0
  421. package/dist/{chunks/backdrop-Dkf2vM81.js → theme/backdrop.js} +5 -5
  422. package/dist/theme/config.js +25 -0
  423. package/dist/theme/define.js +61 -0
  424. package/dist/{chunks/palette-Co8tSASo.js → theme/palette.js} +2 -2
  425. package/dist/theme/presets.js +239 -0
  426. package/dist/theme/studio.js +134 -0
  427. package/dist/theme/tokens.js +63 -0
  428. package/dist/theme/useTheme.d.ts +3 -2
  429. package/dist/theme/useTheme.js +325 -0
  430. package/package.json +8 -1
  431. package/src/component-names.ts +5 -1
  432. package/src/components/AppShellContextNav.vue +44 -0
  433. package/src/components/ChartRangeSelector.vue +120 -0
  434. package/src/components/IconCloseButton.vue +21 -0
  435. package/src/components/LayoutCanvas.vue +3 -0
  436. package/src/components/LayoutNode.vue +37 -38
  437. package/src/components/LpActivityLine.vue +4 -2
  438. package/src/components/LpAddressInput.vue +3 -3
  439. package/src/components/LpAppShell.vue +362 -24
  440. package/src/components/LpAutocomplete.vue +18 -30
  441. package/src/components/LpBottomNav.vue +9 -1
  442. package/src/components/LpBreadcrumbs.vue +2 -1
  443. package/src/components/LpButton.vue +11 -4
  444. package/src/components/LpChart.vue +336 -37
  445. package/src/components/LpCheckbox.vue +43 -9
  446. package/src/components/LpCodeBlock.vue +17 -23
  447. package/src/components/LpCollapse.vue +5 -4
  448. package/src/components/LpColorPicker.vue +8 -9
  449. package/src/components/LpCommandPalette.vue +133 -50
  450. package/src/components/LpContextMenu.vue +15 -13
  451. package/src/components/LpCountdown.vue +2 -1
  452. package/src/components/LpDataList.vue +13 -1
  453. package/src/components/LpDatePicker.vue +5 -17
  454. package/src/components/LpDateRangePicker.vue +5 -16
  455. package/src/components/LpDock.vue +25 -16
  456. package/src/components/LpDrawer.vue +70 -61
  457. package/src/components/LpDropdownMenu.vue +6 -10
  458. package/src/components/LpDropzone.vue +12 -7
  459. package/src/components/LpEditable.vue +6 -2
  460. package/src/components/LpFileTree.vue +507 -66
  461. package/src/components/LpFileTreeNode.vue +106 -5
  462. package/src/components/LpFormField.vue +1 -1
  463. package/src/components/LpFrame.vue +33 -13
  464. package/src/components/LpHoverCard.vue +30 -5
  465. package/src/components/LpIcon.vue +6 -2
  466. package/src/components/LpIdenticon.vue +2 -1
  467. package/src/components/LpImageEditor.vue +5 -2
  468. package/src/components/LpInput.vue +15 -23
  469. package/src/components/LpInspector.vue +197 -0
  470. package/src/components/LpLightbox.vue +74 -29
  471. package/src/components/LpLogInspector.vue +85 -0
  472. package/src/components/LpLogViewer.vue +239 -29
  473. package/src/components/LpMapPicker.vue +2 -1
  474. package/src/components/LpMasonry.vue +8 -5
  475. package/src/components/LpModal.vue +12 -12
  476. package/src/components/LpModalNav.vue +2 -21
  477. package/src/components/LpNumberField.vue +6 -15
  478. package/src/components/LpOtpInput.vue +2 -2
  479. package/src/components/LpPagination.vue +5 -3
  480. package/src/components/LpPhoneInput.vue +8 -19
  481. package/src/components/LpPie.vue +25 -23
  482. package/src/components/LpPipeline.vue +14 -1
  483. package/src/components/LpPopover.vue +23 -2
  484. package/src/components/LpProgress.vue +2 -1
  485. package/src/components/LpRadioGroup.vue +2 -2
  486. package/src/components/LpRangeSlider.vue +5 -6
  487. package/src/components/LpRevealOnScroll.vue +1 -1
  488. package/src/components/LpScrollArea.vue +28 -11
  489. package/src/components/LpSecretField.vue +2 -1
  490. package/src/components/LpSegmented.vue +6 -8
  491. package/src/components/LpSelect.vue +54 -24
  492. package/src/components/LpShaderBackdrop.vue +12 -9
  493. package/src/components/LpShift.vue +18 -15
  494. package/src/components/LpSidebar.vue +77 -74
  495. package/src/components/LpSidebarNav.vue +3 -2
  496. package/src/components/LpSidebarRow.vue +2 -1
  497. package/src/components/LpSlider.vue +5 -6
  498. package/src/components/LpSpotlight.vue +2 -1
  499. package/src/components/LpSwitch.vue +5 -3
  500. package/src/components/LpTable.vue +609 -93
  501. package/src/components/LpTableFilter.vue +1 -1
  502. package/src/components/LpTableOfContents.vue +16 -9
  503. package/src/components/LpTabs.vue +2 -1
  504. package/src/components/LpTagsInput.vue +12 -19
  505. package/src/components/LpTextarea.vue +5 -11
  506. package/src/components/LpThemeProvider.vue +29 -13
  507. package/src/components/LpTimeField.vue +4 -7
  508. package/src/components/LpToaster.vue +7 -6
  509. package/src/components/LpTooltip.vue +62 -18
  510. package/src/components/LpTopologyCanvas.vue +567 -42
  511. package/src/components/LpTour.vue +7 -5
  512. package/src/components/LpTruncate.vue +4 -3
  513. package/src/components/LpTypewriter.vue +2 -1
  514. package/src/components/LpVideoBackdrop.vue +11 -13
  515. package/src/components/LpWindow.vue +373 -0
  516. package/src/components/LpWorkspaceSwitcher.vue +116 -0
  517. package/src/components/PickerField.vue +2 -2
  518. package/src/components/SliderRail.vue +10 -0
  519. package/src/components/address.ts +3 -1
  520. package/src/components/appShell.ts +74 -0
  521. package/src/components/calendar.ts +12 -5
  522. package/src/components/chart.ts +27 -3
  523. package/src/components/chartWindow.ts +198 -0
  524. package/src/components/color.ts +2 -1
  525. package/src/components/commandPalette.ts +66 -5
  526. package/src/components/controlSize.ts +6 -0
  527. package/src/components/dropdown.ts +3 -0
  528. package/src/components/exportData.ts +33 -18
  529. package/src/components/fileTree.ts +215 -0
  530. package/src/components/identicon.ts +4 -2
  531. package/src/components/logTools.ts +298 -0
  532. package/src/components/meter.ts +2 -1
  533. package/src/components/pipeline.ts +6 -0
  534. package/src/components/relativeTime.ts +2 -1
  535. package/src/components/reveal.ts +3 -1
  536. package/src/components/select.ts +14 -0
  537. package/src/components/sidebar.ts +1 -0
  538. package/src/components/tableFeatures.ts +149 -0
  539. package/src/components/topologyGraph.ts +655 -0
  540. package/src/components/tour.ts +5 -3
  541. package/src/components/videoBackdrop.ts +7 -6
  542. package/src/composables/clamp.ts +2 -0
  543. package/src/composables/devWarn.ts +19 -0
  544. package/src/composables/easing.ts +37 -12
  545. package/src/composables/overlayHistory.ts +24 -0
  546. package/src/composables/pointerDrag.ts +54 -0
  547. package/src/composables/propCheck.ts +90 -0
  548. package/src/composables/realm.ts +25 -0
  549. package/src/composables/scroll.ts +4 -2
  550. package/src/composables/scrollMemory.ts +56 -14
  551. package/src/composables/spatialNav.ts +2 -1
  552. package/src/composables/textEntry.ts +33 -0
  553. package/src/composables/useAsyncTask.ts +6 -0
  554. package/src/composables/useChartSync.ts +52 -0
  555. package/src/composables/useCloseGuard.ts +22 -10
  556. package/src/composables/useCommands.ts +123 -0
  557. package/src/composables/useField.ts +41 -2
  558. package/src/composables/useFitScale.ts +4 -1
  559. package/src/composables/useFormDraft.ts +38 -14
  560. package/src/composables/useHintSystem.ts +2 -2
  561. package/src/composables/useHotkeys.ts +19 -48
  562. package/src/composables/useInView.ts +2 -1
  563. package/src/composables/useInputFilter.ts +8 -5
  564. package/src/composables/useInputMask.ts +8 -3
  565. package/src/composables/useLinkedView.ts +222 -0
  566. package/src/composables/useOptimisticToggle.ts +7 -1
  567. package/src/composables/useOverlayHistory.ts +20 -0
  568. package/src/composables/usePaintSelect.ts +20 -14
  569. package/src/composables/usePaletteCommands.ts +13 -0
  570. package/src/composables/usePersisted.ts +10 -0
  571. package/src/composables/usePillTransition.ts +9 -6
  572. package/src/composables/useResizeObserver.ts +2 -1
  573. package/src/composables/useReveal.ts +2 -1
  574. package/src/composables/useRovingFocus.ts +11 -14
  575. package/src/composables/useScramble.ts +12 -1
  576. package/src/composables/useShift.ts +8 -6
  577. package/src/composables/useTilt.ts +2 -1
  578. package/src/composables/useTween.ts +10 -5
  579. package/src/composables/useView.ts +17 -3
  580. package/src/composables/useZoomPan.ts +2 -5
  581. package/src/composables/windowing.ts +190 -0
  582. package/src/icons.ts +27 -6
  583. package/src/index.ts +41 -4
  584. package/src/kit-icons.ts +1 -1
  585. package/src/layout/tree.ts +31 -16
  586. package/src/layout/useLayout.ts +6 -0
  587. package/src/layout/useWorkspaces.ts +234 -0
  588. package/src/nuxt.ts +9 -1
  589. package/src/runtime/propCheck.plugin.ts +10 -0
  590. package/src/theme/studio.ts +7 -5
  591. package/src/theme/useTheme.ts +89 -15
  592. package/src/tokens/tokens.css +8 -1
  593. package/dist/assets/LpTabs-CJ0rYQbx.css +0 -18
  594. package/dist/chunks/LpAppShell-Dgejipzz.js +0 -319
  595. package/dist/chunks/LpFileTree-CooHinsC.js +0 -425
  596. package/dist/chunks/LpThemeProvider-8MG6yqU9.js +0 -640
  597. package/dist/chunks/LpTopologyCanvas-B1gIbS0n.js +0 -298
@@ -3,6 +3,11 @@
3
3
  // appended to it, so opt out of the automatic pass-through that would add it
4
4
  // a second time, unmerged.
5
5
  export default { inheritAttrs: false }
6
+
7
+ /* One text-sm line with the row's own vertical padding. */
8
+ const DEFAULT_ROW_HEIGHT = 28
9
+ /* Rows rendered past each end of the view, so a fast scroll shows no gap. */
10
+ const OVERSCAN_ROWS = 8
6
11
  </script>
7
12
 
8
13
  <script setup lang="ts">
@@ -14,7 +19,8 @@ export default { inheritAttrs: false }
14
19
  * context menus.
15
20
  *
16
21
  * Read-only by design — it navigates, it doesn't mutate. Renames/uploads belong
17
- * to the caller, reachable through `node.menu`.
22
+ * to the caller, reachable through `node.menu`, and `renamable` and `movable`
23
+ * only ask: the tree emits `rename` and `move`, the app changes `nodes`.
18
24
  *
19
25
  * Lazy loading: a directory whose `children` is undefined is treated as
20
26
  * not-yet-loaded — it stays expandable, shows a spinner while the caller fetches
@@ -23,27 +29,57 @@ export default { inheritAttrs: false }
23
29
  * Keyboard (role="tree" conventions): ↑/↓ move through visible rows, → opens a
24
30
  * directory or steps into it, ← collapses or steps out to the parent, Home/End
25
31
  * jump to the ends, Enter/Space activates. A roving tabindex keeps the tree a
26
- * single tab stop.
32
+ * single tab stop. `renamable` adds F2; `multiple` adds Shift+↑/↓ and Ctrl+A.
33
+ *
34
+ * Optional, and off unless asked for:
35
+ * - `searchable`, `filter`: only the matches and the folders leading to them.
36
+ * Only loaded entries can be found. A ticked folder ticks what is shown.
37
+ * - `breadcrumb`: the path of the selected (or focused) row, each step a way back.
38
+ * - `load`: the tree fetches a folder itself, aborting if it is closed meanwhile.
39
+ * - `multiple`: Ctrl/Cmd and Shift pick rows into `selection`, by id, so a pick
40
+ * outlives its folder being closed or filtered away.
41
+ * - `movable`: drag rows onto a folder (mouse only, HTML drag and drop).
42
+ * - `virtual`: renders only the rows in view, for directories of thousands. The
43
+ * DOM is flat, so rows state aria-level, aria-setsize and aria-posinset instead
44
+ * of nesting in role="group"; aria-setsize stays true for rows not rendered.
45
+ * The focused row always stays mounted, so the tab stop never goes. Rows need
46
+ * one height (`rowHeight`), and folders open at once, with no fold.
27
47
  */
28
- import { computed, ref, watch } from "vue"
48
+ import { clamp } from "../composables/clamp"
49
+ import { computed, h, nextTick, onBeforeUnmount, ref, shallowRef, watch, type FunctionalComponent } from "vue"
29
50
  import { useLocale } from "../composables/useLocale"
51
+ import { isElement } from "../composables/useView"
30
52
  import {
31
53
  ancestorIds,
54
+ canDrop,
32
55
  computeStats,
56
+ filterTree,
57
+ flattenVisible,
33
58
  formatSize,
59
+ isFilterEmpty,
60
+ nodeTrail,
61
+ rangeIds,
62
+ renameProblem,
63
+ rowWindow,
34
64
  sortNodes,
35
65
  subtreeIds,
66
+ topmostIds,
67
+ withLoadedChildren,
36
68
  type FileNode,
69
+ type FileTreeFilter,
37
70
  } from "./fileTree"
71
+ import LpBreadcrumbs from "./LpBreadcrumbs.vue"
38
72
  import LpEmptyState from "./LpEmptyState.vue"
39
73
  import LpFileTreeNode from "./LpFileTreeNode.vue"
74
+ import LpInput from "./LpInput.vue"
40
75
  import LpScrollArea from "./LpScrollArea.vue"
41
76
  import LpSkeleton from "./LpSkeleton.vue"
42
77
  import { usePersisted } from "../composables/usePersisted"
43
78
  import { useMergedAttrs } from "../composables/useMergedClass"
44
79
  import { useLabels } from "../composables/useLpConfig"
80
+ import { useResizeObserver } from "../composables/useResizeObserver"
45
81
 
46
- export type { FileNode } from "./fileTree"
82
+ export type { FileNode, FileTreeFilter } from "./fileTree"
47
83
 
48
84
  /** Rolled-up stats for the ticked entries — what a backup picker summarises. */
49
85
  export interface FileTreeLabels {
@@ -54,6 +90,16 @@ export interface FileTreeLabels {
54
90
  files: string
55
91
  folder: string
56
92
  folders: string
93
+ search: string
94
+ noMatches: string
95
+ rename: string
96
+ path: string
97
+ }
98
+
99
+ /** `move`: what was dropped (a moved folder carries its content) and where. */
100
+ export interface FileTreeMove {
101
+ nodes: FileNode[]
102
+ target: FileNode
57
103
  }
58
104
 
59
105
  export interface FileTreeSummary {
@@ -72,7 +118,10 @@ const props = withDefaults(
72
118
  nodes: FileNode[]
73
119
  /** Selected node id (v-model:selected). */
74
120
  selected?: string
75
- /** Expanded directory ids (v-model:expanded). Omit to let the tree own it. */
121
+ /**
122
+ * Expanded directory ids (v-model:expanded). Omit to let the tree own it.
123
+ * Unbinding it later keeps the last set shown; it does not go back to `persist`.
124
+ */
76
125
  expanded?: string[]
77
126
  /**
78
127
  * Remember which folders are open across visits under this key (the
@@ -87,7 +136,11 @@ const props = withDefaults(
87
136
  * to back up" — `selected` stays independent (it's the highlighted row).
88
137
  */
89
138
  checkable?: boolean
90
- /** Ticked node ids (v-model:checked). */
139
+ /**
140
+ * Ticked node ids (v-model:checked). An id no longer in `nodes` stays ticked:
141
+ * with lazy children it may only be unloaded, so dropping a deleted one is
142
+ * the app's call.
143
+ */
91
144
  checked?: string[]
92
145
  /** Right-aligned size column (directories roll up their subtree). */
93
146
  showSize?: boolean
@@ -104,8 +157,36 @@ const props = withDefaults(
104
157
  emptyLabel?: string
105
158
  emptyDescription?: string
106
159
  labels?: Partial<FileTreeLabels>
160
+ /** Narrow the tree by name, path, type, size or date. Combined with `searchable`. */
161
+ filter?: FileTreeFilter
162
+ /** A search field above the tree. */
163
+ searchable?: boolean
164
+ /** The search text (v-model:query). Omit to let the tree own it. */
165
+ query?: string
166
+ /** The path of the selected row above the tree, each step a way back to it. */
167
+ breadcrumb?: boolean
168
+ /**
169
+ * Fetch a folder when it first opens; the result is kept by the tree and
170
+ * `children` in `nodes` win over it. `signal` aborts when the folder is
171
+ * closed first. Rejecting emits `load-error` and the next open tries again.
172
+ * `expand` still fires. Without this, `expand` is yours to answer.
173
+ */
174
+ load?: (node: FileNode, signal: AbortSignal) => Promise<FileNode[]>
175
+ /** Ctrl/Cmd and Shift pick several rows into `selection`. */
176
+ multiple?: boolean
177
+ /** Picked ids (v-model:selection), kept while their folders close or filter away. */
178
+ selection?: string[]
179
+ /** Drag rows onto a folder. Emits `move`; the app changes `nodes`. Mouse only. */
180
+ movable?: boolean
181
+ /** F2 renames the focused row in place. Emits `rename`; the app changes `nodes`. */
182
+ renamable?: boolean
183
+ /** Render only the rows in view. See the note above on what it changes. */
184
+ virtual?: boolean
185
+ /** Height of every row in `virtual` mode, in px. */
186
+ rowHeight?: number
107
187
  }>(),
108
188
  {
189
+ rowHeight: DEFAULT_ROW_HEIGHT,
109
190
  sort: true,
110
191
  iconSize: 15,
111
192
  checkable: false,
@@ -129,6 +210,13 @@ const emit = defineEmits<{
129
210
  (e: "update:checked", ids: string[]): void
130
211
  /** Ticked set changed, with the rolled-up stats already computed. */
131
212
  (e: "summary", summary: FileTreeSummary): void
213
+ (e: "update:query", value: string): void
214
+ (e: "update:selection", ids: string[]): void
215
+ /** Rows dropped on a folder. Nothing moves until the app changes `nodes`. */
216
+ (e: "move", move: FileTreeMove): void
217
+ /** A row given a new, valid name. Nothing changes until the app changes `nodes`. */
218
+ (e: "rename", node: FileNode, name: string): void
219
+ (e: "load-error", node: FileNode, error: unknown): void
132
220
  }>()
133
221
 
134
222
  defineSlots<{
@@ -166,11 +254,20 @@ const text = useLabels<FileTreeLabels>(
166
254
  files: "files",
167
255
  folder: "folder",
168
256
  folders: "folders",
257
+ search: "Search files",
258
+ noMatches: "No matching files",
259
+ rename: "Rename",
260
+ path: "Path",
169
261
  },
170
262
  () => props.labels,
171
263
  )
172
264
 
173
- const loadingSet = computed(() => new Set(props.loadingIds ?? []))
265
+ // What `load` brought back, laid over `nodes`. Everything below reads `source`.
266
+ const loadedChildren = shallowRef<ReadonlyMap<string, FileNode[]>>(new Map())
267
+ const source = computed(() => withLoadedChildren(props.nodes, loadedChildren.value))
268
+
269
+ const ownLoading = ref<Set<string>>(new Set())
270
+ const loadingSet = computed(() => new Set([...(props.loadingIds ?? []), ...ownLoading.value]))
174
271
 
175
272
  // Same controlled/uncontrolled deal as `expanded`.
176
273
  const checkedIds = ref<Set<string>>(new Set(props.checked ?? []))
@@ -196,7 +293,7 @@ const index = computed(() => {
196
293
  if (node.children) walk(node.children, node.id)
197
294
  }
198
295
  }
199
- walk(props.nodes)
296
+ walk(source.value)
200
297
  return { byId, parentOfId }
201
298
  })
202
299
 
@@ -206,7 +303,7 @@ const byId = computed(() => index.value.byId)
206
303
  // walking its own subtree on each render. Declared before `summary`, which
207
304
  // reads it and is watched eagerly — a `const` below that watcher is still in
208
305
  // its temporal dead zone when the watcher first runs.
209
- const stats = computed(() => computeStats(props.nodes, checkedIds.value))
306
+ const stats = computed(() => computeStats(source.value, checkedIds.value))
210
307
 
211
308
  // Read by `summary` below, so declared ahead of it for the same reason.
212
309
  const locale = useLocale()
@@ -262,7 +359,7 @@ const summary = computed<FileTreeSummary>(() => {
262
359
  }
263
360
  }
264
361
 
265
- walk(props.nodes)
362
+ walk(source.value)
266
363
  return {
267
364
  files,
268
365
  dirs,
@@ -322,25 +419,41 @@ function clearChecked() {
322
419
  // doesn't refetch. Callers that want a refresh can drop `children` again.
323
420
  const requested = new Set<string>()
324
421
 
325
- const roots = computed(() => (props.sort ? sortNodes(props.nodes) : props.nodes))
422
+ // The search text: the bound `query`, else the tree's own.
423
+ const ownQuery = ref("")
424
+ const queryText = computed(() => props.query ?? ownQuery.value)
425
+ function setQuery(value: string) {
426
+ ownQuery.value = value
427
+ emit("update:query", value)
428
+ }
326
429
 
327
- /** Rows currently visible, in render order — the axis the arrow keys walk. */
328
- const visible = computed(() => {
329
- const out: FileNode[] = []
330
- function walk(list: FileNode[]) {
331
- for (const node of props.sort ? sortNodes(list) : list) {
332
- out.push(node)
333
- if (node.kind === "dir" && openIds.value.has(node.id) && node.children?.length) {
334
- walk(node.children)
335
- }
336
- }
337
- }
338
- walk(props.nodes)
339
- return out
430
+ const activeFilter = computed<FileTreeFilter>(() => ({ ...props.filter, query: queryText.value || props.filter?.query }))
431
+ const filtering = computed(() => !isFilterEmpty(activeFilter.value))
432
+ const filtered = computed(() => filterTree(source.value, activeFilter.value))
433
+
434
+ // A filter opens the folders holding a match without touching the user's own
435
+ // expansion. Closing one of those lasts until the filter changes.
436
+ const closedWhileFiltering = ref<Set<string>>(new Set())
437
+ watch([() => props.filter, queryText], () => {
438
+ closedWhileFiltering.value = new Set()
340
439
  })
440
+ const shownOpen = computed(() => {
441
+ if (!filtering.value) return openIds.value
442
+ const open = new Set([...openIds.value, ...filtered.value.openIds])
443
+ for (const id of closedWhileFiltering.value) open.delete(id)
444
+ return open
445
+ })
446
+
447
+ const roots = computed(() => (props.sort ? sortNodes(filtered.value.nodes) : filtered.value.nodes))
448
+
449
+ const rows = computed(() => flattenVisible(filtered.value.nodes, shownOpen.value, props.sort))
450
+
451
+ /** Rows currently visible, in render order — the axis the arrow keys walk. */
452
+ const visible = computed(() => rows.value.map((row) => row.node))
341
453
 
342
454
  // The roving-tabindex row. Follows selection when the caller sets one; falls
343
455
  // back to the first row so the tree is reachable by Tab before any click.
456
+ const root = ref<HTMLElement | null>(null)
344
457
  const focusedId = ref<string | undefined>(props.selected)
345
458
  watch(
346
459
  () => props.selected,
@@ -355,17 +468,19 @@ watch(
355
468
  focusedId.value = undefined
356
469
  } else if (!rows.some((n) => n.id === focusedId.value)) {
357
470
  // The focused row scrolled out of existence (parent collapsed, data
358
- // changed) — fall back to the first visible row.
471
+ // changed) — fall back to the first visible row. Runs before the DOM
472
+ // update, so it can still see whether that row held the keyboard, which
473
+ // would otherwise drop to the page with the row.
474
+ const active = root.value?.ownerDocument.activeElement
475
+ const hadFocus = isElement(active) && active.getAttribute("data-node-id") === focusedId.value
359
476
  focusedId.value = rows[0].id
477
+ if (hadFocus) void nextTick(() => focusRow(rows[0].id))
360
478
  }
361
479
  },
362
480
  { immediate: true },
363
481
  )
364
482
 
365
- const root = ref<HTMLElement | null>(null)
366
-
367
- function focusRow(id: string) {
368
- focusedId.value = id
483
+ function focusRowElement(id: string) {
369
484
  // Rows are plain divs keyed by data-node-id; query rather than hold refs
370
485
  // through the recursion.
371
486
  const el = root.value?.querySelector<HTMLElement>(`[data-node-id="${CSS.escape(id)}"]`)
@@ -373,12 +488,67 @@ function focusRow(id: string) {
373
488
  el?.scrollIntoView({ block: "nearest" })
374
489
  }
375
490
 
491
+ function focusRow(id: string) {
492
+ focusedId.value = id
493
+ if (!props.virtual) {
494
+ focusRowElement(id)
495
+ return
496
+ }
497
+ // A row out of the window has no element yet: bring it in, then reach for it.
498
+ scrollToRow(id)
499
+ void nextTick(() => focusRowElement(id))
500
+ }
501
+
502
+ // `virtual`: which rows exist, from the viewport's scroll position and height.
503
+ const scrollArea = ref<{ viewportEl: HTMLElement | null } | null>(null)
504
+ const scrollTop = ref(0)
505
+ const viewportHeight = ref(0)
506
+
507
+ useResizeObserver(
508
+ () => (props.virtual ? scrollArea.value?.viewportEl : null),
509
+ (_, el) => {
510
+ viewportHeight.value = el.clientHeight
511
+ },
512
+ { immediate: true },
513
+ )
514
+
515
+ function onScroll(event: Event) {
516
+ if (props.virtual) scrollTop.value = (event.target as HTMLElement).scrollTop
517
+ }
518
+
519
+ function scrollToRow(id: string) {
520
+ const viewport = scrollArea.value?.viewportEl
521
+ const at = rows.value.findIndex((row) => row.node.id === id)
522
+ if (!viewport || at < 0) return
523
+ const top = at * props.rowHeight
524
+ const bottom = top + props.rowHeight
525
+ if (top < viewport.scrollTop) viewport.scrollTop = top
526
+ else if (bottom > viewport.scrollTop + viewport.clientHeight) viewport.scrollTop = bottom - viewport.clientHeight
527
+ scrollTop.value = viewport.scrollTop
528
+ }
529
+
530
+ /** The rows to mount. The focused one stays out of view too: it holds the tab stop. */
531
+ const mountedRows = computed(() => {
532
+ const { start, end } = rowWindow(scrollTop.value, viewportHeight.value, props.rowHeight, rows.value.length, OVERSCAN_ROWS)
533
+ const mounted: { row: (typeof rows.value)[number]; index: number }[] = []
534
+ for (let index = start; index < end; index++) mounted.push({ row: rows.value[index], index })
535
+ const focused = rows.value.findIndex((row) => row.node.id === focusedId.value)
536
+ if (focused >= 0 && (focused < start || focused >= end)) mounted.push({ row: rows.value[focused], index: focused })
537
+ return mounted
538
+ })
539
+
376
540
  function setExpanded(next: Set<string>) {
377
541
  openIds.value = next
378
542
  emit("update:expanded", [...next])
379
543
  }
380
544
 
381
545
  function expand(node: FileNode) {
546
+ if (closedWhileFiltering.value.has(node.id)) {
547
+ const closed = new Set(closedWhileFiltering.value)
548
+ closed.delete(node.id)
549
+ closedWhileFiltering.value = closed
550
+ if (filtered.value.openIds.has(node.id)) return
551
+ }
382
552
  if (node.kind !== "dir" || openIds.value.has(node.id)) return
383
553
  const next = new Set(openIds.value)
384
554
  next.add(node.id)
@@ -387,11 +557,62 @@ function expand(node: FileNode) {
387
557
  if (node.children === undefined && !requested.has(node.id)) {
388
558
  requested.add(node.id)
389
559
  emit("expand", node)
560
+ void fetchChildren(node)
561
+ }
562
+ }
563
+
564
+ // One request per folder. A controller that is no longer in the map lost its
565
+ // place, so its answer is dropped even if the fetch ignored the signal.
566
+ const fetches = new Map<string, AbortController>()
567
+
568
+ function setFetching(id: string, on: boolean) {
569
+ const next = new Set(ownLoading.value)
570
+ if (on) next.add(id)
571
+ else next.delete(id)
572
+ ownLoading.value = next
573
+ }
574
+
575
+ async function fetchChildren(node: FileNode) {
576
+ if (!props.load) return
577
+ const controller = new AbortController()
578
+ fetches.set(node.id, controller)
579
+ setFetching(node.id, true)
580
+ try {
581
+ const children = await props.load(node, controller.signal)
582
+ if (fetches.get(node.id) !== controller) return
583
+ loadedChildren.value = new Map(loadedChildren.value).set(node.id, children)
584
+ } catch (error) {
585
+ if (fetches.get(node.id) !== controller) return
586
+ requested.delete(node.id)
587
+ emit("load-error", node, error)
588
+ } finally {
589
+ if (fetches.get(node.id) === controller) {
590
+ fetches.delete(node.id)
591
+ setFetching(node.id, false)
592
+ }
390
593
  }
391
594
  }
392
595
 
596
+ function cancelFetch(id: string) {
597
+ const controller = fetches.get(id)
598
+ if (!controller) return
599
+ fetches.delete(id)
600
+ controller.abort()
601
+ setFetching(id, false)
602
+ requested.delete(id)
603
+ }
604
+
605
+ onBeforeUnmount(() => {
606
+ for (const controller of fetches.values()) controller.abort()
607
+ fetches.clear()
608
+ })
609
+
393
610
  function collapse(node: FileNode) {
611
+ if (filtered.value.openIds.has(node.id)) {
612
+ closedWhileFiltering.value = new Set(closedWhileFiltering.value).add(node.id)
613
+ }
394
614
  if (!openIds.value.has(node.id)) return
615
+ cancelFetch(node.id)
395
616
  // A fetch that failed left `children` unset; collapsing lets the next open
396
617
  // ask again instead of showing an empty folder forever.
397
618
  if (node.children === undefined && !loadingSet.value.has(node.id)) requested.delete(node.id)
@@ -402,7 +623,7 @@ function collapse(node: FileNode) {
402
623
  }
403
624
 
404
625
  function onToggle(node: FileNode) {
405
- if (openIds.value.has(node.id)) collapse(node)
626
+ if (shownOpen.value.has(node.id)) collapse(node)
406
627
  else expand(node)
407
628
  }
408
629
 
@@ -412,13 +633,128 @@ function onSelect(node: FileNode) {
412
633
  // without picking something else is a trap.
413
634
  if (props.selected === node.id) {
414
635
  emit("update:selected", "")
636
+ if (props.multiple) setPicked(new Set())
415
637
  return
416
638
  }
417
639
  emit("update:selected", node.id)
640
+ if (props.multiple) {
641
+ pickAnchor = node.id
642
+ setPicked(new Set([node.id]))
643
+ }
418
644
  emit("select", node)
419
645
  if (node.kind === "file") emit("open", node)
420
646
  }
421
647
 
648
+ // `multiple`. Ids, not rows, so a pick outlives its folder closing or a filter
649
+ // hiding it. Same controlled/uncontrolled deal as `expanded`.
650
+ const pickedIds = ref<Set<string>>(new Set(props.selection ?? []))
651
+ watch(
652
+ () => props.selection,
653
+ (ids) => {
654
+ if (ids) pickedIds.value = new Set(ids)
655
+ },
656
+ )
657
+ let pickAnchor: string | undefined
658
+
659
+ function setPicked(next: Set<string>) {
660
+ pickedIds.value = next
661
+ emit("update:selection", [...next])
662
+ }
663
+
664
+ function onPick(node: FileNode, mode: "toggle" | "range") {
665
+ const next = new Set(pickedIds.value)
666
+ if (mode === "range") {
667
+ for (const id of rangeIds(visible.value, pickAnchor, node.id)) next.add(id)
668
+ } else {
669
+ pickAnchor = node.id
670
+ if (!next.delete(node.id)) next.add(node.id)
671
+ }
672
+ setPicked(next)
673
+ }
674
+
675
+ function extendPick(delta: number) {
676
+ pickAnchor ??= focusedId.value
677
+ move(delta)
678
+ if (!focusedId.value) return
679
+ const next = new Set(pickedIds.value)
680
+ for (const id of rangeIds(visible.value, pickAnchor, focusedId.value)) next.add(id)
681
+ setPicked(next)
682
+ }
683
+
684
+ // `movable`. Drags inside this tree only: `moving` is empty for anything else.
685
+ let moving: string[] = []
686
+ const dropTargetId = ref<string>()
687
+
688
+ function onMoveStart(node: FileNode) {
689
+ const ids = props.multiple && pickedIds.value.has(node.id) ? [...pickedIds.value] : [node.id]
690
+ moving = topmostIds(ids.filter((id) => byId.value.has(id)), index.value.parentOfId)
691
+ }
692
+
693
+ function onMoveOver(node: FileNode, event: DragEvent) {
694
+ const allowed = canDrop(moving, node, index.value.parentOfId)
695
+ dropTargetId.value = allowed ? node.id : undefined
696
+ if (!allowed) return
697
+ event.preventDefault()
698
+ if (event.dataTransfer) event.dataTransfer.dropEffect = "move"
699
+ }
700
+
701
+ function onMoveLeave(node: FileNode) {
702
+ if (dropTargetId.value === node.id) dropTargetId.value = undefined
703
+ }
704
+
705
+ function onMoveEnd() {
706
+ moving = []
707
+ dropTargetId.value = undefined
708
+ }
709
+
710
+ function onMoveDrop(node: FileNode) {
711
+ const target = byId.value.get(node.id)
712
+ if (target && canDrop(moving, target, index.value.parentOfId)) {
713
+ emit("move", { nodes: moving.flatMap((id) => byId.value.get(id) ?? []), target })
714
+ }
715
+ onMoveEnd()
716
+ }
717
+
718
+ // `renamable`. The tree holds only the text being typed.
719
+ const renaming = ref<{ node: FileNode; value: string } | null>(null)
720
+
721
+ const siblingsOf = (node: FileNode) => {
722
+ const parent = index.value.parentOfId.get(node.id)
723
+ return (parent === undefined ? source.value : byId.value.get(parent)?.children) ?? []
724
+ }
725
+
726
+ const renameInvalid = computed(() => {
727
+ const edit = renaming.value
728
+ return !!edit && renameProblem(edit.value, edit.node, siblingsOf(edit.node)) !== null
729
+ })
730
+
731
+ function startRename(id: string) {
732
+ const node = byId.value.get(id)
733
+ if (!props.renamable || !node || node.disabled) return
734
+ focusedId.value = id
735
+ renaming.value = { node, value: node.name }
736
+ }
737
+
738
+ function endRename(refocus: boolean) {
739
+ const id = renaming.value?.node.id
740
+ renaming.value = null
741
+ if (refocus && id) void nextTick(() => focusRow(id))
742
+ }
743
+
744
+ // Enter on a bad name stays and says so. Leaving the field gives it up instead:
745
+ // the user has gone elsewhere, and a field that holds them there is a trap.
746
+ function commitRename(blurred = false) {
747
+ const edit = renaming.value
748
+ if (!edit) return
749
+ if (renameInvalid.value) {
750
+ if (blurred) endRename(false)
751
+ return
752
+ }
753
+ const name = edit.value.trim()
754
+ if (name !== edit.node.name) emit("rename", edit.node, name)
755
+ endRename(!blurred)
756
+ }
757
+
422
758
  /** Nearest expanded ancestor of `id`, or undefined at the top level. */
423
759
  function parentOf(id: string): FileNode | undefined {
424
760
  const parentId = index.value.parentOfId.get(id)
@@ -429,7 +765,7 @@ function move(delta: number) {
429
765
  const rows = visible.value
430
766
  if (!rows.length) return
431
767
  const i = rows.findIndex((n) => n.id === focusedId.value)
432
- const next = rows[Math.min(rows.length - 1, Math.max(0, (i < 0 ? 0 : i) + delta))]
768
+ const next = rows[clamp((i < 0 ? 0 : i) + delta, 0, rows.length - 1)]
433
769
  if (next) focusRow(next.id)
434
770
  }
435
771
 
@@ -442,17 +778,29 @@ function onKeydown(e: KeyboardEvent) {
442
778
  switch (e.key) {
443
779
  case "ArrowDown":
444
780
  e.preventDefault()
445
- move(1)
781
+ if (props.multiple && e.shiftKey) extendPick(1)
782
+ else move(1)
446
783
  break
447
784
  case "ArrowUp":
448
785
  e.preventDefault()
449
- move(-1)
786
+ if (props.multiple && e.shiftKey) extendPick(-1)
787
+ else move(-1)
788
+ break
789
+ case "F2":
790
+ if (!node || !props.renamable) return
791
+ e.preventDefault()
792
+ startRename(node.id)
793
+ break
794
+ case "a":
795
+ if (!props.multiple || !(e.ctrlKey || e.metaKey)) return
796
+ e.preventDefault()
797
+ setPicked(new Set([...pickedIds.value, ...visible.value.filter((row) => !row.disabled).map((row) => row.id)]))
450
798
  break
451
799
  case "ArrowRight": {
452
800
  if (!node) return
453
801
  e.preventDefault()
454
802
  // Closed directory opens; already-open one steps to its first child.
455
- if (node.kind === "dir" && !openIds.value.has(node.id)) expand(node)
803
+ if (node.kind === "dir" && !shownOpen.value.has(node.id)) expand(node)
456
804
  else if (node.kind === "dir" && node.children?.length) move(1)
457
805
  break
458
806
  }
@@ -460,7 +808,7 @@ function onKeydown(e: KeyboardEvent) {
460
808
  if (!node) return
461
809
  e.preventDefault()
462
810
  // Open directory closes; anything else steps out to the parent row.
463
- if (node.kind === "dir" && openIds.value.has(node.id)) collapse(node)
811
+ if (node.kind === "dir" && shownOpen.value.has(node.id)) collapse(node)
464
812
  else {
465
813
  const parent = parentOf(node.id)
466
814
  if (parent) focusRow(parent.id)
@@ -503,7 +851,7 @@ function onKeydown(e: KeyboardEvent) {
503
851
  /** Expand every ancestor of `id`, focus it and make it the selection. */
504
852
  function reveal(id: string) {
505
853
  const next = new Set(openIds.value)
506
- for (const ancestor of ancestorIds(props.nodes, id)) next.add(ancestor)
854
+ for (const ancestor of ancestorIds(source.value, id)) next.add(ancestor)
507
855
  setExpanded(next)
508
856
  emit("update:selected", id)
509
857
  // Wait a frame so the newly expanded rows exist before we query for one.
@@ -520,7 +868,7 @@ function expandAll() {
520
868
  }
521
869
  }
522
870
  }
523
- walk(props.nodes)
871
+ walk(source.value)
524
872
  setExpanded(next)
525
873
  }
526
874
 
@@ -528,7 +876,66 @@ function collapseAll() {
528
876
  setExpanded(new Set())
529
877
  }
530
878
 
531
- defineExpose({ reveal, expandAll, collapseAll, checkAll, clearChecked, summary })
879
+ /** Fetch a folder again through `load`. */
880
+ function reload(id: string) {
881
+ const node = byId.value.get(id)
882
+ if (!node || !props.load) return
883
+ cancelFetch(id)
884
+ void fetchChildren(node)
885
+ }
886
+
887
+ defineExpose({ reveal, expandAll, collapseAll, checkAll, clearChecked, summary, rename: startRename, reload })
888
+
889
+ // LpBreadcrumbs clicks through a link component; a step here goes nowhere but a row.
890
+ const CrumbButton: FunctionalComponent<{ to?: string }> = (_, { slots }) => h("button", { type: "button" }, slots.default?.())
891
+ CrumbButton.props = ["to"]
892
+
893
+ const trail = computed(() => {
894
+ const id = props.selected || focusedId.value
895
+ return props.breadcrumb && id ? nodeTrail(id, byId.value, index.value.parentOfId) : []
896
+ })
897
+ const crumbs = computed(() => trail.value.map((node) => ({ label: node.name, to: node.id })))
898
+
899
+ // What every row takes, whichever way the rows are laid out.
900
+ const rowProps = computed(() => ({
901
+ expanded: shownOpen.value,
902
+ loading: loadingSet.value,
903
+ selectedId: props.selected,
904
+ focusedId: focusedId.value,
905
+ sort: props.sort,
906
+ iconSize: props.iconSize,
907
+ checkable: props.checkable,
908
+ checked: checkedIds.value,
909
+ stats: stats.value,
910
+ showSize: props.showSize,
911
+ showModified: props.showModified,
912
+ selectedIds: props.multiple ? pickedIds.value : undefined,
913
+ multiple: props.multiple,
914
+ movable: props.movable,
915
+ dropTargetId: dropTargetId.value,
916
+ renamingId: renaming.value?.node.id,
917
+ renameValue: renaming.value?.value,
918
+ renameInvalid: renameInvalid.value,
919
+ renameLabel: text.value.rename,
920
+ }))
921
+
922
+ const rowEvents = {
923
+ select: onSelect,
924
+ toggle: onToggle,
925
+ focus: (node: FileNode) => (focusedId.value = node.id),
926
+ check: onCheck,
927
+ pick: onPick,
928
+ moveStart: onMoveStart,
929
+ moveOver: onMoveOver,
930
+ moveLeave: onMoveLeave,
931
+ moveDrop: onMoveDrop,
932
+ moveEnd: onMoveEnd,
933
+ renameInput: (value: string) => {
934
+ if (renaming.value) renaming.value = { ...renaming.value, value }
935
+ },
936
+ renameCommit: commitRename,
937
+ renameCancel: () => endRename(true),
938
+ }
532
939
 
533
940
  // The tree fills its container; a consumer giving it a fixed height or width in a split pane must be able to say so.
534
941
  const { class: rootClass, attrs: rest } = useMergedAttrs(
@@ -549,7 +956,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
549
956
  </div>
550
957
 
551
958
  <LpEmptyState
552
- v-else-if="!roots.length"
959
+ v-else-if="!roots.length && !filtering"
553
960
  icon="lucide:folder-search"
554
961
  :title="emptyLabel ?? text.empty"
555
962
  :description="emptyDescription ?? text.emptyDescription"
@@ -560,39 +967,73 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
560
967
  container and nothing ever scrolls. min-w-0/w-full for the same reason
561
968
  sideways: a long path must truncate inside the box rather than widen it. -->
562
969
  <div v-else class="flex h-full w-full min-w-0 flex-col">
563
- <LpScrollArea class="min-h-0 w-full min-w-0 flex-1">
970
+ <LpInput
971
+ v-if="searchable"
972
+ type="search"
973
+ class="mb-1 shrink-0"
974
+ :model-value="queryText"
975
+ :placeholder="text.search"
976
+ :aria-label="text.search"
977
+ @update:model-value="setQuery($event ?? '')"
978
+ />
979
+ <LpBreadcrumbs
980
+ v-if="crumbs.length"
981
+ class="mb-1 shrink-0 text-xs"
982
+ :items="crumbs"
983
+ :as="CrumbButton"
984
+ :labels="{ nav: text.path }"
985
+ @navigate="(_, i) => reveal(trail[i].id)"
986
+ />
987
+ <LpEmptyState
988
+ v-if="!roots.length"
989
+ icon="lucide:search-x"
990
+ :title="text.noMatches"
991
+ />
992
+ <LpScrollArea v-else ref="scrollArea" class="min-h-0 w-full min-w-0 flex-1" @scroll="onScroll">
564
993
  <ul
565
994
  ref="root"
566
995
  role="tree"
567
- :aria-multiselectable="checkable"
996
+ :aria-multiselectable="checkable || multiple"
568
997
  class="flex min-w-0 flex-col"
998
+ :class="virtual ? 'relative' : ''"
999
+ :style="virtual ? { height: `${rows.length * rowHeight}px` } : undefined"
569
1000
  @keydown="onKeydown"
570
1001
  >
571
- <LpFileTreeNode
572
- v-for="node in roots"
573
- :key="node.id"
574
- :node="node"
575
- :depth="0"
576
- :expanded="openIds"
577
- :loading="loadingSet"
578
- :selected-id="selected"
579
- :focused-id="focusedId"
580
- :sort="sort"
581
- :icon-size="iconSize"
582
- :checkable="checkable"
583
- :checked="checkedIds"
584
- :stats="stats"
585
- :show-size="showSize"
586
- :show-modified="showModified"
587
- @select="onSelect"
588
- @toggle="onToggle"
589
- @focus="focusedId = $event.id"
590
- @check="onCheck"
591
- >
592
- <template #row="slotProps">
593
- <slot name="row" v-bind="slotProps" />
594
- </template>
595
- </LpFileTreeNode>
1002
+ <!-- Flat and windowed: each row says its own level and place, and sits at
1003
+ its own offset, so a row kept for focus needs no filler around it. -->
1004
+ <template v-if="virtual">
1005
+ <LpFileTreeNode
1006
+ v-for="{ row, index: at } in mountedRows"
1007
+ :key="row.node.id"
1008
+ class="absolute inset-x-0 top-0"
1009
+ :style="{ height: `${rowHeight}px`, transform: `translateY(${at * rowHeight}px)` }"
1010
+ :node="row.node"
1011
+ :depth="row.depth"
1012
+ flat
1013
+ :set-size="row.setSize"
1014
+ :pos-in-set="row.posInSet"
1015
+ v-bind="rowProps"
1016
+ v-on="rowEvents"
1017
+ >
1018
+ <template #row="slotProps">
1019
+ <slot name="row" v-bind="slotProps" />
1020
+ </template>
1021
+ </LpFileTreeNode>
1022
+ </template>
1023
+ <template v-else>
1024
+ <LpFileTreeNode
1025
+ v-for="node in roots"
1026
+ :key="node.id"
1027
+ :node="node"
1028
+ :depth="0"
1029
+ v-bind="rowProps"
1030
+ v-on="rowEvents"
1031
+ >
1032
+ <template #row="slotProps">
1033
+ <slot name="row" v-bind="slotProps" />
1034
+ </template>
1035
+ </LpFileTreeNode>
1036
+ </template>
596
1037
  </ul>
597
1038
  </LpScrollArea>
598
1039