@leavepulse/ui 0.43.3 → 0.44.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/README.md +75 -7
- package/dist/assets/{LpCommandPalette-DH7sPcKP.css → LpCommandPalette-DCP5g2ik.css} +21 -21
- package/dist/assets/{LpDropzone-BBoWvi-J.css → LpDropzone-i4BdMi1H.css} +12 -12
- package/dist/assets/{LpImageEditor-Ct7Y3aNu.css → LpImageEditor-CbMA82g8.css} +6 -6
- package/dist/assets/{LpShortcutsDialog-C6YoQ8_M.css → LpShortcutsDialog-ChUyU7oV.css} +7 -7
- package/dist/assets/{LpSkeleton-D7xU8IPV.css → LpSkeleton-H3Z5dEIk.css} +4 -1
- package/dist/assets/{LpTable-BgPLJOXa.css → LpTable-DDQNdf86.css} +9 -9
- package/dist/chunks/ContextMenuItems-CrfeNPWT.js +123 -0
- package/dist/chunks/{LayoutCanvas-Dd20boUG.js → LayoutCanvas-D-jPOAvg.js} +26 -9
- package/dist/chunks/{LayoutNode-DGpZA9LX.js → LayoutNode-D8Xy9N-o.js} +20 -8
- package/dist/chunks/{LpActionBar-BlvtGB2P.js → LpActionBar-BbwCF6l5.js} +24 -10
- package/dist/chunks/{LpAddressInput-B-Zvxsn7.js → LpAddressInput-BrOqkZqg.js} +53 -48
- package/dist/chunks/{LpAlert-BAYHDSS1.js → LpAlert-DkOIjsbq.js} +15 -8
- package/dist/chunks/{LpAppShell-BGQVIY-3.js → LpAppShell-BUKPcHco.js} +51 -24
- package/dist/chunks/{LpAutocomplete-DExlIZe_.js → LpAutocomplete-DxV3Y9h4.js} +50 -23
- package/dist/chunks/{LpAvatar-B3-xAOH3.js → LpAvatar-ClBbEYZN.js} +17 -8
- package/dist/chunks/LpBreadcrumbs-B8IJgFIc.js +95 -0
- package/dist/chunks/{LpButton-5VXXCjwF.js → LpButton-kqJBHIIb.js} +44 -14
- package/dist/chunks/{LpCard-BhI1N_vL.js → LpCard-CpRByR0q.js} +1 -1
- package/dist/chunks/{LpCheckbox-CVccKYaH.js → LpCheckbox-DpqchzH4.js} +7 -3
- package/dist/chunks/{LpCodeBlock-Bo0B4TFR.js → LpCodeBlock-DeHJPq7p.js} +48 -16
- package/dist/chunks/{LpColorPicker-BlXea7LM.js → LpColorPicker-Bhf1qxhB.js} +32 -9
- package/dist/chunks/{LpConfirmDialog-Q-bxff8z.js → LpConfirmDialog-CIUOSnWo.js} +58 -16
- package/dist/chunks/LpConfirmHost-CDm0Wtpv.js +49 -0
- package/dist/chunks/LpContextMenu-DQw5dGfG.js +68 -0
- package/dist/chunks/LpDatePicker-Drirx1AK.js +191 -0
- package/dist/chunks/{LpDrawer-BZ6k3hvo.js → LpDrawer-bScx-KMO.js} +63 -26
- package/dist/chunks/{LpFileTree-_5vFBgaK.js → LpFileTree-CSI_5SK2.js} +23 -8
- package/dist/chunks/{LpFileTreeNode-BdcIYKIE.js → LpFileTreeNode-B4MiSQX2.js} +2 -2
- package/dist/chunks/LpFormField-CIk5qosh.js +87 -0
- package/dist/chunks/{LpInput-9EXWUM9B.js → LpInput-C0e8y8_d.js} +26 -11
- package/dist/chunks/{LpLightbox-2sYVMY-S.js → LpLightbox-Mr0W_6HK.js} +147 -60
- package/dist/chunks/{LpLink-DCND-zyu.js → LpLink-Krkjhe_y.js} +15 -6
- package/dist/chunks/{LpLogViewer-CaRy5MeS.js → LpLogViewer-YBOaFnc_.js} +68 -34
- package/dist/chunks/{LpMapPicker-ul4oYno6.js → LpMapPicker-NUzS9e37.js} +38 -13
- package/dist/chunks/{LpMediaGallery-FF-NquIo.js → LpMediaGallery-DfJmMW0I.js} +6 -3
- package/dist/chunks/{LpModal-Bt0pQCI_.js → LpModal-DPwGKBPv.js} +49 -26
- package/dist/chunks/{LpModalNav-YaifHrXC.js → LpModalNav-DPg5H9f3.js} +9 -6
- package/dist/chunks/{LpNotificationBell-DyGGsMAM.js → LpNotificationBell-t5yn-siX.js} +58 -24
- package/dist/chunks/{LpNumberField-CWuX08iX.js → LpNumberField-DWnQKktS.js} +20 -7
- package/dist/chunks/{LpNumberFlow-amWYaeuA.js → LpNumberFlow-z4WPZcZu.js} +22 -12
- package/dist/chunks/{LpOtpInput-y2v-pmiN.js → LpOtpInput-CziPF3Oq.js} +37 -8
- package/dist/chunks/{LpPagination-5FxjmmB5.js → LpPagination-DFngX7Xo.js} +30 -19
- package/dist/chunks/{LpPasswordInput--sjYCkLS.js → LpPasswordInput-CWVDuBJ5.js} +10 -7
- package/dist/chunks/{LpPhoneInput-BDSKVl_z.js → LpPhoneInput-CD-ZTeSd.js} +57 -33
- package/dist/chunks/{LpPie-D6OnzwlJ.js → LpPie-BLco31Nx.js} +78 -21
- package/dist/chunks/{LpPopover-8268UmlC.js → LpPopover-p_e_shN3.js} +4 -2
- package/dist/chunks/{LpProgress-Btq5W4Ut.js → LpProgress-CBU1iI69.js} +5 -4
- package/dist/chunks/{LpRadioGroup-xPIuVDO_.js → LpRadioGroup-DmEW0l78.js} +9 -2
- package/dist/chunks/{LpSegmented-fV0Sk92p.js → LpSegmented-Dejd7cIx.js} +7 -3
- package/dist/chunks/{LpSelect-ANIVtrpM.js → LpSelect-DrRD9I7L.js} +40 -18
- package/dist/chunks/{LpSidebar-ClBUwoEz.js → LpSidebar-BgwUNWpJ.js} +30 -22
- package/dist/chunks/{LpSidebarNav-llOcs_Fn.js → LpSidebarNav-Bqxz8aWT.js} +14 -9
- package/dist/chunks/{LpSkeleton-CTI-vBgW.js → LpSkeleton-BB6XIV-w.js} +1 -1
- package/dist/chunks/{LpSlider-BwNIBkAK.js → LpSlider-Cuzmrd7p.js} +7 -2
- package/dist/chunks/{LpSparkline-C6xQ2dwv.js → LpSparkline-CB80N8SJ.js} +11 -4
- package/dist/chunks/{LpStat-CQaW8R--.js → LpStat-dXtyrdX9.js} +1 -1
- package/dist/chunks/{LpSwitch-Blizvqyt.js → LpSwitch-B1aLKulj.js} +40 -26
- package/dist/chunks/{LpTableOfContents-CjvxPMpU.js → LpTableOfContents-BUcAfGPQ.js} +36 -41
- package/dist/chunks/{LpTabs-Cefe_lys.js → LpTabs-_qMVaZJ6.js} +19 -5
- package/dist/chunks/LpTextarea-yP8hGedR.js +77 -0
- package/dist/chunks/LpThemeProvider-Wjm7Tuit.js +35 -0
- package/dist/chunks/LpThemeSwitcher-CKLc10KZ.js +169 -0
- package/dist/chunks/{LpTilt-DuyKbpkA.js → LpTilt-B7KHX1zH.js} +2 -2
- package/dist/chunks/{LpTimeField-K6nAGboX.js → LpTimeField-C5HRgS5H.js} +13 -7
- package/dist/chunks/{LpToaster-gDB1EaU3.js → LpToaster--oT6lfhL.js} +57 -28
- package/dist/chunks/{LpTooltip-hCLPAqMG.js → LpTooltip-CBdzeKNf.js} +13 -6
- package/dist/chunks/LpTooltipProvider-CftjV7Xc.js +25 -0
- package/dist/chunks/{LpTopologyCanvas-Cm1L39uL.js → LpTopologyCanvas-DocsLal3.js} +6 -1
- package/dist/chunks/{LpUptimeBar-DvAMno0S.js → LpUptimeBar-DLtxOMRy.js} +43 -14
- package/dist/chunks/{LpThemeSwitcher-Cue-myST.js → useTheme-C8JHUlPM.js} +173 -215
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.d.ts +8 -0
- package/dist/components/ContextMenuItems.vue.js +4 -0
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.d.ts +4 -0
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.d.ts +3 -0
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.d.ts +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.d.ts +9 -0
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +10 -0
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.d.ts +31 -6
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.d.ts +3 -3
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.d.ts +2 -2
- package/dist/components/LpBreadcrumbs.vue.d.ts +13 -9
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +35 -98
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.d.ts +31 -2
- package/dist/components/LpChart.vue.js +317 -95
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.d.ts +2 -0
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +8 -10
- package/dist/components/LpCommandPalette.vue.js +88 -78
- package/dist/components/LpConfirmDialog.vue.d.ts +15 -3
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.d.ts +9 -0
- package/dist/components/LpConfirmHost.vue.js +4 -0
- package/dist/components/LpContextMenu.vue.d.ts +3 -5
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.d.ts +24 -6
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDrawer.vue.d.ts +10 -0
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +22 -6
- package/dist/components/LpFileTree.vue.d.ts +11 -2
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.d.ts +9 -0
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +92 -57
- package/dist/components/LpInput.vue.d.ts +3 -3
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.d.ts +17 -0
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLink.vue.d.ts +11 -11
- package/dist/components/LpLink.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +20 -2
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.d.ts +4 -4
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.d.ts +16 -6
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.d.ts +0 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +11 -2
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.d.ts +2 -0
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPagination.vue.d.ts +2 -0
- package/dist/components/LpPagination.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.d.ts +21 -2
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPopover.vue.d.ts +2 -0
- package/dist/components/LpPopover.vue.js +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpSection.vue.d.ts +1 -1
- package/dist/components/LpSegmented.vue.d.ts +5 -0
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.d.ts +1 -0
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.d.ts +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +11 -8
- package/dist/components/LpSidebar.vue.d.ts +6 -2
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSkeleton.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpSparkline.vue.d.ts +1 -0
- package/dist/components/LpSparkline.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.d.ts +23 -1
- package/dist/components/LpTable.vue.js +74 -36
- package/dist/components/LpTableOfContents.vue.d.ts +4 -2
- package/dist/components/LpTableOfContents.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +2 -0
- package/dist/components/LpTabs.vue.js +1 -1
- package/dist/components/LpTextarea.vue.d.ts +19 -6
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.d.ts +22 -0
- package/dist/components/LpThemeProvider.vue.js +4 -0
- package/dist/components/LpThemeSwitcher.vue.d.ts +13 -0
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTilt.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.d.ts +6 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.d.ts +1 -2
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTooltipProvider.vue.d.ts +22 -0
- package/dist/components/LpTooltipProvider.vue.js +4 -0
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.d.ts +4 -0
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/chart.d.ts +32 -1
- package/dist/components/chart.js +39 -4
- package/dist/components/codeHighlight.d.ts +6 -0
- package/dist/components/codeHighlight.js +92 -20
- package/dist/components/color.d.ts +1 -1
- package/dist/components/color.js +20 -9
- package/dist/components/commandSearch.d.ts +0 -1
- package/dist/components/commandSearch.js +1 -17
- package/dist/components/confirm.d.ts +25 -0
- package/dist/components/confirm.js +26 -0
- package/dist/components/countries.d.ts +6 -0
- package/dist/components/countries.js +7 -0
- package/dist/components/fileTree.js +16 -7
- package/dist/components/imageCrop.d.ts +42 -0
- package/dist/components/imageCrop.js +47 -0
- package/dist/components/link.d.ts +12 -0
- package/dist/components/link.js +19 -0
- package/dist/components/modalDepth.d.ts +18 -2
- package/dist/components/modalDepth.js +17 -5
- package/dist/components/otp.d.ts +5 -0
- package/dist/components/otp.js +10 -0
- package/dist/components/sidebar.d.ts +7 -1
- package/dist/components/tableSort.d.ts +8 -0
- package/dist/components/tableSort.js +31 -0
- package/dist/composables/useField.d.ts +34 -0
- package/dist/composables/useField.js +45 -0
- package/dist/composables/useFormDraft.d.ts +10 -3
- package/dist/composables/useFormDraft.js +9 -14
- package/dist/composables/useHotkeys.d.ts +17 -1
- package/dist/composables/useHotkeys.js +55 -18
- package/dist/composables/useInputFilter.d.ts +17 -0
- package/dist/composables/useInputFilter.js +47 -10
- package/dist/composables/useLpConfig.d.ts +24 -0
- package/dist/composables/useLpConfig.js +44 -0
- package/dist/composables/useRovingFocus.js +7 -0
- package/dist/composables/useTilt.d.ts +2 -2
- package/dist/composables/useTilt.js +5 -6
- package/dist/composables/useToast.d.ts +20 -0
- package/dist/composables/useToast.js +28 -4
- package/dist/composables/useZoomPan.d.ts +3 -3
- package/dist/composables/useZoomPan.js +4 -4
- package/dist/index.d.ts +33 -3
- package/dist/index.js +185 -139
- package/dist/layout/tree.d.ts +2 -0
- package/dist/layout/tree.js +16 -1
- package/dist/layout/useLayout.d.ts +15 -0
- package/dist/layout/useLayout.js +41 -14
- package/dist/theme/useTheme.d.ts +32 -9
- package/package.json +8 -7
- package/src/component-names.ts +5 -7
- package/src/components/ContextMenuItems.vue +88 -0
- package/src/components/LayoutCanvas.vue +26 -6
- package/src/components/LayoutNode.vue +19 -2
- package/src/components/LpActionBar.vue +30 -7
- package/src/components/LpAddressInput.vue +57 -41
- package/src/components/LpAlert.vue +16 -3
- package/src/components/LpAppShell.vue +45 -12
- package/src/components/LpAutocomplete.vue +46 -10
- package/src/components/LpAvatar.vue +19 -5
- package/src/components/LpBreadcrumbs.vue +65 -41
- package/src/components/LpButton.vue +59 -13
- package/src/components/LpChart.vue +272 -28
- package/src/components/LpCheckbox.vue +7 -3
- package/src/components/LpCodeBlock.vue +50 -14
- package/src/components/LpColorPicker.vue +39 -9
- package/src/components/LpCommandPalette.vue +36 -25
- package/src/components/LpConfirmDialog.vue +68 -17
- package/src/components/LpConfirmHost.vue +59 -0
- package/src/components/LpContextMenu.vue +14 -115
- package/src/components/LpDatePicker.vue +99 -68
- package/src/components/LpDrawer.vue +56 -19
- package/src/components/LpDropzone.vue +21 -4
- package/src/components/LpFileTree.vue +33 -7
- package/src/components/LpFormField.vue +30 -7
- package/src/components/LpImageEditor.vue +96 -57
- package/src/components/LpInput.vue +18 -7
- package/src/components/LpLightbox.vue +140 -29
- package/src/components/LpLink.vue +24 -13
- package/src/components/LpLogViewer.vue +86 -17
- package/src/components/LpMapPicker.vue +36 -13
- package/src/components/LpMediaGallery.vue +8 -2
- package/src/components/LpModal.vue +53 -27
- package/src/components/LpModalNav.vue +5 -2
- package/src/components/LpNotificationBell.vue +63 -17
- package/src/components/LpNumberField.vue +21 -6
- package/src/components/LpNumberFlow.vue +26 -10
- package/src/components/LpOtpInput.vue +43 -8
- package/src/components/LpPagination.vue +23 -7
- package/src/components/LpPasswordInput.vue +4 -1
- package/src/components/LpPhoneInput.vue +53 -27
- package/src/components/LpPie.vue +94 -15
- package/src/components/LpPopover.vue +3 -0
- package/src/components/LpProgress.vue +5 -3
- package/src/components/LpRadioGroup.vue +9 -2
- package/src/components/LpSegmented.vue +12 -2
- package/src/components/LpSelect.vue +0 -0
- package/src/components/LpShortcutsDialog.vue +8 -3
- package/src/components/LpSidebar.vue +14 -9
- package/src/components/LpSidebarNav.vue +17 -5
- package/src/components/LpSkeleton.vue +4 -1
- package/src/components/LpSlider.vue +7 -1
- package/src/components/LpSparkline.vue +11 -3
- package/src/components/LpSwitch.vue +30 -7
- package/src/components/LpTable.vue +91 -32
- package/src/components/LpTableOfContents.vue +50 -32
- package/src/components/LpTabs.vue +23 -6
- package/src/components/LpTextarea.vue +44 -6
- package/src/components/LpThemeProvider.vue +41 -0
- package/src/components/LpThemeSwitcher.vue +64 -17
- package/src/components/LpTilt.vue +2 -2
- package/src/components/LpTimeField.vue +14 -3
- package/src/components/LpToaster.vue +66 -26
- package/src/components/LpTooltip.vue +22 -5
- package/src/components/LpTooltipProvider.vue +24 -0
- package/src/components/LpTopologyCanvas.vue +12 -1
- package/src/components/LpUptimeBar.vue +46 -8
- package/src/components/chart.ts +77 -5
- package/src/components/codeHighlight.ts +111 -15
- package/src/components/color.ts +30 -9
- package/src/components/commandSearch.ts +0 -17
- package/src/components/confirm.ts +58 -0
- package/src/components/countries.ts +12 -0
- package/src/components/fileTree.ts +19 -7
- package/src/components/imageCrop.ts +107 -0
- package/src/components/link.ts +34 -0
- package/src/components/modalDepth.ts +48 -6
- package/src/components/otp.ts +16 -0
- package/src/components/sidebar.ts +7 -1
- package/src/components/tableSort.ts +37 -0
- package/src/composables/useField.ts +79 -0
- package/src/composables/useFormDraft.ts +25 -20
- package/src/composables/useHotkeys.ts +89 -18
- package/src/composables/useInputFilter.ts +77 -14
- package/src/composables/useLpConfig.ts +87 -0
- package/src/composables/useRovingFocus.ts +11 -0
- package/src/composables/useTilt.ts +5 -6
- package/src/composables/useToast.ts +56 -4
- package/src/composables/useZoomPan.ts +6 -6
- package/src/index.ts +38 -3
- package/src/layout/tree.ts +30 -0
- package/src/layout/useLayout.ts +68 -18
- package/src/nuxt.ts +59 -17
- package/src/runtime/config.plugin.ts +14 -0
- package/src/runtime/theme.plugin.ts +42 -22
- package/src/theme/useTheme.ts +206 -100
- package/dist/chunks/LpBreadcrumbs-p4jHe_V-.js +0 -62
- package/dist/chunks/LpContextMenu-CwW1QXe_.js +0 -217
- package/dist/chunks/LpDatePicker-CwOToxGq.js +0 -164
- package/dist/chunks/LpFormField-CazBc2-a.js +0 -62
- package/dist/chunks/LpTextarea-t7F6gv8E.js +0 -47
|
@@ -6,20 +6,10 @@
|
|
|
6
6
|
* long-press on touch) opens the menu at the pointer. Shares the floating-panel
|
|
7
7
|
* surface + option-row look with the rest of the kit via dropdown.ts.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
ContextMenuContent,
|
|
11
|
-
ContextMenuItem,
|
|
12
|
-
ContextMenuPortal,
|
|
13
|
-
ContextMenuRoot,
|
|
14
|
-
ContextMenuSeparator,
|
|
15
|
-
ContextMenuSub,
|
|
16
|
-
ContextMenuSubContent,
|
|
17
|
-
ContextMenuSubTrigger,
|
|
18
|
-
ContextMenuTrigger,
|
|
19
|
-
} from "reka-ui"
|
|
9
|
+
import { ContextMenuContent, ContextMenuPortal, ContextMenuRoot, ContextMenuTrigger } from "reka-ui"
|
|
20
10
|
import { computed } from "vue"
|
|
11
|
+
import ContextMenuItems from "./ContextMenuItems.vue"
|
|
21
12
|
import { POPOVER_PANEL } from "./dropdown"
|
|
22
|
-
import LpIcon from "./LpIcon.vue"
|
|
23
13
|
|
|
24
14
|
export interface ContextMenuItemDef {
|
|
25
15
|
label: string
|
|
@@ -30,7 +20,7 @@ export interface ContextMenuItemDef {
|
|
|
30
20
|
disabled?: boolean
|
|
31
21
|
separatorBefore?: boolean
|
|
32
22
|
onSelect?: () => void
|
|
33
|
-
/** Nested submenu. When present, onSelect is ignored. */
|
|
23
|
+
/** Nested submenu, to any depth. When present, onSelect is ignored. */
|
|
34
24
|
children?: ContextMenuItemDef[]
|
|
35
25
|
/**
|
|
36
26
|
* Renders as a tick-box rather than a plain item, and the menu stays open on
|
|
@@ -52,17 +42,13 @@ const props = defineProps<{
|
|
|
52
42
|
always?: boolean
|
|
53
43
|
}>()
|
|
54
44
|
|
|
55
|
-
// With no items
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
45
|
+
// With no items the trigger is disabled, so the element keeps the browser's
|
|
46
|
+
// native context menu. Lets callers wrap unconditionally and pass [] to opt out
|
|
47
|
+
// per element. `always` overrides this so a late-populated menu is ready on the
|
|
48
|
+
// very first click.
|
|
59
49
|
const enabled = computed(() => props.always || props.items.length > 0)
|
|
60
50
|
|
|
61
51
|
const PANEL = `${POPOVER_PANEL} z-(--z-popover) min-w-48 p-1 outline-none`
|
|
62
|
-
const ITEM =
|
|
63
|
-
"group/item flex cursor-pointer select-none items-center gap-2 rounded-md px-2.5 py-1.5 text-sm outline-none transition-[background-color,color] duration-[var(--duration-fast)] data-[disabled]:pointer-events-none data-[disabled]:opacity-45 data-[highlighted]:bg-brand-soft"
|
|
64
|
-
const ICON =
|
|
65
|
-
"transition-transform duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] group-data-[highlighted]/item:translate-x-0.5"
|
|
66
52
|
|
|
67
53
|
// A menu opened over a text field takes focus, and on close reka hands it to the
|
|
68
54
|
// trigger, so the caret and the selection the user right-clicked are gone — the
|
|
@@ -92,104 +78,17 @@ function returnFocus(event: Event) {
|
|
|
92
78
|
</script>
|
|
93
79
|
|
|
94
80
|
<template>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
81
|
+
<!-- One tree whether or not there are items: swapping between a bare slot and
|
|
82
|
+
the reka trigger remounted the wrapped content whenever items arrived
|
|
83
|
+
late, losing its state and focus. A disabled trigger leaves the browser's
|
|
84
|
+
own menu in place. -->
|
|
85
|
+
<ContextMenuRoot>
|
|
86
|
+
<ContextMenuTrigger as-child :disabled="!enabled" @contextmenu.capture="rememberField">
|
|
99
87
|
<slot />
|
|
100
88
|
</ContextMenuTrigger>
|
|
101
89
|
<ContextMenuPortal v-if="items.length">
|
|
102
90
|
<ContextMenuContent :class="PANEL" @close-auto-focus="returnFocus">
|
|
103
|
-
<
|
|
104
|
-
<ContextMenuSeparator v-if="item.separatorBefore" class="my-1 h-px bg-line" />
|
|
105
|
-
|
|
106
|
-
<!-- Submenu branch -->
|
|
107
|
-
<ContextMenuSub v-if="item.children?.length">
|
|
108
|
-
<ContextMenuSubTrigger
|
|
109
|
-
:class="[ITEM, 'justify-between text-ink data-[highlighted]:text-brand data-[state=open]:bg-brand-soft data-[state=open]:text-brand']"
|
|
110
|
-
>
|
|
111
|
-
<span class="flex items-center gap-2">
|
|
112
|
-
<LpIcon v-if="item.icon" :name="item.icon" :size="15" :class="ICON" />
|
|
113
|
-
{{ item.label }}
|
|
114
|
-
</span>
|
|
115
|
-
<LpIcon name="lucide:chevron-right" :size="14" class="text-muted" />
|
|
116
|
-
</ContextMenuSubTrigger>
|
|
117
|
-
<ContextMenuPortal>
|
|
118
|
-
<ContextMenuSubContent :class="PANEL" :side-offset="2" :align-offset="-4">
|
|
119
|
-
<template v-for="(sub, j) in item.children" :key="j">
|
|
120
|
-
<ContextMenuSeparator v-if="sub.separatorBefore" class="my-1 h-px bg-line" />
|
|
121
|
-
<!-- The tick-box case, as in the top-level menu: this is
|
|
122
|
-
where most of them live, since a submenu is what a list
|
|
123
|
-
of choices opens into. -->
|
|
124
|
-
<ContextMenuItem
|
|
125
|
-
v-if="sub.checked !== undefined"
|
|
126
|
-
:class="[ITEM, 'justify-between text-ink data-[highlighted]:text-brand']"
|
|
127
|
-
:disabled="sub.disabled"
|
|
128
|
-
@select.prevent="sub.onSelect?.()"
|
|
129
|
-
>
|
|
130
|
-
<span class="flex items-center gap-2">
|
|
131
|
-
<LpIcon
|
|
132
|
-
:name="sub.checked ? 'lucide:check' : 'lucide:minus'"
|
|
133
|
-
:size="15"
|
|
134
|
-
:class="[ICON, sub.checked ? '' : 'opacity-25']"
|
|
135
|
-
/>
|
|
136
|
-
{{ sub.label }}
|
|
137
|
-
</span>
|
|
138
|
-
<kbd v-if="sub.shortcut" class="text-xs text-muted">{{ sub.shortcut }}</kbd>
|
|
139
|
-
</ContextMenuItem>
|
|
140
|
-
|
|
141
|
-
<ContextMenuItem
|
|
142
|
-
v-else
|
|
143
|
-
:class="[ITEM, 'justify-between', sub.danger ? 'text-danger data-[highlighted]:bg-danger-soft data-[highlighted]:text-danger' : 'text-ink data-[highlighted]:text-brand']"
|
|
144
|
-
:disabled="sub.disabled"
|
|
145
|
-
@select="sub.onSelect?.()"
|
|
146
|
-
>
|
|
147
|
-
<span class="flex items-center gap-2">
|
|
148
|
-
<LpIcon v-if="sub.icon" :name="sub.icon" :size="15" :class="ICON" />
|
|
149
|
-
{{ sub.label }}
|
|
150
|
-
</span>
|
|
151
|
-
<kbd v-if="sub.shortcut" class="text-xs text-muted">{{ sub.shortcut }}</kbd>
|
|
152
|
-
</ContextMenuItem>
|
|
153
|
-
</template>
|
|
154
|
-
</ContextMenuSubContent>
|
|
155
|
-
</ContextMenuPortal>
|
|
156
|
-
</ContextMenuSub>
|
|
157
|
-
|
|
158
|
-
<!-- Tick-box item. `@select.prevent` keeps the menu open: these come
|
|
159
|
-
in lists where several are set in one visit, and closing after
|
|
160
|
-
each tick would mean reopening for the next. The tick occupies a
|
|
161
|
-
fixed slot so unticked rows stay aligned with ticked ones. -->
|
|
162
|
-
<ContextMenuItem
|
|
163
|
-
v-else-if="item.checked !== undefined"
|
|
164
|
-
:class="[ITEM, 'justify-between text-ink data-[highlighted]:text-brand']"
|
|
165
|
-
:disabled="item.disabled"
|
|
166
|
-
@select.prevent="item.onSelect?.()"
|
|
167
|
-
>
|
|
168
|
-
<span class="flex items-center gap-2">
|
|
169
|
-
<LpIcon
|
|
170
|
-
:name="item.checked ? 'lucide:check' : 'lucide:minus'"
|
|
171
|
-
:size="15"
|
|
172
|
-
:class="[ICON, item.checked ? '' : 'opacity-25']"
|
|
173
|
-
/>
|
|
174
|
-
{{ item.label }}
|
|
175
|
-
</span>
|
|
176
|
-
<kbd v-if="item.shortcut" class="text-xs text-muted">{{ item.shortcut }}</kbd>
|
|
177
|
-
</ContextMenuItem>
|
|
178
|
-
|
|
179
|
-
<!-- Leaf item -->
|
|
180
|
-
<ContextMenuItem
|
|
181
|
-
v-else
|
|
182
|
-
:class="[ITEM, 'justify-between', item.danger ? 'text-danger data-[highlighted]:bg-danger-soft data-[highlighted]:text-danger' : 'text-ink data-[highlighted]:text-brand']"
|
|
183
|
-
:disabled="item.disabled"
|
|
184
|
-
@select="item.onSelect?.()"
|
|
185
|
-
>
|
|
186
|
-
<span class="flex items-center gap-2">
|
|
187
|
-
<LpIcon v-if="item.icon" :name="item.icon" :size="15" :class="ICON" />
|
|
188
|
-
{{ item.label }}
|
|
189
|
-
</span>
|
|
190
|
-
<kbd v-if="item.shortcut" class="text-xs text-muted">{{ item.shortcut }}</kbd>
|
|
191
|
-
</ContextMenuItem>
|
|
192
|
-
</template>
|
|
91
|
+
<ContextMenuItems :items="items" :panel="PANEL" />
|
|
193
92
|
</ContextMenuContent>
|
|
194
93
|
</ContextMenuPortal>
|
|
195
94
|
</ContextMenuRoot>
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export default { inheritAttrs: false }
|
|
3
|
+
</script>
|
|
4
|
+
|
|
1
5
|
<script setup lang="ts">
|
|
2
6
|
/*
|
|
3
7
|
* Date field + calendar popover. The model is an ISO date string ("YYYY-MM-DD",
|
|
@@ -14,7 +18,10 @@
|
|
|
14
18
|
* the common case.
|
|
15
19
|
*/
|
|
16
20
|
import { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from "reka-ui"
|
|
17
|
-
import { computed, ref } from "vue"
|
|
21
|
+
import { computed, ref, useAttrs } from "vue"
|
|
22
|
+
import { useFieldControl } from "../composables/useField"
|
|
23
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
24
|
+
import { mergeClass } from "../composables/useMergedClass"
|
|
18
25
|
import { POPOVER_PANEL } from "./dropdown"
|
|
19
26
|
import LpCalendar from "./LpCalendar.vue"
|
|
20
27
|
import LpIcon from "./LpIcon.vue"
|
|
@@ -80,7 +87,11 @@ const shellSize = {
|
|
|
80
87
|
lg: "h-(--size-control-lg) text-sm",
|
|
81
88
|
}
|
|
82
89
|
|
|
83
|
-
function onPick(
|
|
90
|
+
function onPick(picked: string | undefined) {
|
|
91
|
+
// Clicking the selected day again is the calendar's "deselect". Here it is
|
|
92
|
+
// someone confirming the date they already have, so it keeps the value and
|
|
93
|
+
// closes like any other pick; clearing has its own button.
|
|
94
|
+
const v = picked ?? (day.value || undefined)
|
|
84
95
|
if (!props.time) {
|
|
85
96
|
emit("update:modelValue", v)
|
|
86
97
|
open.value = false
|
|
@@ -101,75 +112,95 @@ function onTime(at: string) {
|
|
|
101
112
|
function clear() {
|
|
102
113
|
emit("update:modelValue", undefined)
|
|
103
114
|
}
|
|
115
|
+
|
|
116
|
+
const attrs = useAttrs()
|
|
117
|
+
const field = useFieldControl(() => ({
|
|
118
|
+
wrapped: false,
|
|
119
|
+
id: typeof attrs.id === "string" ? attrs.id : undefined,
|
|
120
|
+
invalid: props.invalid,
|
|
121
|
+
disabled: props.disabled,
|
|
122
|
+
}))
|
|
123
|
+
const labels = useLabels("datePicker", { clear: "Clear" })
|
|
124
|
+
const showClear = computed(() => props.clearable && !!props.modelValue && !field.value.disabled)
|
|
104
125
|
</script>
|
|
105
126
|
|
|
106
127
|
<template>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
class="
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
<!-- The clear control sits beside the trigger, not inside it: a button
|
|
129
|
+
nested in a button is invalid, and the nested one was unreachable by
|
|
130
|
+
keyboard. -->
|
|
131
|
+
<div :class="mergeClass('relative w-full', attrs.class as string)" :style="attrs.style as never">
|
|
132
|
+
<PopoverRoot v-model:open="open">
|
|
133
|
+
<PopoverTrigger as-child>
|
|
134
|
+
<button
|
|
135
|
+
v-bind="{ ...attrs, class: undefined, style: undefined }"
|
|
136
|
+
:id="field.id"
|
|
137
|
+
type="button"
|
|
138
|
+
:disabled="field.disabled"
|
|
139
|
+
:aria-invalid="field.invalid || undefined"
|
|
140
|
+
:aria-describedby="field.describedBy"
|
|
141
|
+
class="group flex w-full items-center gap-2 rounded-control border bg-surface-soft px-3 text-left text-ink outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:border-brand disabled:cursor-not-allowed disabled:opacity-55"
|
|
142
|
+
:class="[
|
|
143
|
+
shellSize[size],
|
|
144
|
+
field.invalid ? 'border-danger focus-visible:ring-danger-soft' : 'border-line focus-visible:border-brand',
|
|
145
|
+
]"
|
|
146
|
+
>
|
|
147
|
+
<LpIcon name="lucide:calendar" :size="16" class="shrink-0 text-muted" />
|
|
148
|
+
<span class="min-w-0 flex-1 truncate" :class="display ? '' : 'text-muted'">
|
|
149
|
+
{{ display || placeholder }}
|
|
150
|
+
</span>
|
|
151
|
+
<span v-if="showClear" class="w-[15px] shrink-0" aria-hidden="true" />
|
|
152
|
+
<LpIcon
|
|
153
|
+
name="lucide:chevron-down"
|
|
154
|
+
:size="15"
|
|
155
|
+
class="shrink-0 text-muted transition-transform duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] group-data-[state=open]:rotate-180"
|
|
156
|
+
/>
|
|
157
|
+
</button>
|
|
158
|
+
</PopoverTrigger>
|
|
159
|
+
<PopoverPortal>
|
|
160
|
+
<PopoverContent
|
|
161
|
+
:side-offset="6"
|
|
162
|
+
align="start"
|
|
163
|
+
:class="[POPOVER_PANEL, 'z-(--z-popover) rounded-card p-0 outline-none']"
|
|
130
164
|
>
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
</button>
|
|
139
|
-
</PopoverTrigger>
|
|
140
|
-
<PopoverPortal>
|
|
141
|
-
<PopoverContent
|
|
142
|
-
:side-offset="6"
|
|
143
|
-
align="start"
|
|
144
|
-
:class="[POPOVER_PANEL, 'z-(--z-popover) rounded-card p-0 outline-none']"
|
|
145
|
-
>
|
|
146
|
-
<LpCalendar
|
|
147
|
-
:model-value="day"
|
|
148
|
-
:min="min"
|
|
149
|
-
:max="max"
|
|
150
|
-
:is-disabled="isDisabled"
|
|
151
|
-
class="border-0 bg-transparent"
|
|
152
|
-
@update:model-value="onPick"
|
|
153
|
-
/>
|
|
154
|
-
<div v-if="time" class="flex items-center gap-2 border-t border-line px-3 py-2">
|
|
155
|
-
<LpIcon name="lucide:clock" :size="15" class="shrink-0 text-muted" />
|
|
156
|
-
<LpTimeField
|
|
157
|
-
:model-value="minute || undefined"
|
|
158
|
-
:disabled="!day"
|
|
159
|
-
:icon="false"
|
|
160
|
-
size="sm"
|
|
161
|
-
class="flex-1"
|
|
162
|
-
@update:model-value="(at) => onTime(at || '')"
|
|
165
|
+
<LpCalendar
|
|
166
|
+
:model-value="day"
|
|
167
|
+
:min="min"
|
|
168
|
+
:max="max"
|
|
169
|
+
:is-disabled="isDisabled"
|
|
170
|
+
class="border-0 bg-transparent"
|
|
171
|
+
@update:model-value="onPick"
|
|
163
172
|
/>
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
<div v-if="time" class="flex items-center gap-2 border-t border-line px-3 py-2">
|
|
174
|
+
<LpIcon name="lucide:clock" :size="15" class="shrink-0 text-muted" />
|
|
175
|
+
<LpTimeField
|
|
176
|
+
:model-value="minute || undefined"
|
|
177
|
+
:disabled="!day"
|
|
178
|
+
:icon="false"
|
|
179
|
+
size="sm"
|
|
180
|
+
class="flex-1"
|
|
181
|
+
@update:model-value="(at) => onTime(at || '')"
|
|
182
|
+
/>
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
class="shrink-0 rounded-control px-2 py-1 text-xs text-muted transition-colors hover:text-ink"
|
|
186
|
+
@click="open = false"
|
|
187
|
+
>
|
|
188
|
+
{{ doneLabel }}
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
</PopoverContent>
|
|
192
|
+
</PopoverPortal>
|
|
193
|
+
</PopoverRoot>
|
|
194
|
+
<!-- Positioned over the spacer the trigger leaves for it, just before the
|
|
195
|
+
chevron (gap-2 + 15px chevron + px-3 from the right edge). -->
|
|
196
|
+
<button
|
|
197
|
+
v-if="showClear"
|
|
198
|
+
type="button"
|
|
199
|
+
:aria-label="labels.clear"
|
|
200
|
+
class="absolute top-1/2 right-[calc(0.75rem+15px+0.5rem)] flex -translate-y-1/2 items-center rounded-sm text-muted outline-none transition-colors hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
201
|
+
@click="clear"
|
|
202
|
+
>
|
|
203
|
+
<LpIcon name="lucide:x" :size="15" />
|
|
204
|
+
</button>
|
|
205
|
+
</div>
|
|
175
206
|
</template>
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { ref as sharedRef } from "vue"
|
|
2
|
+
import { ref as sharedRef, type Ref } from "vue"
|
|
3
3
|
|
|
4
|
-
// One pull at a time across every edge-open drawer on the page
|
|
5
|
-
|
|
4
|
+
// One pull at a time across every edge-open drawer on the page, pinned to a
|
|
5
|
+
// global symbol so a barrel and a deep import still share it.
|
|
6
|
+
const EDGE_KEY = Symbol.for("@leavepulse/ui:drawer-edge-owner")
|
|
7
|
+
type GlobalWithEdge = typeof globalThis & { [EDGE_KEY]?: Ref<symbol | null> }
|
|
8
|
+
const edgeOwner = ((globalThis as GlobalWithEdge)[EDGE_KEY] ??= sharedRef<symbol | null>(null))
|
|
6
9
|
</script>
|
|
7
10
|
|
|
8
11
|
<script setup lang="ts">
|
|
@@ -32,6 +35,7 @@ import {
|
|
|
32
35
|
import { computed, onBeforeUnmount, ref, useSlots, watch } from "vue"
|
|
33
36
|
import { CLOSE_ICON } from "./dropdown"
|
|
34
37
|
import { useGlass } from "../composables/useGlass"
|
|
38
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
35
39
|
import LpIcon from "./LpIcon.vue"
|
|
36
40
|
import LpScrollArea from "./LpScrollArea.vue"
|
|
37
41
|
import { anyModalOpen, useModalLayer } from "./modalDepth"
|
|
@@ -109,6 +113,9 @@ const props = withDefaults(
|
|
|
109
113
|
* `provideGlass`, and without one it is off.
|
|
110
114
|
*/
|
|
111
115
|
glass?: boolean
|
|
116
|
+
/** Accessible name for a drawer without a visible `title`. */
|
|
117
|
+
ariaLabel?: string
|
|
118
|
+
labels?: Partial<{ close: string }>
|
|
112
119
|
}>(),
|
|
113
120
|
{
|
|
114
121
|
side: "right",
|
|
@@ -124,6 +131,7 @@ const props = withDefaults(
|
|
|
124
131
|
)
|
|
125
132
|
|
|
126
133
|
const frosted = useGlass(() => props.glass)
|
|
134
|
+
const text = useLabels("drawer", { close: "Close" }, () => props.labels)
|
|
127
135
|
|
|
128
136
|
const emit = defineEmits<{
|
|
129
137
|
(e: "update:open", value: boolean): void
|
|
@@ -148,7 +156,8 @@ const SIZES = { sm: 22, md: 28, lg: 36, xl: 48 } // rem along the main axis
|
|
|
148
156
|
* settings drawer) claimed those same rungs and the tie went to DOM order —
|
|
149
157
|
* the drawer, portalled later on reopen, covered the dialog it had opened.
|
|
150
158
|
*/
|
|
151
|
-
const {
|
|
159
|
+
const contentRef = ref<{ $el: HTMLElement } | null>(null)
|
|
160
|
+
const { claim, release, layer } = useModalLayer({ element: () => contentRef.value?.$el })
|
|
152
161
|
watch(() => props.open, (open) => (open ? claim() : release()), { immediate: true })
|
|
153
162
|
onBeforeUnmount(release)
|
|
154
163
|
|
|
@@ -179,36 +188,60 @@ const sizeStyle = computed(() => {
|
|
|
179
188
|
return isHorizontal.value ? { width: len } : { height: len }
|
|
180
189
|
})
|
|
181
190
|
|
|
191
|
+
// Resolve a CSS length to px against the viewport. Anything it cannot read
|
|
192
|
+
// (calc(), min()) comes back NaN for the caller to fall back on.
|
|
193
|
+
function toPx(css: string): number {
|
|
194
|
+
const n = Number.parseFloat(css)
|
|
195
|
+
if (css.endsWith("rem")) {
|
|
196
|
+
const root = typeof document === "undefined" ? 16 : Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16
|
|
197
|
+
return n * root
|
|
198
|
+
}
|
|
199
|
+
if (css.endsWith("vw")) return (n / 100) * window.innerWidth
|
|
200
|
+
if (css.endsWith("vh")) return (n / 100) * window.innerHeight
|
|
201
|
+
if (css.endsWith("px") || /^[\d.]+$/.test(css.trim())) return n
|
|
202
|
+
return Number.NaN
|
|
203
|
+
}
|
|
204
|
+
|
|
182
205
|
// Drag the inner edge to resize. For a right drawer the panel grows as the
|
|
183
|
-
// pointer moves left (width = viewportRight − clientX); mirror for left.
|
|
206
|
+
// pointer moves left (width = viewportRight − clientX); mirror for left. The
|
|
207
|
+
// pointer is captured, so a release over an iframe or outside the window still
|
|
208
|
+
// ends the drag, and a cancelled touch ends it too.
|
|
209
|
+
let endResize: (() => void) | null = null
|
|
210
|
+
|
|
184
211
|
function startResize(e: PointerEvent) {
|
|
185
212
|
if (!props.resizable || !isHorizontal.value) return
|
|
186
213
|
e.preventDefault()
|
|
214
|
+
endResize?.()
|
|
215
|
+
const grip = e.currentTarget instanceof HTMLElement ? e.currentTarget : null
|
|
216
|
+
grip?.setPointerCapture(e.pointerId)
|
|
187
217
|
const fromRight = dir.value === "right"
|
|
188
|
-
const toPx = (css: string) => {
|
|
189
|
-
// Resolve rem/vw to px against the viewport for clamping.
|
|
190
|
-
if (css.endsWith("rem")) return Number.parseFloat(css) * 16
|
|
191
|
-
if (css.endsWith("vw")) return (Number.parseFloat(css) / 100) * window.innerWidth
|
|
192
|
-
return Number.parseFloat(css)
|
|
193
|
-
}
|
|
194
218
|
const min = toPx(props.minSize)
|
|
195
219
|
const max = toPx(props.maxSize)
|
|
196
220
|
const onMove = (ev: PointerEvent) => {
|
|
221
|
+
if (ev.pointerId !== e.pointerId) return
|
|
197
222
|
const raw = fromRight ? window.innerWidth - ev.clientX : ev.clientX
|
|
198
|
-
resizedPx.value = Math.max(min, Math.min(max, raw))
|
|
223
|
+
resizedPx.value = Math.max(Number.isNaN(min) ? 0 : min, Math.min(Number.isNaN(max) ? Infinity : max, raw))
|
|
199
224
|
}
|
|
200
|
-
const
|
|
225
|
+
const end = () => {
|
|
201
226
|
window.removeEventListener("pointermove", onMove)
|
|
202
|
-
window.removeEventListener("pointerup",
|
|
227
|
+
window.removeEventListener("pointerup", end)
|
|
228
|
+
window.removeEventListener("pointercancel", end)
|
|
229
|
+
grip?.removeEventListener("lostpointercapture", end)
|
|
203
230
|
document.body.style.userSelect = ""
|
|
204
231
|
document.body.style.cursor = ""
|
|
232
|
+
endResize = null
|
|
205
233
|
}
|
|
234
|
+
endResize = end
|
|
206
235
|
document.body.style.userSelect = "none"
|
|
207
236
|
document.body.style.cursor = "ew-resize"
|
|
208
237
|
window.addEventListener("pointermove", onMove)
|
|
209
|
-
window.addEventListener("pointerup",
|
|
238
|
+
window.addEventListener("pointerup", end)
|
|
239
|
+
window.addEventListener("pointercancel", end)
|
|
240
|
+
grip?.addEventListener("lostpointercapture", end)
|
|
210
241
|
}
|
|
211
242
|
|
|
243
|
+
onBeforeUnmount(() => endResize?.())
|
|
244
|
+
|
|
212
245
|
const slots = useSlots()
|
|
213
246
|
const hasHeader = computed(() => Boolean(props.title || slots.title))
|
|
214
247
|
|
|
@@ -281,9 +314,9 @@ watch(
|
|
|
281
314
|
// Resolve the panel's main-axis size in px to map drag distance → progress.
|
|
282
315
|
function panelPx(): number {
|
|
283
316
|
const vw = typeof window === "undefined" ? 0 : window.innerWidth
|
|
284
|
-
|
|
285
|
-
const cap = props.width ?
|
|
286
|
-
return Math.min(vw * 0.94, Number.isFinite(cap) ? cap :
|
|
317
|
+
const preset = toPx(`${SIZES[props.size]}rem`)
|
|
318
|
+
const cap = props.width ? toPx(props.width) : preset
|
|
319
|
+
return Math.min(vw * 0.94, Number.isFinite(cap) ? cap : preset)
|
|
287
320
|
}
|
|
288
321
|
|
|
289
322
|
function edgeEnabled(): boolean {
|
|
@@ -479,6 +512,8 @@ const previewStyle = computed(() => {
|
|
|
479
512
|
:class="{ 'lp-scrim--light': frosted, 'lp-scrim--nested': layer.nested }"
|
|
480
513
|
/>
|
|
481
514
|
<DrawerContent
|
|
515
|
+
ref="contentRef"
|
|
516
|
+
v-bind="description ? {} : { 'aria-describedby': undefined }"
|
|
482
517
|
:class="contentClass"
|
|
483
518
|
:style="[sizeStyle, { zIndex: layer.panel }]"
|
|
484
519
|
:data-vaul-animate="handingOff || skipOpenSlide ? 'false' : undefined"
|
|
@@ -498,6 +533,8 @@ const previewStyle = computed(() => {
|
|
|
498
533
|
/>
|
|
499
534
|
</div>
|
|
500
535
|
|
|
536
|
+
<DrawerTitle v-if="!hasHeader && ariaLabel" class="sr-only">{{ ariaLabel }}</DrawerTitle>
|
|
537
|
+
|
|
501
538
|
<!-- Drag handle: a pill the user grabs; vaul wires the drag to it. -->
|
|
502
539
|
<DrawerHandle
|
|
503
540
|
v-if="showHandle"
|
|
@@ -519,7 +556,7 @@ const previewStyle = computed(() => {
|
|
|
519
556
|
</div>
|
|
520
557
|
<DrawerClose
|
|
521
558
|
class="group flex shrink-0 items-center rounded-md p-1 text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
522
|
-
aria-label="
|
|
559
|
+
:aria-label="text.close"
|
|
523
560
|
>
|
|
524
561
|
<LpIcon
|
|
525
562
|
name="lucide:x"
|
|
@@ -23,7 +23,7 @@ export default { inheritAttrs: false }
|
|
|
23
23
|
* invisible to a keyboard: nothing focusable, nothing that Enter activates, and
|
|
24
24
|
* the file dialog unreachable without a mouse.
|
|
25
25
|
*/
|
|
26
|
-
import { onBeforeUnmount, onMounted, ref } from "vue"
|
|
26
|
+
import { onBeforeUnmount, onMounted, ref, watch } from "vue"
|
|
27
27
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
28
28
|
import { useReveal } from "../composables/useReveal"
|
|
29
29
|
import LpIcon from "./LpIcon.vue"
|
|
@@ -168,17 +168,34 @@ function onPicked(event: Event) {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
function windowPaste(event: ClipboardEvent) {
|
|
171
|
-
|
|
171
|
+
// Already taken by the zone itself, which the paste bubbled up from.
|
|
172
|
+
if (props.disabled || event.defaultPrevented) return
|
|
172
173
|
const target = event.target as HTMLElement | null
|
|
173
174
|
// A paste aimed at somewhere you can type belongs to that thing.
|
|
174
175
|
if (target?.closest("input, textarea, [contenteditable]")) return
|
|
175
176
|
onPaste(event)
|
|
176
177
|
}
|
|
177
178
|
|
|
179
|
+
let listening = false
|
|
180
|
+
function listen(on: boolean) {
|
|
181
|
+
if (on === listening || typeof window === "undefined") return
|
|
182
|
+
listening = on
|
|
183
|
+
if (on) window.addEventListener("paste", windowPaste)
|
|
184
|
+
else window.removeEventListener("paste", windowPaste)
|
|
185
|
+
}
|
|
186
|
+
let mounted = false
|
|
178
187
|
onMounted(() => {
|
|
179
|
-
|
|
188
|
+
mounted = true
|
|
189
|
+
listen(props.pasteTarget)
|
|
190
|
+
})
|
|
191
|
+
watch(
|
|
192
|
+
() => props.pasteTarget,
|
|
193
|
+
(on) => mounted && listen(on),
|
|
194
|
+
)
|
|
195
|
+
onBeforeUnmount(() => {
|
|
196
|
+
mounted = false
|
|
197
|
+
listen(false)
|
|
180
198
|
})
|
|
181
|
-
onBeforeUnmount(() => window.removeEventListener("paste", windowPaste))
|
|
182
199
|
|
|
183
200
|
defineExpose({ browse: () => input.value?.click() })
|
|
184
201
|
|
|
@@ -38,10 +38,21 @@ import LpEmptyState from "./LpEmptyState.vue"
|
|
|
38
38
|
import LpFileTreeNode from "./LpFileTreeNode.vue"
|
|
39
39
|
import LpScrollArea from "./LpScrollArea.vue"
|
|
40
40
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
41
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
41
42
|
|
|
42
43
|
export type { FileNode } from "./fileTree"
|
|
43
44
|
|
|
44
45
|
/** Rolled-up stats for the ticked entries — what a backup picker summarises. */
|
|
46
|
+
export interface FileTreeLabels {
|
|
47
|
+
[key: string]: string
|
|
48
|
+
empty: string
|
|
49
|
+
emptyDescription: string
|
|
50
|
+
file: string
|
|
51
|
+
files: string
|
|
52
|
+
folder: string
|
|
53
|
+
folders: string
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
export interface FileTreeSummary {
|
|
46
57
|
/** Ticked files (directories excluded — they're containers, not payload). */
|
|
47
58
|
files: number
|
|
@@ -84,6 +95,7 @@ const props = withDefaults(
|
|
|
84
95
|
/** Empty-state copy when `nodes` is empty. */
|
|
85
96
|
emptyLabel?: string
|
|
86
97
|
emptyDescription?: string
|
|
98
|
+
labels?: Partial<FileTreeLabels>
|
|
87
99
|
}>(),
|
|
88
100
|
{
|
|
89
101
|
sort: true,
|
|
@@ -93,8 +105,6 @@ const props = withDefaults(
|
|
|
93
105
|
showModified: false,
|
|
94
106
|
loading: false,
|
|
95
107
|
skeletonRows: 8,
|
|
96
|
-
emptyLabel: "No files",
|
|
97
|
-
emptyDescription: "This folder is empty.",
|
|
98
108
|
},
|
|
99
109
|
)
|
|
100
110
|
|
|
@@ -131,6 +141,19 @@ watch(
|
|
|
131
141
|
},
|
|
132
142
|
)
|
|
133
143
|
|
|
144
|
+
const text = useLabels<FileTreeLabels>(
|
|
145
|
+
"fileTree",
|
|
146
|
+
{
|
|
147
|
+
empty: "No files",
|
|
148
|
+
emptyDescription: "This folder is empty.",
|
|
149
|
+
file: "file",
|
|
150
|
+
files: "files",
|
|
151
|
+
folder: "folder",
|
|
152
|
+
folders: "folders",
|
|
153
|
+
},
|
|
154
|
+
() => props.labels,
|
|
155
|
+
)
|
|
156
|
+
|
|
134
157
|
const loadingSet = computed(() => new Set(props.loadingIds ?? []))
|
|
135
158
|
|
|
136
159
|
// Same controlled/uncontrolled deal as `expanded`.
|
|
@@ -229,7 +252,7 @@ const summary = computed<FileTreeSummary>(() => {
|
|
|
229
252
|
}
|
|
230
253
|
})
|
|
231
254
|
|
|
232
|
-
watch(summary, (value) => emit("summary", value), {
|
|
255
|
+
watch(summary, (value) => emit("summary", value), { immediate: true })
|
|
233
256
|
|
|
234
257
|
function setChecked(next: Set<string>) {
|
|
235
258
|
checkedIds.value = next
|
|
@@ -350,6 +373,9 @@ function expand(node: FileNode) {
|
|
|
350
373
|
|
|
351
374
|
function collapse(node: FileNode) {
|
|
352
375
|
if (!openIds.value.has(node.id)) return
|
|
376
|
+
// A fetch that failed left `children` unset; collapsing lets the next open
|
|
377
|
+
// ask again instead of showing an empty folder forever.
|
|
378
|
+
if (node.children === undefined && !loadingSet.value.has(node.id)) requested.delete(node.id)
|
|
353
379
|
const next = new Set(openIds.value)
|
|
354
380
|
next.delete(node.id)
|
|
355
381
|
setExpanded(next)
|
|
@@ -506,8 +532,8 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
506
532
|
<LpEmptyState
|
|
507
533
|
v-else-if="!roots.length"
|
|
508
534
|
icon="lucide:folder-search"
|
|
509
|
-
:title="emptyLabel"
|
|
510
|
-
:description="emptyDescription"
|
|
535
|
+
:title="emptyLabel ?? text.empty"
|
|
536
|
+
:description="emptyDescription ?? text.emptyDescription"
|
|
511
537
|
/>
|
|
512
538
|
|
|
513
539
|
<!-- h-full so the tree fills whatever box the caller gave it and the scroll
|
|
@@ -558,9 +584,9 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
558
584
|
class="shrink-0 border-t border-line px-2 py-1.5 text-xs text-muted"
|
|
559
585
|
>
|
|
560
586
|
<slot name="summary" v-bind="summary">
|
|
561
|
-
{{ summary.files }} {{ summary.files === 1 ?
|
|
587
|
+
{{ summary.files }} {{ summary.files === 1 ? text.file : text.files }}<template
|
|
562
588
|
v-if="summary.dirs"
|
|
563
|
-
>, {{ summary.dirs }} {{ summary.dirs === 1 ?
|
|
589
|
+
>, {{ summary.dirs }} {{ summary.dirs === 1 ? text.folder : text.folders }}</template>
|
|
564
590
|
<template v-if="summary.bytes !== undefined"> · {{ summary.sizeLabel }}</template>
|
|
565
591
|
</slot>
|
|
566
592
|
</div>
|