@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
@@ -7,13 +7,22 @@ export default { inheritAttrs: false }
7
7
 
8
8
  <script setup lang="ts">
9
9
  /*
10
- * Full application shell: a fixed-height (h-dvh) flex frame holding the side
11
- * navigation rail, a top header bar, and a single scrollable main region.
10
+ * Full application shell: the side navigation rail, a top header bar and the
11
+ * page, which is what scrolls.
12
+ *
13
+ * By default the page is the document: it scrolls the window, and the rail,
14
+ * the header and the bottom bar are sticky beside and above it. So everything
15
+ * a browser does with a long page keeps working — a full-page screenshot,
16
+ * printing, the phone's address bar folding away, pull to refresh, a tap on
17
+ * the status bar. `scroll="region"` keeps the older frame instead: the shell
18
+ * is exactly the viewport and the page scrolls inside main (LpScrollArea),
19
+ * which suits a shell embedded in a page of its own. A `fullBleed` page is
20
+ * held to the viewport in either.
12
21
  *
13
22
  * It owns the things every product's cabinet/console layout was re-implementing
14
23
  * by hand — and getting subtly wrong:
15
- * - the rail is full viewport height and never scrolls with the page (only
16
- * `main` scrolls, via LpScrollArea), so the sidebar footer stays pinned;
24
+ * - the rail is full viewport height and never scrolls with the page, so the
25
+ * sidebar footer stays pinned;
17
26
  * - a responsive burger that drives LpSidebar's mobile drawer;
18
27
  * - a default `isActive` (path prefix-match) and a default page title derived
19
28
  * from the nav items, so the header title and the active pill never drift.
@@ -30,14 +39,33 @@ export default { inheritAttrs: false }
30
39
  * #header-title (override the computed title), and the default slot (page body).
31
40
  * Overlays that aren't part of the frame (command palette, toaster) live in the
32
41
  * host next to <LpAppShell>.
42
+ *
43
+ * Every option below is off until asked for, and the shell's markup without
44
+ * them is what it always was: `headerCollapse`, `backToTop`, `density`,
45
+ * `pageTransition`, `safeArea`, the #aside region and `adaptiveNav`.
33
46
  */
34
- import { computed, onMounted, ref, useId, watch } from "vue"
47
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, useSlots, watch } from "vue"
48
+ import { easingToken, durationToken, prefersReducedMotion } from "../composables/easing"
35
49
  import { useLabels } from "../composables/useLpConfig"
36
50
  import { useScrollMemory } from "../composables/scrollMemory"
51
+ import { nodeWindow, useView, viewWindow } from "../composables/useView"
52
+ import AppShellContextNav from "./AppShellContextNav.vue"
53
+ import {
54
+ contextNavItems,
55
+ DENSITY_CLASS,
56
+ headerCollapsedAfter,
57
+ NAV_BREAKPOINT_PX,
58
+ resolveNavMode,
59
+ type AdaptiveNav,
60
+ type NavBreakpoint,
61
+ type NavMode,
62
+ type ShellDensity,
63
+ } from "./appShell"
37
64
  import LpBottomNav from "./LpBottomNav.vue"
38
65
  import LpContextSwitcher from "./LpContextSwitcher.vue"
39
66
  import LpButton from "./LpButton.vue"
40
67
  import LpIcon from "./LpIcon.vue"
68
+ import LpPageScrollbar from "./LpPageScrollbar.vue"
41
69
  import LpScrollArea from "./LpScrollArea.vue"
42
70
  import LpShift from "./LpShift.vue"
43
71
  import LpSidebar from "./LpSidebar.vue"
@@ -46,6 +74,9 @@ import { mergeClass, useMergedAttrs } from "../composables/useMergedClass"
46
74
 
47
75
  export type { SidebarItem, SidebarSection } from "./sidebar"
48
76
 
77
+ /* Past this much scrolled the way back up is worth a button. */
78
+ const BACK_TO_TOP_AFTER_PX = 600
79
+
49
80
  const props = withDefaults(
50
81
  defineProps<{
51
82
  /** Flat items, or sections for grouped nav. Forwarded to LpSidebar. */
@@ -91,10 +122,26 @@ const props = withDefaults(
91
122
  * (for a canvas / map / graph that fills the viewport and pans internally).
92
123
  */
93
124
  fullBleed?: boolean
125
+ /**
126
+ * What scrolls. `"document"` (the default): the window, with the rail,
127
+ * header and bottom bar sticky; a page that filled main with `h-full`
128
+ * grows with its content now, so a page meant to fill the screen asks for
129
+ * `min-h-[calc(100dvh-4rem)]` or `fullBleed`. `"region"`: the shell is
130
+ * the viewport and the page scrolls inside main. Read once: changing it
131
+ * remounts the page.
132
+ */
133
+ scroll?: "document" | "region"
134
+ /**
135
+ * While the document scrolls, draw the kit's overlay bar for it
136
+ * (LpPageScrollbar) under the header and above the bottom bar, in place of
137
+ * the browser's, which takes width from the page. `false` keeps the
138
+ * browser's. Where the browser's bars float anyway (phones) it draws none.
139
+ */
140
+ pageScrollbar?: boolean
94
141
  /** Loading skeleton for the nav. */
95
142
  loading?: boolean
96
143
  /** Text overrides: the burger's name and the skip link. */
97
- labels?: Partial<{ openMenu: string; skipToContent: string }>
144
+ labels?: Partial<{ openMenu: string; skipToContent: string; backToTop: string; aside: string; contextNav: string }>
98
145
  /** Icon-only rail (v-model:compact). Forwarded to LpSidebar. */
99
146
  compact?: boolean
100
147
  /** The person sizes the rail and folds it to icons. Forwarded to LpSidebar. */
@@ -123,14 +170,51 @@ const props = withDefaults(
123
170
  /**
124
171
  * On phones, the main pages along the bottom instead of the burger, with
125
172
  * "More" for the rest (it opens the drawer). `true` takes the first four
126
- * pages, a number that many, a list of ids those pages.
173
+ * pages, a number that many, a list of ids those pages. Top-level pages
174
+ * only: an id from `children` is ignored, and such a page stays behind "More".
127
175
  */
128
176
  bottomNav?: boolean | number | string[]
177
+ /**
178
+ * The header leaves as the page scrolls down and returns on the way up,
179
+ * giving a small screen its height back. A keyboard user tabbing into it
180
+ * brings it back too. A full-bleed page owns its scrolling, so the header
181
+ * stays there.
182
+ */
183
+ headerCollapse?: boolean
184
+ /** A button back to the top of a long page; a number sets how far down it appears, in px. */
185
+ backToTop?: boolean | number
186
+ /** How tight the page sits in main. Unset keeps the shell's own padding. */
187
+ density?: ShellDensity
188
+ /**
189
+ * A new `path` fades the page and the header's title and actions in as one,
190
+ * on the same clock, so actions arriving by teleport do not land apart from
191
+ * their page. Nothing is remounted. Skipped under reduced motion.
192
+ */
193
+ pageTransition?: boolean
194
+ /**
195
+ * Keep the header and the page's end clear of a notch and a home bar
196
+ * (`viewport-fit=cover`). The bottom bar always does.
197
+ */
198
+ safeArea?: boolean
199
+ /** Tailwind breakpoint below which the #aside region is hidden. Defaults to `mobileBreakpoint`. */
200
+ asideBreakpoint?: NavBreakpoint
201
+ /** Classes for the #aside region, its width above all. */
202
+ asideClass?: string
203
+ /**
204
+ * One model for the navigation across widths; see `NavMode`. Below
205
+ * `mobileBreakpoint` it is `bottomNav` or the drawer, as ever; this adds the
206
+ * fold to icons in between and the row of sub-pages where the rail is gone.
207
+ * A `compact` the app controls wins, and a person folding the rail by hand
208
+ * holds until the width crosses into another mode.
209
+ */
210
+ adaptiveNav?: AdaptiveNav
129
211
  }>(),
130
212
  {
131
213
  mobileBreakpoint: "lg",
132
214
  divider: true,
133
215
  contentClass: "mx-auto w-full max-w-6xl",
216
+ scroll: "document",
217
+ pageScrollbar: true,
134
218
  // Unset, not false: an absent boolean would hand the sidebar "expanded"
135
219
  // and override the state it remembers.
136
220
  compact: undefined,
@@ -144,6 +228,8 @@ const emit = defineEmits<{
144
228
  (e: "update:compact", value: boolean): void
145
229
  (e: "update:pinned", ids: string[]): void
146
230
  (e: "update:context", id: string): void
231
+ /** The navigation the width settled on, with `adaptiveNav` or without. */
232
+ (e: "nav-mode", mode: NavMode): void
147
233
  }>()
148
234
 
149
235
  const switching = computed(() => (props.contexts?.length ?? 0) > 1)
@@ -153,11 +239,16 @@ const flatItems = computed<SidebarItem[]>(() =>
153
239
  (props.sections ? props.sections.flatMap((s) => s.items) : (props.items ?? [])).flatMap((i) => [i, ...(i.children ?? [])]),
154
240
  )
155
241
 
242
+ // Compared in one spelling, so "/billing/" and "//billing" are "/billing".
243
+ const SLASH_RUN = /\/{2,}/g
244
+ const TRAILING_SLASH = /(?<=.)\/$/
245
+ const cleanPath = computed(() => props.path?.replace(SLASH_RUN, "/").replace(TRAILING_SLASH, ""))
246
+
156
247
  // Default active test: exact match for "/", prefix match (on a segment
157
248
  // boundary) otherwise — so "/billing" doesn't light up on "/billing-x".
158
249
  function matchesPath(item: SidebarItem): boolean {
159
- if (item.id === "/") return props.path === "/"
160
- return props.path === item.id || props.path!.startsWith(`${item.id}/`)
250
+ if (item.id === "/") return cleanPath.value === "/"
251
+ return cleanPath.value === item.id || cleanPath.value!.startsWith(`${item.id}/`)
161
252
  }
162
253
 
163
254
  // Only the LONGEST match lights up. A parent whose id prefixes its children —
@@ -193,7 +284,7 @@ const pageTitle = computed(() => {
193
284
  const drawerOpen = defineModel<boolean>("open", { default: false })
194
285
  const text = useLabels(
195
286
  "appShell",
196
- { openMenu: "Open menu", skipToContent: "Skip to content" },
287
+ { openMenu: "Open menu", skipToContent: "Skip to content", backToTop: "Back to top", aside: "Details", contextNav: "In this section" },
197
288
  () => props.labels,
198
289
  )
199
290
 
@@ -204,27 +295,230 @@ const text = useLabels(
204
295
  * A full-bleed page owns its scrolling, so the shell leaves it alone.
205
296
  */
206
297
  const scrollArea = ref<{ viewportEl: HTMLElement | null } | null>(null)
298
+ const asideArea = ref<{ viewportEl: HTMLElement | null } | null>(null)
299
+ const pageEl = ref<HTMLElement | null>(null)
207
300
  const bleedId = `lp-shell-bleed-${useId()}`
208
301
  const mounted = ref(false)
209
302
  onMounted(() => (mounted.value = true))
210
303
  const bleeding = computed(() => props.fullBleed && mounted.value)
304
+ const documentScroll = props.scroll !== "region"
305
+ // The window scrolls the page; a full-bleed page holds the shell to the viewport.
306
+ const windowScrolls = computed(() => documentScroll && !props.fullBleed)
211
307
  const mainId = `lp-main-${useId()}`
212
308
 
213
309
  // Focus instead of following the hash, which a router would treat as a navigation.
310
+ const mainEl = ref<HTMLElement | null>(null)
214
311
  function skipToMain() {
215
- document.getElementById(mainId)?.focus()
312
+ mainEl.value?.focus()
216
313
  }
217
314
 
218
315
  watch(
219
316
  () => props.path,
220
317
  () => {
221
318
  if (drawerOpen.value) drawerOpen.value = false
319
+ headerCollapsed.value = false
222
320
  },
223
321
  )
322
+ function pageScroller(): HTMLElement | null | undefined {
323
+ if (bleeding.value) return null
324
+ if (!documentScroll) return scrollArea.value?.viewportEl
325
+ return (pageEl.value?.ownerDocument.scrollingElement as HTMLElement | null | undefined) ?? null
326
+ }
224
327
  useScrollMemory(
225
- () => (bleeding.value ? null : scrollArea.value?.viewportEl),
328
+ pageScroller,
226
329
  () => props.path,
227
330
  () => props.hash,
331
+ "",
332
+ // The sticky header covers the top of the window; a region starts below it.
333
+ () => (documentScroll && !bleeding.value ? (headerEl.value?.offsetHeight ?? 0) : 0),
334
+ )
335
+ // The side region keeps its own place on the same address.
336
+ useScrollMemory(
337
+ () => asideArea.value?.viewportEl,
338
+ () => props.path,
339
+ () => undefined,
340
+ "aside:",
341
+ )
342
+
343
+ const headerCollapsed = ref(false)
344
+ const scrolledFar = ref(false)
345
+ const backToTopAfter = computed(() => (typeof props.backToTop === "number" ? props.backToTop : BACK_TO_TOP_AFTER_PX))
346
+ let lastTop = 0
347
+
348
+ function onScrolled(top: number, max: number) {
349
+ if (!props.headerCollapse && !props.backToTop) return
350
+ if (props.headerCollapse) headerCollapsed.value = headerCollapsedAfter(headerCollapsed.value, lastTop, top, max)
351
+ scrolledFar.value = top > backToTopAfter.value
352
+ lastTop = top
353
+ }
354
+ function onMainScroll(event: Event) {
355
+ const view = event.target as HTMLElement
356
+ onScrolled(view.scrollTop, view.scrollHeight - view.clientHeight)
357
+ }
358
+ function onWindowScroll() {
359
+ const view = pageScroller()
360
+ if (view) onScrolled(view.scrollTop, view.scrollHeight - view.clientHeight)
361
+ }
362
+
363
+ const headerHidden = computed(() => headerCollapsed.value && !bleeding.value)
364
+ const HEADER_HEIGHT = "4rem"
365
+ const headerHeight = computed(() => (props.safeArea ? `calc(${HEADER_HEIGHT} + env(safe-area-inset-top))` : HEADER_HEIGHT))
366
+
367
+ /*
368
+ * A full-bleed page shrinks the document to the viewport, which loses the
369
+ * place; it is taken back on the way out, unless the page changed in between.
370
+ *
371
+ * No scroll-padding-top on the document for the sticky header: with it the
372
+ * browser counts the header as covered by itself, and a control in it taking
373
+ * focus scrolled the page (by 350 px, measured). Anchors get the header's
374
+ * height from scroll memory instead.
375
+ */
376
+ let bleedTop: number | undefined
377
+ watch(
378
+ () => props.path,
379
+ () => (bleedTop = undefined),
380
+ )
381
+ watch(bleeding, (bleed) => {
382
+ if (!documentScroll) return
383
+ const view = pageEl.value?.ownerDocument.scrollingElement
384
+ if (!view) return
385
+ if (bleed) bleedTop = view.scrollTop
386
+ else if (bleedTop !== undefined) {
387
+ const top = bleedTop
388
+ bleedTop = undefined
389
+ void nextTick(() => view.scrollTo({ top, behavior: "instant" }))
390
+ }
391
+ })
392
+ // The bottom bar's height, for the page's scrollbar to stop above it (0 where it is hidden).
393
+ const bottomBarEl = ref<{ $el: HTMLElement } | null>(null)
394
+ const bottomBarHeight = ref(0)
395
+ let bottomBarSize: ResizeObserver | undefined
396
+ onMounted(() => {
397
+ const el = bottomBarEl.value?.$el
398
+ if (!el || el.nodeType !== 1) return
399
+ bottomBarSize = new (nodeWindow(el).ResizeObserver)(() => (bottomBarHeight.value = el.offsetHeight))
400
+ bottomBarSize.observe(el)
401
+ })
402
+ onBeforeUnmount(() => bottomBarSize?.disconnect())
403
+
404
+ let releaseDocument: (() => void) | undefined
405
+ onMounted(() => {
406
+ if (!documentScroll || !pageEl.value) return
407
+ const win = nodeWindow(pageEl.value)
408
+ win.addEventListener("scroll", onWindowScroll, { passive: true })
409
+ releaseDocument = () => win.removeEventListener("scroll", onWindowScroll)
410
+ })
411
+ onBeforeUnmount(() => releaseDocument?.())
412
+
413
+ function scrollToTop() {
414
+ const view = pageScroller()
415
+ if (!view) return
416
+ view.scrollTo({ top: 0, behavior: prefersReducedMotion(nodeWindow(view)) ? "instant" : "smooth" })
417
+ // The button is about to leave, and focus must not leave with it.
418
+ mainEl.value?.focus()
419
+ }
420
+
421
+ const headerClass = computed(() =>
422
+ mergeClass(
423
+ "relative flex h-16 shrink-0 items-center gap-3 border-b border-line bg-surface px-4 md:px-6",
424
+ windowScrolls.value && `${STICKY} top-0`,
425
+ props.headerCollapse &&
426
+ (windowScrolls.value ? "transition-transform" : "transition-[margin-top]") + " duration-[var(--duration-medium)] ease-[var(--ease-settle)]",
427
+ props.safeArea &&
428
+ "h-[calc(4rem+env(safe-area-inset-top))] pt-[env(safe-area-inset-top)] pl-[max(1rem,env(safe-area-inset-left))] pr-[max(1rem,env(safe-area-inset-right))] md:pl-[max(1.5rem,env(safe-area-inset-left))] md:pr-[max(1.5rem,env(safe-area-inset-right))]",
429
+ ),
430
+ )
431
+ const pageClass = computed(() =>
432
+ mergeClass(
433
+ DENSITY_CLASS[props.density ?? "comfortable"],
434
+ props.contentClass,
435
+ props.safeArea && !props.bottomNav && "pb-[calc(1.5rem+env(safe-area-inset-bottom))]",
436
+ ),
437
+ )
438
+
439
+ // Fades on one clock, read from the page's own tokens. Post flush: a teleported
440
+ // action arrives with the render that brought the page.
441
+ const headerEl = ref<HTMLElement | null>(null)
442
+ const bleedEl = ref<HTMLElement | null>(null)
443
+ watch(
444
+ () => props.path,
445
+ () => {
446
+ const content = bleeding.value ? bleedEl.value : documentScroll ? pageEl.value : scrollArea.value?.viewportEl
447
+ if (!props.pageTransition || !content || prefersReducedMotion(nodeWindow(content))) return
448
+ const timing = { duration: durationToken("duration-medium", undefined, content), easing: easingToken("ease-settle", undefined, content) }
449
+ // Title and actions; the burger is the frame, not the page.
450
+ const header = headerEl.value?.querySelectorAll(":scope > :not(button)") ?? []
451
+ for (const el of [content, ...header]) el.animate({ opacity: [0, 1] }, timing)
452
+ },
453
+ { flush: "post" },
454
+ )
455
+
456
+ const slots = useSlots()
457
+ const hasAside = computed(() => !!slots.aside)
458
+ const ASIDE_VISIBLE: Record<NavBreakpoint, string> = {
459
+ sm: "hidden sm:flex",
460
+ md: "hidden md:flex",
461
+ lg: "hidden lg:flex",
462
+ xl: "hidden xl:flex",
463
+ "2xl": "hidden 2xl:flex",
464
+ }
465
+ const asideClasses = computed(() =>
466
+ mergeClass(
467
+ "min-h-0 w-80 flex-col border-l border-line bg-surface",
468
+ windowScrolls.value &&
469
+ "sticky top-(--lp-shell-top) h-[calc(100dvh-var(--lp-shell-top))] self-start transition-[top,height] duration-[var(--duration-medium)] ease-[var(--ease-settle)]",
470
+ ASIDE_VISIBLE[props.asideBreakpoint ?? props.mobileBreakpoint],
471
+ props.asideClass,
472
+ ),
473
+ )
474
+
475
+ /*
476
+ * The aside takes a grid column rather than a wrapper around main: a wrapper
477
+ * that comes and goes with the slot would remount the page. Header, the
478
+ * sub-page row and the bottom bar span both columns.
479
+ */
480
+ const columnClass = computed(() => (hasAside.value ? "grid min-w-0 flex-1 grid-cols-[minmax(0,1fr)_auto]" : "flex min-w-0 flex-1 flex-col"))
481
+ const columnStyle = computed(() => ({
482
+ ...(hasAside.value ? { gridTemplateRows: `auto ${contextVisible.value ? "auto " : ""}minmax(0,1fr) auto` } : {}),
483
+ ...(windowScrolls.value ? { "--lp-shell-top": shellTop.value } : {}),
484
+ }))
485
+ const spanClass = computed(() => (hasAside.value ? "col-span-2" : undefined))
486
+
487
+ // One width rather than a media query per breakpoint: those report one by one,
488
+ // so a jump across two passed through a mode the window was never in.
489
+ const view = useView()
490
+ const width = ref(0)
491
+ const measureWidth = () => (width.value = viewWindow(view.value).innerWidth)
492
+ onMounted(() => {
493
+ measureWidth()
494
+ viewWindow(view.value).addEventListener("resize", measureWidth)
495
+ })
496
+ onBeforeUnmount(() => viewWindow(view.value).removeEventListener("resize", measureWidth))
497
+
498
+ // Before mount there is no width: a server render is the plain rail.
499
+ const navMode = computed<NavMode>(() =>
500
+ mounted.value
501
+ ? resolveNavMode((breakpoint) => width.value >= NAV_BREAKPOINT_PX[breakpoint], {
502
+ mobileBreakpoint: props.mobileBreakpoint,
503
+ bottomNav: !!props.bottomNav,
504
+ compactBelow: props.adaptiveNav?.compactBelow,
505
+ })
506
+ : "rail",
507
+ )
508
+ watch(navMode, (mode) => emit("nav-mode", mode))
509
+
510
+ // What a person chose by hand, for as long as the width stays in this mode.
511
+ const handCompact = ref<boolean | undefined>()
512
+ watch(navMode, () => (handCompact.value = undefined))
513
+ const railCompact = computed(() => props.compact ?? handCompact.value ?? (navMode.value === "compact" ? true : undefined))
514
+ function onCompact(value: boolean) {
515
+ if (props.adaptiveNav) handCompact.value = value
516
+ emit("update:compact", value)
517
+ }
518
+
519
+ const contextItems = computed(() => (props.adaptiveNav?.contextNav ? contextNavItems(topItems.value, activeTest.value) : []))
520
+ const contextVisible = computed(
521
+ () => (navMode.value === "bottom" || navMode.value === "drawer") && (contextItems.value.length > 0 || (!!slots["context-nav"] && !!props.adaptiveNav?.contextNav)),
228
522
  )
229
523
 
230
524
  function onSelect(item: SidebarItem) {
@@ -260,11 +554,33 @@ function onBottom(item: SidebarItem) {
260
554
 
261
555
  // Expose the burger trigger so a host can omit its own header entirely.
262
556
  const _openDrawer = () => (drawerOpen.value = true)
263
- defineExpose({ openDrawer: _openDrawer })
557
+ defineExpose({ openDrawer: _openDrawer, navMode })
558
+
559
+ // In region mode the shell claims the full viewport height, which is wrong when it is embedded in a page that already has a header of its own.
560
+ // The document mode clips sideways with `clip`, not `hidden`: a hidden overflow would make the root the sticky children's scroller.
561
+ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
562
+ windowScrolls.value ? "flex min-h-dvh overflow-x-clip bg-surface text-ink" : "flex h-dvh overflow-hidden bg-surface text-ink",
563
+ )
264
564
 
265
- // The shell claims the full viewport height, which is wrong when it is embedded in a page that already has a header of its own.
266
- const { class: rootClass, attrs: rest } = useMergedAttrs(
267
- "flex h-dvh overflow-hidden bg-surface text-ink",
565
+ /*
566
+ * What stays in view while the window scrolls the page. The header sits on
567
+ * top; the row of sub-pages and the aside sit under it, following it out
568
+ * when it collapses (--lp-shell-top is how much of the header is showing).
569
+ */
570
+ const STICKY = "sticky z-(--z-sticky)"
571
+ const railClass = computed(() => (windowScrolls.value ? "sticky top-0 flex h-dvh shrink-0 self-start" : "contents"))
572
+ const shellTop = computed(() => (headerHidden.value ? "0px" : headerHeight.value))
573
+ const BACK_TO_TOP_ABOVE_BAR: Record<NavBreakpoint, string> = {
574
+ sm: "bottom-[calc(4.5rem+env(safe-area-inset-bottom))] sm:bottom-[calc(1rem+env(safe-area-inset-bottom))]",
575
+ md: "bottom-[calc(4.5rem+env(safe-area-inset-bottom))] md:bottom-[calc(1rem+env(safe-area-inset-bottom))]",
576
+ lg: "bottom-[calc(4.5rem+env(safe-area-inset-bottom))] lg:bottom-[calc(1rem+env(safe-area-inset-bottom))]",
577
+ xl: "bottom-[calc(4.5rem+env(safe-area-inset-bottom))] xl:bottom-[calc(1rem+env(safe-area-inset-bottom))]",
578
+ "2xl": "bottom-[calc(4.5rem+env(safe-area-inset-bottom))] 2xl:bottom-[calc(1rem+env(safe-area-inset-bottom))]",
579
+ }
580
+ const backToTopClass = computed(() =>
581
+ windowScrolls.value
582
+ ? ["fixed right-6 z-(--z-sticky)", props.bottomNav ? BACK_TO_TOP_ABOVE_BAR[props.mobileBreakpoint] : "bottom-[calc(1rem+env(safe-area-inset-bottom))]"]
583
+ : "absolute bottom-[calc(1rem+env(safe-area-inset-bottom))] right-6",
268
584
  )
269
585
  </script>
270
586
 
@@ -279,6 +595,10 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
279
595
  >
280
596
  {{ text.skipToContent }}
281
597
  </a>
598
+ <!-- The rail's own box: a viewport-tall sticky column while the window
599
+ scrolls the page, and no box at all (contents) in the region frame,
600
+ where the rail is the frame's full height already. -->
601
+ <div :class="railClass">
282
602
  <LpSidebar
283
603
  :model-value="modelValue"
284
604
  :items="items"
@@ -292,7 +612,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
292
612
  :loading="loading"
293
613
  :collapsed="collapsed"
294
614
  :auto-collapse-after="autoCollapseAfter"
295
- :compact="compact"
615
+ :compact="railCompact"
296
616
  :resizable="resizable"
297
617
  :pinnable="pinnable"
298
618
  :pinned="pinned"
@@ -303,7 +623,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
303
623
  :hints="hints"
304
624
  class="shrink-0"
305
625
  @update:model-value="onModel"
306
- @update:compact="(v) => emit('update:compact', v)"
626
+ @update:compact="onCompact"
307
627
  @update:pinned="(v) => emit('update:pinned', v)"
308
628
  @update:open="(v) => (drawerOpen = v)"
309
629
  @update:collapsed="(v) => emit('update:collapsed', v)"
@@ -334,9 +654,10 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
334
654
  <slot name="sidebar-item" v-bind="slotProps" />
335
655
  </template>
336
656
  </LpSidebar>
657
+ </div>
337
658
 
338
659
  <!-- Main column: header + the single scroll region. -->
339
- <div class="flex min-w-0 flex-1 flex-col">
660
+ <div :class="columnClass" :style="columnStyle">
340
661
  <!-- `relative` so a consumer can centre something — section tabs, a
341
662
  search box — against the WHOLE bar. Centring it inside the title
342
663
  block instead makes its position depend on how wide the actions are,
@@ -344,7 +665,10 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
344
665
  59 px off centre with two actions and 77 px with three, which reads
345
666
  as the navigation drifting rather than the page changing. -->
346
667
  <header
347
- class="relative flex h-16 shrink-0 items-center gap-3 border-b border-line bg-surface px-4 md:px-6"
668
+ ref="headerEl"
669
+ :class="[headerClass, spanClass]"
670
+ :style="headerHidden ? (windowScrolls ? { transform: 'translateY(-100%)' } : { marginTop: `calc(-1 * ${headerHeight})` }) : undefined"
671
+ @focusin="headerCollapsed = false"
348
672
  >
349
673
  <LpButton
350
674
  :class="burgerClass"
@@ -398,26 +722,63 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
398
722
  tab going from a full-bleed page to a padded one cut instead of
399
723
  playing its transition, and lost the page's state. Until mount the
400
724
  page stays in the scroll region, which is what a server render has. -->
401
- <main :id="mainId" tabindex="-1" class="flex min-h-0 flex-1 flex-col outline-none">
402
- <div v-show="bleeding" :id="bleedId" class="min-h-0 flex-1 overflow-hidden" />
725
+ <template v-if="contextVisible">
726
+ <slot name="context-nav" :items="contextItems" :mode="navMode">
727
+ <AppShellContextNav
728
+ :class="[spanClass, windowScrolls && `${STICKY} top-(--lp-shell-top) transition-[top] duration-[var(--duration-medium)] ease-[var(--ease-settle)]`]"
729
+ :items="contextItems"
730
+ :is-active="activeTest"
731
+ :label="text.contextNav"
732
+ @select="onBottom"
733
+ />
734
+ </slot>
735
+ </template>
736
+ <main ref="mainEl" :id="mainId" tabindex="-1" :data-lp-density="density" :class="['flex min-h-0 flex-1 flex-col outline-none', backToTop && 'relative']">
737
+ <div ref="bleedEl" v-show="bleeding" :id="bleedId" class="min-h-0 flex-1 overflow-hidden" />
738
+ <div v-if="documentScroll" v-show="!bleeding" ref="pageEl" :class="['flex-1', pageClass]">
739
+ <Teleport :to="`#${bleedId}`" :disabled="!bleeding" defer>
740
+ <slot />
741
+ </Teleport>
742
+ </div>
403
743
  <LpScrollArea
744
+ v-else
404
745
  v-show="!bleeding"
405
746
  ref="scrollArea"
406
747
  class="min-h-0 flex-1"
407
- :content-class="mergeClass('px-4 py-6 md:px-6', contentClass)"
748
+ :content-class="pageClass"
749
+ @scroll="onMainScroll"
408
750
  >
409
751
  <Teleport :to="`#${bleedId}`" :disabled="!bleeding" defer>
410
752
  <slot />
411
753
  </Teleport>
412
754
  </LpScrollArea>
755
+ <LpButton
756
+ v-if="backToTop && scrolledFar && !bleeding"
757
+ :class="backToTopClass"
758
+ variant="outline"
759
+ size="sm"
760
+ square
761
+ :aria-label="text.backToTop"
762
+ @click="scrollToTop"
763
+ >
764
+ <LpIcon name="lucide:arrow-up" :size="16" />
765
+ </LpButton>
413
766
  </main>
767
+ <aside v-if="hasAside" :aria-label="text.aside" :class="asideClasses">
768
+ <LpScrollArea ref="asideArea" class="min-h-0 flex-1" content-class="p-4">
769
+ <slot name="aside" />
770
+ </LpScrollArea>
771
+ </aside>
772
+ <LpPageScrollbar v-if="windowScrolls && pageScrollbar" top="var(--lp-shell-top)" :bottom="`${bottomBarHeight}px`" />
414
773
  <LpBottomNav
415
774
  v-if="bottomNav"
416
- :class="burgerHidden[mobileBreakpoint]"
775
+ ref="bottomBarEl"
776
+ :class="[burgerHidden[mobileBreakpoint], spanClass, windowScrolls && `${STICKY} bottom-0`]"
417
777
  :items="bottomItems"
418
778
  :is-active="activeTest"
419
779
  more
420
780
  :more-active="moreActive"
781
+ :more-expanded="drawerOpen"
421
782
  @select="onBottom"
422
783
  @more="drawerOpen = true"
423
784
  />
@@ -10,6 +10,8 @@ export type AutocompleteLabels = {
10
10
  </script>
11
11
 
12
12
  <script setup lang="ts">
13
+ import IconCloseButton from "./IconCloseButton.vue"
14
+ import { CONTROL_SIZE } from "./controlSize"
13
15
  import { usePaletteControl, type PaletteControlOptions } from "../composables/usePaletteCommands"
14
16
  /*
15
17
  * Free-text autocomplete — a text field that suggests as you type but never
@@ -33,11 +35,11 @@ import {
33
35
  ComboboxRoot,
34
36
  ComboboxViewport,
35
37
  } from "reka-ui"
36
- import { computed, nextTick, ref, useAttrs, useId, watch } from "vue"
37
- import { useFieldControl } from "../composables/useField"
38
+ import { computed, nextTick, ref, useId, watch } from "vue"
39
+ import { useAttrsField, mergeDescribedBy } from "../composables/useField"
38
40
  import { useInputFilter } from "../composables/useInputFilter"
39
41
  import { useLabels } from "../composables/useLpConfig"
40
- import { CLOSE_ICON, OPTION_ITEM, optionStagger, POPOVER_PANEL } from "./dropdown"
42
+ import { CLOSE_ICON, OPTION_ITEM, optionStagger, COMBOBOX_PANEL, POPOVER_PANEL } from "./dropdown"
41
43
  import LpIcon from "./LpIcon.vue"
42
44
  import OptionContent from "./OptionContent.vue"
43
45
  import type { LpOptionBase, OptionSelection } from "./options"
@@ -115,18 +117,12 @@ const text = computed<string>({
115
117
 
116
118
  // class/style lay out the whole control; every other attr (name, autocomplete,
117
119
  // @blur, @keydown…) belongs to the text <input>.
118
- const attrs = useAttrs()
119
120
  const inputAttrs = computed(() => {
120
121
  const { class: _c, style: _s, ...rest } = attrs
121
122
  return rest
122
123
  })
123
124
 
124
- const field = useFieldControl(() => ({
125
- wrapped: false,
126
- id: typeof attrs.id === "string" ? attrs.id : undefined,
127
- invalid: props.invalid || isInvalid(props.modelValue),
128
- disabled: props.disabled,
129
- }))
125
+ const { attrs, field } = useAttrsField(() => ({ invalid: props.invalid || isInvalid(props.modelValue), disabled: props.disabled }))
130
126
  const isInvalidState = computed(() => field.value.invalid)
131
127
  const labels = useLabels<AutocompleteLabels>(
132
128
  "autocomplete",
@@ -179,7 +175,7 @@ watch(
179
175
  // reader is told who it is through a description.
180
176
  const selectedId = useId()
181
177
  const describedBy = computed(() =>
182
- [field.value.describedBy, showSelected.value ? selectedId : undefined].filter(Boolean).join(" ") || undefined,
178
+ mergeDescribedBy(attrs["aria-describedby"], field.value.describedBy, showSelected.value ? selectedId : undefined),
183
179
  )
184
180
 
185
181
  // Threshold met AND something to show → the list is allowed to open.
@@ -210,11 +206,7 @@ function onOpenChange(next: boolean) {
210
206
 
211
207
  const hasValue = computed(() => (props.modelValue ?? "").length > 0)
212
208
 
213
- const anchorSize = {
214
- sm: "h-(--size-control-sm) text-xs",
215
- md: "h-(--size-control-md) text-sm",
216
- lg: "h-(--size-control-lg) text-sm",
217
- }
209
+ const anchorSize = CONTROL_SIZE
218
210
 
219
211
  function onInput(event: Event) {
220
212
  filterInput(event)
@@ -373,20 +365,13 @@ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.v
373
365
  </span>
374
366
  </Transition>
375
367
  </div>
376
- <button
368
+ <IconCloseButton
377
369
  v-if="clearable && hasValue && !field.disabled"
378
- type="button"
379
- class="group flex shrink-0 items-center text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink"
380
- :aria-label="labels.clear"
370
+ inline
371
+ :label="labels.clear"
381
372
  @pointerdown.prevent
382
373
  @click="clear"
383
- >
384
- <LpIcon
385
- name="lucide:x"
386
- :size="16"
387
- :class="CLOSE_ICON"
388
- />
389
- </button>
374
+ />
390
375
  </ComboboxAnchor>
391
376
 
392
377
  <!-- A press on the list keeps focus in the field. Otherwise the field
@@ -397,9 +382,7 @@ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.v
397
382
  <ComboboxContent
398
383
  @interact-outside="guardOutside"
399
384
  @escape-key-down="guardOutside"
400
- :collision-padding="8"
401
- position="popper"
402
- :side-offset="6"
385
+ v-bind="COMBOBOX_PANEL"
403
386
  :class="[POPOVER_PANEL, 'z-(--z-popover) max-h-72 w-(--reka-combobox-trigger-width) overflow-hidden']"
404
387
  @open-auto-focus.prevent
405
388
  @mousedown.prevent