@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
|
@@ -8,9 +8,10 @@ export default { inheritAttrs: false }
|
|
|
8
8
|
<script setup lang="ts">
|
|
9
9
|
import { CheckboxIndicator, CheckboxRoot } from "reka-ui"
|
|
10
10
|
import LpIcon from "./LpIcon.vue"
|
|
11
|
+
import { useFieldControl } from "../composables/useField"
|
|
11
12
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
12
13
|
|
|
13
|
-
defineProps<{
|
|
14
|
+
const props = defineProps<{
|
|
14
15
|
modelValue?: boolean
|
|
15
16
|
/** Mixed state (e.g. a "select all" with a partial selection). Shows a dash. */
|
|
16
17
|
indeterminate?: boolean
|
|
@@ -30,6 +31,8 @@ function onUpdate(v: boolean | "indeterminate") {
|
|
|
30
31
|
emit("change", checked)
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
const field = useFieldControl(() => ({ wrapped: false, id: props.id, disabled: props.disabled }))
|
|
35
|
+
|
|
33
36
|
// The label sits a fixed distance from the box; a dense list of options needs that closer.
|
|
34
37
|
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
35
38
|
"group inline-flex cursor-pointer items-center gap-2 text-sm text-ink",
|
|
@@ -41,10 +44,11 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
41
44
|
:class="rootClass"
|
|
42
45
|
v-bind="rest">
|
|
43
46
|
<CheckboxRoot
|
|
44
|
-
:id="id"
|
|
47
|
+
:id="field.id"
|
|
45
48
|
:name="name"
|
|
46
49
|
:model-value="indeterminate ? 'indeterminate' : modelValue"
|
|
47
|
-
:disabled="disabled"
|
|
50
|
+
:disabled="field.disabled"
|
|
51
|
+
:aria-describedby="field.describedBy"
|
|
48
52
|
class="flex size-[18px] items-center justify-center rounded-[5px] border border-line-strong bg-surface-soft outline-none transition-[color,background-color,border-color,box-shadow,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] not-data-[state=checked]:not-data-[state=indeterminate]:group-hover:border-brand active:scale-90 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-transparent data-[state=checked]:bg-brand data-[state=indeterminate]:border-transparent data-[state=indeterminate]:bg-brand disabled:cursor-not-allowed disabled:opacity-55"
|
|
49
53
|
@update:model-value="onUpdate"
|
|
50
54
|
>
|
|
@@ -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,6 +44,7 @@ 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 { useLabels } from "../composables/useLpConfig"
|
|
47
48
|
import { parseCommandQuery, rankCommandItems } from "./commandSearch"
|
|
48
49
|
|
|
49
50
|
export interface Command {
|
|
@@ -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(
|
|
@@ -229,9 +235,10 @@ const props = withDefaults(
|
|
|
229
235
|
)
|
|
230
236
|
|
|
231
237
|
const frosted = useGlass(() => props.glass)
|
|
232
|
-
const {
|
|
238
|
+
const contentRef = ref<{ $el: HTMLElement } | null>(null)
|
|
239
|
+
const { claim, release, layer } = useModalLayer({ element: () => contentRef.value?.$el })
|
|
233
240
|
|
|
234
|
-
const DEFAULT_LABELS: PaletteLabels = {
|
|
241
|
+
const DEFAULT_LABELS: Required<PaletteLabels> = {
|
|
235
242
|
navigate: "navigate",
|
|
236
243
|
select: "select",
|
|
237
244
|
close: "close",
|
|
@@ -245,9 +252,12 @@ const DEFAULT_LABELS: PaletteLabels = {
|
|
|
245
252
|
copyLink: "Copy link",
|
|
246
253
|
linkCopied: "Link copied",
|
|
247
254
|
noPreview: "No preview",
|
|
255
|
+
title: "Command palette",
|
|
256
|
+
description: "Search and run a command.",
|
|
257
|
+
esc: "esc",
|
|
248
258
|
}
|
|
249
259
|
|
|
250
|
-
const text =
|
|
260
|
+
const text = useLabels("palette", DEFAULT_LABELS, () => props.labels)
|
|
251
261
|
|
|
252
262
|
const emit = defineEmits<{
|
|
253
263
|
(e: "update:open", value: boolean): void
|
|
@@ -330,12 +340,14 @@ const selectedActions = computed(() => actionsOf(flat.value[selectedIndex.value]
|
|
|
330
340
|
* actions it keeps that default.
|
|
331
341
|
*/
|
|
332
342
|
function onTab(event: KeyboardEvent) {
|
|
333
|
-
|
|
334
|
-
|
|
343
|
+
// Without actions Tab stays Tab, so the trail chips and a Retry button are
|
|
344
|
+
// still reachable from the keyboard.
|
|
335
345
|
if (event.shiftKey) return
|
|
336
346
|
const c = flat.value[selectedIndex.value]
|
|
337
347
|
const actions = selectedActions.value
|
|
338
348
|
if (!c || !actions.length) return
|
|
349
|
+
event.preventDefault()
|
|
350
|
+
event.stopPropagation()
|
|
339
351
|
enter({ id: `${c.id}:actions`, label: c.label, icon: c.icon, children: actions })
|
|
340
352
|
}
|
|
341
353
|
|
|
@@ -392,7 +404,7 @@ function fetcher(): ((q: string, signal: AbortSignal) => Command[] | Promise<Com
|
|
|
392
404
|
const current = mode.value
|
|
393
405
|
if (current) {
|
|
394
406
|
return typeof current.children === "function"
|
|
395
|
-
?
|
|
407
|
+
? current.children
|
|
396
408
|
: null
|
|
397
409
|
}
|
|
398
410
|
return props.search ?? null
|
|
@@ -599,8 +611,11 @@ watch(
|
|
|
599
611
|
else viewport.scrollTop = 0
|
|
600
612
|
},
|
|
601
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])))
|
|
602
617
|
function flatIndexOf(c: Command): number {
|
|
603
|
-
return
|
|
618
|
+
return flatIndex.value.get(c) ?? -1
|
|
604
619
|
}
|
|
605
620
|
|
|
606
621
|
// ── highlight ────────────────────────────────────────────────
|
|
@@ -689,6 +704,9 @@ function movePage(direction: -1 | 1) {
|
|
|
689
704
|
function onPaletteKeydown(event: KeyboardEvent) {
|
|
690
705
|
if (event.isComposing) return
|
|
691
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
|
|
692
710
|
event.preventDefault()
|
|
693
711
|
event.stopPropagation()
|
|
694
712
|
if (event.key === "ArrowDown") moveDown()
|
|
@@ -828,6 +846,8 @@ useHotkeys(() =>
|
|
|
828
846
|
code: "KeyK",
|
|
829
847
|
ctrl: true,
|
|
830
848
|
allowInInput: true,
|
|
849
|
+
overLayers: true,
|
|
850
|
+
repeat: false,
|
|
831
851
|
handler: () => emit("update:open", !props.open),
|
|
832
852
|
},
|
|
833
853
|
]
|
|
@@ -844,6 +864,7 @@ useHotkeys(() =>
|
|
|
844
864
|
:style="{ zIndex: layer.scrim }"
|
|
845
865
|
/>
|
|
846
866
|
<DialogContent
|
|
867
|
+
ref="contentRef"
|
|
847
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]"
|
|
848
869
|
:class="[frosted ? (layer.nested ? 'lp-glass-overlay--nested' : 'lp-glass-overlay') : 'bg-surface-raised', previewPane && 'lp-cmd-split']"
|
|
849
870
|
:style="{ zIndex: layer.panel }"
|
|
@@ -851,8 +872,8 @@ useHotkeys(() =>
|
|
|
851
872
|
@open-auto-focus.prevent
|
|
852
873
|
@keydown.capture="onPaletteKeydown"
|
|
853
874
|
>
|
|
854
|
-
<DialogTitle class="sr-only">
|
|
855
|
-
<DialogDescription class="sr-only">
|
|
875
|
+
<DialogTitle class="sr-only">{{ text.title }}</DialogTitle>
|
|
876
|
+
<DialogDescription class="sr-only">{{ text.description }}</DialogDescription>
|
|
856
877
|
|
|
857
878
|
<!-- Filter. The trail sits inside the box as chips, the way a scoped
|
|
858
879
|
search reads everywhere else: what you type is filtered by what is
|
|
@@ -895,7 +916,7 @@ useHotkeys(() =>
|
|
|
895
916
|
@keydown.esc="onEscape"
|
|
896
917
|
@keydown.tab="onTab"
|
|
897
918
|
/>
|
|
898
|
-
<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>
|
|
899
920
|
</div>
|
|
900
921
|
|
|
901
922
|
<!-- Results -->
|
|
@@ -1069,7 +1090,7 @@ useHotkeys(() =>
|
|
|
1069
1090
|
{{ text.back }}
|
|
1070
1091
|
</span>
|
|
1071
1092
|
<span class="ml-auto flex items-center gap-1">
|
|
1072
|
-
<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>
|
|
1073
1094
|
{{ text.close }}
|
|
1074
1095
|
</span>
|
|
1075
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>
|