@leavepulse/ui 0.43.4 → 0.45.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 +121 -7
- package/dist/assets/{LpCommandPalette-B-0Est4Z.css → LpCommandPalette-nGNnzXqQ.css} +21 -21
- package/dist/assets/{LpDropzone-BBoWvi-J.css → LpDropzone-i4BdMi1H.css} +12 -12
- package/dist/assets/{LpImageEditor-Ct7Y3aNu.css → LpImageEditor-CbMA82g8.css} +6 -6
- package/dist/assets/{LpRollingText-CR1mb0Vt.css → LpRollingText-BGnE6q9j.css} +3 -0
- package/dist/assets/{LpShortcutsDialog-C6YoQ8_M.css → LpShortcutsDialog-CqYX3yy6.css} +28 -7
- package/dist/assets/{LpSkeleton-D7xU8IPV.css → LpSkeleton-H3Z5dEIk.css} +4 -1
- package/dist/assets/{LpTable-BgPLJOXa.css → LpTable-CPwWxTJS.css} +49 -9
- package/dist/chunks/ContextMenuItems-BtKU7074.js +124 -0
- package/dist/chunks/{LayoutCanvas-Dd20boUG.js → LayoutCanvas-BkaGXTjx.js} +26 -9
- package/dist/chunks/{LayoutNode-DGpZA9LX.js → LayoutNode-B3_9E1Xa.js} +28 -12
- package/dist/chunks/{LpActionBar-BlvtGB2P.js → LpActionBar-CnfFVIwC.js} +26 -12
- package/dist/chunks/{LpAddressInput-B-Zvxsn7.js → LpAddressInput-BwX8uo4_.js} +54 -49
- package/dist/chunks/{LpAlert-BAYHDSS1.js → LpAlert-B6ilrM7T.js} +16 -9
- package/dist/chunks/{LpAppShell-BGQVIY-3.js → LpAppShell-C53_RaGF.js} +53 -26
- package/dist/chunks/{LpAutocomplete-DExlIZe_.js → LpAutocomplete-C8IgSMsi.js} +52 -24
- package/dist/chunks/{LpAvatar-B3-xAOH3.js → LpAvatar-C1XStdOo.js} +19 -10
- package/dist/chunks/LpBreadcrumbs-CQJd73Gm.js +95 -0
- package/dist/chunks/{LpButton-5VXXCjwF.js → LpButton-CKSwC6Mu.js} +45 -15
- package/dist/chunks/{LpCalendar-B7Ko5RdR.js → LpCalendar-DrA2McTU.js} +1 -1
- package/dist/chunks/{LpCard-BhI1N_vL.js → LpCard-C2IvucmX.js} +1 -1
- package/dist/chunks/{LpCheckbox-CVccKYaH.js → LpCheckbox-Bkj13CbW.js} +8 -4
- package/dist/chunks/{LpCodeBlock-Bo0B4TFR.js → LpCodeBlock-ca7ANFyW.js} +49 -17
- package/dist/chunks/{LpColorPicker-BlXea7LM.js → LpColorPicker-zz4VGkG4.js} +75 -26
- package/dist/chunks/{LpConfirmDialog-Q-bxff8z.js → LpConfirmDialog-DCw5jR1t.js} +59 -17
- package/dist/chunks/LpConfirmHost-CbywMpMP.js +49 -0
- package/dist/chunks/LpContextMenu-BqkAYa9X.js +69 -0
- package/dist/chunks/LpDatePicker-DuusgT69.js +195 -0
- package/dist/chunks/LpDateRangePicker-DHeuznfC.js +208 -0
- package/dist/chunks/{LpDisclosure-ChQYmjlR.js → LpDisclosure-Cc23-JOT.js} +1 -1
- package/dist/chunks/{LpDrawer-BZ6k3hvo.js → LpDrawer-Dt4aB4Fr.js} +65 -28
- package/dist/chunks/{LpDropdownMenu-DJpoqRCb.js → LpDropdownMenu-DdJGQzPn.js} +2 -1
- package/dist/chunks/{LpEmptyState-Dsdqz1Wj.js → LpEmptyState-BJbZXC8D.js} +1 -1
- package/dist/chunks/LpFieldNotice-wtieLUTj.js +64 -0
- package/dist/chunks/{LpFileTree-_5vFBgaK.js → LpFileTree-CNurOvkV.js} +25 -10
- package/dist/chunks/{LpFileTreeNode-BdcIYKIE.js → LpFileTreeNode-D_qRRdsH.js} +3 -3
- package/dist/chunks/LpFormField-CtVYj6tO.js +101 -0
- package/dist/chunks/{LpIcon-CCnX5_2j.js → LpIcon-DYtaid0K.js} +20 -6
- package/dist/chunks/{LpInput-9EXWUM9B.js → LpInput-D-YGQbEv.js} +58 -20
- package/dist/chunks/{LpLightbox-2sYVMY-S.js → LpLightbox-DSDahF23.js} +148 -61
- package/dist/chunks/{LpLink-DCND-zyu.js → LpLink-Krkjhe_y.js} +15 -6
- package/dist/chunks/{LpLogViewer-CaRy5MeS.js → LpLogViewer-KypbgOyo.js} +300 -72
- package/dist/chunks/{LpMapPicker-ul4oYno6.js → LpMapPicker-Cc84LaM3.js} +39 -14
- package/dist/chunks/{LpMediaGallery-FF-NquIo.js → LpMediaGallery-YKr-cQPN.js} +7 -4
- package/dist/chunks/{LpModal-Bt0pQCI_.js → LpModal-DMMDQHAu.js} +52 -29
- package/dist/chunks/{LpModalNav-YaifHrXC.js → LpModalNav-BVPDyIup.js} +10 -7
- package/dist/chunks/{LpNotificationBell-DyGGsMAM.js → LpNotificationBell-CCwyJP5C.js} +79 -33
- package/dist/chunks/{LpNumberField-CWuX08iX.js → LpNumberField-B77O-z28.js} +21 -8
- package/dist/chunks/{LpNumberFlow-amWYaeuA.js → LpNumberFlow-z4WPZcZu.js} +22 -12
- package/dist/chunks/{LpOtpInput-y2v-pmiN.js → LpOtpInput-CziPF3Oq.js} +37 -8
- package/dist/chunks/{LpPagination-5FxjmmB5.js → LpPagination-BcDlo3r_.js} +31 -20
- package/dist/chunks/{LpPasswordInput--sjYCkLS.js → LpPasswordInput-eKzQhbQP.js} +11 -8
- package/dist/chunks/LpPhoneInput-BCbrrhgE.js +336 -0
- package/dist/chunks/{LpPie-D6OnzwlJ.js → LpPie-BEJ2SfI6.js} +84 -28
- package/dist/chunks/{LpPopover-8268UmlC.js → LpPopover-DCKeqiVn.js} +5 -2
- package/dist/chunks/{LpProgress-Btq5W4Ut.js → LpProgress-CBU1iI69.js} +5 -4
- package/dist/chunks/{LpRadioGroup-xPIuVDO_.js → LpRadioGroup-DmEW0l78.js} +9 -2
- package/dist/chunks/LpRangeCalendar-DZuaRV0W.js +331 -0
- package/dist/chunks/{LpRollingText-CM5gkSeF.js → LpRollingText-DB7upf-5.js} +1 -1
- package/dist/chunks/{LpScrollArea-D8xyKXcF.js → LpScrollArea-Bry1fdOM.js} +14 -2
- package/dist/chunks/{LpSection-aM6J_u51.js → LpSection-tKEH0jYf.js} +1 -1
- package/dist/chunks/{LpSegmented-fV0Sk92p.js → LpSegmented-C09EEam2.js} +8 -4
- package/dist/chunks/{LpSelect-ANIVtrpM.js → LpSelect-DNIL6MB9.js} +50 -31
- package/dist/chunks/{LpSidebar-ClBUwoEz.js → LpSidebar-CQNB0-yU.js} +30 -22
- package/dist/chunks/{LpSidebarNav-llOcs_Fn.js → LpSidebarNav-kx-LdqNU.js} +16 -11
- package/dist/chunks/{LpSkeleton-CTI-vBgW.js → LpSkeleton-BB6XIV-w.js} +1 -1
- package/dist/chunks/{LpSlider-BwNIBkAK.js → LpSlider-Cuzmrd7p.js} +7 -2
- package/dist/chunks/{LpSparkline-C6xQ2dwv.js → LpSparkline-CB80N8SJ.js} +11 -4
- package/dist/chunks/{LpStat-CQaW8R--.js → LpStat-B-7udBRP.js} +2 -2
- package/dist/chunks/{LpStepper-Bk-XaQYU.js → LpStepper-C7XiVKZj.js} +34 -19
- package/dist/chunks/{LpSwitch-Blizvqyt.js → LpSwitch-OrcOJX9m.js} +41 -27
- package/dist/chunks/LpTableFilter-BV8PpLM9.js +158 -0
- package/dist/chunks/{LpTableOfContents-CjvxPMpU.js → LpTableOfContents-BUcAfGPQ.js} +36 -41
- package/dist/chunks/{LpTabs-Cefe_lys.js → LpTabs-29iW1XOx.js} +20 -6
- package/dist/chunks/LpTagsInput-D1SuqbhB.js +300 -0
- package/dist/chunks/LpTextarea-uuScWo6-.js +77 -0
- package/dist/chunks/LpThemeProvider-Be4r2TQX.js +35 -0
- package/dist/chunks/LpThemeSwitcher-B4aVPbNC.js +166 -0
- package/dist/chunks/{LpTilt-DuyKbpkA.js → LpTilt-B7KHX1zH.js} +2 -2
- package/dist/chunks/{LpTimeField-K6nAGboX.js → LpTimeField-Cqe7fXnh.js} +14 -8
- package/dist/chunks/{LpToaster-gDB1EaU3.js → LpToaster-DpJvoCOh.js} +94 -31
- package/dist/chunks/{LpTooltip-hCLPAqMG.js → LpTooltip-CXVKcOXT.js} +14 -6
- package/dist/chunks/LpTooltipProvider-CftjV7Xc.js +25 -0
- package/dist/chunks/{LpTopologyCanvas-Cm1L39uL.js → LpTopologyCanvas-6ozNrQKh.js} +8 -2
- package/dist/chunks/{LpUptimeBar-DvAMno0S.js → LpUptimeBar-ugcv25xj.js} +44 -15
- package/dist/chunks/icons-CkA0njzm.js +43 -0
- package/dist/chunks/{LpThemeSwitcher-Cue-myST.js → useTheme-DJm1JSsY.js} +215 -260
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.d.ts +8 -0
- package/dist/components/ContextMenuItems.vue.js +4 -0
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.d.ts +4 -0
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.d.ts +3 -0
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.d.ts +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.d.ts +9 -0
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +10 -0
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.d.ts +31 -6
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.d.ts +3 -3
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.d.ts +2 -2
- package/dist/components/LpBreadcrumbs.vue.d.ts +13 -9
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +35 -98
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.d.ts +47 -3
- package/dist/components/LpChart.vue.js +377 -97
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.d.ts +2 -0
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.d.ts +2 -2
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +14 -3
- package/dist/components/LpCommandPalette.vue.js +142 -77
- package/dist/components/LpConfirmDialog.vue.d.ts +15 -3
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.d.ts +9 -0
- package/dist/components/LpConfirmHost.vue.js +4 -0
- package/dist/components/LpContextMenu.vue.d.ts +3 -5
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.d.ts +24 -6
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.d.ts +60 -0
- package/dist/components/LpDateRangePicker.vue.js +4 -0
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDrawer.vue.d.ts +10 -0
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +23 -7
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFieldNotice.vue.d.ts +8 -0
- package/dist/components/LpFieldNotice.vue.js +4 -0
- package/dist/components/LpFileTree.vue.d.ts +11 -2
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.d.ts +13 -1
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpIcon.vue.d.ts +0 -1
- package/dist/components/LpIcon.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +93 -58
- package/dist/components/LpInput.vue.d.ts +22 -3
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.d.ts +17 -0
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLink.vue.d.ts +11 -11
- package/dist/components/LpLink.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +72 -3
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.d.ts +4 -4
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.d.ts +16 -6
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.d.ts +0 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +11 -2
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.d.ts +2 -0
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPagination.vue.d.ts +2 -0
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.d.ts +13 -0
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.d.ts +21 -2
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPopover.vue.d.ts +2 -0
- package/dist/components/LpPopover.vue.js +1 -1
- package/dist/components/LpProgress.vue.d.ts +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeCalendar.vue.d.ts +51 -0
- package/dist/components/LpRangeCalendar.vue.js +4 -0
- package/dist/components/LpRollingText.vue.d.ts +1 -1
- package/dist/components/LpRollingText.vue.js +1 -1
- package/dist/components/LpScrollArea.vue.d.ts +2 -2
- package/dist/components/LpScrollArea.vue.js +1 -1
- package/dist/components/LpSection.vue.d.ts +1 -1
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSegmented.vue.d.ts +5 -0
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.d.ts +1 -0
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.d.ts +7 -1
- package/dist/components/LpShortcutsDialog.vue.js +60 -15
- package/dist/components/LpSidebar.vue.d.ts +6 -2
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSkeleton.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpSparkline.vue.d.ts +1 -0
- package/dist/components/LpSparkline.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpStepper.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.d.ts +107 -3
- package/dist/components/LpTable.vue.js +540 -128
- package/dist/components/LpTableFilter.vue.d.ts +31 -0
- package/dist/components/LpTableFilter.vue.js +4 -0
- package/dist/components/LpTableOfContents.vue.d.ts +4 -2
- package/dist/components/LpTableOfContents.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +2 -0
- package/dist/components/LpTabs.vue.js +1 -1
- package/dist/components/LpTagsInput.vue.d.ts +77 -0
- package/dist/components/LpTagsInput.vue.js +4 -0
- package/dist/components/LpTextarea.vue.d.ts +19 -6
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.d.ts +22 -0
- package/dist/components/LpThemeProvider.vue.js +4 -0
- package/dist/components/LpThemeSwitcher.vue.d.ts +15 -2
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTilt.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.d.ts +6 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.d.ts +1 -2
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTooltipProvider.vue.d.ts +22 -0
- package/dist/components/LpTooltipProvider.vue.js +4 -0
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.d.ts +4 -0
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/chart.d.ts +32 -1
- package/dist/components/chart.js +42 -6
- package/dist/components/codeHighlight.d.ts +6 -0
- package/dist/components/codeHighlight.js +92 -20
- package/dist/components/color.d.ts +1 -1
- package/dist/components/color.js +20 -9
- package/dist/components/commandSearch.js +3 -0
- package/dist/components/confirm.d.ts +25 -0
- package/dist/components/confirm.js +26 -0
- package/dist/components/countries.d.ts +6 -0
- package/dist/components/countries.js +7 -0
- package/dist/components/dateRange.d.ts +43 -0
- package/dist/components/dateRange.js +76 -0
- package/dist/components/exportData.d.ts +58 -0
- package/dist/components/exportData.js +165 -0
- package/dist/components/fileTree.js +16 -7
- package/dist/components/imageCrop.d.ts +42 -0
- package/dist/components/imageCrop.js +47 -0
- package/dist/components/link.d.ts +12 -0
- package/dist/components/link.js +19 -0
- package/dist/components/logSearch.d.ts +18 -0
- package/dist/components/logSearch.js +36 -0
- package/dist/components/modalDepth.d.ts +18 -2
- package/dist/components/modalDepth.js +17 -5
- package/dist/components/otp.d.ts +5 -0
- package/dist/components/otp.js +10 -0
- package/dist/components/phone.d.ts +41 -0
- package/dist/components/phone.js +80 -0
- package/dist/components/sidebar.d.ts +7 -1
- package/dist/components/tableFilter.d.ts +36 -0
- package/dist/components/tableFilter.js +84 -0
- package/dist/components/tableSort.d.ts +8 -0
- package/dist/components/tableSort.js +31 -0
- package/dist/components/tags.d.ts +20 -0
- package/dist/components/tags.js +37 -0
- package/dist/composables/useField.d.ts +38 -0
- package/dist/composables/useField.js +45 -0
- package/dist/composables/useFieldNotice.d.ts +17 -0
- package/dist/composables/useFieldNotice.js +28 -0
- package/dist/composables/useForm.d.ts +53 -0
- package/dist/composables/useForm.js +207 -0
- package/dist/composables/useFormDraft.d.ts +10 -3
- package/dist/composables/useFormDraft.js +9 -14
- package/dist/composables/useHotkeys.d.ts +81 -5
- package/dist/composables/useHotkeys.js +223 -25
- package/dist/composables/useInputFilter.d.ts +17 -0
- package/dist/composables/useInputFilter.js +47 -10
- package/dist/composables/useInputMask.d.ts +59 -0
- package/dist/composables/useInputMask.js +170 -0
- package/dist/composables/useLpConfig.d.ts +24 -0
- package/dist/composables/useLpConfig.js +44 -0
- package/dist/composables/usePaintSelect.d.ts +22 -0
- package/dist/composables/usePaintSelect.js +169 -0
- package/dist/composables/useRovingFocus.js +7 -0
- package/dist/composables/useShift.js +7 -2
- package/dist/composables/useTilt.d.ts +2 -2
- package/dist/composables/useTilt.js +5 -6
- package/dist/composables/useToast.d.ts +20 -0
- package/dist/composables/useToast.js +28 -4
- package/dist/composables/useZoomPan.d.ts +3 -3
- package/dist/composables/useZoomPan.js +4 -4
- package/dist/icons-all.d.ts +1 -0
- package/dist/icons-all.js +3 -0
- package/dist/icons.d.ts +14 -1
- package/dist/index.d.ts +60 -4
- package/dist/index.js +245 -141
- package/dist/kit-icons.d.ts +2 -0
- package/dist/layout/tree.d.ts +2 -0
- package/dist/layout/tree.js +16 -1
- package/dist/layout/useLayout.d.ts +15 -0
- package/dist/layout/useLayout.js +41 -14
- package/dist/theme/backdrop.d.ts +12 -0
- package/dist/theme/presets.d.ts +0 -2
- package/dist/theme/useTheme.d.ts +32 -9
- package/package.json +18 -8
- package/src/component-names.ts +9 -7
- package/src/components/ContextMenuItems.vue +88 -0
- package/src/components/LayoutCanvas.vue +26 -6
- package/src/components/LayoutNode.vue +30 -4
- package/src/components/LpActionBar.vue +31 -8
- package/src/components/LpAddressInput.vue +57 -41
- package/src/components/LpAlert.vue +16 -3
- package/src/components/LpAppShell.vue +45 -12
- package/src/components/LpAutocomplete.vue +47 -10
- package/src/components/LpAvatar.vue +20 -6
- package/src/components/LpBreadcrumbs.vue +65 -41
- package/src/components/LpButton.vue +60 -14
- package/src/components/LpChart.vue +351 -28
- package/src/components/LpCheckbox.vue +7 -3
- package/src/components/LpCodeBlock.vue +50 -14
- package/src/components/LpColorPicker.vue +73 -10
- package/src/components/LpCommandPalette.vue +115 -20
- package/src/components/LpConfirmDialog.vue +68 -17
- package/src/components/LpConfirmHost.vue +59 -0
- package/src/components/LpContextMenu.vue +15 -116
- package/src/components/LpDatePicker.vue +104 -68
- package/src/components/LpDateRangePicker.vue +224 -0
- package/src/components/LpDrawer.vue +56 -19
- package/src/components/LpDropdownMenu.vue +1 -0
- package/src/components/LpDropzone.vue +21 -4
- package/src/components/LpFieldNotice.vue +65 -0
- package/src/components/LpFileTree.vue +33 -7
- package/src/components/LpFormField.vue +59 -7
- package/src/components/LpIcon.vue +22 -4
- package/src/components/LpImageEditor.vue +96 -57
- package/src/components/LpInput.vue +62 -11
- package/src/components/LpLightbox.vue +140 -29
- package/src/components/LpLink.vue +24 -13
- package/src/components/LpLogViewer.vue +311 -39
- package/src/components/LpMapPicker.vue +36 -13
- package/src/components/LpMediaGallery.vue +8 -2
- package/src/components/LpModal.vue +54 -28
- package/src/components/LpModalNav.vue +5 -2
- package/src/components/LpNotificationBell.vue +77 -23
- package/src/components/LpNumberField.vue +21 -6
- package/src/components/LpNumberFlow.vue +26 -10
- package/src/components/LpOtpInput.vue +43 -8
- package/src/components/LpPagination.vue +23 -7
- package/src/components/LpPasswordInput.vue +4 -1
- package/src/components/LpPhoneInput.vue +224 -39
- package/src/components/LpPie.vue +102 -20
- package/src/components/LpPopover.vue +4 -0
- package/src/components/LpProgress.vue +5 -3
- package/src/components/LpRadioGroup.vue +9 -2
- package/src/components/LpRangeCalendar.vue +365 -0
- package/src/components/LpRollingText.vue +3 -0
- package/src/components/LpScrollArea.vue +22 -2
- package/src/components/LpSegmented.vue +12 -2
- package/src/components/LpSelect.vue +0 -0
- package/src/components/LpShortcutsDialog.vue +83 -9
- package/src/components/LpSidebar.vue +14 -9
- package/src/components/LpSidebarNav.vue +17 -5
- package/src/components/LpSkeleton.vue +4 -1
- package/src/components/LpSlider.vue +7 -1
- package/src/components/LpSparkline.vue +11 -3
- package/src/components/LpStepper.vue +23 -10
- package/src/components/LpSwitch.vue +31 -8
- package/src/components/LpTable.vue +668 -90
- package/src/components/LpTableFilter.vue +175 -0
- package/src/components/LpTableOfContents.vue +50 -32
- package/src/components/LpTabs.vue +23 -6
- package/src/components/LpTagsInput.vue +364 -0
- package/src/components/LpTextarea.vue +44 -6
- package/src/components/LpThemeProvider.vue +41 -0
- package/src/components/LpThemeSwitcher.vue +76 -33
- package/src/components/LpTilt.vue +2 -2
- package/src/components/LpTimeField.vue +14 -3
- package/src/components/LpToaster.vue +115 -27
- package/src/components/LpTooltip.vue +29 -5
- package/src/components/LpTooltipProvider.vue +24 -0
- package/src/components/LpTopologyCanvas.vue +19 -2
- package/src/components/LpUptimeBar.vue +46 -8
- package/src/components/chart.ts +82 -8
- package/src/components/codeHighlight.ts +111 -15
- package/src/components/color.ts +30 -9
- package/src/components/commandSearch.ts +6 -0
- package/src/components/confirm.ts +58 -0
- package/src/components/countries.ts +12 -0
- package/src/components/dateRange.ts +134 -0
- package/src/components/exportData.ts +228 -0
- package/src/components/fileTree.ts +19 -7
- package/src/components/imageCrop.ts +107 -0
- package/src/components/link.ts +34 -0
- package/src/components/logSearch.ts +46 -0
- package/src/components/modalDepth.ts +48 -6
- package/src/components/otp.ts +16 -0
- package/src/components/phone.ts +140 -0
- package/src/components/sidebar.ts +7 -1
- package/src/components/tableFilter.ts +122 -0
- package/src/components/tableSort.ts +37 -0
- package/src/components/tags.ts +62 -0
- package/src/composables/useField.ts +83 -0
- package/src/composables/useFieldNotice.ts +59 -0
- package/src/composables/useForm.ts +314 -0
- package/src/composables/useFormDraft.ts +25 -20
- package/src/composables/useHotkeys.ts +355 -28
- package/src/composables/useInputFilter.ts +77 -14
- package/src/composables/useInputMask.ts +254 -0
- package/src/composables/useLpConfig.ts +87 -0
- package/src/composables/usePaintSelect.ts +243 -0
- package/src/composables/useRovingFocus.ts +11 -0
- package/src/composables/useShift.ts +13 -2
- package/src/composables/useTilt.ts +5 -6
- package/src/composables/useToast.ts +56 -4
- package/src/composables/useZoomPan.ts +6 -6
- package/src/iconSubset.d.mts +10 -0
- package/src/iconSubset.mjs +51 -0
- package/src/icons-all.ts +10 -0
- package/src/icons.ts +59 -8
- package/src/index.ts +73 -4
- package/src/kit-icons.ts +5 -0
- package/src/layout/tree.ts +30 -0
- package/src/layout/useLayout.ts +68 -18
- package/src/nuxt.ts +87 -17
- package/src/runtime/config.plugin.ts +14 -0
- package/src/runtime/theme.plugin.ts +43 -23
- package/src/theme/backdrop.ts +69 -0
- package/src/theme/presets.ts +6 -64
- package/src/theme/useTheme.ts +206 -100
- package/src/tokens/tokens.css +46 -0
- package/dist/chunks/LpBreadcrumbs-p4jHe_V-.js +0 -62
- package/dist/chunks/LpContextMenu-CwW1QXe_.js +0 -217
- package/dist/chunks/LpDatePicker-CwOToxGq.js +0 -164
- package/dist/chunks/LpFormField-CazBc2-a.js +0 -62
- package/dist/chunks/LpPhoneInput-BDSKVl_z.js +0 -209
- package/dist/chunks/LpTextarea-t7F6gv8E.js +0 -47
|
@@ -31,12 +31,12 @@ const props = withDefaults(
|
|
|
31
31
|
{ max: 10, lift: 8, scale: 1.03, glare: false, perspective: 900 },
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
const tilt = useTilt({
|
|
34
|
+
const tilt = useTilt(() => ({
|
|
35
35
|
max: props.max,
|
|
36
36
|
lift: props.lift,
|
|
37
37
|
scale: props.scale,
|
|
38
38
|
glare: props.glare,
|
|
39
|
-
})
|
|
39
|
+
}))
|
|
40
40
|
|
|
41
41
|
const stageStyle = computed(() => ({ perspective: `${props.perspective}px` }))
|
|
42
42
|
</script>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { Time } from "@internationalized/date"
|
|
14
14
|
import { TimeFieldInput, TimeFieldRoot } from "reka-ui"
|
|
15
15
|
import { computed } from "vue"
|
|
16
|
+
import { useFieldControl } from "../composables/useField"
|
|
16
17
|
import LpIcon from "./LpIcon.vue"
|
|
17
18
|
|
|
18
19
|
const props = withDefaults(
|
|
@@ -50,6 +51,12 @@ function onUpdate(next: unknown) {
|
|
|
50
51
|
emit("update:modelValue", `${pad(next.hour)}:${pad(next.minute)}`)
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
const field = useFieldControl(() => ({
|
|
55
|
+
wrapped: false,
|
|
56
|
+
invalid: props.invalid,
|
|
57
|
+
disabled: props.disabled,
|
|
58
|
+
}))
|
|
59
|
+
|
|
53
60
|
const shellSize = {
|
|
54
61
|
sm: "h-(--size-control-sm) text-xs",
|
|
55
62
|
md: "h-(--size-control-md) text-sm",
|
|
@@ -64,16 +71,20 @@ const shellSize = {
|
|
|
64
71
|
:hour-cycle="hourCycle"
|
|
65
72
|
:locale="locale"
|
|
66
73
|
granularity="minute"
|
|
67
|
-
:disabled="disabled"
|
|
74
|
+
:disabled="field.disabled"
|
|
75
|
+
:aria-invalid="field.invalid || undefined"
|
|
76
|
+
:aria-describedby="field.describedBy"
|
|
68
77
|
class="flex w-full items-center gap-0.5 rounded-control border bg-surface-soft px-3 text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-55"
|
|
69
78
|
:class="[
|
|
70
79
|
shellSize[size],
|
|
71
|
-
invalid ? 'border-danger focus-within:ring-danger-soft' : 'border-line focus-within:border-brand',
|
|
80
|
+
field.invalid ? 'border-danger focus-within:ring-danger-soft' : 'border-line focus-within:border-brand',
|
|
72
81
|
]"
|
|
73
82
|
@update:model-value="onUpdate"
|
|
74
83
|
>
|
|
75
84
|
<LpIcon v-if="icon" name="lucide:clock" :size="15" class="mr-1.5 shrink-0 text-muted" />
|
|
76
|
-
|
|
85
|
+
<!-- Indexed keys: in 12-hour mode the segments hold two "literal" parts
|
|
86
|
+
(the ":" and the space before AM/PM), which a key by part repeats. -->
|
|
87
|
+
<template v-for="(item, i) in segments" :key="i">
|
|
77
88
|
<TimeFieldInput v-if="item.part === 'literal'" :part="item.part" class="text-muted">
|
|
78
89
|
{{ item.value }}
|
|
79
90
|
</TimeFieldInput>
|
|
@@ -11,38 +11,95 @@ import {
|
|
|
11
11
|
ToastTitle,
|
|
12
12
|
ToastViewport,
|
|
13
13
|
} from "reka-ui"
|
|
14
|
-
import { onBeforeUnmount, ref, watch } from "vue"
|
|
14
|
+
import { nextTick, onBeforeUnmount, onMounted, ref, useId, watch } from "vue"
|
|
15
|
+
import { prefersReducedMotion } from "../composables/easing"
|
|
16
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
15
17
|
import { useToast, type ToastItem } from "../composables/useToast"
|
|
16
18
|
import { CLOSE_ICON } from "./dropdown"
|
|
17
19
|
import LpIcon from "./LpIcon.vue"
|
|
18
20
|
|
|
21
|
+
const props = defineProps<{ labels?: Partial<{ dismiss: string }> }>()
|
|
22
|
+
const text = useLabels("toast", { dismiss: "Dismiss" }, () => props.labels)
|
|
23
|
+
|
|
19
24
|
const { queue, dismiss } = useToast()
|
|
20
25
|
|
|
26
|
+
/*
|
|
27
|
+
* The stack slides rather than jumps. A new toast lands at the bottom and the
|
|
28
|
+
* ones above used to leap up by its height in a single frame; one leaving let
|
|
29
|
+
* the rest drop the same way. Each toast's layout position (offsetTop, which
|
|
30
|
+
* ignores transforms) is remembered, and when the list changes every toast
|
|
31
|
+
* that moved is played from where it was to where it is (FLIP). The observer
|
|
32
|
+
* fires after the DOM changes and before the frame is painted, so the jump is
|
|
33
|
+
* never seen. Toasts render through reka's portal into the viewport, which is
|
|
34
|
+
* why this watches the DOM rather than a TransitionGroup.
|
|
35
|
+
*/
|
|
36
|
+
let observer: MutationObserver | undefined
|
|
37
|
+
const tops = new WeakMap<Element, number>()
|
|
38
|
+
|
|
39
|
+
function toastsIn(root: Element): HTMLElement[] {
|
|
40
|
+
return [...root.querySelectorAll<HTMLElement>("li[data-state]")]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function slide(root: Element) {
|
|
44
|
+
const reduce = prefersReducedMotion()
|
|
45
|
+
const height = (root as HTMLElement).offsetHeight
|
|
46
|
+
for (const el of toastsIn(root)) {
|
|
47
|
+
const before = tops.get(el)
|
|
48
|
+
const now = el.offsetTop - height
|
|
49
|
+
tops.set(el, now)
|
|
50
|
+
if (before === undefined || before === now || reduce || el.hasAttribute("data-swipe")) continue
|
|
51
|
+
el.animate([{ translate: `0 ${before - now}px` }, { translate: "0 0" }], {
|
|
52
|
+
duration: 220,
|
|
53
|
+
easing: "cubic-bezier(0.2, 0, 0, 1)",
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The viewport's component root is a wrapper fragment, not the list, so the
|
|
59
|
+
// list is found by a mark of its own once it is teleported into place.
|
|
60
|
+
const viewportId = useId()
|
|
61
|
+
onMounted(() =>
|
|
62
|
+
nextTick(() => {
|
|
63
|
+
const root = document.querySelector(`[data-lp-toasts="${viewportId}"]`)
|
|
64
|
+
if (!root || typeof MutationObserver === "undefined") return
|
|
65
|
+
for (const el of toastsIn(root)) tops.set(el, el.offsetTop - (root as HTMLElement).offsetHeight)
|
|
66
|
+
observer = new MutationObserver(() => slide(root))
|
|
67
|
+
observer.observe(root, { childList: true, subtree: true })
|
|
68
|
+
}),
|
|
69
|
+
)
|
|
70
|
+
onBeforeUnmount(() => observer?.disconnect())
|
|
71
|
+
|
|
72
|
+
// A toast replaced through its `key` keeps its id and gets a new revision, so
|
|
73
|
+
// everything tied to one rendered toast goes by both.
|
|
74
|
+
const tag = (t: ToastItem) => `${t.id}:${t.rev}`
|
|
75
|
+
|
|
21
76
|
// Toasts that are playing their exit animation. We don't splice them out of the
|
|
22
77
|
// queue immediately on close — that would unmount before `toast-out` can run.
|
|
23
78
|
// Instead we flip `open` to false (reka sets data-state=closed → the keyframe
|
|
24
79
|
// plays), then drop the toast for real once `animationend` fires. A safety
|
|
25
80
|
// timer removes it even if the animationend event is missed (e.g. tab hidden).
|
|
26
|
-
const closing = ref<Set<
|
|
81
|
+
const closing = ref<Set<string>>(new Set())
|
|
27
82
|
|
|
28
|
-
function beginClose(
|
|
29
|
-
|
|
30
|
-
|
|
83
|
+
function beginClose(t: ToastItem) {
|
|
84
|
+
const at = tag(t)
|
|
85
|
+
if (closing.value.has(at)) return
|
|
86
|
+
closing.value = new Set(closing.value).add(at)
|
|
31
87
|
// Fallback slightly longer than the 140ms toast-out duration.
|
|
32
|
-
window.setTimeout(() => finishClose(id), 220)
|
|
88
|
+
window.setTimeout(() => finishClose(t.id, at), 220)
|
|
33
89
|
}
|
|
34
90
|
|
|
35
|
-
function finishClose(id: number) {
|
|
36
|
-
if (!closing.value.has(
|
|
91
|
+
function finishClose(id: number, at: string) {
|
|
92
|
+
if (!closing.value.has(at)) return
|
|
37
93
|
const next = new Set(closing.value)
|
|
38
|
-
next.delete(
|
|
94
|
+
next.delete(at)
|
|
39
95
|
closing.value = next
|
|
40
|
-
|
|
96
|
+
// Replaced while it was leaving: the new revision stays.
|
|
97
|
+
if (queue.some((t) => tag(t) === at)) dismiss(id)
|
|
41
98
|
}
|
|
42
99
|
|
|
43
|
-
function onAnimEnd(e: AnimationEvent,
|
|
100
|
+
function onAnimEnd(e: AnimationEvent, t: ToastItem) {
|
|
44
101
|
// Only the exit keyframe should trigger removal, not toast-in.
|
|
45
|
-
if (e.animationName === "toast-out") finishClose(id)
|
|
102
|
+
if (e.animationName === "toast-out") finishClose(t.id, tag(t))
|
|
46
103
|
}
|
|
47
104
|
|
|
48
105
|
const variantIcon: Record<ToastItem["variant"], string> = {
|
|
@@ -97,7 +154,7 @@ watch(
|
|
|
97
154
|
// Same for the closing set — a toast evicted by the queue cap or clear()
|
|
98
155
|
// leaves without an animationend, so prune dead ids here.
|
|
99
156
|
if (closing.value.size) {
|
|
100
|
-
const live = new Set([...closing.value].filter((
|
|
157
|
+
const live = new Set([...closing.value].filter((at) => queue.some((t) => tag(t) === at)))
|
|
101
158
|
if (live.size !== closing.value.size) closing.value = live
|
|
102
159
|
}
|
|
103
160
|
if (queue.some((t) => t.dismissAt !== null)) startTimer()
|
|
@@ -126,16 +183,41 @@ function progress(t: ToastItem): number {
|
|
|
126
183
|
return Math.max(0, Math.min(100, (remaining / t.duration) * 100))
|
|
127
184
|
}
|
|
128
185
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
186
|
+
/*
|
|
187
|
+
* reka cancels the click that ends a swipe by adding a listener after ours, so
|
|
188
|
+
* by the time it runs our handler has already acted on it: a swiped-away
|
|
189
|
+
* "View details" toast navigated. The swipe marks the toast instead, and the
|
|
190
|
+
* mark lasts only until that trailing click has had its chance.
|
|
191
|
+
*/
|
|
192
|
+
let swiped: string | null = null
|
|
193
|
+
function onSwipeDone(t: ToastItem) {
|
|
194
|
+
swiped = tag(t)
|
|
195
|
+
window.setTimeout(() => (swiped = null), 0)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const busy = ref<Set<string>>(new Set())
|
|
199
|
+
|
|
200
|
+
async function settle(t: ToastItem, run: () => void | Promise<void>) {
|
|
201
|
+
const at = tag(t)
|
|
202
|
+
if (busy.value.has(at)) return
|
|
203
|
+
busy.value = new Set(busy.value).add(at)
|
|
204
|
+
try {
|
|
205
|
+
await run()
|
|
206
|
+
beginClose(t)
|
|
207
|
+
} finally {
|
|
208
|
+
const next = new Set(busy.value)
|
|
209
|
+
next.delete(at)
|
|
210
|
+
busy.value = next
|
|
133
211
|
}
|
|
134
212
|
}
|
|
135
213
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
214
|
+
function runClick(t: ToastItem) {
|
|
215
|
+
if (swiped === tag(t)) return
|
|
216
|
+
if (t.onClick) return settle(t, t.onClick)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>[number]) {
|
|
220
|
+
return settle(t, action.onClick)
|
|
139
221
|
}
|
|
140
222
|
</script>
|
|
141
223
|
|
|
@@ -143,13 +225,16 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
|
|
|
143
225
|
<ToastProvider>
|
|
144
226
|
<ToastRoot
|
|
145
227
|
v-for="t in queue"
|
|
146
|
-
:key="t
|
|
147
|
-
:open="!closing.has(t
|
|
228
|
+
:key="tag(t)"
|
|
229
|
+
:open="!closing.has(tag(t))"
|
|
148
230
|
:duration="t.duration"
|
|
231
|
+
:type="t.variant === 'error' || t.variant === 'warning' ? 'foreground' : 'background'"
|
|
149
232
|
class="pointer-events-auto relative overflow-hidden rounded-card border border-line bg-surface-raised shadow-panel data-[state=open]:animate-[toast-in_200ms_var(--ease-emphasized)] data-[state=closed]:animate-[toast-out_140ms_ease]"
|
|
150
233
|
:class="{ 'cursor-pointer': t.onClick }"
|
|
151
|
-
@update:open="(open) => !open && beginClose(t
|
|
152
|
-
@animationend="(e: AnimationEvent) => onAnimEnd(e, t
|
|
234
|
+
@update:open="(open) => !open && beginClose(t)"
|
|
235
|
+
@animationend="(e: AnimationEvent) => onAnimEnd(e, t)"
|
|
236
|
+
@swipe-end="onSwipeDone(t)"
|
|
237
|
+
@swipe-cancel="onSwipeDone(t)"
|
|
153
238
|
@pause="onPause(t)"
|
|
154
239
|
@resume="onResume(t)"
|
|
155
240
|
@click="runClick(t)"
|
|
@@ -178,7 +263,9 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
|
|
|
178
263
|
v-for="(action, i) in t.actions"
|
|
179
264
|
:key="i"
|
|
180
265
|
type="button"
|
|
181
|
-
|
|
266
|
+
:disabled="busy.has(tag(t))"
|
|
267
|
+
:aria-busy="busy.has(tag(t)) || undefined"
|
|
268
|
+
class="inline-flex items-center gap-1.5 rounded-control disabled:opacity-60 border border-line bg-surface-soft px-2.5 py-1 text-xs font-medium text-ink outline-none transition-[border-color,background-color,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] hover:border-line-strong hover:bg-surface-soft/70 active:scale-95 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring"
|
|
182
269
|
@click="runAction(t, action)"
|
|
183
270
|
>
|
|
184
271
|
<LpIcon v-if="action.icon" :name="action.icon" :size="14" />
|
|
@@ -190,8 +277,8 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
|
|
|
190
277
|
<button
|
|
191
278
|
type="button"
|
|
192
279
|
class="group -mr-1 -mt-1 rounded-md p-1 text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink"
|
|
193
|
-
aria-label="
|
|
194
|
-
@click.stop="beginClose(t
|
|
280
|
+
:aria-label="text.dismiss"
|
|
281
|
+
@click.stop="beginClose(t)"
|
|
195
282
|
>
|
|
196
283
|
<LpIcon
|
|
197
284
|
name="lucide:x"
|
|
@@ -216,6 +303,7 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
|
|
|
216
303
|
--z-toast (200) > --z-modal (110) wins as intended. -->
|
|
217
304
|
<Teleport to="body">
|
|
218
305
|
<ToastViewport
|
|
306
|
+
:data-lp-toasts="viewportId"
|
|
219
307
|
class="fixed bottom-0 right-0 z-(--z-toast) flex w-[min(92vw,22rem)] flex-col gap-2 p-4 outline-none"
|
|
220
308
|
/>
|
|
221
309
|
</Teleport>
|
|
@@ -28,13 +28,14 @@
|
|
|
28
28
|
* them and the control stops opening. Put `aria-label` on the trigger instead.
|
|
29
29
|
*/
|
|
30
30
|
import {
|
|
31
|
+
injectTooltipProviderContext,
|
|
31
32
|
TooltipContent,
|
|
32
33
|
TooltipPortal,
|
|
33
34
|
TooltipProvider,
|
|
34
35
|
TooltipRoot,
|
|
35
36
|
TooltipTrigger,
|
|
36
37
|
} from "reka-ui"
|
|
37
|
-
import { onBeforeUnmount, ref, watch } from "vue"
|
|
38
|
+
import { onBeforeUnmount, ref, watch, type FunctionalComponent } from "vue"
|
|
38
39
|
import {
|
|
39
40
|
springSettled,
|
|
40
41
|
springStep,
|
|
@@ -54,7 +55,7 @@ const props = withDefaults(
|
|
|
54
55
|
* adding a description.
|
|
55
56
|
*/
|
|
56
57
|
label?: boolean
|
|
57
|
-
/** Milliseconds before the tip appears on hover. */
|
|
58
|
+
/** Milliseconds before the tip appears on hover. Unset follows the provider, else 200. */
|
|
58
59
|
delay?: number
|
|
59
60
|
/**
|
|
60
61
|
* Show on any focus, not just keyboard focus. Off by default: a tip that
|
|
@@ -83,7 +84,7 @@ const props = withDefaults(
|
|
|
83
84
|
*/
|
|
84
85
|
disabled?: boolean
|
|
85
86
|
}>(),
|
|
86
|
-
{
|
|
87
|
+
{ side: "top" },
|
|
87
88
|
)
|
|
88
89
|
|
|
89
90
|
/*
|
|
@@ -101,6 +102,23 @@ const props = withDefaults(
|
|
|
101
102
|
* the pointer exactly reads as jitter, while a slight lag reads as following.
|
|
102
103
|
* Same reasoning, and the same shared "float" feel, as LpChart's readout.
|
|
103
104
|
*/
|
|
105
|
+
/*
|
|
106
|
+
* A provider of its own only when none is above. Each tip in its own provider
|
|
107
|
+
* meant moving along a toolbar waited the full delay at every button; under a
|
|
108
|
+
* shared one (LpTooltipProvider at the root) the next tip opens at once.
|
|
109
|
+
*/
|
|
110
|
+
const shared = injectTooltipProviderContext(null)
|
|
111
|
+
/*
|
|
112
|
+
* Under a shared provider the tip renders straight into it. `Fragment` cannot
|
|
113
|
+
* stand in for the wrapper: given through `<component :is>` it takes slots,
|
|
114
|
+
* not children, and rendered nothing at all, so every tip inside an
|
|
115
|
+
* LpTooltipProvider vanished together with its trigger.
|
|
116
|
+
*/
|
|
117
|
+
const PassThrough: FunctionalComponent = (_, { slots }) => slots.default?.()
|
|
118
|
+
|
|
119
|
+
const reducedMotion = () =>
|
|
120
|
+
typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
|
121
|
+
|
|
104
122
|
const target = { x: 0, y: 0 }
|
|
105
123
|
const offset = ref({ x: 0, y: 0 })
|
|
106
124
|
|
|
@@ -155,6 +173,11 @@ const onPointerMove = (e: PointerEvent) => {
|
|
|
155
173
|
const half = props.side === "top" || props.side === "bottom" ? rect.height / 2 : 0
|
|
156
174
|
const dir = props.side === "bottom" ? -1 : 1
|
|
157
175
|
target.y = e.clientY - (rect.top + rect.height / 2) + half * dir
|
|
176
|
+
// Without motion the tip goes straight to the reading instead of trailing.
|
|
177
|
+
if (reducedMotion()) {
|
|
178
|
+
offset.value = { x: target.x, y: target.y }
|
|
179
|
+
return
|
|
180
|
+
}
|
|
158
181
|
if (!frame) frame = requestAnimationFrame(settle)
|
|
159
182
|
}
|
|
160
183
|
|
|
@@ -205,7 +228,7 @@ onBeforeUnmount(() => {
|
|
|
205
228
|
</script>
|
|
206
229
|
|
|
207
230
|
<template>
|
|
208
|
-
<TooltipProvider
|
|
231
|
+
<component :is="shared ? PassThrough : TooltipProvider" v-bind="shared ? {} : { delayDuration: delay ?? 200 }">
|
|
209
232
|
<!--
|
|
210
233
|
`disable-closing-trigger` in follow mode: reka dismisses a tip when its
|
|
211
234
|
trigger is clicked, which is right for a button (the tip explained the
|
|
@@ -214,6 +237,7 @@ onBeforeUnmount(() => {
|
|
|
214
237
|
-->
|
|
215
238
|
<TooltipRoot
|
|
216
239
|
:open="open"
|
|
240
|
+
:delay-duration="delay"
|
|
217
241
|
:ignore-non-keyboard-focus="!showOnPointerFocus"
|
|
218
242
|
:disable-hoverable-content="followCursor"
|
|
219
243
|
:disable-closing-trigger="followCursor"
|
|
@@ -245,7 +269,7 @@ onBeforeUnmount(() => {
|
|
|
245
269
|
</TooltipContent>
|
|
246
270
|
</TooltipPortal>
|
|
247
271
|
</TooltipRoot>
|
|
248
|
-
</
|
|
272
|
+
</component>
|
|
249
273
|
</template>
|
|
250
274
|
|
|
251
275
|
<style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* Shared timing for every LpTooltip below it: once one tip is showing, moving
|
|
4
|
+
* to the next opens it without waiting the delay again. Mount once near the
|
|
5
|
+
* app root; tooltips outside any provider keep working on their own.
|
|
6
|
+
*/
|
|
7
|
+
import { TooltipProvider } from "reka-ui"
|
|
8
|
+
|
|
9
|
+
withDefaults(
|
|
10
|
+
defineProps<{
|
|
11
|
+
/** Milliseconds before the first tip appears on hover. */
|
|
12
|
+
delay?: number
|
|
13
|
+
/** How long after a tip closes the next one still opens at once. */
|
|
14
|
+
skipDelay?: number
|
|
15
|
+
}>(),
|
|
16
|
+
{ delay: 200, skipDelay: 300 },
|
|
17
|
+
)
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<TooltipProvider :delay-duration="delay" :skip-delay-duration="skipDelay">
|
|
22
|
+
<slot />
|
|
23
|
+
</TooltipProvider>
|
|
24
|
+
</template>
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
type NodeTypesObject,
|
|
23
23
|
} from "@vue-flow/core"
|
|
24
24
|
import { MiniMap } from "@vue-flow/minimap"
|
|
25
|
-
import { computed, markRaw, watch, type Component } from "vue"
|
|
25
|
+
import { computed, defineComponent, h, markRaw, watch, type Component } from "vue"
|
|
26
26
|
import LpInfraNode, { type InfraNodeData } from "./LpInfraNode.vue"
|
|
27
27
|
import LpLaneNode from "./LpLaneNode.vue"
|
|
28
28
|
import LpServiceNode, { type ServiceNodeData } from "./LpServiceNode.vue"
|
|
@@ -193,7 +193,18 @@ const emit = defineEmits<{
|
|
|
193
193
|
// their own props (data/selected) which Vue Flow supplies at runtime as part of
|
|
194
194
|
// NodeProps. Widen to `Component` (not the concrete DefineComponent) so the
|
|
195
195
|
// registry types cleanly without a cast.
|
|
196
|
+
// Vue Flow ships no "group" renderer, so a host frame without one warns and
|
|
197
|
+
// draws nothing inside its styled box. The frame only needs its label.
|
|
198
|
+
const GroupNode = defineComponent({
|
|
199
|
+
props: { label: { type: [String, Object], default: undefined } },
|
|
200
|
+
setup: (props) => () =>
|
|
201
|
+
typeof props.label === "string"
|
|
202
|
+
? h("div", { class: "px-3 pt-2 text-xs font-medium text-muted" }, props.label)
|
|
203
|
+
: null,
|
|
204
|
+
})
|
|
205
|
+
|
|
196
206
|
const nodeTypes: NodeTypesObject = {
|
|
207
|
+
group: markRaw(GroupNode) as Component,
|
|
197
208
|
infra: markRaw(LpInfraNode) as Component,
|
|
198
209
|
service: markRaw(LpServiceNode) as Component,
|
|
199
210
|
lane: markRaw(LpLaneNode) as Component,
|
|
@@ -406,8 +417,12 @@ defineExpose({ fitView, setViewport: applyViewport })
|
|
|
406
417
|
class="lp-topology"
|
|
407
418
|
>
|
|
408
419
|
<Background :gap="22" :size="1.4" pattern-color="var(--color-line-strong)" />
|
|
420
|
+
<!-- On a phone the minimap, the zoom controls and the legend would all
|
|
421
|
+
share the bottom of a canvas a few hundred pixels wide and cover each
|
|
422
|
+
other and the graph; pinch-zoom and panning do the minimap's job there. -->
|
|
409
423
|
<MiniMap
|
|
410
424
|
v-if="minimap"
|
|
425
|
+
class="max-sm:!hidden"
|
|
411
426
|
pannable
|
|
412
427
|
zoomable
|
|
413
428
|
node-color="var(--color-surface-soft)"
|
|
@@ -416,7 +431,9 @@ defineExpose({ fitView, setViewport: applyViewport })
|
|
|
416
431
|
<Controls v-if="controls" />
|
|
417
432
|
|
|
418
433
|
<!-- Edge-type legend: makes the colour/dash encoding self-explanatory. -->
|
|
419
|
-
|
|
434
|
+
<!-- On a phone the legend would sit on the graph itself; the edge styles
|
|
435
|
+
stay readable without it there. -->
|
|
436
|
+
<Panel v-if="legend" :position="legendPosition" :class="legendOffset" class="max-sm:hidden">
|
|
420
437
|
<div
|
|
421
438
|
class="flex flex-col gap-1.5 rounded-card border border-line bg-surface-overlay/90 px-3 py-2 text-[11px] backdrop-blur"
|
|
422
439
|
>
|
|
@@ -11,6 +11,7 @@ import LpNumberFlow from "./LpNumberFlow.vue"
|
|
|
11
11
|
import LpRollingText from "./LpRollingText.vue"
|
|
12
12
|
import LpTooltip from "./LpTooltip.vue"
|
|
13
13
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
14
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
14
15
|
|
|
15
16
|
export type UptimeStatus = "operational" | "degraded" | "down" | "maintenance" | "empty"
|
|
16
17
|
|
|
@@ -39,6 +40,8 @@ const props = withDefaults(
|
|
|
39
40
|
* `{ degraded: '#ffb84d' }` to recolour just one state.
|
|
40
41
|
*/
|
|
41
42
|
colors?: Partial<Record<UptimeStatus, string>>
|
|
43
|
+
/** Status names, for the tooltip and screen readers. */
|
|
44
|
+
labels?: Partial<Record<UptimeStatus, string>>
|
|
42
45
|
}>(),
|
|
43
46
|
{ showUptime: true },
|
|
44
47
|
)
|
|
@@ -64,13 +67,17 @@ const opacityFor = (status: UptimeStatus): number =>
|
|
|
64
67
|
const colorFor = (status: UptimeStatus): string =>
|
|
65
68
|
props.colors?.[status] ?? defaultColor[status]
|
|
66
69
|
|
|
67
|
-
const statusLabel
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
const statusLabel = useLabels(
|
|
71
|
+
"uptimeBar",
|
|
72
|
+
{
|
|
73
|
+
operational: "Operational",
|
|
74
|
+
degraded: "Degraded",
|
|
75
|
+
down: "Down",
|
|
76
|
+
maintenance: "Maintenance",
|
|
77
|
+
empty: "No data",
|
|
78
|
+
},
|
|
79
|
+
() => props.labels,
|
|
80
|
+
)
|
|
74
81
|
|
|
75
82
|
// Uptime counts only slices with data; operational + maintenance are "up".
|
|
76
83
|
const uptimePct = computed(() => {
|
|
@@ -114,9 +121,35 @@ const tip = computed(() => {
|
|
|
114
121
|
if (i == null) return null
|
|
115
122
|
const seg = props.segments[i]
|
|
116
123
|
if (!seg) return null
|
|
117
|
-
return { status: seg.status, text: seg.label ?? statusLabel[seg.status] }
|
|
124
|
+
return { status: seg.status, text: seg.label ?? statusLabel.value[seg.status] }
|
|
118
125
|
})
|
|
119
126
|
|
|
127
|
+
/*
|
|
128
|
+
* One Tab stop for the whole bar, arrows to walk it. A quarter's worth of
|
|
129
|
+
* slices as separate stops made the bar ninety Tab presses to get past.
|
|
130
|
+
*/
|
|
131
|
+
const focusIndex = ref(-1)
|
|
132
|
+
const slots = ref<HTMLElement[]>([])
|
|
133
|
+
const tabStop = computed(() =>
|
|
134
|
+
focusIndex.value >= 0 && focusIndex.value < props.segments.length
|
|
135
|
+
? focusIndex.value
|
|
136
|
+
: props.segments.length - 1,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
function onSlotKeydown(e: KeyboardEvent, i: number) {
|
|
140
|
+
const last = props.segments.length - 1
|
|
141
|
+
const next =
|
|
142
|
+
e.key === "ArrowRight" ? Math.min(last, i + 1)
|
|
143
|
+
: e.key === "ArrowLeft" ? Math.max(0, i - 1)
|
|
144
|
+
: e.key === "Home" ? 0
|
|
145
|
+
: e.key === "End" ? last
|
|
146
|
+
: -1
|
|
147
|
+
if (next < 0) return
|
|
148
|
+
e.preventDefault()
|
|
149
|
+
focusIndex.value = next
|
|
150
|
+
slots.value[next]?.focus()
|
|
151
|
+
}
|
|
152
|
+
|
|
120
153
|
const uptimeDecimals = computed(() => {
|
|
121
154
|
const v = uptimePct.value
|
|
122
155
|
if (v == null) return 0
|
|
@@ -168,10 +201,15 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
168
201
|
<button
|
|
169
202
|
v-for="(seg, i) in segments"
|
|
170
203
|
:key="i"
|
|
204
|
+
ref="slots"
|
|
171
205
|
type="button"
|
|
172
206
|
class="lp-uptime__slot min-w-0 flex-1 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
207
|
+
:tabindex="i === tabStop ? 0 : -1"
|
|
173
208
|
:aria-label="seg.label ?? statusLabel[seg.status]"
|
|
174
209
|
@pointerenter="hovered = i"
|
|
210
|
+
@focus="focusIndex = i; hovered = i"
|
|
211
|
+
@blur="hovered === i && (hovered = null)"
|
|
212
|
+
@keydown="onSlotKeydown($event, i)"
|
|
175
213
|
>
|
|
176
214
|
<span
|
|
177
215
|
class="lp-uptime__mark block h-full rounded-[1px] transition-[opacity,scale] duration-[var(--duration-fast)] motion-reduce:transition-none"
|
package/src/components/chart.ts
CHANGED
|
@@ -30,6 +30,62 @@ export interface ChartSeries {
|
|
|
30
30
|
|
|
31
31
|
export type ChartAxis = "left" | "right"
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* A reference level drawn across the plot: a line at `value`, or a band from
|
|
35
|
+
* `value` to `to`. SLOs, budgets, alert thresholds.
|
|
36
|
+
*/
|
|
37
|
+
export interface ChartThreshold {
|
|
38
|
+
value: number
|
|
39
|
+
/** Upper (or lower) edge of a band. Omit for a single line. */
|
|
40
|
+
to?: number
|
|
41
|
+
label?: string
|
|
42
|
+
/** Any CSS colour. Defaults to the strong line token. */
|
|
43
|
+
color?: string
|
|
44
|
+
axis?: ChartAxis
|
|
45
|
+
/** Solid instead of the default dashed line. */
|
|
46
|
+
solid?: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A vertical marker at one x position: a deploy, an incident, a release.
|
|
51
|
+
* `at` is a point index or one of the x labels.
|
|
52
|
+
*/
|
|
53
|
+
export interface ChartMarker {
|
|
54
|
+
at: number | string
|
|
55
|
+
label?: string
|
|
56
|
+
color?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The x index a marker points at, or -1 when it names nothing on the axis. */
|
|
60
|
+
export const markerIndex = (at: number | string, labels: string[], count: number): number => {
|
|
61
|
+
if (typeof at === "number") return Number.isInteger(at) && at >= 0 && at < count ? at : -1
|
|
62
|
+
return labels.indexOf(at)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A one-sentence reading of the chart for assistive technology: what is
|
|
67
|
+
* plotted, over how many points, and each series' range and latest value.
|
|
68
|
+
*/
|
|
69
|
+
export const chartSummary = (
|
|
70
|
+
series: NormalSeries[],
|
|
71
|
+
labels: string[],
|
|
72
|
+
fmt: (s: NormalSeries, v: number) => string,
|
|
73
|
+
title?: string,
|
|
74
|
+
): string => {
|
|
75
|
+
const parts: string[] = []
|
|
76
|
+
const span = labels.length ? `${labels.length} points, ${labels[0]} to ${labels[labels.length - 1]}` : ""
|
|
77
|
+
parts.push([title, span].filter(Boolean).join(", "))
|
|
78
|
+
for (const s of series) {
|
|
79
|
+
const values = s.values.filter((v): v is number => v != null)
|
|
80
|
+
if (!values.length) continue
|
|
81
|
+
const last = values[values.length - 1]
|
|
82
|
+
parts.push(
|
|
83
|
+
`${s.name}: from ${fmt(s, Math.min(...values))} to ${fmt(s, Math.max(...values))}, latest ${fmt(s, last)}`,
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
return parts.filter(Boolean).join(". ")
|
|
87
|
+
}
|
|
88
|
+
|
|
33
89
|
export interface NormalSeries {
|
|
34
90
|
name: string
|
|
35
91
|
color: string
|
|
@@ -101,10 +157,12 @@ export const niceScale = (
|
|
|
101
157
|
): { min: number; max: number; step: number } => {
|
|
102
158
|
if (!Number.isFinite(min) || !Number.isFinite(max)) return { min: 0, max: 1, step: 1 }
|
|
103
159
|
if (min === max) {
|
|
104
|
-
// A flat series still needs a band to sit in
|
|
160
|
+
// A flat series still needs a band to sit in, but the band must not cross
|
|
161
|
+
// zero: an all-zero count would otherwise read as dipping to -0.5.
|
|
105
162
|
const pad = Math.abs(min) || 1
|
|
106
|
-
min
|
|
107
|
-
max
|
|
163
|
+
min = min >= 0 ? Math.max(0, min - pad / 2) : min - pad / 2
|
|
164
|
+
max = max <= 0 ? Math.min(0, max + pad / 2) : max + pad / 2
|
|
165
|
+
if (min === max) max = min + pad
|
|
108
166
|
}
|
|
109
167
|
const raw = (max - min) / Math.max(1, ticks)
|
|
110
168
|
const mag = 10 ** Math.floor(Math.log10(raw))
|
|
@@ -592,6 +650,7 @@ export const calloutLabels = (
|
|
|
592
650
|
return side === "right" ? a : -a
|
|
593
651
|
}
|
|
594
652
|
const list = sides[side].sort((p, q) => rank(p.mid) - rank(q.mid))
|
|
653
|
+
if (!list.length) continue
|
|
595
654
|
|
|
596
655
|
/*
|
|
597
656
|
* Resolve collisions by NUDGING, not by stacking.
|
|
@@ -676,11 +735,26 @@ export const calloutLabels = (
|
|
|
676
735
|
return out
|
|
677
736
|
}
|
|
678
737
|
|
|
679
|
-
/** Compact default tick formatting: 1.2k, 3.4M — never 1200.0000001. */
|
|
738
|
+
/** Compact default tick formatting: 1.2k, 3.4M — never 1200.0000001 or 1000k. */
|
|
680
739
|
export const formatTick = (v: number): string => {
|
|
740
|
+
if (!Number.isFinite(v)) return String(v)
|
|
741
|
+
const units: [number, string][] = [
|
|
742
|
+
[1e3, "k"],
|
|
743
|
+
[1e6, "M"],
|
|
744
|
+
[1e9, "B"],
|
|
745
|
+
[1e12, "T"],
|
|
746
|
+
]
|
|
681
747
|
const abs = Math.abs(v)
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if (
|
|
685
|
-
|
|
748
|
+
let i = units.length - 1
|
|
749
|
+
while (i >= 0 && abs < units[i][0]) i--
|
|
750
|
+
if (i < 0) {
|
|
751
|
+
const plain = Number(v.toFixed(2))
|
|
752
|
+
return Math.abs(plain) >= 1000 ? formatTick(Math.sign(v) * 1000) : String(plain)
|
|
753
|
+
}
|
|
754
|
+
const scaled = Number((v / units[i][0]).toFixed(1))
|
|
755
|
+
// 999 950 rounds to "1000k"; it reads as the next unit up.
|
|
756
|
+
if (Math.abs(scaled) >= 1000 && i < units.length - 1) {
|
|
757
|
+
return `${Number((v / units[i + 1][0]).toFixed(1))}${units[i + 1][1]}`
|
|
758
|
+
}
|
|
759
|
+
return `${scaled}${units[i][1]}`
|
|
686
760
|
}
|