@leavepulse/ui 0.70.0 → 0.71.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 +103 -0
- package/dist/assets/{LpCommandPalette-u-zOxKWa.css → LpCommandPalette-DgPZX0us.css} +24 -17
- package/dist/assets/{LpPaletteFacts-B9FdVmIO.css → LpPaletteFacts-CVNh5ICn.css} +16 -16
- package/dist/assets/{LpPaletteStats-ClE-37MK.css → LpPaletteStats-ZiZmR6vz.css} +5 -5
- package/dist/assets/LpRepeater-C1Cw27hj.css +21 -0
- package/dist/assets/{LpRollingText-BGnE6q9j.css → LpRollingText-dyOXedGO.css} +7 -0
- package/dist/assets/{LpTable-DHxEQ_-3.css → LpTable-DHvorDiU.css} +19 -19
- package/dist/assets/LpTabs-CO3EcsNV.css +18 -0
- package/dist/chunks/{ContextMenuItems-B5uE8Znw.js → ContextMenuItems-eS7qx9gD.js} +11 -5
- package/dist/chunks/{DropzoneLinkField-Bw8Flkhk.js → DropzoneLinkField-DkFhdfEJ.js} +2 -2
- package/dist/chunks/{FieldWrapper-BiennROB.js → FieldWrapper-YzbvvC3M.js} +1 -1
- package/dist/chunks/{LayoutCanvas-BsJIzhdd.js → LayoutCanvas-Ba4A9QC5.js} +1 -1
- package/dist/chunks/{LayoutNode-DJVhkSGk.js → LayoutNode-CTTTLMDM.js} +1 -1
- package/dist/chunks/{LpActionBar-CzOFar5t.js → LpActionBar-B7anGlkI.js} +2 -2
- package/dist/chunks/{LpAddressInput-CNnLTDZg.js → LpAddressInput-CD0bzrt-.js} +7 -7
- package/dist/chunks/{LpAlert-C5igj802.js → LpAlert-BHi7ARqu.js} +1 -1
- package/dist/chunks/{LpAppShell-CEUAB8kK.js → LpAppShell-1RmBp6xQ.js} +3 -3
- package/dist/chunks/{LpAutocomplete-D92niDHE.js → LpAutocomplete-DDqe2M5g.js} +5 -2
- package/dist/chunks/{LpAvatar-CLPl-qOF.js → LpAvatar-DqfKCIdr.js} +1 -1
- package/dist/chunks/{LpBadge-HOEPBYmi.js → LpBadge-C8fKEmTF.js} +2 -2
- package/dist/chunks/{LpBreadcrumbs-DYoPc3P4.js → LpBreadcrumbs-DhcoStFa.js} +2 -2
- package/dist/chunks/{LpButton-CVxBfGjd.js → LpButton-BQDAFXBd.js} +3 -0
- package/dist/chunks/{LpCalendar-pVL91-Pl.js → LpCalendar-NmrA47J9.js} +1 -1
- package/dist/chunks/{LpCheckbox-DHc2bHDe.js → LpCheckbox-BoOuLnGB.js} +5 -2
- package/dist/chunks/{LpCodeBlock-BGsQhxw8.js → LpCodeBlock-Czu-fq1M.js} +2 -2
- package/dist/chunks/{LpCodeDisplay-CIKKT2iW.js → LpCodeDisplay-BK2FHbtM.js} +6 -6
- package/dist/chunks/{LpColorPicker-DFugXNUi.js → LpColorPicker-Ee1DZiRd.js} +3 -3
- package/dist/chunks/LpCommandScope-ubtWDMa0.js +53 -0
- package/dist/chunks/{LpConfirmDialog-CfGCux8m.js → LpConfirmDialog-i15pulMT.js} +3 -3
- package/dist/chunks/{LpConfirmHost-exB7JmcO.js → LpConfirmHost-D0oGavtd.js} +1 -1
- package/dist/chunks/{LpContextMenu-COwOwRgI.js → LpContextMenu-DXpFJjlO.js} +17 -6
- package/dist/chunks/{LpCopyButton-DzHjnFs0.js → LpCopyButton-Dmq7DmFw.js} +3 -3
- package/dist/chunks/{LpCountdown-Dtq1ihlJ.js → LpCountdown-DTdMF8DL.js} +2 -2
- package/dist/chunks/{LpDataList-B-5nHfNw.js → LpDataList-CFPtSsBB.js} +7 -6
- package/dist/chunks/{LpDatePicker-BUe1VUC0.js → LpDatePicker-cC9K-Pk8.js} +7 -6
- package/dist/chunks/{LpDateRangePicker-C_k65vGR.js → LpDateRangePicker-BVQKPbvo.js} +5 -4
- package/dist/chunks/{LpDisclosure-DOarzlAG.js → LpDisclosure-hRnX3FWL.js} +4 -1
- package/dist/chunks/{LpDrawer-DGNKRsGq.js → LpDrawer-u4rxnuR-.js} +1 -1
- package/dist/chunks/{LpDropdownMenu-CRgrhdAH.js → LpDropdownMenu-BgZ0Z_Im.js} +17 -4
- package/dist/chunks/{LpEditable-DTqLt2lT.js → LpEditable-CjxuOVh_.js} +7 -5
- package/dist/chunks/{LpEmptyState-KTKipws2.js → LpEmptyState-CKFTtQgJ.js} +1 -1
- package/dist/chunks/{LpFieldNotice-CurbNuXI.js → LpFieldNotice-CPI8EwtU.js} +1 -1
- package/dist/chunks/{LpFileTree-DMISXVHC.js → LpFileTree-BH0gNlSJ.js} +2 -2
- package/dist/chunks/{LpFileTreeNode-CPXB_Yy3.js → LpFileTreeNode-gPVB1oXM.js} +4 -4
- package/dist/chunks/{LpFormField-BWwgm1E4.js → LpFormField-gRASfyj2.js} +4 -1
- package/dist/chunks/{LpGauge-Bk8TwJj0.js → LpGauge-BdjPGnqw.js} +2 -2
- package/dist/chunks/{LpHintLayer-p4hqzdyA.js → LpHintLayer-C2asBIfs.js} +2 -2
- package/dist/chunks/{LpHints-CCrhRfy9.js → LpHints-CNZQ76JV.js} +3 -3
- package/dist/chunks/{LpIcon-HJP5xMz4.js → LpIcon-DkP-9mzK.js} +1 -1
- package/dist/chunks/{LpInfraNode-CUC3t4na.js → LpInfraNode-yTh4-5qa.js} +2 -2
- package/dist/chunks/{LpInput-CDuwODJG.js → LpInput-2H-5ILOB.js} +5 -2
- package/dist/chunks/{LpLightbox-XJ0LpTcZ.js → LpLightbox-lAt5uIfx.js} +2 -2
- package/dist/chunks/{LpLogViewer-elZHbj85.js → LpLogViewer-x78YfhJf.js} +5 -5
- package/dist/chunks/{LpMapPicker-BtIl7NOM.js → LpMapPicker-Dp5RSCi9.js} +1 -1
- package/dist/chunks/{LpMediaGallery-D5swlmZ_.js → LpMediaGallery-D92oJfY_.js} +3 -3
- package/dist/chunks/{LpModal-eBMt2Fqn.js → LpModal-CpH0bNo7.js} +1 -1
- package/dist/chunks/{LpModalNav-D4Z0wVHR.js → LpModalNav-BH4ANQGJ.js} +4 -4
- package/dist/chunks/{LpNotificationBell-DPZmP5dM.js → LpNotificationBell-BdUQqZhs.js} +8 -8
- package/dist/chunks/{LpNumberField-B9k-h2HE.js → LpNumberField-DZY3Z670.js} +4 -1
- package/dist/chunks/{LpNumberFlow-Cfyh033Q.js → LpNumberFlow-5khYxsfX.js} +6 -3
- package/dist/chunks/{LpPagination-VNCX6pFC.js → LpPagination-DbeACavM.js} +1 -1
- package/dist/chunks/LpPaletteTheme-CqWNRwHE.js +104 -0
- package/dist/chunks/{LpPasswordInput-DTV18FIh.js → LpPasswordInput-BzAZsf5V.js} +14 -8
- package/dist/chunks/{LpPhoneInput-C60tx1tC.js → LpPhoneInput-BMpd-0QR.js} +2 -2
- package/dist/chunks/{LpPie-CFuV7pZv.js → LpPie-DdFSTbGE.js} +5 -5
- package/dist/chunks/{LpPipeline-D81yh_NX.js → LpPipeline-zjEM1Lwc.js} +4 -4
- package/dist/chunks/{LpPopover-BEIcqb9p.js → LpPopover-DmZ3wUtP.js} +6 -5
- package/dist/chunks/{LpProgress-CE-m5OpD.js → LpProgress-BX33EKkN.js} +1 -1
- package/dist/chunks/{LpQuota-2Sw_gqcJ.js → LpQuota-DqNv1yjs.js} +3 -3
- package/dist/chunks/{LpRadioGroup-CGBr5nIp.js → LpRadioGroup-CsXeV9Ct.js} +4 -1
- package/dist/chunks/{LpRangeCalendar-c5qj7YcC.js → LpRangeCalendar-l-0nSFmY.js} +1 -1
- package/dist/chunks/{LpRangeSlider-Dv6lNvHY.js → LpRangeSlider-CM-0ZITZ.js} +3 -0
- package/dist/chunks/{LpRelativeTime-CKzVqAo9.js → LpRelativeTime-UM8EkfrN.js} +2 -2
- package/dist/chunks/LpRepeater-OvzByE5L.js +180 -0
- package/dist/chunks/{LpRollingText-DB7upf-5.js → LpRollingText-DiL717LK.js} +4 -3
- package/dist/chunks/{LpSecretField-DMfdJ1cQ.js → LpSecretField-DwQiPOko.js} +27 -16
- package/dist/chunks/{LpSection-BW4xJ-Li.js → LpSection-DcQsieHG.js} +1 -1
- package/dist/chunks/{LpSegmented-BO99Sr4m.js → LpSegmented-D2VST0o3.js} +4 -1
- package/dist/chunks/{LpSelect-B4f_yRi5.js → LpSelect-DYX2-u5A.js} +43 -18
- package/dist/chunks/{LpSidebar-D-M0Y_7U.js → LpSidebar-D44peKq2.js} +3 -3
- package/dist/chunks/{LpSidebarNav-DF-TYxOV.js → LpSidebarNav--VwkXO_a.js} +3 -3
- package/dist/chunks/{LpSlider-DuoRpOrH.js → LpSlider-DxYoCJJO.js} +3 -0
- package/dist/chunks/{LpStat-B2ix1Q7t.js → LpStat-DIsQfbeD.js} +3 -3
- package/dist/chunks/{LpStatus-C7kx5PKE.js → LpStatus-BQU1TUsv.js} +2 -2
- package/dist/chunks/{LpStepMark-DK-5BKAt.js → LpStepMark-CXa7DJy_.js} +2 -2
- package/dist/chunks/{LpStepper-DRFfdJaq.js → LpStepper-C9RM_yTG.js} +1 -1
- package/dist/chunks/{LpSwitch-DrzKrZZV.js → LpSwitch-bGZxUDSx.js} +3 -0
- package/dist/chunks/{LpTableFilter-DyvHGbOk.js → LpTableFilter-CTZMqFDU.js} +2 -2
- package/dist/chunks/{LpTableOfContents-rYvo8z5T.js → LpTableOfContents-CraicEs4.js} +2 -2
- package/dist/chunks/{LpTagsInput-DgpBFRV2.js → LpTagsInput-DrjmrOge.js} +8 -5
- package/dist/chunks/{LpTextarea-DiWjeVoJ.js → LpTextarea-BbYqmG1S.js} +4 -1
- package/dist/chunks/{LpThemeStudio-DguwHdGt.js → LpThemeStudio-B2nPaG-h.js} +18 -14
- package/dist/chunks/{LpThemeSwitcher-D5uz0MhK.js → LpThemeSwitcher-C0A5sg_E.js} +6 -4
- package/dist/chunks/{LpTimeField-BFQVf_PW.js → LpTimeField-DQ8WvPr2.js} +4 -1
- package/dist/chunks/{LpToaster-Wv9ksj4q.js → LpToaster-BTTl_wuy.js} +2 -2
- package/dist/chunks/{LpTooltip-Cz8SQwRt.js → LpTooltip-BcBvIx4z.js} +23 -5
- package/dist/chunks/{LpTopologyCanvas-ZhKthfgz.js → LpTopologyCanvas-B0wEvAoq.js} +1 -1
- package/dist/chunks/{LpTour-NfRJNEIP.js → LpTour-BgCfNlaW.js} +1 -1
- package/dist/chunks/{LpTruncate-D_5Pfv5B.js → LpTruncate-TcqfkyJM.js} +2 -2
- package/dist/chunks/{LpUptimeBar-DWegV2p6.js → LpUptimeBar-DRadGq4Y.js} +136 -47
- package/dist/chunks/{MenuTitle-BjQZX8tQ.js → MenuTitle-Dab3EGOp.js} +1 -1
- package/dist/chunks/{OptionContent-CmYUUeWj.js → OptionContent-DPzNsybd.js} +1 -1
- package/dist/chunks/PalettePreviewBoundary-B0Hhu3Yw.js +20 -0
- package/dist/chunks/{PickerField-CHRS1h7K.js → PickerField-DwLw7j0L.js} +26 -17
- package/dist/chunks/{RevealToggle-C-KzaQGJ.js → RevealToggle-BVFM_FAU.js} +3 -3
- package/dist/chunks/{TourCard-BXuVHsfn.js → TourCard--2IfgnUU.js} +3 -3
- package/dist/chunks/icons-CLu1iR1j.js +43 -0
- package/dist/chunks/palette-BiY7yQZL.js +29 -0
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.js +1 -1
- package/dist/components/DropzoneLinkField.vue.js +1 -1
- package/dist/components/FieldWrapper.vue.js +1 -1
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.d.ts +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.d.ts +3 -3
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.d.ts +7 -1
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.d.ts +2 -2
- package/dist/components/LpBadge.vue.js +1 -1
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +4 -0
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpChart.vue.js +27 -25
- package/dist/components/LpCheckbox.vue.d.ts +8 -1
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpCodeDisplay.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.d.ts +2 -2
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +43 -7
- package/dist/components/LpCommandPalette.vue.js +451 -126
- package/dist/components/LpCommandScope.vue.d.ts +28 -0
- package/dist/components/LpCommandScope.vue.js +4 -0
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.js +1 -1
- package/dist/components/LpContextMenu.vue.d.ts +5 -1
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpCopyButton.vue.d.ts +1 -1
- package/dist/components/LpCopyButton.vue.js +1 -1
- package/dist/components/LpCountdown.vue.js +1 -1
- package/dist/components/LpDataList.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.d.ts +2 -0
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.d.ts +2 -0
- package/dist/components/LpDateRangePicker.vue.js +1 -1
- package/dist/components/LpDisclosure.vue.d.ts +4 -0
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDock.vue.js +2 -2
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.d.ts +5 -1
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +2 -2
- package/dist/components/LpEditable.vue.js +1 -1
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFieldNotice.vue.js +1 -1
- package/dist/components/LpFileTree.vue.d.ts +1 -1
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpGauge.vue.js +1 -1
- package/dist/components/LpHintLayer.vue.js +1 -1
- package/dist/components/LpHints.vue.js +1 -1
- package/dist/components/LpIcon.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +3 -2
- package/dist/components/LpInfraNode.vue.js +1 -1
- package/dist/components/LpInput.vue.d.ts +6 -0
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLoader.vue.d.ts +1 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.d.ts +2 -2
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.d.ts +6 -0
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPaletteFacts.vue.d.ts +1 -0
- package/dist/components/LpPaletteFacts.vue.js +40 -26
- package/dist/components/LpPaletteHeader.vue.js +2 -2
- package/dist/components/LpPaletteStats.vue.d.ts +1 -0
- package/dist/components/LpPaletteStats.vue.js +45 -21
- package/dist/components/LpPaletteTheme.vue.d.ts +7 -0
- package/dist/components/LpPaletteTheme.vue.js +4 -0
- package/dist/components/LpPasswordInput.vue.d.ts +7 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPipeline.vue.js +1 -1
- package/dist/components/LpPopover.vue.js +1 -1
- package/dist/components/LpProgress.vue.d.ts +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpQuota.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.d.ts +6 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeCalendar.vue.js +1 -1
- package/dist/components/LpRangeSlider.vue.d.ts +6 -0
- package/dist/components/LpRangeSlider.vue.js +1 -1
- package/dist/components/LpRelativeTime.vue.js +1 -1
- package/dist/components/LpRepeater.vue.js +1 -1
- package/dist/components/LpRollingText.vue.d.ts +2 -0
- package/dist/components/LpRollingText.vue.js +1 -1
- package/dist/components/LpSecretField.vue.js +1 -1
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSegmented.vue.d.ts +6 -0
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.d.ts +3 -0
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShinyText.vue.d.ts +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSlider.vue.d.ts +6 -0
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpStatus.vue.js +1 -1
- package/dist/components/LpStepMark.vue.js +1 -1
- package/dist/components/LpStepper.vue.js +1 -1
- package/dist/components/LpSwitch.vue.d.ts +6 -0
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.d.ts +6 -0
- package/dist/components/LpTable.vue.js +74 -35
- package/dist/components/LpTableFilter.vue.js +1 -1
- package/dist/components/LpTableOfContents.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +1 -1
- package/dist/components/LpTabs.vue.js +15 -6
- package/dist/components/LpTagsInput.vue.d.ts +6 -0
- package/dist/components/LpTagsInput.vue.js +1 -1
- package/dist/components/LpTextarea.vue.d.ts +8 -1
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeStudio.vue.js +1 -1
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTimeField.vue.d.ts +6 -0
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpTour.vue.js +1 -1
- package/dist/components/LpTruncate.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.d.ts +18 -9
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/MenuTitle.vue.js +1 -1
- package/dist/components/OptionContent.vue.js +1 -1
- package/dist/components/PalettePreviewBoundary.vue.d.ts +13 -0
- package/dist/components/PalettePreviewBoundary.vue.js +4 -0
- package/dist/components/PickerField.vue.d.ts +5 -3
- package/dist/components/PickerField.vue.js +1 -1
- package/dist/components/RevealToggle.vue.js +1 -1
- package/dist/components/TourCard.vue.js +1 -1
- package/dist/components/commandPalette.d.ts +33 -0
- package/dist/components/commandPalette.js +21 -1
- package/dist/components/commandSearch.d.ts +5 -0
- package/dist/components/commandSearch.js +22 -2
- package/dist/components/format.d.ts +7 -0
- package/dist/components/format.js +4 -0
- package/dist/components/menuWhen.js +1 -1
- package/dist/components/uptimeBar.d.ts +45 -0
- package/dist/components/uptimeBar.js +51 -0
- package/dist/composables/scroll.d.ts +5 -0
- package/dist/composables/scroll.js +7 -0
- package/dist/composables/useField.d.ts +9 -0
- package/dist/composables/useField.js +24 -1
- package/dist/composables/useForm.js +13 -4
- package/dist/composables/usePaletteCommands.d.ts +61 -0
- package/dist/composables/usePaletteCommands.js +220 -0
- package/dist/composables/useReveal.js +21 -0
- package/dist/composables/useView.js +9 -2
- package/dist/icons-all.js +1 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +103 -87
- package/dist/theme/palette.d.ts +9 -0
- package/dist/theme/studio.d.ts +12 -1
- package/package.json +4 -1
- package/src/component-names.ts +2 -0
- package/src/components/ContextMenuItems.vue +5 -2
- package/src/components/LpAutocomplete.vue +6 -1
- package/src/components/LpButton.vue +5 -1
- package/src/components/LpChart.vue +19 -17
- package/src/components/LpCheckbox.vue +7 -2
- package/src/components/LpCommandPalette.vue +263 -29
- package/src/components/LpCommandScope.vue +35 -0
- package/src/components/LpContextMenu.vue +17 -5
- package/src/components/LpDataList.vue +2 -2
- package/src/components/LpDatePicker.vue +2 -0
- package/src/components/LpDateRangePicker.vue +2 -0
- package/src/components/LpDisclosure.vue +5 -1
- package/src/components/LpDropdownMenu.vue +14 -3
- package/src/components/LpEditable.vue +3 -1
- package/src/components/LpFormField.vue +3 -0
- package/src/components/LpInput.vue +6 -1
- package/src/components/LpNumberField.vue +6 -1
- package/src/components/LpNumberFlow.vue +12 -2
- package/src/components/LpPaletteFacts.vue +11 -6
- package/src/components/LpPaletteStats.vue +12 -5
- package/src/components/LpPaletteTheme.vue +48 -0
- package/src/components/LpPasswordInput.vue +13 -5
- package/src/components/LpPopover.vue +4 -0
- package/src/components/LpRadioGroup.vue +4 -1
- package/src/components/LpRangeSlider.vue +6 -1
- package/src/components/LpRepeater.vue +64 -13
- package/src/components/LpRollingText.vue +12 -1
- package/src/components/LpSecretField.vue +18 -8
- package/src/components/LpSegmented.vue +6 -1
- package/src/components/LpSelect.vue +44 -10
- package/src/components/LpSlider.vue +6 -1
- package/src/components/LpSwitch.vue +6 -1
- package/src/components/LpTable.vue +35 -2
- package/src/components/LpTableOfContents.vue +2 -4
- package/src/components/LpTabs.vue +7 -0
- package/src/components/LpTagsInput.vue +6 -1
- package/src/components/LpTextarea.vue +7 -2
- package/src/components/LpThemeStudio.vue +7 -5
- package/src/components/LpThemeSwitcher.vue +3 -0
- package/src/components/LpTimeField.vue +6 -1
- package/src/components/LpTooltip.vue +23 -1
- package/src/components/LpUptimeBar.vue +69 -41
- package/src/components/PalettePreviewBoundary.vue +5 -0
- package/src/components/PickerField.vue +5 -4
- package/src/components/commandPalette.ts +22 -1
- package/src/components/commandSearch.ts +23 -1
- package/src/components/format.ts +10 -0
- package/src/components/uptimeBar.ts +98 -0
- package/src/composables/scroll.ts +11 -0
- package/src/composables/useField.ts +33 -1
- package/src/composables/useForm.ts +14 -5
- package/src/composables/usePaletteCommands.ts +220 -0
- package/src/composables/useReveal.ts +29 -0
- package/src/composables/useView.ts +8 -1
- package/src/index.ts +18 -1
- package/src/kit-icons.ts +1 -1
- package/src/theme/palette.ts +26 -0
- package/src/theme/studio.ts +15 -8
- package/dist/assets/LpTabs-BJ-nrJuI.css +0 -18
- package/dist/chunks/LpRepeater-D0VjmD6y.js +0 -140
- package/dist/chunks/icons-4IywzkHk.js +0 -43
|
@@ -36,7 +36,11 @@ import {
|
|
|
36
36
|
DialogRoot,
|
|
37
37
|
DialogTitle,
|
|
38
38
|
} from "reka-ui"
|
|
39
|
-
import { computed, nextTick, onBeforeUnmount, ref, shallowRef, useId, watch } from "vue"
|
|
39
|
+
import { type Component, computed, h, nextTick, onBeforeUnmount, ref, shallowRef, useId, watch } from "vue"
|
|
40
|
+
import { mergePaletteCommands, suppressPaletteDiscovery, usePaletteScope } from "../composables/usePaletteCommands"
|
|
41
|
+
import { useToast } from "../composables/useToast"
|
|
42
|
+
import LpButton from "./LpButton.vue"
|
|
43
|
+
import PalettePreviewBoundary from "./PalettePreviewBoundary.vue"
|
|
40
44
|
import LpAvatar from "./LpAvatar.vue"
|
|
41
45
|
import LpIcon from "./LpIcon.vue"
|
|
42
46
|
import LpLoader from "./LpLoader.vue"
|
|
@@ -57,12 +61,14 @@ import {
|
|
|
57
61
|
highlightParts,
|
|
58
62
|
parseCommandQuery,
|
|
59
63
|
parsePaletteQuery,
|
|
60
|
-
|
|
64
|
+
searchCommandItems,
|
|
61
65
|
type HighlightPart,
|
|
62
66
|
type PaletteQuery,
|
|
63
67
|
} from "./commandSearch"
|
|
64
68
|
import {
|
|
65
69
|
commandHasPreview,
|
|
70
|
+
PALETTE_MODES,
|
|
71
|
+
type PaletteMode,
|
|
66
72
|
fallbackRows,
|
|
67
73
|
matchModePrefix,
|
|
68
74
|
modeChipLabel,
|
|
@@ -227,7 +233,23 @@ export interface Command {
|
|
|
227
233
|
* placeholder. For a whole class of rows, `hasPreview` says it once.
|
|
228
234
|
*/
|
|
229
235
|
noPreview?: boolean
|
|
230
|
-
|
|
236
|
+
/** Render a live editor or other rich preview without an application-wide slot. */
|
|
237
|
+
previewComponent?: Component
|
|
238
|
+
/** Enter or click focuses the preview editor, including on narrow screens. */
|
|
239
|
+
editInPreview?: boolean
|
|
240
|
+
/** Runs after the dialog releases its focus trap (e.g. focus the original field). */
|
|
241
|
+
afterClose?: () => void
|
|
242
|
+
prompt?: CommandPrompt
|
|
243
|
+
/** Called only when execution succeeded, with its result. */
|
|
244
|
+
undo?: (result: unknown) => unknown | Promise<unknown>
|
|
245
|
+
onSelect?: () => unknown | Promise<unknown>
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export interface CommandPrompt {
|
|
249
|
+
placeholder?: string
|
|
250
|
+
initial?: string
|
|
251
|
+
validate?: (value: string) => string | undefined | Promise<string | undefined>
|
|
252
|
+
submit: (value: string) => unknown | Promise<unknown>
|
|
231
253
|
}
|
|
232
254
|
|
|
233
255
|
/**
|
|
@@ -257,8 +279,17 @@ export type CommandFetcher = (
|
|
|
257
279
|
) => Command[] | Promise<Command[]>
|
|
258
280
|
|
|
259
281
|
/** Footer hints and the mode chip, spelled out so an app can translate them. */
|
|
260
|
-
export type PaletteLabels = {
|
|
282
|
+
export type PaletteLabels = Partial<Record<PaletteMode, string>> & {
|
|
261
283
|
navigate: string
|
|
284
|
+
pin?: string
|
|
285
|
+
unpin?: string
|
|
286
|
+
undo?: string
|
|
287
|
+
backToResults?: string
|
|
288
|
+
actionFailed?: string
|
|
289
|
+
submit?: string
|
|
290
|
+
running?: string
|
|
291
|
+
search?: string
|
|
292
|
+
pinned?: string
|
|
262
293
|
select: string
|
|
263
294
|
close: string
|
|
264
295
|
/** Hint for leaving a mode, shown only while inside one. */
|
|
@@ -292,7 +323,14 @@ export type PaletteLabels = {
|
|
|
292
323
|
const props = withDefaults(
|
|
293
324
|
defineProps<{
|
|
294
325
|
open?: boolean
|
|
295
|
-
commands
|
|
326
|
+
commands?: Command[]
|
|
327
|
+
/** Include live commands registered by enclosing LpCommandScope components. */
|
|
328
|
+
context?: boolean
|
|
329
|
+
/** Built-in sigils; people, channels and roles appear when a provider is supplied. */
|
|
330
|
+
modes?: Partial<Record<PaletteMode, Command[] | CommandFetcher>>
|
|
331
|
+
pinned?: RecentItem[]
|
|
332
|
+
pinnedKey?: string
|
|
333
|
+
pinnedLabel?: string
|
|
296
334
|
placeholder?: string
|
|
297
335
|
emptyText?: string
|
|
298
336
|
/** Bind a global ⌘K / Ctrl-K toggle. */
|
|
@@ -392,6 +430,8 @@ const props = withDefaults(
|
|
|
392
430
|
{
|
|
393
431
|
placeholder: "Type a command or search…",
|
|
394
432
|
emptyText: "No results",
|
|
433
|
+
commands: () => [],
|
|
434
|
+
context: true,
|
|
395
435
|
hotkey: true,
|
|
396
436
|
debounce: 250,
|
|
397
437
|
minChars: 1,
|
|
@@ -405,12 +445,49 @@ const props = withDefaults(
|
|
|
405
445
|
},
|
|
406
446
|
)
|
|
407
447
|
|
|
448
|
+
const paletteScope = usePaletteScope()
|
|
449
|
+
suppressPaletteDiscovery()
|
|
450
|
+
const baseCommands = computed(() => mergePaletteCommands(props.context ? paletteScope?.registry.commands.value ?? [] : [], props.commands))
|
|
451
|
+
const rootCommands = computed<Command[]>(() => mergePaletteCommands([
|
|
452
|
+
...baseCommands.value,
|
|
453
|
+
...Object.entries(PALETTE_MODES).flatMap(([key, definition]) => {
|
|
454
|
+
const name = key as PaletteMode
|
|
455
|
+
const children = props.modes?.[name] ?? (name === "commands" ? baseCommands.value.filter(c => !c.href && !c.children && !c.editInPreview)
|
|
456
|
+
: name === "help" ? [{ id: "palette/help/keys", label: "Keyboard navigation", editInPreview: true, previewComponent: { render: () => h("p", { class: "whitespace-pre-line text-sm leading-relaxed" }, "↑ ↓ Navigate results\nEnter Run or edit\nTab Result actions\nAlt+P Pin a result\nEscape Back or close") } }] : undefined)
|
|
457
|
+
return children ? [{ id: `palette/mode/${name}`, ...definition, label: text.value[name], children, group: text.value.search, remember: false }] : []
|
|
458
|
+
}),
|
|
459
|
+
], []))
|
|
460
|
+
const toast = useToast()
|
|
461
|
+
const editing = shallowRef<Command>()
|
|
462
|
+
let stopEditorFocus: (() => void) | undefined
|
|
463
|
+
const promptCommand = shallowRef<Command>()
|
|
464
|
+
const promptValue = ref("")
|
|
465
|
+
const promptError = ref("")
|
|
466
|
+
const promptValidation = useAsyncTask<string | undefined>()
|
|
467
|
+
const validating = promptValidation.pending
|
|
468
|
+
watch(promptCommand, () => promptValidation.cancel(), { flush: "sync" })
|
|
469
|
+
const executing = ref(false)
|
|
470
|
+
const actionError = ref("")
|
|
471
|
+
let executionEpoch = 0
|
|
472
|
+
let afterClose: (() => void) | undefined
|
|
473
|
+
const inputQuery = computed({ get: () => promptCommand.value ? promptValue.value : query.value, set: value => { if (promptCommand.value) { promptValidation.cancel(); promptValue.value = value; promptError.value = "" } else query.value = value } })
|
|
474
|
+
|
|
408
475
|
const frosted = useGlass(() => props.glass)
|
|
409
476
|
const contentRef = ref<{ $el: HTMLElement } | null>(null)
|
|
410
477
|
const { claim, release, layer } = useModalLayer({ element: () => contentRef.value?.$el })
|
|
411
478
|
|
|
412
479
|
const DEFAULT_LABELS: Required<PaletteLabels> = {
|
|
480
|
+
...Object.fromEntries(Object.entries(PALETTE_MODES).map(([key, mode]) => [key, mode.label])) as Record<PaletteMode, string>,
|
|
481
|
+
search: "Search",
|
|
482
|
+
pinned: "Pinned",
|
|
413
483
|
navigate: "navigate",
|
|
484
|
+
pin: "Pin",
|
|
485
|
+
unpin: "Unpin",
|
|
486
|
+
undo: "Undo",
|
|
487
|
+
backToResults: "Back to results",
|
|
488
|
+
actionFailed: "The action could not be completed.",
|
|
489
|
+
submit: "Submit",
|
|
490
|
+
running: "Running…",
|
|
414
491
|
select: "select",
|
|
415
492
|
close: "close",
|
|
416
493
|
back: "back",
|
|
@@ -429,11 +506,13 @@ const DEFAULT_LABELS: Required<PaletteLabels> = {
|
|
|
429
506
|
tab: "tab",
|
|
430
507
|
}
|
|
431
508
|
|
|
432
|
-
const text = useLabels("palette", DEFAULT_LABELS, () => props.labels)
|
|
509
|
+
const text = useLabels<Required<PaletteLabels>>("palette", DEFAULT_LABELS, () => props.labels)
|
|
510
|
+
const pinnedHeading = computed(() => props.pinnedLabel ?? text.value.pinned)
|
|
433
511
|
|
|
434
512
|
const emit = defineEmits<{
|
|
435
513
|
(e: "update:open", value: boolean): void
|
|
436
514
|
(e: "select", command: Command): void
|
|
515
|
+
(e: "update:pinned", items: RecentItem[]): void
|
|
437
516
|
/**
|
|
438
517
|
* A command ran — id first, because the common use is appending it to a
|
|
439
518
|
* stored recency list. Fires for leaf commands only: opening a mode is
|
|
@@ -519,6 +598,7 @@ const selectedActions = computed(() => actionsOf(flat.value[selectedIndex.value]
|
|
|
519
598
|
* actions it keeps that default.
|
|
520
599
|
*/
|
|
521
600
|
function onTab(event: KeyboardEvent) {
|
|
601
|
+
if (promptCommand.value) return
|
|
522
602
|
// Without actions Tab stays Tab, so the trail chips and a Retry button are
|
|
523
603
|
// still reachable from the keyboard.
|
|
524
604
|
if (event.shiftKey) return
|
|
@@ -527,10 +607,16 @@ function onTab(event: KeyboardEvent) {
|
|
|
527
607
|
if (!c || !actions.length) return
|
|
528
608
|
event.preventDefault()
|
|
529
609
|
event.stopPropagation()
|
|
610
|
+
openActions(c)
|
|
611
|
+
}
|
|
612
|
+
function openActions(c: Command) {
|
|
613
|
+
const actions = actionsOf(c)
|
|
614
|
+
if (!actions.length) return
|
|
530
615
|
const actionsMode: Command = { id: `${c.id}:actions`, label: c.label, icon: c.icon, children: actions }
|
|
531
616
|
syntheticModes.add(actionsMode)
|
|
532
617
|
hints.use(HINT_SIGNALS.paletteActions)
|
|
533
618
|
enter(actionsMode)
|
|
619
|
+
inputRef.value?.focus()
|
|
534
620
|
}
|
|
535
621
|
|
|
536
622
|
// A returning user who never pressed Tab gets the footer key lit up, nothing
|
|
@@ -551,7 +637,7 @@ const showActionsKey = computed(() => {
|
|
|
551
637
|
* `keepOpen` pick inside it shows its own effect.
|
|
552
638
|
*/
|
|
553
639
|
watch(
|
|
554
|
-
() =>
|
|
640
|
+
() => rootCommands.value,
|
|
555
641
|
(commands) => {
|
|
556
642
|
if (!trail.value.length) return
|
|
557
643
|
let level: Command[] = commands
|
|
@@ -671,7 +757,7 @@ function refresh() {
|
|
|
671
757
|
* next to "which service?" is how a palette gets someone to restart the wrong
|
|
672
758
|
* thing.
|
|
673
759
|
*/
|
|
674
|
-
const source = computed<Command[]>(() => (mode.value ? modeItems.value :
|
|
760
|
+
const source = computed<Command[]>(() => (mode.value ? modeItems.value : rootCommands.value))
|
|
675
761
|
|
|
676
762
|
/*
|
|
677
763
|
* Keyboard layouts typed through each other use a QWERTY ↔ Cyrillic map.
|
|
@@ -692,8 +778,7 @@ const local = computed<{ items: Command[]; terms: string[] }>(() => {
|
|
|
692
778
|
// Empty query: show everything EXCEPT hidden commands (they only surface once
|
|
693
779
|
// typed). With a query, hidden commands rank like any other.
|
|
694
780
|
if (!terms.include.length && !terms.exclude.length && !terms.groups.length) return { items: filtered, terms: [] }
|
|
695
|
-
|
|
696
|
-
return { items, terms: terms.include }
|
|
781
|
+
return searchCommandItems(filtered, terms)
|
|
697
782
|
})
|
|
698
783
|
|
|
699
784
|
const ranked = computed<Command[]>(() => local.value.items)
|
|
@@ -774,7 +859,7 @@ const recentItems = computed<Command[]>(() => {
|
|
|
774
859
|
if (mode.value || !recentList.value.length) return []
|
|
775
860
|
const terms = parseCommandQuery(query.value)
|
|
776
861
|
if (terms.include.length) return []
|
|
777
|
-
const found = resolveRecentItems(recentList.value,
|
|
862
|
+
const found = resolveRecentItems(recentList.value, rootCommands.value, {
|
|
778
863
|
limit: Number.POSITIVE_INFINITY,
|
|
779
864
|
resolve: props.resolveRecent,
|
|
780
865
|
fromEntry,
|
|
@@ -789,6 +874,26 @@ const recentItems = computed<Command[]>(() => {
|
|
|
789
874
|
return out
|
|
790
875
|
})
|
|
791
876
|
|
|
877
|
+
const storedPinned = usePersisted<unknown>(() => props.pinnedKey ?? (props.recentKey ? `${props.recentKey}:pinned` : undefined), [])
|
|
878
|
+
const pinList = computed(() => props.pinned ?? readRecent(storedPinned.value))
|
|
879
|
+
const pinResolved = computed(() => resolveRecentItems(pinList.value, rootCommands.value, { limit: Infinity, resolve: props.resolveRecent, fromEntry }))
|
|
880
|
+
const pinnedItems = computed(() => {
|
|
881
|
+
if (mode.value || parseCommandQuery(query.value).include.length) return []
|
|
882
|
+
return pinResolved.value.filter(({ command }) => recentVisible(command, parseCommandQuery(query.value), pinnedHeading.value)).map(({ command, path }) => { recentPath.set(command, path); return command })
|
|
883
|
+
})
|
|
884
|
+
function isPinned(command: Command) {
|
|
885
|
+
const path = recentPath.get(command) ?? trail.value.map(step => step.id)
|
|
886
|
+
return pinList.value.some(item => typeof item === "string" ? !path.length && item === command.id : item.id === command.id && item.path.join("\0") === path.join("\0"))
|
|
887
|
+
}
|
|
888
|
+
function togglePin(command: Command | undefined) {
|
|
889
|
+
if (!command || moreOf.has(command) || command.children || trail.value.some(step => syntheticModes.has(step))) return
|
|
890
|
+
const path = recentPath.get(command) ?? trail.value.map(step => step.id)
|
|
891
|
+
const next = isPinned(command) ? pinList.value.filter(item => typeof item === "string" ? item !== command.id : item.id !== command.id || item.path.join("\0") !== path.join("\0"))
|
|
892
|
+
: pushRecent(pinList.value, toRecentItem(command, path), Infinity)
|
|
893
|
+
if (props.pinned === undefined) storedPinned.value = next
|
|
894
|
+
emit("update:pinned", next)
|
|
895
|
+
}
|
|
896
|
+
|
|
792
897
|
// Group preserving first-seen order; ungrouped commands sit under "".
|
|
793
898
|
// Recent first (it answers "the thing I always run"), then the matched list,
|
|
794
899
|
// then whatever the provider found.
|
|
@@ -802,11 +907,13 @@ const grouped = computed<{ name: string; items: Command[] }[]>(() => {
|
|
|
802
907
|
}
|
|
803
908
|
map.get(group)!.push(c)
|
|
804
909
|
}
|
|
805
|
-
const
|
|
910
|
+
const pinned = pinnedItems.value
|
|
911
|
+
for (const c of pinned) push(c, pinnedHeading.value)
|
|
912
|
+
const recent = recentItems.value.filter(c => !pinned.some(p => p.id === c.id))
|
|
806
913
|
for (const c of recent) push(c, props.recentLabel)
|
|
807
914
|
// A recent entry is shown once: leaving the copy in its own group would put
|
|
808
915
|
// the same row on screen twice with nothing to tell them apart.
|
|
809
|
-
for (const c of ranked.value) if (!recent.includes(c)) push(c, c.group ?? "")
|
|
916
|
+
for (const c of ranked.value) if (!recent.includes(c) && !pinned.some(p => p.id === c.id)) push(c, c.group ?? "")
|
|
810
917
|
for (const c of fetched.value) push(c, c.group ?? "")
|
|
811
918
|
// Last, whatever their group: they are the way out when nothing above is it.
|
|
812
919
|
const tail = new Map<string, Command[]>()
|
|
@@ -869,6 +976,7 @@ const active = computed<Command | undefined>(() => {
|
|
|
869
976
|
const c = flat.value[selectedIndex.value]
|
|
870
977
|
return c && !moreOf.has(c) ? c : undefined
|
|
871
978
|
})
|
|
979
|
+
const canPin = computed(() => active.value && !active.value.children && !moreOf.has(active.value) && !trail.value.some(step => syntheticModes.has(step)))
|
|
872
980
|
const previewRef = ref<{ viewportEl: HTMLElement | null } | null>(null)
|
|
873
981
|
|
|
874
982
|
/*
|
|
@@ -877,7 +985,7 @@ const previewRef = ref<{ viewportEl: HTMLElement | null } | null>(null)
|
|
|
877
985
|
* decides what to show, so any highlighted row opens it.
|
|
878
986
|
*/
|
|
879
987
|
const previewable = computed(() =>
|
|
880
|
-
props.previewPane && commandHasPreview(active.value, { slot: !!slots.preview, hasPreview: props.hasPreview }),
|
|
988
|
+
(props.previewPane || !!editing.value) && commandHasPreview(active.value, { slot: !!slots.preview, hasPreview: props.hasPreview }),
|
|
881
989
|
)
|
|
882
990
|
|
|
883
991
|
/*
|
|
@@ -891,7 +999,7 @@ const PANE_LINGER_MS = 160
|
|
|
891
999
|
const paneCommand = shallowRef<Command | undefined>()
|
|
892
1000
|
let lingerTimer: ReturnType<typeof setTimeout> | undefined
|
|
893
1001
|
watch(
|
|
894
|
-
[active, previewable],
|
|
1002
|
+
[() => editing.value ?? active.value, previewable],
|
|
895
1003
|
([command, show]) => {
|
|
896
1004
|
clearTimeout(lingerTimer)
|
|
897
1005
|
if (show) {
|
|
@@ -905,7 +1013,7 @@ watch(
|
|
|
905
1013
|
)
|
|
906
1014
|
onBeforeUnmount(() => clearTimeout(lingerTimer))
|
|
907
1015
|
|
|
908
|
-
const paneOpen = computed(() => props.previewPane && !!paneCommand.value)
|
|
1016
|
+
const paneOpen = computed(() => !promptCommand.value && (!!editing.value || (props.previewPane && !!paneCommand.value)))
|
|
909
1017
|
|
|
910
1018
|
function paneHighlight(text: string): HighlightPart[] {
|
|
911
1019
|
return highlight(text, paneBody.value?.highlight)
|
|
@@ -1061,7 +1169,12 @@ function movePage(direction: -1 | 1) {
|
|
|
1061
1169
|
select(selectedIndex.value + direction * 8, true)
|
|
1062
1170
|
}
|
|
1063
1171
|
function onPaletteKeydown(event: KeyboardEvent) {
|
|
1064
|
-
if (event.isComposing) return
|
|
1172
|
+
if (event.isComposing || event.target !== inputRef.value || promptCommand.value) return
|
|
1173
|
+
if (event.altKey && event.key.toLowerCase() === "p") {
|
|
1174
|
+
event.preventDefault()
|
|
1175
|
+
togglePin(active.value)
|
|
1176
|
+
return
|
|
1177
|
+
}
|
|
1065
1178
|
if (!(["ArrowDown", "ArrowUp", "Home", "End", "PageDown", "PageUp"] as string[]).includes(event.key)) return
|
|
1066
1179
|
// Home and End belong to the caret while there is text to move it through.
|
|
1067
1180
|
const typing = event.target instanceof HTMLInputElement && event.target.value !== ""
|
|
@@ -1077,10 +1190,38 @@ function onPaletteKeydown(event: KeyboardEvent) {
|
|
|
1077
1190
|
function onPointerMove() {
|
|
1078
1191
|
keyboardSelection.value = false
|
|
1079
1192
|
}
|
|
1193
|
+
function onCloseAutoFocus(event: Event) {
|
|
1194
|
+
if (!afterClose) return
|
|
1195
|
+
event.preventDefault()
|
|
1196
|
+
const focus = afterClose
|
|
1197
|
+
afterClose = undefined
|
|
1198
|
+
requestAnimationFrame(focus)
|
|
1199
|
+
}
|
|
1200
|
+
async function focusEditor() {
|
|
1201
|
+
stopEditorFocus?.()
|
|
1202
|
+
await nextTick()
|
|
1203
|
+
if (!editing.value) return
|
|
1204
|
+
const root = previewRef.value?.viewportEl
|
|
1205
|
+
if (!root) return
|
|
1206
|
+
const focus = () => {
|
|
1207
|
+
const body = root.querySelector("[data-lp-command-preview]")
|
|
1208
|
+
const field = body?.querySelector<HTMLElement>('input:not([type="hidden"]):not([disabled]):not([tabindex="-1"]):not([aria-hidden="true"]),textarea:not([disabled]):not([tabindex="-1"]),select:not([disabled]),[contenteditable="true"]')
|
|
1209
|
+
?? body?.querySelector<HTMLElement>('button:not([disabled]):not([tabindex="-1"]),[tabindex="0"]')
|
|
1210
|
+
field?.focus()
|
|
1211
|
+
return !!field
|
|
1212
|
+
}
|
|
1213
|
+
if (focus()) return
|
|
1214
|
+
const observer = new MutationObserver(() => { if (focus()) { observer.disconnect(); stopEditorFocus = undefined } })
|
|
1215
|
+
observer.observe(root, { childList: true, subtree: true })
|
|
1216
|
+
stopEditorFocus = () => { observer.disconnect(); stopEditorFocus = undefined }
|
|
1217
|
+
}
|
|
1218
|
+
watch([editing, () => props.open], () => { if (!editing.value || !props.open) stopEditorFocus?.() })
|
|
1219
|
+
onBeforeUnmount(() => stopEditorFocus?.())
|
|
1080
1220
|
function onPointerEnter(c: Command) {
|
|
1081
1221
|
if (!keyboardSelection.value) select(flatIndexOf(c))
|
|
1082
1222
|
}
|
|
1083
1223
|
function runSelected(event?: KeyboardEvent) {
|
|
1224
|
+
if (promptCommand.value) { void submitPrompt(); return }
|
|
1084
1225
|
const c = flat.value[selectedIndex.value]
|
|
1085
1226
|
if (c?.href && !c.disabled && (event?.ctrlKey || event?.metaKey)) {
|
|
1086
1227
|
openInNewTab(c)
|
|
@@ -1108,8 +1249,8 @@ function onRowClick(event: MouseEvent, c: Command) {
|
|
|
1108
1249
|
run(c)
|
|
1109
1250
|
}
|
|
1110
1251
|
|
|
1111
|
-
function run(row: Command | undefined) {
|
|
1112
|
-
if (!row || row.disabled) return
|
|
1252
|
+
async function run(row: Command | undefined) {
|
|
1253
|
+
if (!row || row.disabled || executing.value) return
|
|
1113
1254
|
const c = fallbackOf.get(row) ?? row
|
|
1114
1255
|
// The highlight stays on the same index, which is now the first row that
|
|
1115
1256
|
// was hidden: the arrows carry on from where the fold was.
|
|
@@ -1120,8 +1261,20 @@ function run(row: Command | undefined) {
|
|
|
1120
1261
|
inputRef.value?.focus()
|
|
1121
1262
|
return
|
|
1122
1263
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1264
|
+
if (c.editInPreview && c.previewComponent) {
|
|
1265
|
+
editing.value = c
|
|
1266
|
+
paneCommand.value = c
|
|
1267
|
+
await focusEditor()
|
|
1268
|
+
return
|
|
1269
|
+
}
|
|
1270
|
+
if (c.prompt) {
|
|
1271
|
+
promptCommand.value = c
|
|
1272
|
+
promptValue.value = c.prompt.initial ?? ""
|
|
1273
|
+
promptError.value = ""
|
|
1274
|
+
inputRef.value?.focus()
|
|
1275
|
+
return
|
|
1276
|
+
}
|
|
1277
|
+
if (!await execute(c, () => c.onSelect?.())) return
|
|
1125
1278
|
if (!c.onSelect && c.href && !c.children) window.location.assign(c.href)
|
|
1126
1279
|
// A command with children drills in instead of closing, and is navigation
|
|
1127
1280
|
// rather than a pick — so it stays out of the recency history.
|
|
@@ -1145,16 +1298,53 @@ function run(row: Command | undefined) {
|
|
|
1145
1298
|
inputRef.value?.focus()
|
|
1146
1299
|
return
|
|
1147
1300
|
}
|
|
1301
|
+
afterClose = c.afterClose
|
|
1148
1302
|
emit("update:open", false)
|
|
1149
1303
|
}
|
|
1150
1304
|
|
|
1305
|
+
async function execute(command: Command, action: () => unknown | Promise<unknown>): Promise<boolean> {
|
|
1306
|
+
if (executing.value) return false
|
|
1307
|
+
executing.value = true
|
|
1308
|
+
actionError.value = ""
|
|
1309
|
+
const epoch = executionEpoch
|
|
1310
|
+
try {
|
|
1311
|
+
emit("select", command)
|
|
1312
|
+
const result = await action()
|
|
1313
|
+
if (command.undo) toast.success(command.label, { duration: 8000, actions: [{ label: text.value.undo, onClick: async () => { await command.undo!(result) } }] })
|
|
1314
|
+
return epoch === executionEpoch
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
if (epoch === executionEpoch) actionError.value = error instanceof Error ? error.message : text.value.actionFailed
|
|
1317
|
+
return false
|
|
1318
|
+
} finally { executing.value = false }
|
|
1319
|
+
}
|
|
1320
|
+
function submitPrompt() {
|
|
1321
|
+
const command = promptCommand.value
|
|
1322
|
+
if (!command?.prompt || executing.value || validating.value) return
|
|
1323
|
+
const value = promptValue.value
|
|
1324
|
+
promptValidation.run(() => command.prompt!.validate?.(value), {
|
|
1325
|
+
apply: error => { void completePrompt(command, value, error) },
|
|
1326
|
+
fail: error => { promptError.value = error instanceof Error ? error.message : text.value.actionFailed },
|
|
1327
|
+
})
|
|
1328
|
+
}
|
|
1329
|
+
async function completePrompt(command: Command, value: string, error: string | undefined) {
|
|
1330
|
+
if (promptCommand.value !== command || promptValue.value !== value) return
|
|
1331
|
+
promptError.value = error ?? ""
|
|
1332
|
+
if (promptError.value || !await execute(command, () => command.prompt!.submit(value))) return
|
|
1333
|
+
remember(command)
|
|
1334
|
+
emit("run", command.id, command)
|
|
1335
|
+
if (promptCommand.value !== command) return
|
|
1336
|
+
promptCommand.value = undefined
|
|
1337
|
+
afterClose = command.afterClose
|
|
1338
|
+
if (!command.keepOpen) emit("update:open", false)
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1151
1341
|
/*
|
|
1152
1342
|
* Backspace on an empty query leaves the mode. Mirrors how the Slack and
|
|
1153
1343
|
* Linear palettes behave, and means the way out is the key already under the
|
|
1154
1344
|
* finger — Escape stays the way out of the palette itself.
|
|
1155
1345
|
*/
|
|
1156
1346
|
function onBackspace(event: KeyboardEvent) {
|
|
1157
|
-
if (query.value) return
|
|
1347
|
+
if (promptCommand.value || query.value) return
|
|
1158
1348
|
if (back()) event.preventDefault()
|
|
1159
1349
|
}
|
|
1160
1350
|
|
|
@@ -1165,6 +1355,14 @@ function onBackspace(event: KeyboardEvent) {
|
|
|
1165
1355
|
* (stopPropagation) only while there is a mode to pop.
|
|
1166
1356
|
*/
|
|
1167
1357
|
function onEscape(event: KeyboardEvent) {
|
|
1358
|
+
if (promptCommand.value || editing.value) {
|
|
1359
|
+
event.preventDefault()
|
|
1360
|
+
event.stopPropagation()
|
|
1361
|
+
promptCommand.value = undefined
|
|
1362
|
+
editing.value = undefined
|
|
1363
|
+
inputRef.value?.focus()
|
|
1364
|
+
return
|
|
1365
|
+
}
|
|
1168
1366
|
if (!trail.value.length) return
|
|
1169
1367
|
event.preventDefault()
|
|
1170
1368
|
event.stopPropagation()
|
|
@@ -1187,6 +1385,11 @@ watch(
|
|
|
1187
1385
|
claim()
|
|
1188
1386
|
hints.use(HINT_SIGNALS.palette)
|
|
1189
1387
|
} else release()
|
|
1388
|
+
executionEpoch++
|
|
1389
|
+
editing.value = undefined
|
|
1390
|
+
promptCommand.value = undefined
|
|
1391
|
+
promptError.value = ""
|
|
1392
|
+
actionError.value = ""
|
|
1190
1393
|
cancel()
|
|
1191
1394
|
query.value = ""
|
|
1192
1395
|
select(0)
|
|
@@ -1242,12 +1445,14 @@ const guardOutside = useOutsideGuard()
|
|
|
1242
1445
|
/>
|
|
1243
1446
|
<DialogContent
|
|
1244
1447
|
@interact-outside="guardOutside"
|
|
1245
|
-
@escape-key-down="guardOutside"
|
|
1448
|
+
@escape-key-down="event => { guardOutside(event); if (!event.defaultPrevented) onEscape(event) }"
|
|
1449
|
+
@close-auto-focus="onCloseAutoFocus"
|
|
1246
1450
|
ref="contentRef"
|
|
1247
1451
|
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]"
|
|
1248
1452
|
:class="[frosted ? (layer.nested ? 'lp-glass-overlay lp-glass-overlay--nested' : 'lp-glass-overlay') : 'bg-surface-raised', previewPane && 'lp-cmd-split']"
|
|
1249
1453
|
:style="{ zIndex: layer.panel }"
|
|
1250
1454
|
:data-pane="paneOpen ? 'open' : undefined"
|
|
1455
|
+
:data-editing="editing ? '' : undefined"
|
|
1251
1456
|
@open-auto-focus.prevent
|
|
1252
1457
|
@keydown.capture="onPaletteKeydown"
|
|
1253
1458
|
>
|
|
@@ -1259,7 +1464,7 @@ const guardOutside = useOutsideGuard()
|
|
|
1259
1464
|
to its left. -->
|
|
1260
1465
|
<div class="flex shrink-0 items-center gap-2.5 border-b border-line px-4">
|
|
1261
1466
|
<LpIcon
|
|
1262
|
-
v-if="!searching"
|
|
1467
|
+
v-if="!searching && !executing && !validating"
|
|
1263
1468
|
name="lucide:search"
|
|
1264
1469
|
:size="18"
|
|
1265
1470
|
class="shrink-0 text-muted"
|
|
@@ -1270,7 +1475,7 @@ const guardOutside = useOutsideGuard()
|
|
|
1270
1475
|
v-else
|
|
1271
1476
|
size="inline"
|
|
1272
1477
|
:delay="0"
|
|
1273
|
-
:labels="{ loading: text.searching }"
|
|
1478
|
+
:labels="{ loading: executing || validating ? text.running : text.searching }"
|
|
1274
1479
|
class="shrink-0 text-[18px] text-muted"
|
|
1275
1480
|
/>
|
|
1276
1481
|
|
|
@@ -1289,14 +1494,16 @@ const guardOutside = useOutsideGuard()
|
|
|
1289
1494
|
|
|
1290
1495
|
<input
|
|
1291
1496
|
ref="inputRef"
|
|
1292
|
-
v-model="
|
|
1497
|
+
v-model="inputQuery"
|
|
1498
|
+
:readonly="executing && !!promptCommand"
|
|
1499
|
+
@focus="editing = undefined"
|
|
1293
1500
|
type="text"
|
|
1294
1501
|
role="combobox"
|
|
1295
1502
|
aria-autocomplete="list"
|
|
1296
1503
|
aria-expanded="true"
|
|
1297
1504
|
:aria-controls="listId"
|
|
1298
1505
|
:aria-activedescendant="activeId"
|
|
1299
|
-
:placeholder="mode?.childPlaceholder ?? placeholder"
|
|
1506
|
+
:placeholder="promptCommand?.prompt?.placeholder ?? mode?.childPlaceholder ?? placeholder"
|
|
1300
1507
|
class="h-12 min-w-0 flex-1 bg-transparent text-sm text-ink outline-none placeholder:text-muted"
|
|
1301
1508
|
@keydown.enter.prevent="runSelected"
|
|
1302
1509
|
@keydown.backspace="onBackspace"
|
|
@@ -1306,9 +1513,19 @@ const guardOutside = useOutsideGuard()
|
|
|
1306
1513
|
<kbd class="shrink-0 rounded-small border border-line px-1.5 py-0.5 text-[10px] text-muted">{{ text.esc }}</kbd>
|
|
1307
1514
|
</div>
|
|
1308
1515
|
|
|
1516
|
+
<div v-if="promptCommand" class="flex flex-col gap-3 p-4">
|
|
1517
|
+
<p class="text-sm font-semibold">{{ promptCommand.label }}</p>
|
|
1518
|
+
<p v-if="promptError" role="alert" class="text-sm text-danger">{{ promptError }}</p>
|
|
1519
|
+
<div class="flex gap-2"><LpButton :loading="executing || validating" @click="submitPrompt">{{ text.submit }}</LpButton><LpButton variant="ghost" @click="promptCommand = undefined; inputRef?.focus()">{{ text.backToResults }}</LpButton></div>
|
|
1520
|
+
</div>
|
|
1521
|
+
<p v-if="actionError" role="alert" class="px-4 py-2 text-sm text-danger">{{ actionError }}</p>
|
|
1522
|
+
<div v-if="active && (canPin || selectedActions.length) && !promptCommand" class="flex items-center justify-end gap-2 border-b border-line px-3 py-1">
|
|
1523
|
+
<LpButton v-if="selectedActions.length" size="xs" variant="ghost" @click="openActions(active)">{{ text.actions }} <span class="text-muted">Tab</span></LpButton>
|
|
1524
|
+
<LpButton v-if="canPin" size="xs" variant="ghost" :aria-label="isPinned(active) ? text.unpin : text.pin" :aria-pressed="isPinned(active)" @click="togglePin(active)"><LpIcon name="lucide:star" :size="14" />{{ isPinned(active) ? text.unpin : text.pin }} <span class="text-muted">Alt+P</span></LpButton>
|
|
1525
|
+
</div>
|
|
1309
1526
|
<!-- Results -->
|
|
1310
1527
|
<div class="flex min-h-0 flex-1" @pointermove="onPointerMove">
|
|
1311
|
-
<LpScrollArea ref="scrollRef" class="lp-cmd-list min-h-0 min-w-0 flex-1" content-class="p-2">
|
|
1528
|
+
<LpScrollArea v-show="!editing && !promptCommand" ref="scrollRef" class="lp-cmd-list min-h-0 min-w-0 flex-1" content-class="p-2">
|
|
1312
1529
|
<!-- While a request is out there is nothing to say yet: "no results"
|
|
1313
1530
|
under a spinner reads as an answer, and it is not one. -->
|
|
1314
1531
|
<p v-if="flat.length === 0 && !failed" class="px-2 py-10 text-center text-sm text-muted">
|
|
@@ -1441,13 +1658,18 @@ const guardOutside = useOutsideGuard()
|
|
|
1441
1658
|
<Transition name="lp-cmd-pane">
|
|
1442
1659
|
<aside
|
|
1443
1660
|
v-if="paneOpen"
|
|
1444
|
-
class="lp-cmd-pane
|
|
1661
|
+
class="lp-cmd-pane min-h-0 flex-col"
|
|
1662
|
+
:class="editing ? 'flex min-w-0 flex-1' : 'hidden shrink-0 border-l border-line md:flex'"
|
|
1445
1663
|
>
|
|
1446
1664
|
<LpScrollArea ref="previewRef" class="min-h-0 flex-1" content-class="px-5 py-4">
|
|
1447
1665
|
<!-- Keyed by the row, so moving the highlight replays the
|
|
1448
1666
|
entrance rather than swapping text in place. -->
|
|
1449
1667
|
<div v-if="paneBody" :key="paneBody.id">
|
|
1668
|
+
<LpButton v-if="editing" size="sm" variant="ghost" class="mb-3" @click="editing = undefined; inputRef?.focus()">{{ text.backToResults }}</LpButton>
|
|
1669
|
+
<PalettePreviewBoundary>
|
|
1450
1670
|
<slot name="preview" :command="paneBody" :highlight="paneHighlight" :query="paneQuery">
|
|
1671
|
+
<component v-if="paneBody.previewComponent" :is="paneBody.previewComponent" />
|
|
1672
|
+
<template v-else>
|
|
1451
1673
|
<div class="lp-cmd-para flex items-center gap-2 text-ink">
|
|
1452
1674
|
<LpAvatar
|
|
1453
1675
|
v-if="imageOf(paneBody)"
|
|
@@ -1477,7 +1699,12 @@ const guardOutside = useOutsideGuard()
|
|
|
1477
1699
|
<template v-else>{{ p.text }}</template>
|
|
1478
1700
|
</template>
|
|
1479
1701
|
</p>
|
|
1702
|
+
</template>
|
|
1480
1703
|
</slot>
|
|
1704
|
+
</PalettePreviewBoundary>
|
|
1705
|
+
<div v-if="actionsOf(paneBody).length" class="mt-4 flex flex-wrap gap-2 border-t border-line pt-3">
|
|
1706
|
+
<LpButton v-for="action in actionsOf(paneBody)" :key="action.id" size="sm" variant="outline" :disabled="action.disabled || executing" @click="run({ ...action, remember: false })">{{ action.label }}</LpButton>
|
|
1707
|
+
</div>
|
|
1481
1708
|
</div>
|
|
1482
1709
|
</LpScrollArea>
|
|
1483
1710
|
</aside>
|
|
@@ -1556,6 +1783,13 @@ const guardOutside = useOutsideGuard()
|
|
|
1556
1783
|
.lp-cmd-pane {
|
|
1557
1784
|
width: var(--lp-cmd-pane);
|
|
1558
1785
|
}
|
|
1786
|
+
.lp-cmd-dialog[data-editing] .lp-cmd-pane {
|
|
1787
|
+
width: 100%;
|
|
1788
|
+
}
|
|
1789
|
+
.lp-cmd-dialog[data-editing] {
|
|
1790
|
+
width: var(--lp-cmd-list);
|
|
1791
|
+
min-height: 0;
|
|
1792
|
+
}
|
|
1559
1793
|
/*
|
|
1560
1794
|
* The pane rides in from the right edge while the dialog opens up to it, and
|
|
1561
1795
|
* leaves the same way, for as long as the dialog takes to close over it.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { bindPaletteScopeElement, createPaletteRegistry, providePaletteScope, usePaletteCommands, usePaletteScope, type PaletteScope } from "../composables/usePaletteCommands"
|
|
3
|
+
import type { ComponentPublicInstance } from "vue"
|
|
4
|
+
import type { Command } from "./LpCommandPalette.vue"
|
|
5
|
+
import { usePaletteTheme } from "../theme/palette"
|
|
6
|
+
import type { ThemeStudio } from "../theme/studio"
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(defineProps<{
|
|
9
|
+
id: string
|
|
10
|
+
label?: string
|
|
11
|
+
enabled?: boolean
|
|
12
|
+
discover?: boolean
|
|
13
|
+
/** Start a separate registry, e.g. an embedded demo inside an application's palette scope. */
|
|
14
|
+
isolated?: boolean
|
|
15
|
+
theme?: ThemeStudio
|
|
16
|
+
commands?: Command[]
|
|
17
|
+
}>(), { enabled: true, discover: true })
|
|
18
|
+
const parent = usePaletteScope()
|
|
19
|
+
const scope: PaletteScope = {
|
|
20
|
+
registry: !props.isolated && parent ? parent.registry : createPaletteRegistry(),
|
|
21
|
+
get id() { return parent && !props.isolated ? `${parent.id}/${props.id}` : props.id },
|
|
22
|
+
get label() { return props.label ?? props.id },
|
|
23
|
+
depth: parent && !props.isolated ? parent.depth + 1 : 0,
|
|
24
|
+
get enabled() { return props.enabled && (props.isolated || !parent || parent.enabled) },
|
|
25
|
+
get discover() { return props.discover },
|
|
26
|
+
}
|
|
27
|
+
providePaletteScope(scope)
|
|
28
|
+
usePaletteTheme(() => props.theme, { scope })
|
|
29
|
+
usePaletteCommands(() => props.commands ?? [], { scope })
|
|
30
|
+
function bind(element: Element | ComponentPublicInstance | null) {
|
|
31
|
+
if (element && "nodeType" in element) bindPaletteScopeElement(element as Element, scope)
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template><div :ref="bind" data-lp-command-scope class="contents"><slot /></div></template>
|