@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { usePaletteTheme } from "../theme/palette"
|
|
2
3
|
/*
|
|
3
4
|
* The theme, tuned in place: a base theme, any brand colour, corners, control
|
|
4
5
|
* size and motion speed, all applied live, and the result ready to take away
|
|
@@ -102,8 +103,9 @@ const TILE =
|
|
|
102
103
|
"flex min-w-0 flex-col items-center gap-1 rounded-control p-1.5 text-[11px] text-muted transition-colors duration-(--duration-fast) hover:bg-surface-soft hover:text-ink"
|
|
103
104
|
const SWATCH = "relative size-8 overflow-hidden rounded-full ring-2 transition-[box-shadow] duration-(--duration-fast)"
|
|
104
105
|
|
|
105
|
-
const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K) =>
|
|
106
|
-
|
|
106
|
+
const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K, value: string) => studio.tune({ [key]: value } as Partial<StudioState>)
|
|
107
|
+
|
|
108
|
+
usePaletteTheme(() => studio ?? undefined)
|
|
107
109
|
</script>
|
|
108
110
|
|
|
109
111
|
<template>
|
|
@@ -171,7 +173,7 @@ const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K) =>
|
|
|
171
173
|
:model-value="state.corners"
|
|
172
174
|
size="sm"
|
|
173
175
|
:options="[{ value: 'sharp', label: text.sharp }, { value: 'default', label: text.default }, { value: 'round', label: text.round }]"
|
|
174
|
-
@update:model-value="tuneTo('corners')"
|
|
176
|
+
@update:model-value="tuneTo('corners', $event)"
|
|
175
177
|
/>
|
|
176
178
|
</LpThemeStudioSection>
|
|
177
179
|
|
|
@@ -180,7 +182,7 @@ const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K) =>
|
|
|
180
182
|
:model-value="state.size"
|
|
181
183
|
size="sm"
|
|
182
184
|
:options="[{ value: 'compact', label: text.compact }, { value: 'default', label: text.default }, { value: 'comfortable', label: text.comfortable }]"
|
|
183
|
-
@update:model-value="tuneTo('size')"
|
|
185
|
+
@update:model-value="tuneTo('size', $event)"
|
|
184
186
|
/>
|
|
185
187
|
</LpThemeStudioSection>
|
|
186
188
|
|
|
@@ -189,7 +191,7 @@ const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K) =>
|
|
|
189
191
|
:model-value="state.motion"
|
|
190
192
|
size="sm"
|
|
191
193
|
:options="[{ value: 'fast', label: text.fast }, { value: 'normal', label: text.normal }, { value: 'slow', label: text.slow }]"
|
|
192
|
-
@update:model-value="tuneTo('motion')"
|
|
194
|
+
@update:model-value="tuneTo('motion', $event)"
|
|
193
195
|
/>
|
|
194
196
|
</LpThemeStudioSection>
|
|
195
197
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { usePaletteTheme } from "../theme/palette"
|
|
2
3
|
/*
|
|
3
4
|
* Theme switcher. The kit convention for theming, so consumers stop hand-rolling
|
|
4
5
|
* their own. Default behaviour mirrors LeaveHosting: LEFT-click cycles to the
|
|
@@ -298,6 +299,8 @@ const labelName = computed(() =>
|
|
|
298
299
|
const triggerLabel = computed(
|
|
299
300
|
() => props["aria-label"] ?? text.value.trigger.replace("{name}", labelName.value),
|
|
300
301
|
)
|
|
302
|
+
|
|
303
|
+
usePaletteTheme(() => tuner ?? undefined)
|
|
301
304
|
</script>
|
|
302
305
|
|
|
303
306
|
<template>
|
|
@@ -6,6 +6,7 @@ export default { inheritAttrs: false }
|
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<script setup lang="ts">
|
|
9
|
+
import { usePaletteControl, type PaletteControlOptions } from "../composables/usePaletteCommands"
|
|
9
10
|
/*
|
|
10
11
|
* Time-of-day field: hour and minute segments, typed or stepped with the arrow
|
|
11
12
|
* keys (reka TimeField). The model is "HH:mm" (v-model), the same shape a
|
|
@@ -26,6 +27,8 @@ import LpIcon from "./LpIcon.vue"
|
|
|
26
27
|
|
|
27
28
|
const props = withDefaults(
|
|
28
29
|
defineProps<{
|
|
30
|
+
/** Contextual palette metadata; false excludes this control. */
|
|
31
|
+
palette?: PaletteControlOptions
|
|
29
32
|
/** Selected time "HH:mm" (v-model). */
|
|
30
33
|
modelValue?: string
|
|
31
34
|
disabled?: boolean
|
|
@@ -38,7 +41,7 @@ const props = withDefaults(
|
|
|
38
41
|
/** Show the clock icon at the start of the field. */
|
|
39
42
|
icon?: boolean
|
|
40
43
|
}>(),
|
|
41
|
-
{ size: "md", hourCycle: 24, icon: true },
|
|
44
|
+
{ palette: undefined, size: "md", hourCycle: 24, icon: true },
|
|
42
45
|
)
|
|
43
46
|
|
|
44
47
|
const emit = defineEmits<{
|
|
@@ -84,6 +87,8 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
|
84
87
|
field.value.invalid ? "border-danger focus-within:ring-danger-soft" : "border-line focus-within:border-brand",
|
|
85
88
|
].join(" "),
|
|
86
89
|
)
|
|
90
|
+
|
|
91
|
+
usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.value.invalid })
|
|
87
92
|
</script>
|
|
88
93
|
|
|
89
94
|
<template>
|
|
@@ -120,6 +120,27 @@ const PassThrough: FunctionalComponent = (_, { slots }) => slots.default?.()
|
|
|
120
120
|
|
|
121
121
|
const target = { x: 0, y: 0 }
|
|
122
122
|
const offset = ref({ x: 0, y: 0 })
|
|
123
|
+
const tipBody = ref<HTMLElement | null>(null)
|
|
124
|
+
|
|
125
|
+
/* reka's own collision padding, which the follow offset would otherwise undo. */
|
|
126
|
+
const EDGE = 8
|
|
127
|
+
|
|
128
|
+
/*
|
|
129
|
+
* reka keeps the tip inside the window for the box it was placed against; the
|
|
130
|
+
* follow offset is added after that, so near the right edge a wide tip (an
|
|
131
|
+
* uptime slice with its breakdown) slid half off screen. The offset is held to
|
|
132
|
+
* the room the window has on each side of where reka put the tip.
|
|
133
|
+
*/
|
|
134
|
+
function inView(x: number): number {
|
|
135
|
+
const panel = tipBody.value?.parentElement
|
|
136
|
+
if (!panel) return x
|
|
137
|
+
const rect = panel.getBoundingClientRect()
|
|
138
|
+
const placed = rect.left - offset.value.x
|
|
139
|
+
const width = panel.ownerDocument.defaultView?.innerWidth ?? window.innerWidth
|
|
140
|
+
const min = EDGE - placed
|
|
141
|
+
const max = width - EDGE - rect.width - placed
|
|
142
|
+
return max < min ? x : Math.min(max, Math.max(min, x))
|
|
143
|
+
}
|
|
123
144
|
|
|
124
145
|
/*
|
|
125
146
|
* The spring constants come from the kit's shared "float" feel — the same ones
|
|
@@ -172,6 +193,7 @@ const onPointerMove = (e: PointerEvent) => {
|
|
|
172
193
|
const half = props.side === "top" || props.side === "bottom" ? rect.height / 2 : 0
|
|
173
194
|
const dir = props.side === "bottom" ? -1 : 1
|
|
174
195
|
target.y = e.clientY - (rect.top + rect.height / 2) + half * dir
|
|
196
|
+
target.x = inView(target.x)
|
|
175
197
|
// Without motion the tip goes straight to the reading instead of trailing.
|
|
176
198
|
if (prefersReducedMotion()) {
|
|
177
199
|
offset.value = { x: target.x, y: target.y }
|
|
@@ -272,7 +294,7 @@ const guardOutside = useOutsideGuard()
|
|
|
272
294
|
]"
|
|
273
295
|
:style="followCursor ? { translate: `${offset.x}px ${offset.y}px` } : undefined"
|
|
274
296
|
>
|
|
275
|
-
<slot name="content">{{ content }}</slot>
|
|
297
|
+
<div ref="tipBody"><slot name="content">{{ content }}</slot></div>
|
|
276
298
|
</TooltipContent>
|
|
277
299
|
</TooltipPortal>
|
|
278
300
|
</TooltipRoot>
|
|
@@ -3,27 +3,24 @@
|
|
|
3
3
|
// appended to it, so opt out of the automatic pass-through that would add it
|
|
4
4
|
// a second time, unmerged.
|
|
5
5
|
export default { inheritAttrs: false }
|
|
6
|
+
export type { UptimeEvent, UptimeSegment, UptimeStatus } from "./uptimeBar"
|
|
6
7
|
</script>
|
|
7
8
|
|
|
8
9
|
<script setup lang="ts">
|
|
9
10
|
import { computed, ref } from "vue"
|
|
10
11
|
import LpNumberFlow from "./LpNumberFlow.vue"
|
|
11
12
|
import LpRollingText from "./LpRollingText.vue"
|
|
13
|
+
import LpShift from "./LpShift.vue"
|
|
12
14
|
import LpSkeleton from "./LpSkeleton.vue"
|
|
13
15
|
import LpSwap from "./LpSwap.vue"
|
|
14
16
|
import LpTooltip from "./LpTooltip.vue"
|
|
15
17
|
import { TONES } from "./tone"
|
|
16
18
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
17
19
|
import { useLabels } from "../composables/useLpConfig"
|
|
20
|
+
import { useLocale } from "../composables/useLocale"
|
|
21
|
+
import { DEFAULT_UNIT_LABELS, formatTime, toEpoch, type TimeUnitLabels } from "./timeSpan"
|
|
22
|
+
import { UPTIME_SEVERITY, uptimeFigure, uptimeRange, uptimeShare, type UptimeSegment, type UptimeStatus } from "./uptimeBar"
|
|
18
23
|
|
|
19
|
-
export type UptimeStatus = "operational" | "degraded" | "down" | "maintenance" | "empty"
|
|
20
|
-
|
|
21
|
-
export interface UptimeSegment {
|
|
22
|
-
/** Health of this slice. `empty` renders a faded placeholder (no data). */
|
|
23
|
-
status: UptimeStatus
|
|
24
|
-
/** Optional rich tooltip body; falls back to a humanised status. */
|
|
25
|
-
label?: string
|
|
26
|
-
}
|
|
27
24
|
|
|
28
25
|
const props = withDefaults(
|
|
29
26
|
defineProps<{
|
|
@@ -35,6 +32,13 @@ const props = withDefaults(
|
|
|
35
32
|
endLabel?: string
|
|
36
33
|
/** Show the computed uptime % above the bar. */
|
|
37
34
|
showUptime?: boolean
|
|
35
|
+
/**
|
|
36
|
+
* The uptime figure, when the app has a more exact one than the slices
|
|
37
|
+
* (a server that counted every minute). Otherwise it is worked out from
|
|
38
|
+
* the slices: their `durations`, else their spans, else their count.
|
|
39
|
+
*/
|
|
40
|
+
uptime?: number | null
|
|
41
|
+
locale?: string
|
|
38
42
|
/** Override the headline shown next to the uptime %. */
|
|
39
43
|
title?: string
|
|
40
44
|
/**
|
|
@@ -44,7 +48,7 @@ const props = withDefaults(
|
|
|
44
48
|
*/
|
|
45
49
|
colors?: Partial<Record<UptimeStatus, string>>
|
|
46
50
|
/** Status names, for the tooltip and screen readers. */
|
|
47
|
-
labels?: Partial<Record<UptimeStatus | "loading", string>>
|
|
51
|
+
labels?: Partial<Record<UptimeStatus | "loading" | "uptime", string>>
|
|
48
52
|
/**
|
|
49
53
|
* The history is on its way. The row of ticks pulses in place of the
|
|
50
54
|
* slices (as many as `segments` already holds, else `skeletonSlices`) at
|
|
@@ -88,26 +92,18 @@ const statusLabel = useLabels(
|
|
|
88
92
|
maintenance: "Maintenance",
|
|
89
93
|
empty: "No data",
|
|
90
94
|
loading: "Loading",
|
|
95
|
+
uptime: "uptime",
|
|
91
96
|
},
|
|
92
97
|
() => props.labels,
|
|
93
98
|
)
|
|
94
99
|
|
|
95
100
|
const skeletonCount = computed(() => props.segments.length || props.skeletonSlices)
|
|
96
101
|
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const up = tracked.filter((s) => s.status === "operational" || s.status === "maintenance").length
|
|
102
|
-
return (up / tracked.length) * 100
|
|
103
|
-
})
|
|
102
|
+
const locale = useLocale(() => props.locale)
|
|
103
|
+
const units = useLabels<TimeUnitLabels>("time", DEFAULT_UNIT_LABELS)
|
|
104
|
+
|
|
105
|
+
const uptimePct = computed(() => (props.uptime ?? null) !== null ? props.uptime! : uptimeShare(props.segments))
|
|
104
106
|
|
|
105
|
-
/*
|
|
106
|
-
* Places actually needed, so trailing zeros stay trimmed: 100 rather than
|
|
107
|
-
* 100.00, 99.9 rather than 99.90. The counter needs a fixed count per render —
|
|
108
|
-
* it cannot "trim" mid-roll without changing the row's shape — so it is derived
|
|
109
|
-
* from the value instead of hard-coded at two.
|
|
110
|
-
*/
|
|
111
107
|
/*
|
|
112
108
|
* The gap is drawn INSIDE each slice, not between slices.
|
|
113
109
|
*
|
|
@@ -136,7 +132,24 @@ const tip = computed(() => {
|
|
|
136
132
|
if (i == null) return null
|
|
137
133
|
const seg = props.segments[i]
|
|
138
134
|
if (!seg) return null
|
|
139
|
-
return {
|
|
135
|
+
return {
|
|
136
|
+
status: seg.status,
|
|
137
|
+
text: seg.label ?? statusLabel.value[seg.status],
|
|
138
|
+
range: seg.start !== undefined ? uptimeRange(seg.start, seg.end, locale.value) : "",
|
|
139
|
+
parts: UPTIME_SEVERITY.filter((st) => (seg.durations?.[st] ?? 0) > 0).map((st) => ({
|
|
140
|
+
status: st,
|
|
141
|
+
text: statusLabel.value[st],
|
|
142
|
+
time: formatTime(seg.durations![st]!, "units", units.value, 2),
|
|
143
|
+
})),
|
|
144
|
+
events: (seg.events ?? []).slice(0, 4).map((e) => ({
|
|
145
|
+
status: e.status ?? seg.status,
|
|
146
|
+
text: e.text,
|
|
147
|
+
at: e.at !== undefined && Number.isFinite(toEpoch(e.at))
|
|
148
|
+
? new Intl.DateTimeFormat(locale.value, { hour: "2-digit", minute: "2-digit" }).format(toEpoch(e.at))
|
|
149
|
+
: "",
|
|
150
|
+
})),
|
|
151
|
+
more: Math.max(0, (seg.events?.length ?? 0) - 4),
|
|
152
|
+
}
|
|
140
153
|
})
|
|
141
154
|
|
|
142
155
|
/*
|
|
@@ -165,12 +178,7 @@ function onSlotKeydown(e: KeyboardEvent, i: number) {
|
|
|
165
178
|
slots.value[next]?.focus()
|
|
166
179
|
}
|
|
167
180
|
|
|
168
|
-
const
|
|
169
|
-
const v = uptimePct.value
|
|
170
|
-
if (v == null) return 0
|
|
171
|
-
const trimmed = Number(v.toFixed(2))
|
|
172
|
-
return Number.isInteger(trimmed) ? 0 : String(trimmed).split(".")[1].length
|
|
173
|
-
})
|
|
181
|
+
const figure = computed(() => (uptimePct.value == null ? null : uptimeFigure(uptimePct.value)))
|
|
174
182
|
|
|
175
183
|
// The bar sets its own gap between the strip and its caption, which a compact status row has to close.
|
|
176
184
|
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
@@ -210,13 +218,32 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
210
218
|
</LpSkeleton>
|
|
211
219
|
<LpTooltip v-else :delay="80" follow-cursor side="top" :disabled="hovered === null">
|
|
212
220
|
<template #content>
|
|
213
|
-
<
|
|
214
|
-
<span
|
|
215
|
-
class="
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
<LpShift v-if="tip" axis="both" class="flex">
|
|
222
|
+
<span class="flex min-w-36 flex-col gap-1">
|
|
223
|
+
<span class="flex items-center gap-1.5">
|
|
224
|
+
<span
|
|
225
|
+
class="size-1.5 shrink-0 rounded-full transition-colors duration-[var(--duration-fast)]"
|
|
226
|
+
:style="{ backgroundColor: colorFor(tip.status) }"
|
|
227
|
+
/>
|
|
228
|
+
<LpRollingText :value="tip.text" :duration="180" align="start" class="font-medium" />
|
|
229
|
+
</span>
|
|
230
|
+
<LpRollingText v-if="tip.range" :value="tip.range" :duration="180" align="start" class="text-[0.7rem] opacity-70 tabular-nums" />
|
|
231
|
+
<span v-if="tip.parts.length > 1 || (tip.parts.length === 1 && tip.parts[0].status !== tip.status)" class="mt-0.5 flex flex-col gap-0.5">
|
|
232
|
+
<span v-for="part in tip.parts" :key="part.status" class="flex items-center gap-1.5 tabular-nums">
|
|
233
|
+
<span class="size-1.5 shrink-0 rounded-full" :style="{ backgroundColor: colorFor(part.status) }" />
|
|
234
|
+
<span class="flex-1">{{ part.text }}</span>
|
|
235
|
+
<LpRollingText :value="part.time" :duration="180" align="end" class="opacity-80" />
|
|
236
|
+
</span>
|
|
237
|
+
</span>
|
|
238
|
+
<span v-if="tip.events.length" class="mt-0.5 flex flex-col gap-0.5 border-t border-current/15 pt-1">
|
|
239
|
+
<span v-for="(event, n) in tip.events" :key="n" class="flex items-baseline gap-1.5">
|
|
240
|
+
<LpRollingText v-if="event.at" :value="event.at" :duration="180" align="start" class="shrink-0 tabular-nums opacity-70" />
|
|
241
|
+
<LpRollingText :value="event.text" :duration="180" align="start" wrap />
|
|
242
|
+
</span>
|
|
243
|
+
<span v-if="tip.more" class="opacity-70">+{{ tip.more }}</span>
|
|
244
|
+
</span>
|
|
245
|
+
</span>
|
|
246
|
+
</LpShift>
|
|
220
247
|
</template>
|
|
221
248
|
|
|
222
249
|
<!--
|
|
@@ -235,7 +262,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
235
262
|
type="button"
|
|
236
263
|
class="lp-uptime__slot min-w-0 flex-1 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
237
264
|
:tabindex="i === tabStop ? 0 : -1"
|
|
238
|
-
:aria-label="seg.label ?? statusLabel[seg.status]"
|
|
265
|
+
:aria-label="[seg.label ?? statusLabel[seg.status], seg.start !== undefined ? uptimeRange(seg.start, seg.end, locale) : ''].filter(Boolean).join(', ')"
|
|
239
266
|
@pointerenter="hovered = i"
|
|
240
267
|
@focus="focusIndex = i; hovered = i"
|
|
241
268
|
@blur="hovered === i && (hovered = null)"
|
|
@@ -275,13 +302,14 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
275
302
|
class="inline-block h-[0.9em] w-8 align-middle"
|
|
276
303
|
/>
|
|
277
304
|
<LpNumberFlow
|
|
278
|
-
v-else-if="
|
|
279
|
-
:value="
|
|
280
|
-
:decimals="
|
|
305
|
+
v-else-if="figure"
|
|
306
|
+
:value="figure.value"
|
|
307
|
+
:decimals="figure.decimals"
|
|
308
|
+
grouped
|
|
281
309
|
suffix="%"
|
|
282
310
|
/>
|
|
283
311
|
<template v-else>—</template>
|
|
284
|
-
<span class="text-muted"> uptime</span>
|
|
312
|
+
<span class="ml-1 text-muted">{{ statusLabel.uptime }}</span>
|
|
285
313
|
</span>
|
|
286
314
|
<span class="h-px flex-1 bg-line" />
|
|
287
315
|
<span class="shrink-0">{{ endLabel }}</span>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useAttrs } from "vue"
|
|
3
|
-
import type
|
|
3
|
+
import { useFieldBlur, type useFieldControl } from "../composables/useField"
|
|
4
4
|
import { mergeClass } from "../composables/useMergedClass"
|
|
5
5
|
import { PICKER_CLEAR, PICKER_PRESET, PICKER_PRESETS, PICKER_SIZE, PICKER_TRIGGER, pickerTone } from "./calendar"
|
|
6
6
|
import LpIcon from "./LpIcon.vue"
|
|
@@ -21,13 +21,14 @@ withDefaults(defineProps<{
|
|
|
21
21
|
presetsClass: string
|
|
22
22
|
}>(), { size: "md" })
|
|
23
23
|
const open = defineModel<boolean>("open")
|
|
24
|
-
defineEmits<{ clear: [] }>()
|
|
24
|
+
const emit = defineEmits<{ clear: []; blur: [event: FocusEvent] }>()
|
|
25
|
+
const { partId, onFocusOut } = useFieldBlur((event) => emit("blur", event))
|
|
25
26
|
const attrs = useAttrs()
|
|
26
27
|
</script>
|
|
27
28
|
|
|
28
29
|
<template>
|
|
29
|
-
<div :class="mergeClass('relative w-full', attrs.class as string)" :style="attrs.style as never">
|
|
30
|
-
<LpPopover v-model:open="open" align="start" panel-class="flex w-auto max-w-[calc(100vw-1rem)] flex-col rounded-card p-0 sm:flex-row">
|
|
30
|
+
<div :data-lp-field-part="partId" @focusout="onFocusOut" :class="mergeClass('relative w-full', attrs.class as string)" :style="attrs.style as never">
|
|
31
|
+
<LpPopover v-model:open="open" :data-lp-field-part="partId" @focusout="onFocusOut" align="start" panel-class="flex w-auto max-w-[calc(100vw-1rem)] flex-col rounded-card p-0 sm:flex-row">
|
|
31
32
|
<template #trigger>
|
|
32
33
|
<button
|
|
33
34
|
v-bind="{ ...attrs, class: undefined, style: undefined }"
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { applyQueryFilters, parsePaletteQuery, type CommandSearchTerms, type HighlightPart, type SearchableCommand } from "./commandSearch"
|
|
2
|
+
import type { Command } from "./LpCommandPalette.vue"
|
|
3
|
+
import type { ContextMenuItemDef } from "./LpContextMenu.vue"
|
|
4
|
+
|
|
5
|
+
/** The same action definitions serve a row menu and the command palette. */
|
|
6
|
+
export function menuPaletteCommands(items: ContextMenuItemDef[], prefix: string): Command[] {
|
|
7
|
+
return items.map((item, index) => ({
|
|
8
|
+
id: `${prefix}/${index}`, label: item.label, icon: item.icon, disabled: item.disabled,
|
|
9
|
+
onSelect: item.onSelect, keepOpen: item.checked !== undefined,
|
|
10
|
+
children: item.children ? menuPaletteCommands(item.children, `${prefix}/${index}`) : undefined,
|
|
11
|
+
}))
|
|
12
|
+
}
|
|
2
13
|
|
|
3
14
|
/*
|
|
4
15
|
* The parts of LpCommandPalette that are plain logic, kept out of the SFC so
|
|
@@ -7,6 +18,7 @@ import { applyQueryFilters, parsePaletteQuery, type CommandSearchTerms, type Hig
|
|
|
7
18
|
|
|
8
19
|
/** The fields of a command that decide whether the preview pane opens for it. */
|
|
9
20
|
export interface PreviewableCommand {
|
|
21
|
+
previewComponent?: unknown
|
|
10
22
|
preview?: string
|
|
11
23
|
excerpt?: string
|
|
12
24
|
noPreview?: boolean
|
|
@@ -23,9 +35,18 @@ export function commandHasPreview<T extends PreviewableCommand>(
|
|
|
23
35
|
): boolean {
|
|
24
36
|
if (!command || command.noPreview) return false
|
|
25
37
|
if (options.hasPreview) return options.hasPreview(command)
|
|
26
|
-
return options.slot || !!(command.preview || command.excerpt)
|
|
38
|
+
return options.slot || !!(command.previewComponent || command.preview || command.excerpt)
|
|
27
39
|
}
|
|
28
40
|
|
|
41
|
+
export const PALETTE_MODES = {
|
|
42
|
+
people: { prefix: "@", label: "People", icon: "lucide:users" },
|
|
43
|
+
channels: { prefix: "#", label: "Channels", icon: "lucide:hash" },
|
|
44
|
+
roles: { prefix: "&", label: "Roles", icon: "lucide:shield" },
|
|
45
|
+
commands: { prefix: ">", label: "Commands", icon: "lucide:terminal" },
|
|
46
|
+
help: { prefix: "?", label: "Help", icon: "lucide:circle-help" },
|
|
47
|
+
} as const
|
|
48
|
+
export type PaletteMode = keyof typeof PALETTE_MODES
|
|
49
|
+
|
|
29
50
|
/** One line of LpPaletteFacts: what it is, and what it is set to now. */
|
|
30
51
|
export interface PaletteFact {
|
|
31
52
|
/** Text, or the preview slot's `highlight(text)` to mark the query. */
|
|
@@ -72,7 +72,7 @@ function score(command: SearchableCommand, terms: CommandSearchTerms): number {
|
|
|
72
72
|
return total
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
function rankExact<T extends SearchableCommand>(items: T[], terms: CommandSearchTerms): T[] {
|
|
76
76
|
return items
|
|
77
77
|
.map((command) => ({ command, score: score(command, terms) }))
|
|
78
78
|
.filter((entry) => entry.score > 0)
|
|
@@ -80,6 +80,28 @@ export function rankCommandItems<T extends SearchableCommand>(items: T[], terms:
|
|
|
80
80
|
.map((entry) => entry.command)
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
const LATIN_KEYS = "`qwertyuiop[]asdfghjkl;'zxcvbnm,."
|
|
84
|
+
const CYRILLIC_KEYS = "ёйцукенгшщзхъфывапролджэячсмитьбю"
|
|
85
|
+
const LAYOUT_KEYS = new Map([...LATIN_KEYS].flatMap((key, i) => [[key, CYRILLIC_KEYS[i]!], [CYRILLIC_KEYS[i]!, key]]))
|
|
86
|
+
|
|
87
|
+
export function swapCommandLayout(value: string): string {
|
|
88
|
+
return [...value].map(char => {
|
|
89
|
+
const replacement = LAYOUT_KEYS.get(char.toLowerCase())
|
|
90
|
+
return replacement ? char === char.toUpperCase() ? replacement.toUpperCase() : replacement : char
|
|
91
|
+
}).join("")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function searchCommandItems<T extends SearchableCommand>(items: T[], terms: CommandSearchTerms): { items: T[]; terms: string[] } {
|
|
95
|
+
const exact = rankExact(items, terms)
|
|
96
|
+
if (exact.length || !terms.include.length) return { items: exact, terms: terms.include }
|
|
97
|
+
const alternate = { include: terms.include.map(swapCommandLayout), exclude: terms.exclude, groups: terms.groups }
|
|
98
|
+
return { items: rankExact(items, alternate), terms: alternate.include }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function rankCommandItems<T extends SearchableCommand>(items: T[], terms: CommandSearchTerms): T[] {
|
|
102
|
+
return searchCommandItems(items, terms).items
|
|
103
|
+
}
|
|
104
|
+
|
|
83
105
|
/**
|
|
84
106
|
* A query as a fetcher receives it. `text` is what a remote search should be
|
|
85
107
|
* sent: the positive words and phrases, in the case they were typed, without
|
package/src/components/format.ts
CHANGED
|
@@ -134,3 +134,13 @@ export function compactNumber(value: number, options: CompactNumberOptions = {})
|
|
|
134
134
|
}).format(scaled)
|
|
135
135
|
return `${number}${COMPACT_UNITS[i][1]}`
|
|
136
136
|
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The character a locale writes between the whole and the fractional part:
|
|
140
|
+
* "." in English, "," in Russian and German. A counter that splits a grouped
|
|
141
|
+
* figure into columns has to find the fraction by this mark, not by ".",
|
|
142
|
+
* because in German "." is the thousands separator.
|
|
143
|
+
*/
|
|
144
|
+
export function decimalMark(locale?: string): string {
|
|
145
|
+
return numberFormat(locale, {}).formatToParts(1.5).find((p) => p.type === "decimal")?.value ?? "."
|
|
146
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The arithmetic behind LpUptimeBar, kept pure so the corners are tested
|
|
3
|
+
* without a DOM: what share of the time was up, how that figure is printed,
|
|
4
|
+
* and how a slice's span reads in its tooltip.
|
|
5
|
+
*/
|
|
6
|
+
import { toEpoch } from "./timeSpan"
|
|
7
|
+
|
|
8
|
+
export type UptimeStatus = "operational" | "degraded" | "down" | "maintenance" | "empty"
|
|
9
|
+
|
|
10
|
+
export type UptimeMoment = Date | string | number
|
|
11
|
+
|
|
12
|
+
/** Something that happened inside a slice: a restart, a deploy, an alert. */
|
|
13
|
+
export interface UptimeEvent {
|
|
14
|
+
text: string
|
|
15
|
+
at?: UptimeMoment
|
|
16
|
+
status?: UptimeStatus
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UptimeSegment {
|
|
20
|
+
/** Health of this slice. `empty` renders a faded placeholder (no data). */
|
|
21
|
+
status: UptimeStatus
|
|
22
|
+
/** Optional rich tooltip body; falls back to a humanised status. */
|
|
23
|
+
label?: string
|
|
24
|
+
/** When the slice begins and ends. Shown in the tooltip, and weights the uptime figure. */
|
|
25
|
+
start?: UptimeMoment
|
|
26
|
+
end?: UptimeMoment
|
|
27
|
+
/**
|
|
28
|
+
* Time spent in each state inside the slice, in ms. With it the figure
|
|
29
|
+
* counts real time rather than slices (one bad minute in a day no longer
|
|
30
|
+
* costs the whole day), and the tooltip says how long each state lasted.
|
|
31
|
+
*/
|
|
32
|
+
durations?: Partial<Record<Exclude<UptimeStatus, "empty">, number>>
|
|
33
|
+
/** What happened in the slice, a line each in the tooltip. */
|
|
34
|
+
events?: UptimeEvent[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const UP: ReadonlySet<UptimeStatus> = new Set(["operational", "maintenance"])
|
|
38
|
+
|
|
39
|
+
/** Worst first: the order a tooltip lists the time a slice spent in each state. */
|
|
40
|
+
export const UPTIME_SEVERITY: readonly Exclude<UptimeStatus, "empty">[] = ["down", "degraded", "maintenance", "operational"]
|
|
41
|
+
|
|
42
|
+
function span(seg: UptimeSegment): number | null {
|
|
43
|
+
if (seg.start === undefined || seg.end === undefined) return null
|
|
44
|
+
const ms = toEpoch(seg.end) - toEpoch(seg.start)
|
|
45
|
+
return Number.isFinite(ms) && ms > 0 ? ms : null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Percent of the tracked time that was up (operational or planned
|
|
50
|
+
* maintenance). Slices with `durations` count their real minutes; the rest
|
|
51
|
+
* count by their span when every one of them has a span, and as one slice
|
|
52
|
+
* each otherwise. Empty slices are not tracked. Null when nothing is.
|
|
53
|
+
*/
|
|
54
|
+
export function uptimeShare(segments: readonly UptimeSegment[]): number | null {
|
|
55
|
+
const tracked = segments.filter((s) => s.status !== "empty")
|
|
56
|
+
const bySpan = tracked.every((s) => s.durations || span(s) !== null)
|
|
57
|
+
let up = 0
|
|
58
|
+
let total = 0
|
|
59
|
+
for (const seg of tracked) {
|
|
60
|
+
if (seg.durations) {
|
|
61
|
+
for (const [status, ms] of Object.entries(seg.durations)) {
|
|
62
|
+
if (!ms || ms < 0) continue
|
|
63
|
+
total += ms
|
|
64
|
+
if (UP.has(status as UptimeStatus)) up += ms
|
|
65
|
+
}
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
const weight = bySpan ? (span(seg) ?? 0) : 1
|
|
69
|
+
total += weight
|
|
70
|
+
if (UP.has(seg.status)) up += weight
|
|
71
|
+
}
|
|
72
|
+
return total > 0 ? (up / total) * 100 : null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The figure and its decimal places: trailing zeros trimmed (100, 99.9), and
|
|
77
|
+
* never rounded up to 100 while some time was down — 99.999 prints as 99.99,
|
|
78
|
+
* because "100%" over an outage reads as no outage at all.
|
|
79
|
+
*/
|
|
80
|
+
export function uptimeFigure(pct: number): { value: number; decimals: number } {
|
|
81
|
+
let value = Number(pct.toFixed(2))
|
|
82
|
+
if (pct < 100 && value >= 100) value = 99.99
|
|
83
|
+
const decimals = Number.isInteger(value) ? 0 : String(value).split(".")[1].length
|
|
84
|
+
return { value, decimals }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** "26 Oct, 12:00 – 12:15", the date written once when both ends share a day. */
|
|
88
|
+
export function uptimeRange(start: UptimeMoment, end: UptimeMoment | undefined, locale?: string): string {
|
|
89
|
+
const from = toEpoch(start)
|
|
90
|
+
if (!Number.isFinite(from)) return ""
|
|
91
|
+
const full = new Intl.DateTimeFormat(locale, { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" })
|
|
92
|
+
if (end === undefined) return full.format(from)
|
|
93
|
+
const to = toEpoch(end)
|
|
94
|
+
if (!Number.isFinite(to)) return full.format(from)
|
|
95
|
+
const sameDay = new Date(from).toDateString() === new Date(to).toDateString()
|
|
96
|
+
const tail = sameDay ? new Intl.DateTimeFormat(locale, { hour: "2-digit", minute: "2-digit" }) : full
|
|
97
|
+
return `${full.format(from)} – ${tail.format(to)}`
|
|
98
|
+
}
|
|
@@ -26,6 +26,17 @@ export function scrollParent(el: Element | null | undefined, { axis = "y", self
|
|
|
26
26
|
return null
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Whether `el`'s scroller (or the page) has no more room to scroll down.
|
|
31
|
+
* `slack` absorbs the pixel a rounded-up scrollHeight can sit above the sum.
|
|
32
|
+
*/
|
|
33
|
+
export function atScrollEnd(el: Element | null | undefined, slack = 2): boolean {
|
|
34
|
+
if (!el) return false
|
|
35
|
+
const box = scrollParent(el) ?? (el.ownerDocument.scrollingElement as HTMLElement | null)
|
|
36
|
+
if (!box) return true
|
|
37
|
+
return box.scrollTop + box.clientHeight >= box.scrollHeight - slack
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
export interface ScrollToElementOptions {
|
|
30
41
|
/** `nearest` scrolls only when the element is out of view. */
|
|
31
42
|
block?: "nearest" | "center" | "start"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, inject, provide, useId, type ComputedRef, type InjectionKey } from "vue"
|
|
1
|
+
import { computed, inject, onScopeDispose, provide, useId, type ComputedRef, type InjectionKey } from "vue"
|
|
2
2
|
import type { FieldNoticeOptions } from "./useFieldNotice"
|
|
3
3
|
|
|
4
4
|
/*
|
|
@@ -9,6 +9,10 @@ import type { FieldNoticeOptions } from "./useFieldNotice"
|
|
|
9
9
|
*/
|
|
10
10
|
export interface FieldContext {
|
|
11
11
|
id: string
|
|
12
|
+
/** Human-readable name shared by controls and contextual commands. */
|
|
13
|
+
label?: string
|
|
14
|
+
hint?: string
|
|
15
|
+
error?: string
|
|
12
16
|
/** Id of the visible label, for controls a `<label for>` cannot name (groups). */
|
|
13
17
|
labelId?: string
|
|
14
18
|
describedBy?: string
|
|
@@ -74,6 +78,9 @@ export function useFieldControl(
|
|
|
74
78
|
return {
|
|
75
79
|
id: state.id ?? field?.id,
|
|
76
80
|
labelId: field?.labelId,
|
|
81
|
+
label: field?.label,
|
|
82
|
+
hint: field?.hint,
|
|
83
|
+
error: field?.error,
|
|
77
84
|
describedBy: field?.describedBy,
|
|
78
85
|
invalid: !!state.invalid || !!field?.invalid,
|
|
79
86
|
required: !!state.required || !!field?.required,
|
|
@@ -93,3 +100,28 @@ export function focusLeft(event: FocusEvent): boolean {
|
|
|
93
100
|
const next = event.relatedTarget
|
|
94
101
|
return !(next instanceof Node && (event.currentTarget as Node | null)?.contains(next))
|
|
95
102
|
}
|
|
103
|
+
|
|
104
|
+
/** Trigger and portalled content share a boundary for the control's public blur. */
|
|
105
|
+
export function useFieldBlur(blur: (event: FocusEvent) => void) {
|
|
106
|
+
const partId = `lp-field-${useId()}`
|
|
107
|
+
let cancelPending: (() => void) | undefined
|
|
108
|
+
const inside = (target: EventTarget | null) =>
|
|
109
|
+
!!(target as Element | null)?.closest?.(`[data-lp-field-part="${partId}"]`)
|
|
110
|
+
|
|
111
|
+
function onFocusOut(event: FocusEvent) {
|
|
112
|
+
cancelPending?.()
|
|
113
|
+
if (inside(event.relatedTarget)) return
|
|
114
|
+
if (event.relatedTarget) return blur(event)
|
|
115
|
+
// A removed calendar cell can lose focus before its trigger gets it back.
|
|
116
|
+
const doc = (event.target as Node).ownerDocument!
|
|
117
|
+
const view = doc.defaultView!
|
|
118
|
+
const frame = view.requestAnimationFrame(() => {
|
|
119
|
+
cancelPending = undefined
|
|
120
|
+
if (!inside(doc.activeElement)) blur(event)
|
|
121
|
+
})
|
|
122
|
+
cancelPending = () => view.cancelAnimationFrame(frame)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
onScopeDispose(() => cancelPending?.())
|
|
126
|
+
return { partId, onFocusOut }
|
|
127
|
+
}
|