@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
|
@@ -24,7 +24,8 @@ export default { inheritAttrs: false }
|
|
|
24
24
|
*/
|
|
25
25
|
import { computed, reactive, ref, watch } from "vue"
|
|
26
26
|
import { useClipboard } from "../composables/useClipboard"
|
|
27
|
-
import { type CodeLang,
|
|
27
|
+
import { type CodeLang, tokenizeLines } from "./codeHighlight"
|
|
28
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
28
29
|
import LpIcon from "./LpIcon.vue"
|
|
29
30
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
30
31
|
|
|
@@ -49,6 +50,7 @@ const props = withDefaults(
|
|
|
49
50
|
height?: string
|
|
50
51
|
/** Auto-collapse a leading import/package block (no markers needed). */
|
|
51
52
|
foldImports?: boolean
|
|
53
|
+
labels?: Partial<Record<"unlock" | "lock" | "copy" | "copied" | "imports" | "details" | "lines", string>>
|
|
52
54
|
}>(),
|
|
53
55
|
{
|
|
54
56
|
lang: "plain",
|
|
@@ -66,6 +68,21 @@ const emit = defineEmits<{
|
|
|
66
68
|
(e: "update:locked", value: boolean): void
|
|
67
69
|
}>()
|
|
68
70
|
|
|
71
|
+
const text = useLabels(
|
|
72
|
+
"codeBlock",
|
|
73
|
+
{
|
|
74
|
+
unlock: "Unlock to edit",
|
|
75
|
+
lock: "Lock (read-only)",
|
|
76
|
+
copy: "Copy code",
|
|
77
|
+
copied: "Copied",
|
|
78
|
+
imports: "imports",
|
|
79
|
+
details: "details",
|
|
80
|
+
/** `{n}` is replaced by the count. */
|
|
81
|
+
lines: "({n} lines)",
|
|
82
|
+
},
|
|
83
|
+
() => props.labels,
|
|
84
|
+
)
|
|
85
|
+
|
|
69
86
|
const code = computed(() => props.modelValue ?? "")
|
|
70
87
|
const rawLines = computed(() => code.value.replace(/\n$/, "").split("\n"))
|
|
71
88
|
|
|
@@ -114,7 +131,7 @@ const segments = computed<Segment[]>(() => {
|
|
|
114
131
|
flushPlain()
|
|
115
132
|
const lines: NumberedLine[] = []
|
|
116
133
|
for (let k = start; k < realEnd; k++) lines.push({ n: k + 1, text: src[k] })
|
|
117
|
-
out.push({ kind: "fold", id: foldId++, label:
|
|
134
|
+
out.push({ kind: "fold", id: foldId++, label: text.value.imports, defaultOpen: false, lines })
|
|
118
135
|
i = realEnd
|
|
119
136
|
}
|
|
120
137
|
}
|
|
@@ -126,7 +143,7 @@ const segments = computed<Segment[]>(() => {
|
|
|
126
143
|
flushPlain()
|
|
127
144
|
const rest = startMatch[1] ?? ""
|
|
128
145
|
const defaultOpen = /\bopen\b/.test(rest)
|
|
129
|
-
const label = rest.replace(/\bopen\b/, "").trim() ||
|
|
146
|
+
const label = rest.replace(/\bopen\b/, "").trim() || text.value.details
|
|
130
147
|
const lines: NumberedLine[] = []
|
|
131
148
|
i++
|
|
132
149
|
for (; i < src.length; i++) {
|
|
@@ -143,6 +160,7 @@ const segments = computed<Segment[]>(() => {
|
|
|
143
160
|
})
|
|
144
161
|
|
|
145
162
|
const hasFolds = computed(() => segments.value.some((s) => s.kind === "fold"))
|
|
163
|
+
const editing = computed(() => !props.locked && !hasFolds.value)
|
|
146
164
|
|
|
147
165
|
// Per-region open state, keyed by fold id, defaulting to each region's declared
|
|
148
166
|
// open flag. Reset when the set of folds changes.
|
|
@@ -159,9 +177,21 @@ function toggleFold(id: number) {
|
|
|
159
177
|
openState[id] = !openState[id]
|
|
160
178
|
}
|
|
161
179
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
180
|
+
/* Highlighted once for the whole snippet, so a block comment that spans lines
|
|
181
|
+
is coloured as one; looked up by the line's 1-based number. */
|
|
182
|
+
const highlighted = computed(() => tokenizeLines(rawLines.value, props.lang))
|
|
183
|
+
function tokens(n: number) {
|
|
184
|
+
return highlighted.value[n - 1] ?? []
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* The textarea is the scroller while editing; the highlighted copy under it
|
|
188
|
+
follows, or the caret drifts over the wrong text on a long line. */
|
|
189
|
+
const preEl = ref<HTMLElement | null>(null)
|
|
190
|
+
function syncScroll(e: Event) {
|
|
191
|
+
const area = e.target as HTMLTextAreaElement
|
|
192
|
+
if (!preEl.value) return
|
|
193
|
+
preEl.value.scrollLeft = area.scrollLeft
|
|
194
|
+
preEl.value.scrollTop = area.scrollTop
|
|
165
195
|
}
|
|
166
196
|
|
|
167
197
|
// Copied text is the real code with marker lines removed (so it pastes clean),
|
|
@@ -214,7 +244,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
214
244
|
v-if="lockToggle"
|
|
215
245
|
type="button"
|
|
216
246
|
class="group/lock flex items-center gap-1 rounded px-1.5 py-1 outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
217
|
-
:aria-label="locked ?
|
|
247
|
+
:aria-label="locked ? text.unlock : text.lock"
|
|
218
248
|
@click="toggleLock"
|
|
219
249
|
>
|
|
220
250
|
<LpIcon
|
|
@@ -227,7 +257,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
227
257
|
v-if="copyable"
|
|
228
258
|
type="button"
|
|
229
259
|
class="group/copy flex items-center gap-1 rounded px-1.5 py-1 outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
230
|
-
:aria-label="copied ?
|
|
260
|
+
:aria-label="copied ? text.copied : text.copy"
|
|
231
261
|
@click="copy"
|
|
232
262
|
>
|
|
233
263
|
<LpIcon
|
|
@@ -245,7 +275,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
245
275
|
<div
|
|
246
276
|
class="lp-scrollbar-none relative"
|
|
247
277
|
:style="height ? { height } : undefined"
|
|
248
|
-
:class="height ? 'overflow-auto' : ''"
|
|
278
|
+
:class="height ? (editing ? 'overflow-hidden' : 'overflow-auto') : ''"
|
|
249
279
|
>
|
|
250
280
|
<!-- Editable overlay: only when unlocked and there are no folds. -->
|
|
251
281
|
<textarea
|
|
@@ -253,12 +283,18 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
253
283
|
:value="code"
|
|
254
284
|
spellcheck="false"
|
|
255
285
|
class="lp-scrollbar-none absolute inset-0 size-full resize-none overflow-auto whitespace-pre bg-transparent p-3 leading-relaxed text-transparent caret-brand outline-none"
|
|
256
|
-
:class="[wrap ? 'whitespace-pre-wrap break-words' : 'whitespace-pre', lineNumbers ? 'pl-
|
|
286
|
+
:class="[wrap ? 'whitespace-pre-wrap break-words' : 'whitespace-pre', lineNumbers ? 'pl-13' : '']"
|
|
257
287
|
@input="onInput"
|
|
288
|
+
@scroll="syncScroll"
|
|
258
289
|
/>
|
|
259
290
|
<pre
|
|
291
|
+
ref="preEl"
|
|
260
292
|
class="lp-scrollbar-none m-0 overflow-auto p-3 leading-relaxed"
|
|
261
|
-
:class="[
|
|
293
|
+
:class="[
|
|
294
|
+
wrap ? 'whitespace-pre-wrap break-words' : 'whitespace-pre',
|
|
295
|
+
editing ? 'pointer-events-none' : '',
|
|
296
|
+
editing && height ? 'h-full overflow-hidden' : '',
|
|
297
|
+
]"
|
|
262
298
|
><code class="block"><template
|
|
263
299
|
v-for="(seg, s) in segments"
|
|
264
300
|
:key="s"
|
|
@@ -271,7 +307,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
271
307
|
class="mr-3 inline-block w-7 select-none text-right text-muted/60"
|
|
272
308
|
aria-hidden="true"
|
|
273
309
|
>{{ ln.n }}</span><span
|
|
274
|
-
v-for="(tok, j) in tokens(ln.
|
|
310
|
+
v-for="(tok, j) in tokens(ln.n)"
|
|
275
311
|
:key="j"
|
|
276
312
|
:class="tok.cls"
|
|
277
313
|
>{{ tok.text }}</span></span></template><!-- Fold region --><template
|
|
@@ -288,7 +324,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
288
324
|
/><span class="select-none">{{ openState[seg.id] ? seg.label : seg.label + ' …' }}</span><span
|
|
289
325
|
v-if="!openState[seg.id]"
|
|
290
326
|
class="select-none text-muted/50"
|
|
291
|
-
>
|
|
327
|
+
>{{ text.lines.replace("{n}", String(seg.lines.length)) }}</span></button><template
|
|
292
328
|
v-if="openState[seg.id]"
|
|
293
329
|
><span
|
|
294
330
|
v-for="ln in seg.lines"
|
|
@@ -299,7 +335,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
299
335
|
class="mr-3 inline-block w-7 select-none text-right text-muted/60"
|
|
300
336
|
aria-hidden="true"
|
|
301
337
|
>{{ ln.n }}</span><span
|
|
302
|
-
v-for="(tok, j) in tokens(ln.
|
|
338
|
+
v-for="(tok, j) in tokens(ln.n)"
|
|
303
339
|
:key="j"
|
|
304
340
|
:class="tok.cls"
|
|
305
341
|
>{{ tok.text }}</span></span></template></template></template></code></pre>
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
PopoverTrigger,
|
|
38
38
|
} from "reka-ui"
|
|
39
39
|
import { computed, ref, watch } from "vue"
|
|
40
|
+
import { useFieldControl } from "../composables/useField"
|
|
40
41
|
import {
|
|
41
42
|
clampHsv,
|
|
42
43
|
DEFAULT_SWATCHES,
|
|
@@ -271,7 +272,9 @@ function trackPointer(e: PointerEvent, onMove: (x: number, y: number) => void) {
|
|
|
271
272
|
|
|
272
273
|
const onFieldPointer = (e: PointerEvent) =>
|
|
273
274
|
trackPointer(e, (x, y) => set({ s: x, v: 1 - y }))
|
|
274
|
-
|
|
275
|
+
// 360° is 0° again, so the far edge of the rail would throw the thumb back to
|
|
276
|
+
// the start; it stops just short instead, as End does.
|
|
277
|
+
const onHuePointer = (e: PointerEvent) => trackPointer(e, (x) => set({ h: Math.min(x * 360, 359.9) }))
|
|
275
278
|
const onAlphaPointer = (e: PointerEvent) => trackPointer(e, (x) => set({ a: x }))
|
|
276
279
|
|
|
277
280
|
/* ── keyboard ─────────────────────────────────────
|
|
@@ -303,11 +306,13 @@ function step(e: KeyboardEvent, axis: "sv" | "h" | "a") {
|
|
|
303
306
|
}
|
|
304
307
|
|
|
305
308
|
/* ── text field ───────────────────────────────────
|
|
306
|
-
Typed text is parsed on every keystroke
|
|
307
|
-
a real colour,
|
|
308
|
-
|
|
309
|
-
|
|
309
|
+
Typed text is parsed on every keystroke and previews as soon as it resolves
|
|
310
|
+
to a real colour, but is only committed (`change`) on Enter or when the field
|
|
311
|
+
loses focus: "#3b8" and "#3b82" are valid colours on the way to "#3b82f6",
|
|
312
|
+
and committing each one handed listeners three colours nobody chose. The
|
|
313
|
+
field shows what is typed while it has focus and the value once it doesn't. */
|
|
310
314
|
const typed = ref<string | null>(null)
|
|
315
|
+
let typedChange = false
|
|
311
316
|
const inputValue = computed(() => typed.value ?? current.value)
|
|
312
317
|
|
|
313
318
|
function onInput(e: Event) {
|
|
@@ -318,11 +323,19 @@ function onInput(e: Event) {
|
|
|
318
323
|
const next = rgbToHsv(rgb)
|
|
319
324
|
if (next.s === 0) next.h = hsv.value.h
|
|
320
325
|
if (next.v === 0) next.s = hsv.value.s
|
|
321
|
-
set(props.alpha ? next : { ...next, a: 1 }
|
|
326
|
+
set(props.alpha ? next : { ...next, a: 1 })
|
|
327
|
+
typedChange = true
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function commitTyped() {
|
|
331
|
+
if (!typedChange) return
|
|
332
|
+
typedChange = false
|
|
333
|
+
emit("change", current.value)
|
|
322
334
|
}
|
|
323
335
|
|
|
324
336
|
function onInputBlur() {
|
|
325
337
|
typed.value = null
|
|
338
|
+
commitTyped()
|
|
326
339
|
}
|
|
327
340
|
|
|
328
341
|
function pickSwatch(value: string) {
|
|
@@ -463,8 +476,12 @@ function remember(value: string) {
|
|
|
463
476
|
// Remember on close rather than on every commit: the colour a person leaves the
|
|
464
477
|
// popover on is the one they chose, while the ones they passed through on the
|
|
465
478
|
// way are not history, they are the search for it.
|
|
479
|
+
// Only a colour that changed while the popover was open: opening it to look,
|
|
480
|
+
// or to copy the value out, is not a choice worth recording.
|
|
481
|
+
let openedOn = ""
|
|
466
482
|
watch(open, (isOpen, was) => {
|
|
467
|
-
if (
|
|
483
|
+
if (isOpen) openedOn = current.value
|
|
484
|
+
else if (was && current.value !== openedOn) remember(current.value)
|
|
468
485
|
})
|
|
469
486
|
|
|
470
487
|
/**
|
|
@@ -573,6 +590,12 @@ const menuItems = computed<ContextMenuItemDef[]>(() => {
|
|
|
573
590
|
: builtin
|
|
574
591
|
})
|
|
575
592
|
|
|
593
|
+
const field = useFieldControl(() => ({
|
|
594
|
+
wrapped: false,
|
|
595
|
+
invalid: props.invalid,
|
|
596
|
+
disabled: props.disabled,
|
|
597
|
+
}))
|
|
598
|
+
|
|
576
599
|
defineExpose({ open })
|
|
577
600
|
</script>
|
|
578
601
|
|
|
@@ -592,12 +615,15 @@ defineExpose({ open })
|
|
|
592
615
|
<PopoverTrigger as-child>
|
|
593
616
|
<button
|
|
594
617
|
type="button"
|
|
595
|
-
:
|
|
618
|
+
:id="field.id"
|
|
619
|
+
:disabled="field.disabled"
|
|
596
620
|
:aria-label="ariaLabel"
|
|
621
|
+
:aria-invalid="field.invalid || undefined"
|
|
622
|
+
:aria-describedby="field.describedBy"
|
|
597
623
|
class="group inline-flex w-full items-center gap-2 rounded-control border bg-surface-soft px-2 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"
|
|
598
624
|
:class="[
|
|
599
625
|
triggerSize[size],
|
|
600
|
-
invalid ? 'border-danger focus-visible:ring-danger-soft' : 'border-line focus-visible:border-brand',
|
|
626
|
+
field.invalid ? 'border-danger focus-visible:ring-danger-soft' : 'border-line focus-visible:border-brand',
|
|
601
627
|
]"
|
|
602
628
|
>
|
|
603
629
|
<!-- The swatch sits on the checkerboard so a transparent colour looks
|
|
@@ -645,6 +671,9 @@ defineExpose({ open })
|
|
|
645
671
|
role="slider"
|
|
646
672
|
:tabindex="disabled ? -1 : 0"
|
|
647
673
|
:aria-label="l.saturation"
|
|
674
|
+
:aria-valuemin="0"
|
|
675
|
+
:aria-valuemax="100"
|
|
676
|
+
:aria-valuenow="Math.round(hsv.s * 100)"
|
|
648
677
|
:aria-valuetext="fill(l.saturationValueText, { s: Math.round(hsv.s * 100), v: Math.round(hsv.v * 100) })"
|
|
649
678
|
class="relative h-36 w-full cursor-crosshair touch-none overflow-hidden rounded-control outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
|
|
650
679
|
:style="fieldStyle"
|
|
@@ -728,6 +757,7 @@ defineExpose({ open })
|
|
|
728
757
|
:aria-label="l.value"
|
|
729
758
|
class="min-w-0 flex-1 select-text bg-transparent font-mono text-xs text-ink outline-none placeholder:text-muted"
|
|
730
759
|
@input="onInput"
|
|
760
|
+
@keydown.enter="commitTyped"
|
|
731
761
|
@blur="onInputBlur"
|
|
732
762
|
/>
|
|
733
763
|
</div>
|
|
@@ -44,7 +44,8 @@ import { useGlass } from "../composables/useGlass"
|
|
|
44
44
|
import { useClipboard } from "../composables/useClipboard"
|
|
45
45
|
import { useHotkeys } from "../composables/useHotkeys"
|
|
46
46
|
import { useModalLayer } from "./modalDepth"
|
|
47
|
-
import {
|
|
47
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
48
|
+
import { parseCommandQuery, rankCommandItems } from "./commandSearch"
|
|
48
49
|
|
|
49
50
|
export interface Command {
|
|
50
51
|
id: string
|
|
@@ -87,7 +88,7 @@ export interface Command {
|
|
|
87
88
|
* `onSelect` still runs when the mode opens, so a command can both act and
|
|
88
89
|
* drill in.
|
|
89
90
|
*/
|
|
90
|
-
children?: Command[] | ((query: string) => Command[] | Promise<Command[]>)
|
|
91
|
+
children?: Command[] | ((query: string, signal: AbortSignal) => Command[] | Promise<Command[]>)
|
|
91
92
|
/** Placeholder while inside this command's mode. */
|
|
92
93
|
childPlaceholder?: string
|
|
93
94
|
/**
|
|
@@ -121,7 +122,7 @@ export interface Command {
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
/** Footer hints and the mode chip, spelled out so an app can translate them. */
|
|
124
|
-
export
|
|
125
|
+
export type PaletteLabels = {
|
|
125
126
|
navigate: string
|
|
126
127
|
select: string
|
|
127
128
|
close: string
|
|
@@ -144,6 +145,11 @@ export interface PaletteLabels {
|
|
|
144
145
|
* text is never displayed. Kept so existing `labels` objects still type-check.
|
|
145
146
|
*/
|
|
146
147
|
noPreview?: string
|
|
148
|
+
/** Accessible name and description of the dialog, read by screen readers only. */
|
|
149
|
+
title?: string
|
|
150
|
+
description?: string
|
|
151
|
+
/** The key hint printed beside the box and in the footer. */
|
|
152
|
+
esc?: string
|
|
147
153
|
}
|
|
148
154
|
|
|
149
155
|
const props = withDefaults(
|
|
@@ -190,12 +196,6 @@ const props = withDefaults(
|
|
|
190
196
|
* other group off the screen. 0 shows everything.
|
|
191
197
|
*/
|
|
192
198
|
groupLimit?: number
|
|
193
|
-
/**
|
|
194
|
-
* When the query matches nothing, retry it as if typed in the other
|
|
195
|
-
* keyboard layout (QWERTY ↔ Cyrillic). Off for an app whose commands are
|
|
196
|
-
* meant to be typed in one layout only.
|
|
197
|
-
*/
|
|
198
|
-
layoutFallback?: boolean
|
|
199
199
|
/**
|
|
200
200
|
* A pane beside the list showing the highlighted result in full — its
|
|
201
201
|
* `preview` text, or the `preview` slot. For palettes that search
|
|
@@ -229,16 +229,16 @@ const props = withDefaults(
|
|
|
229
229
|
resultsLabel: "Results",
|
|
230
230
|
excerptLines: 4,
|
|
231
231
|
groupLimit: 0,
|
|
232
|
-
layoutFallback: true,
|
|
233
232
|
previewPane: false,
|
|
234
233
|
glass: undefined,
|
|
235
234
|
},
|
|
236
235
|
)
|
|
237
236
|
|
|
238
237
|
const frosted = useGlass(() => props.glass)
|
|
239
|
-
const {
|
|
238
|
+
const contentRef = ref<{ $el: HTMLElement } | null>(null)
|
|
239
|
+
const { claim, release, layer } = useModalLayer({ element: () => contentRef.value?.$el })
|
|
240
240
|
|
|
241
|
-
const DEFAULT_LABELS: PaletteLabels = {
|
|
241
|
+
const DEFAULT_LABELS: Required<PaletteLabels> = {
|
|
242
242
|
navigate: "navigate",
|
|
243
243
|
select: "select",
|
|
244
244
|
close: "close",
|
|
@@ -252,9 +252,12 @@ const DEFAULT_LABELS: PaletteLabels = {
|
|
|
252
252
|
copyLink: "Copy link",
|
|
253
253
|
linkCopied: "Link copied",
|
|
254
254
|
noPreview: "No preview",
|
|
255
|
+
title: "Command palette",
|
|
256
|
+
description: "Search and run a command.",
|
|
257
|
+
esc: "esc",
|
|
255
258
|
}
|
|
256
259
|
|
|
257
|
-
const text =
|
|
260
|
+
const text = useLabels("palette", DEFAULT_LABELS, () => props.labels)
|
|
258
261
|
|
|
259
262
|
const emit = defineEmits<{
|
|
260
263
|
(e: "update:open", value: boolean): void
|
|
@@ -337,12 +340,14 @@ const selectedActions = computed(() => actionsOf(flat.value[selectedIndex.value]
|
|
|
337
340
|
* actions it keeps that default.
|
|
338
341
|
*/
|
|
339
342
|
function onTab(event: KeyboardEvent) {
|
|
340
|
-
|
|
341
|
-
|
|
343
|
+
// Without actions Tab stays Tab, so the trail chips and a Retry button are
|
|
344
|
+
// still reachable from the keyboard.
|
|
342
345
|
if (event.shiftKey) return
|
|
343
346
|
const c = flat.value[selectedIndex.value]
|
|
344
347
|
const actions = selectedActions.value
|
|
345
348
|
if (!c || !actions.length) return
|
|
349
|
+
event.preventDefault()
|
|
350
|
+
event.stopPropagation()
|
|
346
351
|
enter({ id: `${c.id}:actions`, label: c.label, icon: c.icon, children: actions })
|
|
347
352
|
}
|
|
348
353
|
|
|
@@ -399,7 +404,7 @@ function fetcher(): ((q: string, signal: AbortSignal) => Command[] | Promise<Com
|
|
|
399
404
|
const current = mode.value
|
|
400
405
|
if (current) {
|
|
401
406
|
return typeof current.children === "function"
|
|
402
|
-
?
|
|
407
|
+
? current.children
|
|
403
408
|
: null
|
|
404
409
|
}
|
|
405
410
|
return props.search ?? null
|
|
@@ -469,10 +474,7 @@ const local = computed<{ items: Command[]; terms: string[] }>(() => {
|
|
|
469
474
|
// typed). With a query, hidden commands rank like any other.
|
|
470
475
|
if (!terms.include.length && !terms.exclude.length && !terms.groups.length) return { items: filtered, terms: [] }
|
|
471
476
|
const items = rankCommandItems(filtered, terms)
|
|
472
|
-
|
|
473
|
-
const swapped = swapCommandTerms(terms)
|
|
474
|
-
const other = rankCommandItems(filtered, swapped)
|
|
475
|
-
return other.length ? { items: other, terms: swapped.include } : { items, terms: terms.include }
|
|
477
|
+
return { items, terms: terms.include }
|
|
476
478
|
})
|
|
477
479
|
|
|
478
480
|
const ranked = computed<Command[]>(() => local.value.items)
|
|
@@ -609,8 +611,11 @@ watch(
|
|
|
609
611
|
else viewport.scrollTop = 0
|
|
610
612
|
},
|
|
611
613
|
)
|
|
614
|
+
// Rows ask for their index several times per render; a lookup keeps that O(1)
|
|
615
|
+
// instead of a scan of the whole list per row.
|
|
616
|
+
const flatIndex = computed(() => new Map(flat.value.map((c, i) => [c, i])))
|
|
612
617
|
function flatIndexOf(c: Command): number {
|
|
613
|
-
return
|
|
618
|
+
return flatIndex.value.get(c) ?? -1
|
|
614
619
|
}
|
|
615
620
|
|
|
616
621
|
// ── highlight ────────────────────────────────────────────────
|
|
@@ -699,6 +704,9 @@ function movePage(direction: -1 | 1) {
|
|
|
699
704
|
function onPaletteKeydown(event: KeyboardEvent) {
|
|
700
705
|
if (event.isComposing) return
|
|
701
706
|
if (!(["ArrowDown", "ArrowUp", "Home", "End", "PageDown", "PageUp"] as string[]).includes(event.key)) return
|
|
707
|
+
// Home and End belong to the caret while there is text to move it through.
|
|
708
|
+
const typing = event.target instanceof HTMLInputElement && event.target.value !== ""
|
|
709
|
+
if (typing && (event.key === "Home" || event.key === "End")) return
|
|
702
710
|
event.preventDefault()
|
|
703
711
|
event.stopPropagation()
|
|
704
712
|
if (event.key === "ArrowDown") moveDown()
|
|
@@ -838,6 +846,8 @@ useHotkeys(() =>
|
|
|
838
846
|
code: "KeyK",
|
|
839
847
|
ctrl: true,
|
|
840
848
|
allowInInput: true,
|
|
849
|
+
overLayers: true,
|
|
850
|
+
repeat: false,
|
|
841
851
|
handler: () => emit("update:open", !props.open),
|
|
842
852
|
},
|
|
843
853
|
]
|
|
@@ -854,6 +864,7 @@ useHotkeys(() =>
|
|
|
854
864
|
:style="{ zIndex: layer.scrim }"
|
|
855
865
|
/>
|
|
856
866
|
<DialogContent
|
|
867
|
+
ref="contentRef"
|
|
857
868
|
class="lp-cmd-dialog fixed left-1/2 top-[12vh] flex max-h-[76vh] -translate-x-1/2 flex-col overflow-hidden rounded-card border border-line shadow-panel outline-none data-[state=open]:animate-[pop-in_160ms_var(--ease-emphasized)] data-[state=closed]:animate-[pop-out_130ms_ease]"
|
|
858
869
|
:class="[frosted ? (layer.nested ? 'lp-glass-overlay--nested' : 'lp-glass-overlay') : 'bg-surface-raised', previewPane && 'lp-cmd-split']"
|
|
859
870
|
:style="{ zIndex: layer.panel }"
|
|
@@ -861,8 +872,8 @@ useHotkeys(() =>
|
|
|
861
872
|
@open-auto-focus.prevent
|
|
862
873
|
@keydown.capture="onPaletteKeydown"
|
|
863
874
|
>
|
|
864
|
-
<DialogTitle class="sr-only">
|
|
865
|
-
<DialogDescription class="sr-only">
|
|
875
|
+
<DialogTitle class="sr-only">{{ text.title }}</DialogTitle>
|
|
876
|
+
<DialogDescription class="sr-only">{{ text.description }}</DialogDescription>
|
|
866
877
|
|
|
867
878
|
<!-- Filter. The trail sits inside the box as chips, the way a scoped
|
|
868
879
|
search reads everywhere else: what you type is filtered by what is
|
|
@@ -905,7 +916,7 @@ useHotkeys(() =>
|
|
|
905
916
|
@keydown.esc="onEscape"
|
|
906
917
|
@keydown.tab="onTab"
|
|
907
918
|
/>
|
|
908
|
-
<kbd class="shrink-0 rounded border border-line px-1.5 py-0.5 text-[10px] text-muted">esc</kbd>
|
|
919
|
+
<kbd class="shrink-0 rounded border border-line px-1.5 py-0.5 text-[10px] text-muted">{{ text.esc }}</kbd>
|
|
909
920
|
</div>
|
|
910
921
|
|
|
911
922
|
<!-- Results -->
|
|
@@ -1079,7 +1090,7 @@ useHotkeys(() =>
|
|
|
1079
1090
|
{{ text.back }}
|
|
1080
1091
|
</span>
|
|
1081
1092
|
<span class="ml-auto flex items-center gap-1">
|
|
1082
|
-
<kbd class="rounded border border-line px-1 text-[10px]">esc</kbd>
|
|
1093
|
+
<kbd class="rounded border border-line px-1 text-[10px]">{{ text.esc }}</kbd>
|
|
1083
1094
|
{{ text.close }}
|
|
1084
1095
|
</span>
|
|
1085
1096
|
</div>
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
// Confirmation dialog built on LpModal. Resolves an action via confirm/cancel.
|
|
3
|
-
import { computed } from "vue"
|
|
3
|
+
import { computed, nextTick, ref, watch } from "vue"
|
|
4
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
4
5
|
import LpButton from "./LpButton.vue"
|
|
5
6
|
import LpIcon from "./LpIcon.vue"
|
|
6
7
|
import LpModal from "./LpModal.vue"
|
|
7
8
|
|
|
9
|
+
export type ConfirmLabels = {
|
|
10
|
+
confirm: string
|
|
11
|
+
cancel: string
|
|
12
|
+
loading: string
|
|
13
|
+
failed: string
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
const props = withDefaults(
|
|
9
17
|
defineProps<{
|
|
10
18
|
open?: boolean
|
|
@@ -33,13 +41,22 @@ const props = withDefaults(
|
|
|
33
41
|
* The confirm action is in flight: the button disables and swaps to
|
|
34
42
|
* `loadingLabel`, and confirming no longer closes the dialog — the owner
|
|
35
43
|
* closes it once the work settles, so a failure can stay on screen.
|
|
44
|
+
* Setting it from the `confirm` handler is enough; the dialog waits a tick
|
|
45
|
+
* for it before deciding to close.
|
|
36
46
|
*/
|
|
37
47
|
loading?: boolean
|
|
38
48
|
loadingLabel?: string
|
|
49
|
+
/**
|
|
50
|
+
* The work behind the confirm button. When given, the dialog runs it,
|
|
51
|
+
* shows the loading state itself, closes once it resolves and stays open
|
|
52
|
+
* with the error's message when it rejects.
|
|
53
|
+
*/
|
|
54
|
+
action?: () => unknown
|
|
39
55
|
/** Frosted glass panel, passed to LpModal. Follows `provideGlass` when unset. */
|
|
40
56
|
glass?: boolean
|
|
57
|
+
labels?: Partial<ConfirmLabels>
|
|
41
58
|
}>(),
|
|
42
|
-
{
|
|
59
|
+
{ glass: undefined },
|
|
43
60
|
)
|
|
44
61
|
|
|
45
62
|
const emit = defineEmits<{
|
|
@@ -48,29 +65,61 @@ const emit = defineEmits<{
|
|
|
48
65
|
(e: "cancel"): void
|
|
49
66
|
}>()
|
|
50
67
|
|
|
68
|
+
const text = useLabels<ConfirmLabels>(
|
|
69
|
+
"confirm",
|
|
70
|
+
{ confirm: "Confirm", cancel: "Cancel", loading: "Working…", failed: "Something went wrong." },
|
|
71
|
+
() => props.labels,
|
|
72
|
+
)
|
|
73
|
+
|
|
51
74
|
/*
|
|
52
75
|
* Only a destructive prompt is marked by default. A routine confirmation
|
|
53
76
|
* ("Save these changes?") with a warning badge cries wolf, and the badge stops
|
|
54
77
|
* meaning anything on the dialog where it matters.
|
|
55
|
-
*
|
|
56
|
-
* "Not given" cannot be tested with `undefined` here. A prop whose type union
|
|
57
|
-
* includes Boolean is boolean-cast by Vue, so an absent `icon` arrives as
|
|
58
|
-
* `false` — the same value the caller passes to suppress the marker — and the
|
|
59
|
-
* default silently never applied. A string is an override, and everything else
|
|
60
|
-
* (absent, or an explicit false) falls through to the danger default.
|
|
61
78
|
*/
|
|
62
79
|
const marker = computed<string>(() => {
|
|
63
80
|
if (props.icon !== undefined) return props.icon
|
|
64
81
|
return props.danger ? "lucide:triangle-alert" : ""
|
|
65
82
|
})
|
|
66
83
|
|
|
67
|
-
|
|
84
|
+
const running = ref(false)
|
|
85
|
+
const error = ref("")
|
|
86
|
+
const busy = computed(() => props.loading || running.value)
|
|
87
|
+
|
|
88
|
+
watch(
|
|
89
|
+
() => props.open,
|
|
90
|
+
(open) => {
|
|
91
|
+
if (open) error.value = ""
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
async function confirm() {
|
|
96
|
+
if (busy.value) return
|
|
97
|
+
error.value = ""
|
|
68
98
|
emit("confirm")
|
|
69
|
-
|
|
70
|
-
|
|
99
|
+
if (props.action) {
|
|
100
|
+
running.value = true
|
|
101
|
+
try {
|
|
102
|
+
await props.action()
|
|
103
|
+
} catch (e) {
|
|
104
|
+
error.value = e instanceof Error && e.message ? e.message : text.value.failed
|
|
105
|
+
return
|
|
106
|
+
} finally {
|
|
107
|
+
running.value = false
|
|
108
|
+
}
|
|
109
|
+
emit("update:open", false)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
// A handler that sets `loading` does so through the parent, whose new props
|
|
113
|
+
// reach this component only on the next render.
|
|
114
|
+
await nextTick()
|
|
71
115
|
if (!props.loading) emit("update:open", false)
|
|
72
116
|
}
|
|
73
|
-
|
|
117
|
+
|
|
118
|
+
// Every way out that is not the confirm button counts as a cancel: Escape, the
|
|
119
|
+
// scrim, the close button. None of them may close it while work is in flight.
|
|
120
|
+
function dismiss(open: boolean) {
|
|
121
|
+
if (open) return emit("update:open", true)
|
|
122
|
+
if (busy.value) return
|
|
74
123
|
emit("cancel")
|
|
75
124
|
emit("update:open", false)
|
|
76
125
|
}
|
|
@@ -82,7 +131,8 @@ function cancel() {
|
|
|
82
131
|
:open="open"
|
|
83
132
|
:title="title"
|
|
84
133
|
:description="description"
|
|
85
|
-
|
|
134
|
+
role="alertdialog"
|
|
135
|
+
@update:open="dismiss"
|
|
86
136
|
>
|
|
87
137
|
<template v-if="marker" #title>
|
|
88
138
|
<span class="inline-flex items-center gap-2.5">
|
|
@@ -100,16 +150,17 @@ function cancel() {
|
|
|
100
150
|
</span>
|
|
101
151
|
</template>
|
|
102
152
|
<slot />
|
|
153
|
+
<p v-if="error" role="alert" class="mt-3 text-sm text-danger">{{ error }}</p>
|
|
103
154
|
<template #footer>
|
|
104
|
-
<LpButton variant="ghost" :disabled="
|
|
105
|
-
{{ cancelLabel }}
|
|
155
|
+
<LpButton variant="ghost" :disabled="busy" @click="dismiss(false)">
|
|
156
|
+
{{ cancelLabel ?? text.cancel }}
|
|
106
157
|
</LpButton>
|
|
107
158
|
<LpButton
|
|
108
159
|
:variant="danger ? 'danger' : 'solid'"
|
|
109
|
-
:disabled="
|
|
160
|
+
:disabled="busy"
|
|
110
161
|
@click="confirm"
|
|
111
162
|
>
|
|
112
|
-
{{
|
|
163
|
+
{{ busy ? (loadingLabel ?? text.loading) : (confirmLabel ?? text.confirm) }}
|
|
113
164
|
</LpButton>
|
|
114
165
|
</template>
|
|
115
166
|
</LpModal>
|
|
@@ -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>
|