@leavepulse/ui 0.43.4 → 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-B-0Est4Z.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 -3
- package/dist/components/LpCommandPalette.vue.js +86 -72
- 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/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 +32 -2
- package/dist/index.js +184 -137
- 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 +34 -13
- 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/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 +37 -2
- 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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* Renders the requests made through `confirm()` / `useConfirm()`. Mount one
|
|
4
|
+
* near the app root, next to LpToaster.
|
|
5
|
+
*/
|
|
6
|
+
import { computed, ref, watch } from "vue"
|
|
7
|
+
import { confirmQueue, settleConfirm } from "./confirm"
|
|
8
|
+
import LpConfirmDialog from "./LpConfirmDialog.vue"
|
|
9
|
+
|
|
10
|
+
withDefaults(
|
|
11
|
+
defineProps<{
|
|
12
|
+
/** Frosted glass panel. Follows `provideGlass` when unset. */
|
|
13
|
+
glass?: boolean
|
|
14
|
+
}>(),
|
|
15
|
+
{ glass: undefined },
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const current = computed(() => confirmQueue[0])
|
|
19
|
+
const open = ref(false)
|
|
20
|
+
let confirmed = false
|
|
21
|
+
const answer = (value: boolean) => (confirmed = value)
|
|
22
|
+
|
|
23
|
+
watch(
|
|
24
|
+
() => current.value?.id,
|
|
25
|
+
(id) => {
|
|
26
|
+
confirmed = false
|
|
27
|
+
open.value = id !== undefined
|
|
28
|
+
},
|
|
29
|
+
{ immediate: true },
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// The dialog closes itself after a successful action, or straight away for a
|
|
33
|
+
// plain confirmation; a failed action keeps it open and never gets here.
|
|
34
|
+
function onOpen(value: boolean) {
|
|
35
|
+
open.value = value
|
|
36
|
+
const request = current.value
|
|
37
|
+
if (!value && request) settleConfirm(request.id, confirmed)
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<LpConfirmDialog
|
|
43
|
+
v-if="current"
|
|
44
|
+
:key="current.id"
|
|
45
|
+
:open="open"
|
|
46
|
+
:glass="glass"
|
|
47
|
+
:title="current.title"
|
|
48
|
+
:description="current.description"
|
|
49
|
+
:danger="current.danger"
|
|
50
|
+
:icon="current.icon"
|
|
51
|
+
:confirm-label="current.confirmLabel"
|
|
52
|
+
:cancel-label="current.cancelLabel"
|
|
53
|
+
:loading-label="current.loadingLabel"
|
|
54
|
+
:action="current.action"
|
|
55
|
+
@confirm="answer(true)"
|
|
56
|
+
@cancel="answer(false)"
|
|
57
|
+
@update:open="onOpen"
|
|
58
|
+
/>
|
|
59
|
+
</template>
|
|
@@ -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
|
|