@leavepulse/ui 0.71.1 → 0.72.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.
- package/dist/assets/{LpCommandPalette-DgPZX0us.css → LpCommandPalette-Rtbg669l.css} +19 -19
- package/dist/assets/{LpNumberFlow-rZkwQwlU.css → LpNumberFlow-Chp9pEDe.css} +8 -0
- package/dist/assets/LpTabs-CDchzoRL.css +18 -0
- package/dist/chunks/{ContextMenuItems-eS7qx9gD.js → ContextMenuItems-BXumicbh.js} +1 -1
- package/dist/chunks/{DropzoneLinkField-DkFhdfEJ.js → DropzoneLinkField-D2Rnkcys.js} +2 -2
- package/dist/chunks/{FieldWrapper-YzbvvC3M.js → FieldWrapper-DnA1rxZ8.js} +1 -1
- package/dist/chunks/{LayoutCanvas-Ba4A9QC5.js → LayoutCanvas-CNOAUk7D.js} +1 -1
- package/dist/chunks/{LayoutNode-CTTTLMDM.js → LayoutNode-DF5BMN0J.js} +1 -1
- package/dist/chunks/{LpActionBar-B7anGlkI.js → LpActionBar-DfiBL1dE.js} +3 -3
- package/dist/chunks/{LpAddressInput-CD0bzrt-.js → LpAddressInput-B1bB3F6M.js} +7 -7
- package/dist/chunks/{LpAlert-BHi7ARqu.js → LpAlert-CpRF9eYr.js} +3 -3
- package/dist/chunks/{LpAppShell-1RmBp6xQ.js → LpAppShell-D8Q0Jv9d.js} +68 -18
- package/dist/chunks/{LpAutocomplete-DDqe2M5g.js → LpAutocomplete-DOu7FN5P.js} +2 -2
- package/dist/chunks/{LpAvatar-DqfKCIdr.js → LpAvatar-Cg6Az3wz.js} +1 -1
- package/dist/chunks/{LpBadge-C8fKEmTF.js → LpBadge-vtCpsDmv.js} +3 -3
- package/dist/chunks/LpBottomNav-VXJf0cKc.js +89 -0
- package/dist/chunks/{LpBreadcrumbs-DhcoStFa.js → LpBreadcrumbs-BdzjQcql.js} +3 -3
- package/dist/chunks/{LpButton-BQDAFXBd.js → LpButton-Dk7eT4ro.js} +1 -1
- package/dist/chunks/{LpCalendar-NmrA47J9.js → LpCalendar-CuoWks-0.js} +1 -1
- package/dist/chunks/{LpCard-CqKLnEhl.js → LpCard-Ce3rw8zm.js} +1 -1
- package/dist/chunks/{LpCheckbox-BoOuLnGB.js → LpCheckbox-DLX4amba.js} +2 -2
- package/dist/chunks/{LpCodeBlock-Czu-fq1M.js → LpCodeBlock-j0W5bpmF.js} +2 -2
- package/dist/chunks/{LpCodeDisplay-BK2FHbtM.js → LpCodeDisplay-COdiv4xu.js} +6 -6
- package/dist/chunks/{LpColorPicker-Ee1DZiRd.js → LpColorPicker-1y1QJHZR.js} +56 -28
- package/dist/chunks/{LpCommandScope-ubtWDMa0.js → LpCommandScope-BlKcgBnK.js} +1 -1
- package/dist/chunks/{LpConfirmDialog-i15pulMT.js → LpConfirmDialog-w1ni5nxh.js} +3 -3
- package/dist/chunks/{LpConfirmHost-D0oGavtd.js → LpConfirmHost-Br_-Gn-6.js} +1 -1
- package/dist/chunks/{LpContextMenu-DXpFJjlO.js → LpContextMenu-rx2tCjwk.js} +2 -2
- package/dist/chunks/{LpCopyButton-Dmq7DmFw.js → LpCopyButton-DaD1KCn6.js} +2 -2
- package/dist/chunks/{LpCountdown-DTdMF8DL.js → LpCountdown-Cgy0mVEq.js} +3 -3
- package/dist/chunks/{LpDataList-CFPtSsBB.js → LpDataList-CkcGFJgv.js} +3 -3
- package/dist/chunks/{LpDatePicker-cC9K-Pk8.js → LpDatePicker-CA0QTfBC.js} +4 -4
- package/dist/chunks/{LpDateRangePicker-BVQKPbvo.js → LpDateRangePicker-u3J51xXk.js} +2 -2
- package/dist/chunks/{LpDisclosure-hRnX3FWL.js → LpDisclosure-Cpa7Hc5T.js} +1 -1
- package/dist/chunks/{LpDrawer-u4rxnuR-.js → LpDrawer-RRamxbLV.js} +1 -1
- package/dist/chunks/{LpDropdownMenu-BgZ0Z_Im.js → LpDropdownMenu-Dl7Z4ZhS.js} +2 -2
- package/dist/chunks/{LpEditable-CjxuOVh_.js → LpEditable-D3PvD6Zm.js} +5 -5
- package/dist/chunks/{LpEmptyState-CKFTtQgJ.js → LpEmptyState-EQHiRYhG.js} +7 -3
- package/dist/chunks/{LpFieldNotice-CPI8EwtU.js → LpFieldNotice-CzQ2_5rQ.js} +1 -1
- package/dist/chunks/{LpFileTree-BH0gNlSJ.js → LpFileTree-D7KTaJiM.js} +2 -2
- package/dist/chunks/{LpFileTreeNode-gPVB1oXM.js → LpFileTreeNode-14GVqQTd.js} +5 -5
- package/dist/chunks/{LpFormField-gRASfyj2.js → LpFormField-MSQhRCTz.js} +2 -2
- package/dist/chunks/{LpGauge-BdjPGnqw.js → LpGauge-BO1PYYqD.js} +2 -2
- package/dist/chunks/{LpHintLayer-C2asBIfs.js → LpHintLayer-CmYm37z7.js} +2 -2
- package/dist/chunks/{LpHints-CNZQ76JV.js → LpHints-iLIRUkCj.js} +3 -3
- package/dist/chunks/{LpIcon-DkP-9mzK.js → LpIcon--5WSb3jJ.js} +1 -1
- package/dist/chunks/{LpInfraNode-yTh4-5qa.js → LpInfraNode-BT0VQrOa.js} +2 -2
- package/dist/chunks/{LpInput-2H-5ILOB.js → LpInput-1eNvtzXs.js} +2 -2
- package/dist/chunks/{LpLightbox-lAt5uIfx.js → LpLightbox-DWw6OSA4.js} +3 -3
- package/dist/chunks/{LpLoader-DjJEX8hL.js → LpLoader-B82_CcV0.js} +1 -1
- package/dist/chunks/{LpLogViewer-x78YfhJf.js → LpLogViewer-pzanZASC.js} +6 -6
- package/dist/chunks/{LpMapPicker-Dp5RSCi9.js → LpMapPicker-DNIUvF6V.js} +2 -2
- package/dist/chunks/{LpMediaGallery-D92oJfY_.js → LpMediaGallery-Dx8xzg5N.js} +3 -3
- package/dist/chunks/{LpModal-CpH0bNo7.js → LpModal-BnjZ2iem.js} +1 -1
- package/dist/chunks/{LpModalNav-BH4ANQGJ.js → LpModalNav-Cu04W1pV.js} +4 -4
- package/dist/chunks/{LpNotificationBell-BdUQqZhs.js → LpNotificationBell-PSQqFqEv.js} +8 -8
- package/dist/chunks/{LpNumberField-DZY3Z670.js → LpNumberField-e0IBkWU1.js} +1 -1
- package/dist/chunks/{LpNumberFlow-5khYxsfX.js → LpNumberFlow-CcxEGUy0.js} +10 -7
- package/dist/chunks/{LpPagination-DbeACavM.js → LpPagination-BIIiYHXR.js} +1 -1
- package/dist/chunks/{LpPaletteTheme-CqWNRwHE.js → LpPaletteTheme-D05ywM1W.js} +8 -6
- package/dist/chunks/{LpPasswordInput-BzAZsf5V.js → LpPasswordInput-vdcw-koG.js} +2 -2
- package/dist/chunks/{LpPhoneInput-BMpd-0QR.js → LpPhoneInput-CETEp5W7.js} +2 -2
- package/dist/chunks/{LpPie-DdFSTbGE.js → LpPie-BlWDxCtG.js} +6 -6
- package/dist/chunks/{LpPipeline-zjEM1Lwc.js → LpPipeline-BXjoxkq_.js} +4 -4
- package/dist/chunks/{LpProgress-BX33EKkN.js → LpProgress-rOlyafqp.js} +1 -1
- package/dist/chunks/{LpQuota-DqNv1yjs.js → LpQuota-B12yijdB.js} +3 -3
- package/dist/chunks/{LpRadioGroup-CsXeV9Ct.js → LpRadioGroup-ggaPL92T.js} +1 -1
- package/dist/chunks/{LpRangeCalendar-l-0nSFmY.js → LpRangeCalendar-CYYUYqJ7.js} +1 -1
- package/dist/chunks/{LpRelativeTime-UM8EkfrN.js → LpRelativeTime-DwgCb5-8.js} +1 -1
- package/dist/chunks/{LpRepeater-OvzByE5L.js → LpRepeater-fliVBcN7.js} +3 -3
- package/dist/chunks/LpRollingText-B54Pje7P.js +86 -0
- package/dist/chunks/{LpScramble-TDO5Kdou.js → LpScramble-Cw-af0Yh.js} +1 -1
- package/dist/chunks/{LpSecretField-DwQiPOko.js → LpSecretField-D_IMxEGW.js} +7 -7
- package/dist/chunks/{LpSection-DcQsieHG.js → LpSection-DFK_uoJE.js} +1 -1
- package/dist/chunks/{LpSegmented-D2VST0o3.js → LpSegmented-CeACsMMA.js} +4 -2
- package/dist/chunks/{LpSelect-DYX2-u5A.js → LpSelect-Cxq4yBGd.js} +7 -7
- package/dist/chunks/LpShift-BAp8lGoa.js +160 -0
- package/dist/chunks/LpSidebar-BftYsk5d.js +494 -0
- package/dist/chunks/LpSidebarNav-BzNWMZfc.js +357 -0
- package/dist/chunks/LpSidebarRow-BIReMT1f.js +302 -0
- package/dist/chunks/{LpStat-DIsQfbeD.js → LpStat-BFCM6fUn.js} +9 -7
- package/dist/chunks/{LpStatus-BQU1TUsv.js → LpStatus-BWTLVPVh.js} +3 -3
- package/dist/chunks/{LpStepMark-CXa7DJy_.js → LpStepMark-DkdkebLC.js} +3 -3
- package/dist/chunks/{LpStepper-C9RM_yTG.js → LpStepper-BgqjKrqg.js} +1 -1
- package/dist/chunks/{LpSwap-DzE6M3Mg.js → LpSwap-B5YOXgtx.js} +1 -1
- package/dist/chunks/{LpSwitch-bGZxUDSx.js → LpSwitch-DyrRes7s.js} +1 -1
- package/dist/chunks/LpTabFace-D6BMCbAP.js +77 -0
- package/dist/chunks/{LpTableFilter-CTZMqFDU.js → LpTableFilter-Dkzamzkl.js} +3 -3
- package/dist/chunks/{LpTagsInput-DrjmrOge.js → LpTagsInput-DAZNNehI.js} +5 -5
- package/dist/chunks/{LpTextarea-BbYqmG1S.js → LpTextarea-DbmVPj2X.js} +32 -7
- package/dist/chunks/{useTheme-BOV_-AiY.js → LpThemeProvider-8MG6yqU9.js} +66 -1
- package/dist/chunks/{LpThemeStudio-B2nPaG-h.js → LpThemeStudio-DWtG6OZZ.js} +140 -106
- package/dist/chunks/LpThemeStudioSection-B7bt8Iyg.js +32 -0
- package/dist/chunks/{LpThemeSwitcher-C0A5sg_E.js → LpThemeSwitcher-Dcx-VkSk.js} +9 -8
- package/dist/chunks/{LpTimeField-DQ8WvPr2.js → LpTimeField-BawSVsmt.js} +1 -1
- package/dist/chunks/{LpToaster-BTTl_wuy.js → LpToaster-50hp_B5O.js} +2 -2
- package/dist/chunks/{LpTopologyCanvas-B0wEvAoq.js → LpTopologyCanvas-CnUh8uzS.js} +43 -13
- package/dist/chunks/{LpTour-BgCfNlaW.js → LpTour-DC3LTlxg.js} +1 -1
- package/dist/chunks/{LpTruncate-TcqfkyJM.js → LpTruncate-Ced4gquB.js} +1 -1
- package/dist/chunks/{LpUptimeBar-DRadGq4Y.js → LpUptimeBar-CuB0LX8K.js} +4 -4
- package/dist/chunks/{MenuTitle-Dab3EGOp.js → MenuTitle-BmCNNTKH.js} +1 -1
- package/dist/chunks/{OptionContent-DPzNsybd.js → OptionContent-22UT8zLn.js} +1 -1
- package/dist/chunks/{PickerField-DwLw7j0L.js → PickerField-Bfh8id3d.js} +2 -2
- package/dist/chunks/{RevealToggle-BVFM_FAU.js → RevealToggle-B0ITuIX-.js} +2 -2
- package/dist/chunks/{TourCard--2IfgnUU.js → TourCard-CBkj1GYi.js} +5 -5
- package/dist/chunks/{icons-CLu1iR1j.js → icons-DMITlfw5.js} +1 -1
- package/dist/chunks/{palette-BiY7yQZL.js → palette-Cvxl_1tM.js} +1 -1
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.js +1 -1
- package/dist/components/DropzoneLinkField.vue.js +1 -1
- package/dist/components/FieldWrapper.vue.js +1 -1
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +67 -2
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.js +1 -1
- package/dist/components/LpBottomNav.vue.d.ts +28 -0
- package/dist/components/LpBottomNav.vue.js +4 -0
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.js +26 -8
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpCodeDisplay.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.d.ts +15 -2
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +47 -26
- package/dist/components/LpCommandScope.vue.js +1 -1
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.js +1 -1
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpCopyButton.vue.js +1 -1
- package/dist/components/LpCountdown.vue.js +1 -1
- package/dist/components/LpDataList.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.js +1 -1
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDock.vue.js +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +2 -2
- package/dist/components/LpEditable.vue.js +1 -1
- package/dist/components/LpEmptyState.vue.d.ts +2 -0
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFieldNotice.vue.js +1 -1
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpGauge.vue.js +1 -1
- package/dist/components/LpHintLayer.vue.js +1 -1
- package/dist/components/LpHints.vue.js +1 -1
- package/dist/components/LpIcon.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +2 -2
- package/dist/components/LpInfraNode.vue.js +1 -1
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLoader.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +1 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPaletteFacts.vue.js +2 -2
- package/dist/components/LpPaletteHeader.vue.js +2 -2
- package/dist/components/LpPaletteStats.vue.js +2 -2
- package/dist/components/LpPaletteTheme.vue.d.ts +2 -0
- package/dist/components/LpPaletteTheme.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPipeline.vue.js +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpQuota.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeCalendar.vue.js +1 -1
- package/dist/components/LpRelativeTime.vue.js +1 -1
- package/dist/components/LpRepeater.vue.js +1 -1
- package/dist/components/LpRollingText.vue.d.ts +2 -2
- package/dist/components/LpRollingText.vue.js +1 -1
- package/dist/components/LpScramble.vue.js +1 -1
- package/dist/components/LpSecretField.vue.js +1 -1
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShift.vue.d.ts +20 -0
- package/dist/components/LpShift.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +1 -1
- package/dist/components/LpSidebar.vue.d.ts +74 -23
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.d.ts +30 -2
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSidebarRow.vue.d.ts +49 -0
- package/dist/components/LpSidebarRow.vue.js +4 -0
- package/dist/components/LpStat.vue.d.ts +11 -0
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpStatus.vue.js +1 -1
- package/dist/components/LpStepMark.vue.js +1 -1
- package/dist/components/LpStepper.vue.js +1 -1
- package/dist/components/LpSwap.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTabFace.vue.d.ts +14 -0
- package/dist/components/LpTabFace.vue.js +4 -0
- package/dist/components/LpTable.vue.js +11 -11
- package/dist/components/LpTableFilter.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +12 -4
- package/dist/components/LpTabs.vue.js +22 -74
- package/dist/components/LpTagsInput.vue.js +1 -1
- package/dist/components/LpTextarea.vue.d.ts +19 -1
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.js +1 -1
- package/dist/components/LpThemeStudio.vue.d.ts +2 -2
- package/dist/components/LpThemeStudio.vue.js +1 -1
- package/dist/components/LpThemeStudioSection.vue.d.ts +4 -1
- package/dist/components/LpThemeStudioSection.vue.js +1 -1
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTopologyCanvas.vue.d.ts +3 -1
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpTour.vue.js +1 -1
- package/dist/components/LpTruncate.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/MenuTitle.vue.js +1 -1
- package/dist/components/OptionContent.vue.js +1 -1
- package/dist/components/PickerField.vue.js +1 -1
- package/dist/components/RevealToggle.vue.js +1 -1
- package/dist/components/TourCard.vue.js +1 -1
- package/dist/components/chart.d.ts +14 -0
- package/dist/components/chart.js +13 -0
- package/dist/components/format.d.ts +10 -3
- package/dist/components/format.js +14 -2
- package/dist/components/menuWhen.js +1 -1
- package/dist/components/rolling.d.ts +5 -0
- package/dist/components/rolling.js +23 -0
- package/dist/components/sidebar.d.ts +33 -0
- package/dist/components/tabs.d.ts +3 -0
- package/dist/components/tabs.js +6 -0
- package/dist/composables/usePaletteCommands.js +1 -0
- package/dist/composables/useReveal.js +1 -22
- package/dist/icons-all.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +156 -154
- package/dist/theme/palette.d.ts +3 -0
- package/package.json +1 -1
- package/src/component-names.ts +1 -0
- package/src/components/LpAppShell.vue +75 -7
- package/src/components/LpBottomNav.vue +84 -0
- package/src/components/LpChart.vue +23 -3
- package/src/components/LpColorPicker.vue +32 -1
- package/src/components/LpCommandPalette.vue +23 -5
- package/src/components/LpEmptyState.vue +4 -1
- package/src/components/LpNumberFlow.vue +23 -9
- package/src/components/LpPaletteTheme.vue +3 -3
- package/src/components/LpRollingText.vue +27 -2
- package/src/components/LpSegmented.vue +3 -1
- package/src/components/LpSelect.vue +5 -4
- package/src/components/LpShift.vue +174 -2
- package/src/components/LpSidebar.vue +304 -18
- package/src/components/LpSidebarNav.vue +200 -70
- package/src/components/LpSidebarRow.vue +238 -0
- package/src/components/LpStat.vue +12 -4
- package/src/components/LpTabFace.vue +52 -0
- package/src/components/LpTabs.vue +15 -42
- package/src/components/LpTextarea.vue +34 -4
- package/src/components/LpThemeStudio.vue +92 -56
- package/src/components/LpThemeStudioSection.vue +15 -7
- package/src/components/LpThemeSwitcher.vue +4 -1
- package/src/components/LpTopologyCanvas.vue +35 -4
- package/src/components/chart.ts +26 -0
- package/src/components/format.ts +31 -5
- package/src/components/rolling.ts +28 -0
- package/src/components/sidebar.ts +33 -0
- package/src/components/tabs.ts +9 -0
- package/src/composables/usePaletteCommands.ts +3 -0
- package/src/composables/useReveal.ts +7 -30
- package/src/index.ts +2 -1
- package/src/kit-icons.ts +1 -1
- package/src/theme/palette.ts +4 -2
- package/dist/assets/LpTabs-CO3EcsNV.css +0 -18
- package/dist/chunks/LpRollingText-DiL717LK.js +0 -51
- package/dist/chunks/LpShift-DXNsauQZ.js +0 -40
- package/dist/chunks/LpSidebar-D44peKq2.js +0 -256
- package/dist/chunks/LpSidebarNav--VwkXO_a.js +0 -211
- package/dist/chunks/LpThemeProvider-DcbfOxsU.js +0 -69
- package/dist/chunks/LpThemeStudioSection-BwhqYDLf.js +0 -20
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
* The mechanism — measure the natural size, pin it in pixels, tween — lives in
|
|
26
26
|
* `useShift`, and LpModal drives its panel with the same composable.
|
|
27
27
|
*/
|
|
28
|
-
import { computed } from "vue"
|
|
28
|
+
import { computed, onBeforeUnmount, watch } from "vue"
|
|
29
|
+
import { prefersReducedMotion } from "../composables/easing"
|
|
29
30
|
import { useShift, type ShiftAxis } from "../composables/useShift"
|
|
30
31
|
|
|
31
32
|
const props = withDefaults(
|
|
@@ -43,8 +44,26 @@ const props = withDefaults(
|
|
|
43
44
|
* canvas), where a tween would fight the thing already animating it.
|
|
44
45
|
*/
|
|
45
46
|
disabled?: boolean
|
|
47
|
+
/**
|
|
48
|
+
* Which edge the content holds to while the box catches up, for a flex
|
|
49
|
+
* row. A box pinned to the right (`ml-auto`) grows leftwards, and content
|
|
50
|
+
* laid out from its left edge throws every unchanged control to the right
|
|
51
|
+
* for the first frame and slides it back: the controls that did not change
|
|
52
|
+
* are the ones that jump. `"end"` keeps them where they are and lets the
|
|
53
|
+
* new one come in from the free side.
|
|
54
|
+
*/
|
|
55
|
+
anchor?: "start" | "end"
|
|
56
|
+
/**
|
|
57
|
+
* Fade the box's own children in and out as they come and go. Without it
|
|
58
|
+
* the box glides but a control still pops into being as the edge uncovers
|
|
59
|
+
* it, and one that leaves is gone in a frame while its space closes up.
|
|
60
|
+
* Only direct children: what a child does inside itself is its own affair,
|
|
61
|
+
* so a control that changes its label carries the label as its `key` and
|
|
62
|
+
* is replaced, the old one fading as the new one arrives.
|
|
63
|
+
*/
|
|
64
|
+
presence?: boolean
|
|
46
65
|
}>(),
|
|
47
|
-
{ axis: "height", disabled: false },
|
|
66
|
+
{ axis: "height", disabled: false, anchor: "start", presence: false },
|
|
48
67
|
)
|
|
49
68
|
|
|
50
69
|
const { el, tweening, resizing } = useShift({
|
|
@@ -67,6 +86,157 @@ const TRANSITION_CLASS: Record<ShiftAxis, string> = {
|
|
|
67
86
|
}
|
|
68
87
|
const transitionClass = computed(() => TRANSITION_CLASS[props.axis])
|
|
69
88
|
|
|
89
|
+
/*
|
|
90
|
+
* Presence. A leaving element is already out of the DOM when the observer
|
|
91
|
+
* hears of it, so where it stood is read from the last time it was seen: its
|
|
92
|
+
* offset from the anchored edge, which stays true while the box eases towards
|
|
93
|
+
* that edge. A clone stands there, out of the flow, and fades.
|
|
94
|
+
*
|
|
95
|
+
* What stays but is moved by the change (a title centred in a box that just
|
|
96
|
+
* lost its subtitle) glides from where it was instead of jumping (FLIP).
|
|
97
|
+
*/
|
|
98
|
+
const PRESENCE_MS = 200
|
|
99
|
+
const MOVE_MS = 160
|
|
100
|
+
const EASE = "cubic-bezier(0.2, 0, 0, 1)"
|
|
101
|
+
const GHOST = "data-lp-shift-ghost"
|
|
102
|
+
|
|
103
|
+
interface Seen {
|
|
104
|
+
from: number
|
|
105
|
+
top: number
|
|
106
|
+
width: number
|
|
107
|
+
height: number
|
|
108
|
+
x: number
|
|
109
|
+
y: number
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const seen = new WeakMap<Element, Seen>()
|
|
113
|
+
let presenceObserver: MutationObserver | undefined
|
|
114
|
+
let rememberTimer: ReturnType<typeof setTimeout> | undefined
|
|
115
|
+
let presenceSize: ResizeObserver | undefined
|
|
116
|
+
|
|
117
|
+
function rememberSoon(box: HTMLElement, wait = PRESENCE_MS + 80) {
|
|
118
|
+
clearTimeout(rememberTimer)
|
|
119
|
+
rememberTimer = setTimeout(() => remember(box), wait)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function elementsOf(box: HTMLElement): Element[] {
|
|
123
|
+
return [...box.querySelectorAll("*")].filter((node) => !node.closest(`[${GHOST}]`) && !(node instanceof SVGElement && !(node instanceof SVGSVGElement)))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function remember(box: HTMLElement) {
|
|
127
|
+
const outer = box.getBoundingClientRect()
|
|
128
|
+
for (const node of elementsOf(box)) {
|
|
129
|
+
const r = node.getBoundingClientRect()
|
|
130
|
+
seen.set(node, {
|
|
131
|
+
from: props.anchor === "end" ? outer.right - r.right : r.left - outer.left,
|
|
132
|
+
top: r.top - outer.top,
|
|
133
|
+
width: r.width,
|
|
134
|
+
height: r.height,
|
|
135
|
+
x: r.left,
|
|
136
|
+
y: r.top,
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const offset = () => (props.anchor === "end" ? -6 : 6)
|
|
142
|
+
|
|
143
|
+
function ghost(box: HTMLElement, node: Element) {
|
|
144
|
+
const at = seen.get(node)
|
|
145
|
+
if (!at || !(node instanceof HTMLElement) || node.hasAttribute(GHOST) || at.width === 0) return
|
|
146
|
+
const copy = node.cloneNode(true) as HTMLElement
|
|
147
|
+
copy.setAttribute(GHOST, "")
|
|
148
|
+
copy.setAttribute("aria-hidden", "true")
|
|
149
|
+
copy.inert = true
|
|
150
|
+
Object.assign(copy.style, {
|
|
151
|
+
position: "absolute",
|
|
152
|
+
top: `${at.top}px`,
|
|
153
|
+
[props.anchor === "end" ? "right" : "left"]: `${at.from}px`,
|
|
154
|
+
width: `${at.width}px`,
|
|
155
|
+
height: `${at.height}px`,
|
|
156
|
+
margin: "0",
|
|
157
|
+
pointerEvents: "none",
|
|
158
|
+
})
|
|
159
|
+
box.appendChild(copy)
|
|
160
|
+
copy
|
|
161
|
+
.animate(
|
|
162
|
+
[
|
|
163
|
+
{ opacity: 1, transform: "none", filter: "blur(0)" },
|
|
164
|
+
{ opacity: 0, transform: `translateX(${offset()}px) scale(0.96)`, filter: "blur(2px)" },
|
|
165
|
+
],
|
|
166
|
+
{ duration: PRESENCE_MS, easing: EASE },
|
|
167
|
+
)
|
|
168
|
+
.finished.catch(() => undefined)
|
|
169
|
+
.finally(() => copy.remove())
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function arrive(node: Element) {
|
|
173
|
+
if (!(node instanceof HTMLElement) || node.hasAttribute(GHOST)) return
|
|
174
|
+
node.animate(
|
|
175
|
+
[
|
|
176
|
+
{ opacity: 0, transform: `translateX(${offset()}px) scale(0.96)`, filter: "blur(2px)" },
|
|
177
|
+
{ opacity: 1, transform: "none", filter: "blur(0)" },
|
|
178
|
+
],
|
|
179
|
+
{ duration: PRESENCE_MS, delay: 40, easing: EASE, fill: "backwards" },
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function glide(node: Element) {
|
|
184
|
+
const was = seen.get(node)
|
|
185
|
+
if (!was || !(node instanceof HTMLElement)) return
|
|
186
|
+
const now = node.getBoundingClientRect()
|
|
187
|
+
const dx = was.x - now.left
|
|
188
|
+
const dy = was.y - now.top
|
|
189
|
+
if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) return
|
|
190
|
+
node.animate([{ transform: `translate(${dx}px, ${dy}px)` }, { transform: "none" }], { duration: MOVE_MS, easing: EASE })
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
watch(
|
|
194
|
+
[el, () => props.presence && !props.disabled],
|
|
195
|
+
([box, on]) => {
|
|
196
|
+
presenceObserver?.disconnect()
|
|
197
|
+
presenceObserver = undefined
|
|
198
|
+
presenceSize?.disconnect()
|
|
199
|
+
presenceSize = undefined
|
|
200
|
+
if (!box || !on) return
|
|
201
|
+
remember(box)
|
|
202
|
+
presenceObserver = new MutationObserver((records) => {
|
|
203
|
+
const real = records.filter(
|
|
204
|
+
(r) =>
|
|
205
|
+
![...r.addedNodes, ...r.removedNodes].every((n) => n instanceof Element && n.hasAttribute(GHOST)),
|
|
206
|
+
)
|
|
207
|
+
if (real.length && !prefersReducedMotion()) {
|
|
208
|
+
const added = new Set<Node>()
|
|
209
|
+
for (const record of real) {
|
|
210
|
+
record.removedNodes.forEach((node) => node instanceof Element && ghost(box, node))
|
|
211
|
+
record.addedNodes.forEach((node) => {
|
|
212
|
+
added.add(node)
|
|
213
|
+
if (node instanceof Element) arrive(node)
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
// Measured in the next frame, which still comes before the paint: by
|
|
217
|
+
// then the box has taken the classes the change gave it (held to its
|
|
218
|
+
// edge), and the place an element lands is the one it will keep.
|
|
219
|
+
const parents = real.map((r) => r.target).filter((t): t is Element => t instanceof Element && !t.hasAttribute(GHOST))
|
|
220
|
+
requestAnimationFrame(() => {
|
|
221
|
+
for (const parent of parents) for (const child of parent.children) if (!added.has(child)) glide(child)
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
rememberSoon(box)
|
|
225
|
+
})
|
|
226
|
+
presenceObserver.observe(box, { childList: true, subtree: true })
|
|
227
|
+
presenceSize = new ResizeObserver(() => rememberSoon(box, 60))
|
|
228
|
+
presenceSize.observe(box)
|
|
229
|
+
void document.fonts?.ready.then(() => rememberSoon(box, 0))
|
|
230
|
+
},
|
|
231
|
+
{ flush: "post" },
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
onBeforeUnmount(() => {
|
|
235
|
+
presenceObserver?.disconnect()
|
|
236
|
+
presenceSize?.disconnect()
|
|
237
|
+
clearTimeout(rememberTimer)
|
|
238
|
+
})
|
|
239
|
+
|
|
70
240
|
defineExpose({ resizing })
|
|
71
241
|
</script>
|
|
72
242
|
|
|
@@ -83,6 +253,8 @@ defineExpose({ resizing })
|
|
|
83
253
|
? `${transitionClass} duration-fast ease-[var(--ease-emphasized)] motion-reduce:transition-none`
|
|
84
254
|
: '',
|
|
85
255
|
resizing ? 'overflow-hidden' : '',
|
|
256
|
+
resizing && anchor === 'end' ? 'justify-end' : '',
|
|
257
|
+
presence ? 'relative' : '',
|
|
86
258
|
]"
|
|
87
259
|
>
|
|
88
260
|
<slot :resizing="resizing" />
|
|
@@ -13,11 +13,14 @@
|
|
|
13
13
|
* auto-closes the drawer. With `responsive` off the component renders exactly
|
|
14
14
|
* the static <nav> it always has — existing call sites are unaffected.
|
|
15
15
|
*/
|
|
16
|
-
import { computed, ref, useId } from "vue"
|
|
16
|
+
import { computed, nextTick, ref, useId } from "vue"
|
|
17
|
+
import { parseCombo, useHotkeys } from "../composables/useHotkeys"
|
|
17
18
|
import { usePersisted } from "../composables/usePersisted"
|
|
18
19
|
import { useLabels } from "../composables/useLpConfig"
|
|
19
20
|
import LpDrawer from "./LpDrawer.vue"
|
|
21
|
+
import LpIcon from "./LpIcon.vue"
|
|
20
22
|
import LpInput from "./LpInput.vue"
|
|
23
|
+
import LpTooltip from "./LpTooltip.vue"
|
|
21
24
|
import LpSidebarNav from "./LpSidebarNav.vue"
|
|
22
25
|
import type { SidebarItem, SidebarSection } from "./sidebar"
|
|
23
26
|
|
|
@@ -50,7 +53,17 @@ const props = withDefaults(
|
|
|
50
53
|
/** Placeholder for that field (defaults to the `search` label). */
|
|
51
54
|
searchPlaceholder?: string
|
|
52
55
|
/** Text overrides: `nav` names the landmark, `search` the filter field. */
|
|
53
|
-
labels?: Partial<{
|
|
56
|
+
labels?: Partial<{
|
|
57
|
+
nav: string
|
|
58
|
+
search: string
|
|
59
|
+
pinned: string
|
|
60
|
+
pin: string
|
|
61
|
+
unpin: string
|
|
62
|
+
expand: string
|
|
63
|
+
compact: string
|
|
64
|
+
expandRail: string
|
|
65
|
+
resize: string
|
|
66
|
+
}>
|
|
54
67
|
/** The query, when the app wants to own it (v-model:search). */
|
|
55
68
|
search?: string
|
|
56
69
|
/** Show a skeleton placeholder instead of the items. */
|
|
@@ -126,6 +139,44 @@ const props = withDefaults(
|
|
|
126
139
|
* see LpSidebarNav for why a tall nav keeps it pinned.
|
|
127
140
|
*/
|
|
128
141
|
hoverFollow?: boolean
|
|
142
|
+
/**
|
|
143
|
+
* Icons only (v-model:compact): each row's name, count and the pages under
|
|
144
|
+
* it move into a card beside the icon. With `resizable` the person decides,
|
|
145
|
+
* and the choice is remembered under `persist`.
|
|
146
|
+
*/
|
|
147
|
+
compact?: boolean
|
|
148
|
+
/**
|
|
149
|
+
* The person sizes the rail: drag its edge (double-click resets), drag it
|
|
150
|
+
* narrow enough and it folds to icons, and a button and Ctrl+B fold it
|
|
151
|
+
* outright. One mechanism, not two: a narrow rail that only truncates its
|
|
152
|
+
* labels is the worst of both, so below a point it becomes the icon rail.
|
|
153
|
+
*/
|
|
154
|
+
resizable?: boolean
|
|
155
|
+
/** Rail width bounds while resizing, in px. */
|
|
156
|
+
minWidth?: number
|
|
157
|
+
maxWidth?: number
|
|
158
|
+
/** The key that folds the rail when `resizable`. False turns it off. */
|
|
159
|
+
compactHotkey?: string | false
|
|
160
|
+
/**
|
|
161
|
+
* Rows offer "Pin to top" in their menu, and pinned pages lead the nav
|
|
162
|
+
* (v-model:pinned, ids; remembered under `persist` when unbound).
|
|
163
|
+
*/
|
|
164
|
+
pinnable?: boolean
|
|
165
|
+
pinned?: string[]
|
|
166
|
+
/** How long the pointer rests on a row with a `preview` before its card opens, in ms. */
|
|
167
|
+
previewDelay?: number
|
|
168
|
+
/**
|
|
169
|
+
* Mark the app as not production: a coloured band along the top of the rail
|
|
170
|
+
* with the label hanging from it ("staging", "local"). It is there so an
|
|
171
|
+
* edit on the test copy is not mistaken for one on the real thing; leave it
|
|
172
|
+
* unset in production.
|
|
173
|
+
*/
|
|
174
|
+
environment?: string | { label: string; tone?: "warning" | "danger" | "action" | "brand" }
|
|
175
|
+
/**
|
|
176
|
+
* The app's hint system (createHintSystem). Rows that name a `hintPage`
|
|
177
|
+
* then mark unread news on it with a dot, with nothing for the app to wire.
|
|
178
|
+
*/
|
|
179
|
+
hints?: { unreadNews: (page?: string) => readonly unknown[] }
|
|
129
180
|
}>(),
|
|
130
181
|
{
|
|
131
182
|
skeletonRows: 6,
|
|
@@ -136,6 +187,11 @@ const props = withDefaults(
|
|
|
136
187
|
variant: "rail",
|
|
137
188
|
autoCollapseAfter: 14,
|
|
138
189
|
hoverFollow: false,
|
|
190
|
+
compact: undefined,
|
|
191
|
+
minWidth: 180,
|
|
192
|
+
maxWidth: 420,
|
|
193
|
+
compactHotkey: "Ctrl+B",
|
|
194
|
+
previewDelay: 900,
|
|
139
195
|
},
|
|
140
196
|
)
|
|
141
197
|
|
|
@@ -145,15 +201,47 @@ const emit = defineEmits<{
|
|
|
145
201
|
(e: "update:collapsed", keys: string[]): void
|
|
146
202
|
(e: "select", item: SidebarItem): void
|
|
147
203
|
(e: "update:search", value: string): void
|
|
204
|
+
(e: "update:compact", value: boolean): void
|
|
205
|
+
(e: "update:pinned", ids: string[]): void
|
|
148
206
|
}>()
|
|
149
207
|
|
|
150
|
-
|
|
208
|
+
defineSlots<{
|
|
209
|
+
/** The brand; `compact` says the rail is down to icons. */
|
|
210
|
+
header?: (props: { compact?: boolean }) => unknown
|
|
211
|
+
actions?: (props: { compact?: boolean }) => unknown
|
|
212
|
+
footer?: (props: { compact?: boolean }) => unknown
|
|
213
|
+
item?: (props: { item: SidebarItem; active: boolean }) => unknown
|
|
214
|
+
}>()
|
|
215
|
+
|
|
216
|
+
const text = useLabels(
|
|
217
|
+
"sidebar",
|
|
218
|
+
{
|
|
219
|
+
nav: "Sidebar",
|
|
220
|
+
search: "Search",
|
|
221
|
+
pinned: "Pinned",
|
|
222
|
+
pin: "Pin to top",
|
|
223
|
+
unpin: "Unpin",
|
|
224
|
+
expand: "Show the pages under it",
|
|
225
|
+
compact: "Collapse the sidebar",
|
|
226
|
+
expandRail: "Expand the sidebar",
|
|
227
|
+
resize: "Drag to resize, double-click to reset",
|
|
228
|
+
},
|
|
229
|
+
() => props.labels,
|
|
230
|
+
)
|
|
151
231
|
const placeholder = computed(() => props.searchPlaceholder ?? text.value.search)
|
|
152
232
|
|
|
233
|
+
function withNews(item: SidebarItem): SidebarItem {
|
|
234
|
+
const children = item.children?.map(withNews)
|
|
235
|
+
const pages = item.hintPage === undefined ? [] : Array.isArray(item.hintPage) ? item.hintPage : [item.hintPage]
|
|
236
|
+
const fresh = !!props.hints && pages.some((p) => props.hints!.unreadNews(p).length > 0)
|
|
237
|
+
return fresh || children ? { ...item, children, dot: item.dot || fresh } : item
|
|
238
|
+
}
|
|
239
|
+
|
|
153
240
|
// Normalise both inputs to a single section list.
|
|
154
|
-
const allGroups = computed<SidebarSection[]>(() =>
|
|
155
|
-
props.sections ?? (props.items ? [{ items: props.items }] : [])
|
|
156
|
-
)
|
|
241
|
+
const allGroups = computed<SidebarSection[]>(() => {
|
|
242
|
+
const groups = props.sections ?? (props.items ? [{ items: props.items }] : [])
|
|
243
|
+
return props.hints ? groups.map((g) => ({ ...g, items: g.items.map(withNews) })) : groups
|
|
244
|
+
})
|
|
157
245
|
|
|
158
246
|
/*
|
|
159
247
|
* The filter query. Controlled through v-model:search when the app wants it —
|
|
@@ -179,6 +267,13 @@ function matches(item: SidebarItem, needle: string): boolean {
|
|
|
179
267
|
return (item.keywords ?? []).some((k) => normalise(k).includes(needle))
|
|
180
268
|
}
|
|
181
269
|
|
|
270
|
+
/** The item if it matches, with every page under it; else only the pages under it that match. */
|
|
271
|
+
function narrowed(item: SidebarItem, needle: string): SidebarItem | null {
|
|
272
|
+
if (matches(item, needle)) return item
|
|
273
|
+
const children = (item.children ?? []).filter((c) => matches(c, needle))
|
|
274
|
+
return children.length ? { ...item, children } : null
|
|
275
|
+
}
|
|
276
|
+
|
|
182
277
|
const groups = computed<SidebarSection[]>(() => {
|
|
183
278
|
const needle = normalise(query.value.trim())
|
|
184
279
|
if (!needle) return allGroups.value
|
|
@@ -186,7 +281,10 @@ const groups = computed<SidebarSection[]>(() => {
|
|
|
186
281
|
// under it reads as a section that failed to load rather than one that
|
|
187
282
|
// matched nothing.
|
|
188
283
|
return allGroups.value
|
|
189
|
-
.map((section) => ({
|
|
284
|
+
.map((section) => ({
|
|
285
|
+
...section,
|
|
286
|
+
items: section.items.map((i) => narrowed(i, needle)).filter((i): i is SidebarItem => i !== null),
|
|
287
|
+
}))
|
|
190
288
|
.filter((section) => section.items.length > 0)
|
|
191
289
|
})
|
|
192
290
|
|
|
@@ -226,7 +324,7 @@ const autoCollapsed = computed<string[]>(() => {
|
|
|
226
324
|
const limit = props.autoCollapseAfter
|
|
227
325
|
if (!limit || navLength.value <= limit) return []
|
|
228
326
|
return groups.value
|
|
229
|
-
.filter((g) => g.collapsible && !g.items.some(itemActive))
|
|
327
|
+
.filter((g) => g.collapsible && !g.items.some((i) => itemActive(i) || !!i.children?.some(itemActive)))
|
|
230
328
|
.map(keyOf)
|
|
231
329
|
.filter(Boolean)
|
|
232
330
|
})
|
|
@@ -246,6 +344,117 @@ function toggleSection(key: string, collapsed: boolean) {
|
|
|
246
344
|
emit("update:collapsed", next)
|
|
247
345
|
}
|
|
248
346
|
|
|
347
|
+
/*
|
|
348
|
+
* Compact and width. Bound, the app owns them; unbound, the sidebar keeps them,
|
|
349
|
+
* across visits under `persist`.
|
|
350
|
+
*/
|
|
351
|
+
const ownCompact = usePersisted<boolean>(() => props.persist && `lp-sidebar:${props.persist}:compact`, false)
|
|
352
|
+
const isCompact = computed(() => props.compact ?? ownCompact.value)
|
|
353
|
+
function setCompact(value: boolean) {
|
|
354
|
+
ownCompact.value = value
|
|
355
|
+
emit("update:compact", value)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const ENV_TONE = {
|
|
359
|
+
warning: "bg-accent text-ink-inverse",
|
|
360
|
+
danger: "bg-danger text-white",
|
|
361
|
+
action: "bg-action text-ink-inverse",
|
|
362
|
+
brand: "bg-brand text-ink-inverse",
|
|
363
|
+
} as const
|
|
364
|
+
const env = computed(() => {
|
|
365
|
+
const e = props.environment
|
|
366
|
+
if (!e) return null
|
|
367
|
+
const { label, tone = "warning" } = typeof e === "string" ? { label: e } : e
|
|
368
|
+
return { label, tone: ENV_TONE[tone] }
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
const DEFAULT_WIDTH = 240
|
|
372
|
+
const COMPACT_WIDTH = 64
|
|
373
|
+
const ownWidth = usePersisted<number>(() => props.resizable && props.persist && `lp-sidebar:${props.persist}:width`, DEFAULT_WIDTH)
|
|
374
|
+
const dragging = ref(false)
|
|
375
|
+
|
|
376
|
+
const railWidth = computed(() => {
|
|
377
|
+
if (isCompact.value) return `${COMPACT_WIDTH}px`
|
|
378
|
+
if (props.resizable) return `${ownWidth.value}px`
|
|
379
|
+
return props.width
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
/*
|
|
383
|
+
* Dragging past the narrowest width folds the rail to icons, and dragging a
|
|
384
|
+
* folded rail out unfolds it: below a point labels only truncate to nothing
|
|
385
|
+
* useful, and the icon rail is the honest form of "narrow".
|
|
386
|
+
*/
|
|
387
|
+
const FOLD_AT = 132
|
|
388
|
+
function startResize(event: PointerEvent) {
|
|
389
|
+
if (event.button !== 0) return
|
|
390
|
+
const handle = event.currentTarget as HTMLElement
|
|
391
|
+
handle.setPointerCapture(event.pointerId)
|
|
392
|
+
const from = event.clientX
|
|
393
|
+
const start = isCompact.value ? COMPACT_WIDTH : ownWidth.value
|
|
394
|
+
dragging.value = true
|
|
395
|
+
const move = (e: PointerEvent) => {
|
|
396
|
+
const wanted = start + (e.clientX - from)
|
|
397
|
+
if (wanted < FOLD_AT) {
|
|
398
|
+
// Folded by dragging: the width it unfolds to is the one it had, not
|
|
399
|
+
// the narrowest the drag passed through on its way down.
|
|
400
|
+
if (!isCompact.value) setCompact(true)
|
|
401
|
+
if (start !== COMPACT_WIDTH) ownWidth.value = start
|
|
402
|
+
return
|
|
403
|
+
}
|
|
404
|
+
if (isCompact.value) setCompact(false)
|
|
405
|
+
ownWidth.value = Math.round(Math.min(props.maxWidth, Math.max(props.minWidth, wanted)))
|
|
406
|
+
}
|
|
407
|
+
const stop = () => {
|
|
408
|
+
dragging.value = false
|
|
409
|
+
handle.removeEventListener("pointermove", move)
|
|
410
|
+
handle.removeEventListener("pointerup", stop)
|
|
411
|
+
handle.removeEventListener("pointercancel", stop)
|
|
412
|
+
}
|
|
413
|
+
handle.addEventListener("pointermove", move)
|
|
414
|
+
handle.addEventListener("pointerup", stop)
|
|
415
|
+
handle.addEventListener("pointercancel", stop)
|
|
416
|
+
}
|
|
417
|
+
const railEl = ref<HTMLElement | null>(null)
|
|
418
|
+
async function searchFromCompact() {
|
|
419
|
+
setCompact(false)
|
|
420
|
+
await nextTick()
|
|
421
|
+
railEl.value?.querySelector<HTMLInputElement>("input")?.focus()
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function resetWidth() {
|
|
425
|
+
ownWidth.value = DEFAULT_WIDTH
|
|
426
|
+
setCompact(false)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
useHotkeys(() =>
|
|
430
|
+
props.resizable && props.compactHotkey
|
|
431
|
+
? [
|
|
432
|
+
{
|
|
433
|
+
...parseCombo(props.compactHotkey),
|
|
434
|
+
help: { group: text.value.nav, label: text.value.compact, combo: props.compactHotkey },
|
|
435
|
+
handler: () => setCompact(!isCompact.value),
|
|
436
|
+
},
|
|
437
|
+
]
|
|
438
|
+
: [],
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
/* Pinned pages, by id; unbound they are kept under `persist`. */
|
|
442
|
+
const ownPinned = usePersisted<string[]>(() => props.persist && `lp-sidebar:${props.persist}:pinned`, [], {
|
|
443
|
+
validate: (value): value is string[] => Array.isArray(value) && value.every((k) => typeof k === "string"),
|
|
444
|
+
})
|
|
445
|
+
const pinnedIds = computed(() => props.pinned ?? ownPinned.value)
|
|
446
|
+
const everyItem = computed(() => allGroups.value.flatMap((g) => g.items.flatMap((i) => [i, ...(i.children ?? [])])))
|
|
447
|
+
const pinnedItems = computed<SidebarItem[]>(() =>
|
|
448
|
+
props.pinnable
|
|
449
|
+
? pinnedIds.value.map((id) => everyItem.value.find((i) => i.id === id)).filter((i): i is SidebarItem => !!i)
|
|
450
|
+
: [],
|
|
451
|
+
)
|
|
452
|
+
function togglePin(id: string) {
|
|
453
|
+
const next = pinnedIds.value.includes(id) ? pinnedIds.value.filter((k) => k !== id) : [...pinnedIds.value, id]
|
|
454
|
+
ownPinned.value = next
|
|
455
|
+
emit("update:pinned", next)
|
|
456
|
+
}
|
|
457
|
+
|
|
249
458
|
function activate(item: SidebarItem) {
|
|
250
459
|
if (item.disabled) return
|
|
251
460
|
emit("update:modelValue", item.id)
|
|
@@ -291,12 +500,28 @@ defineOptions({ inheritAttrs: false })
|
|
|
291
500
|
<template>
|
|
292
501
|
<!-- Desktop / always-on rail -->
|
|
293
502
|
<nav
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
:
|
|
503
|
+
ref="railEl"
|
|
504
|
+
class="relative h-full shrink-0 flex-col gap-1"
|
|
505
|
+
:class="[
|
|
506
|
+
railClass,
|
|
507
|
+
chromeClass,
|
|
508
|
+
isCompact ? 'px-2 py-3' : 'p-3',
|
|
509
|
+
railWidth ? '' : 'w-60',
|
|
510
|
+
dragging ? '' : 'transition-[width] duration-(--duration-medium) ease-[var(--ease-emphasized)] motion-reduce:transition-none',
|
|
511
|
+
]"
|
|
512
|
+
:style="railWidth ? { width: railWidth } : undefined"
|
|
513
|
+
:data-compact="isCompact || undefined"
|
|
297
514
|
:aria-label="text.nav"
|
|
298
515
|
v-bind="$attrs"
|
|
299
516
|
>
|
|
517
|
+
<div v-if="env" class="pointer-events-none absolute inset-x-0 top-0 z-20 h-[3px]" :class="env.tone" />
|
|
518
|
+
<span
|
|
519
|
+
v-if="env && !isCompact"
|
|
520
|
+
class="pointer-events-none absolute left-1/2 top-0 z-20 -translate-x-1/2 rounded-b-small px-2 pb-0.5 text-[10px] font-semibold uppercase tracking-wide"
|
|
521
|
+
:class="env.tone"
|
|
522
|
+
>
|
|
523
|
+
{{ env.label }}
|
|
524
|
+
</span>
|
|
300
525
|
<!-- Rail: edge-to-edge (negative margins cancel the p-3) and h-16 to match
|
|
301
526
|
the app header, so its bottom hairline continues the header's border-b.
|
|
302
527
|
Panel: kept inside the padding — bleeding to the edge would cut across
|
|
@@ -305,18 +530,33 @@ defineOptions({ inheritAttrs: false })
|
|
|
305
530
|
v-if="$slots.header"
|
|
306
531
|
class="mb-3 flex shrink-0 items-center"
|
|
307
532
|
:class="[
|
|
308
|
-
variant === 'panel' ? 'pb-3' : '-mx-3 -mt-3 h-16 px-3',
|
|
533
|
+
variant === 'panel' ? 'pb-3' : isCompact ? '-mx-2 -mt-3 h-16 overflow-hidden' : '-mx-3 -mt-3 h-16 px-3',
|
|
309
534
|
divider ? 'border-b border-line' : '',
|
|
310
535
|
]"
|
|
311
536
|
>
|
|
312
|
-
<
|
|
537
|
+
<div v-if="isCompact" class="flex w-16 shrink-0 justify-center">
|
|
538
|
+
<slot name="header" :compact="true" />
|
|
539
|
+
</div>
|
|
540
|
+
<slot v-else name="header" :compact="false" />
|
|
313
541
|
</div>
|
|
314
542
|
|
|
315
543
|
<!-- The filter sits between the brand and the nav: it belongs to the list,
|
|
316
544
|
not to the header, and a person reaching for it is already looking at
|
|
317
545
|
the items. -->
|
|
546
|
+
<!-- Folded, the filter is a button: a field forty pixels wide holds no
|
|
547
|
+
query, so the rail opens to give it room and puts the caret in it. -->
|
|
548
|
+
<LpTooltip v-if="searchable && isCompact" :content="placeholder" side="right">
|
|
549
|
+
<button
|
|
550
|
+
type="button"
|
|
551
|
+
class="mb-2 ml-1 grid size-10 shrink-0 place-items-center rounded-control text-muted outline-none transition-colors hover:bg-ink/[0.05] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
552
|
+
:aria-label="placeholder"
|
|
553
|
+
@click="searchFromCompact"
|
|
554
|
+
>
|
|
555
|
+
<LpIcon name="lucide:search" :size="17" />
|
|
556
|
+
</button>
|
|
557
|
+
</LpTooltip>
|
|
318
558
|
<LpInput
|
|
319
|
-
v-if="searchable"
|
|
559
|
+
v-if="searchable && !isCompact"
|
|
320
560
|
v-model="query"
|
|
321
561
|
size="sm"
|
|
322
562
|
icon="lucide:search"
|
|
@@ -328,6 +568,11 @@ defineOptions({ inheritAttrs: false })
|
|
|
328
568
|
<LpSidebarNav
|
|
329
569
|
:groups="groups"
|
|
330
570
|
:pill-id="railPillId"
|
|
571
|
+
:compact="isCompact"
|
|
572
|
+
:pinnable="pinnable"
|
|
573
|
+
:pinned="pinnedItems"
|
|
574
|
+
:preview-delay="previewDelay"
|
|
575
|
+
:labels="text"
|
|
331
576
|
:item-active="itemActive"
|
|
332
577
|
:loading="loading"
|
|
333
578
|
:skeleton-rows="skeletonRows"
|
|
@@ -336,21 +581,57 @@ defineOptions({ inheritAttrs: false })
|
|
|
336
581
|
:hover-follow="hoverFollow"
|
|
337
582
|
@activate="activate"
|
|
338
583
|
@toggle-section="toggleSection"
|
|
584
|
+
@toggle-pin="togglePin"
|
|
339
585
|
>
|
|
340
586
|
<template v-if="$slots.item" #item="slotProps">
|
|
341
587
|
<slot name="item" v-bind="slotProps" />
|
|
342
588
|
</template>
|
|
343
589
|
</LpSidebarNav>
|
|
344
590
|
|
|
591
|
+
<LpTooltip v-if="resizable" :content="isCompact ? text.expandRail : text.compact" side="right">
|
|
592
|
+
<button
|
|
593
|
+
type="button"
|
|
594
|
+
class="mt-1 flex h-8 shrink-0 items-center gap-2 rounded-control px-3 text-xs text-muted outline-none transition-colors hover:bg-ink/[0.05] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
595
|
+
:class="isCompact ? 'ml-1 w-10 justify-center px-0' : ''"
|
|
596
|
+
:aria-label="isCompact ? text.expandRail : text.compact"
|
|
597
|
+
:aria-pressed="isCompact"
|
|
598
|
+
@click="setCompact(!isCompact)"
|
|
599
|
+
>
|
|
600
|
+
<LpIcon :name="isCompact ? 'lucide:panel-left-open' : 'lucide:panel-left-close'" :size="16" class="shrink-0" />
|
|
601
|
+
<span v-if="!isCompact" class="truncate">{{ text.compact }}</span>
|
|
602
|
+
</button>
|
|
603
|
+
</LpTooltip>
|
|
604
|
+
|
|
345
605
|
<!-- Actions (search, "new …") sit just above the footer, pinned to the
|
|
346
606
|
bottom of the rail and outside the scroll region — not trailing the
|
|
347
607
|
nav list. -->
|
|
348
|
-
<div v-if="$slots.actions" class="mt-2 flex shrink-0 flex-col gap-2">
|
|
349
|
-
<slot name="actions" />
|
|
608
|
+
<div v-if="$slots.actions" class="mt-2 flex shrink-0 flex-col gap-2" :class="isCompact ? 'w-12 items-center' : ''">
|
|
609
|
+
<slot name="actions" :compact="isCompact" />
|
|
350
610
|
</div>
|
|
351
611
|
|
|
352
|
-
<div v-if="$slots.footer" class="mt-2 shrink-0 border-t border-line pt-3">
|
|
353
|
-
<
|
|
612
|
+
<div v-if="$slots.footer" class="mt-2 shrink-0 overflow-hidden border-t border-line pt-3">
|
|
613
|
+
<div v-if="isCompact" class="flex w-12 justify-center">
|
|
614
|
+
<slot name="footer" :compact="true" />
|
|
615
|
+
</div>
|
|
616
|
+
<slot v-else name="footer" :compact="false" />
|
|
617
|
+
</div>
|
|
618
|
+
|
|
619
|
+
<!-- The edge itself is the handle: a few pixels either side of the
|
|
620
|
+
hairline, a brand line while it is held. -->
|
|
621
|
+
<div
|
|
622
|
+
v-if="resizable && variant === 'rail'"
|
|
623
|
+
role="separator"
|
|
624
|
+
aria-orientation="vertical"
|
|
625
|
+
:aria-label="text.resize"
|
|
626
|
+
:title="text.resize"
|
|
627
|
+
class="group/resize absolute inset-y-0 -right-1.5 z-10 w-3 cursor-col-resize touch-none"
|
|
628
|
+
@pointerdown="startResize"
|
|
629
|
+
@dblclick="resetWidth"
|
|
630
|
+
>
|
|
631
|
+
<span
|
|
632
|
+
class="absolute inset-y-0 left-1/2 w-0.5 -translate-x-1/2 bg-brand opacity-0 transition-opacity duration-(--duration-fast) group-hover/resize:opacity-60"
|
|
633
|
+
:class="dragging ? '!opacity-100' : ''"
|
|
634
|
+
/>
|
|
354
635
|
</div>
|
|
355
636
|
</nav>
|
|
356
637
|
|
|
@@ -389,6 +670,10 @@ defineOptions({ inheritAttrs: false })
|
|
|
389
670
|
<LpSidebarNav
|
|
390
671
|
:groups="groups"
|
|
391
672
|
:pill-id="drawerPillId"
|
|
673
|
+
:pinnable="pinnable"
|
|
674
|
+
:pinned="pinnedItems"
|
|
675
|
+
:preview-delay="previewDelay"
|
|
676
|
+
:labels="text"
|
|
392
677
|
:item-active="itemActive"
|
|
393
678
|
:loading="loading"
|
|
394
679
|
:skeleton-rows="skeletonRows"
|
|
@@ -397,6 +682,7 @@ defineOptions({ inheritAttrs: false })
|
|
|
397
682
|
:hover-follow="hoverFollow"
|
|
398
683
|
@activate="activate"
|
|
399
684
|
@toggle-section="toggleSection"
|
|
685
|
+
@toggle-pin="togglePin"
|
|
400
686
|
>
|
|
401
687
|
<template v-if="$slots.item" #item="slotProps">
|
|
402
688
|
<slot name="item" v-bind="slotProps" />
|