@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
|
@@ -18,6 +18,7 @@ export default { inheritAttrs: false }
|
|
|
18
18
|
*/
|
|
19
19
|
import { Motion } from "motion-v"
|
|
20
20
|
import { computed, ref, shallowRef, watch, onMounted, onBeforeUnmount } from "vue"
|
|
21
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
21
22
|
import { easeTravel, prefersReducedMotion } from "../composables/easing"
|
|
22
23
|
import { usePillTransition } from "../composables/usePillTransition"
|
|
23
24
|
import { useReveal } from "../composables/useReveal"
|
|
@@ -25,6 +26,8 @@ import {
|
|
|
25
26
|
areaPath,
|
|
26
27
|
axisTicks,
|
|
27
28
|
barPath,
|
|
29
|
+
chartSummary,
|
|
30
|
+
markerIndex,
|
|
28
31
|
extent,
|
|
29
32
|
formatTick,
|
|
30
33
|
linePath,
|
|
@@ -38,12 +41,32 @@ import {
|
|
|
38
41
|
xAt,
|
|
39
42
|
yAt,
|
|
40
43
|
type Box,
|
|
44
|
+
type ChartMarker,
|
|
41
45
|
type ChartSeries,
|
|
46
|
+
type ChartThreshold,
|
|
42
47
|
} from "./chart"
|
|
43
48
|
import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
|
|
44
49
|
import LpIcon from "./LpIcon.vue"
|
|
45
50
|
import LpShift from "./LpShift.vue"
|
|
46
51
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
52
|
+
import { backgroundOf, downloadFile, fileStem, resolveCssColor, svgToPng, toCsv, type CsvOptions } from "./exportData"
|
|
53
|
+
|
|
54
|
+
export interface ChartText {
|
|
55
|
+
[key: string]: string
|
|
56
|
+
empty: string
|
|
57
|
+
rightAxis: string
|
|
58
|
+
/** Prefix of the keyboard readout, before the point's label. */
|
|
59
|
+
point: string
|
|
60
|
+
/** Header of the label column in a CSV export. */
|
|
61
|
+
xColumn: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ChartExportOptions {
|
|
65
|
+
/** File name without extension. Defaults to the title. */
|
|
66
|
+
filename?: string
|
|
67
|
+
/** Return the data without offering a download. */
|
|
68
|
+
download?: boolean
|
|
69
|
+
}
|
|
47
70
|
|
|
48
71
|
/** What was under the pointer when a point was clicked or right-clicked. */
|
|
49
72
|
export interface ChartPointEvent {
|
|
@@ -94,6 +117,16 @@ const props = withDefaults(
|
|
|
94
117
|
animate?: boolean
|
|
95
118
|
/** Shown when every series is empty. */
|
|
96
119
|
emptyLabel?: string
|
|
120
|
+
/** Reference lines and bands (SLOs, budgets, alert levels). */
|
|
121
|
+
thresholds?: ChartThreshold[]
|
|
122
|
+
/** Vertical markers at x positions (deploys, incidents). */
|
|
123
|
+
markers?: ChartMarker[]
|
|
124
|
+
/** Accessible name for the plot. Defaults to a generated summary. */
|
|
125
|
+
ariaLabel?: string
|
|
126
|
+
/** Render the data as a visually hidden table for screen readers. */
|
|
127
|
+
dataTable?: boolean
|
|
128
|
+
/** Text overrides (`labels` is taken by the x axis). */
|
|
129
|
+
text?: Partial<ChartText>
|
|
97
130
|
/**
|
|
98
131
|
* Right-click menu for a point, built from whatever was hit. Return [] to
|
|
99
132
|
* leave the browser's own menu alone for that point. Which actions a point
|
|
@@ -113,10 +146,18 @@ const props = withDefaults(
|
|
|
113
146
|
showLegend: true,
|
|
114
147
|
yTicks: 4,
|
|
115
148
|
animate: true,
|
|
116
|
-
|
|
149
|
+
thresholds: () => [],
|
|
150
|
+
markers: () => [],
|
|
151
|
+
dataTable: false,
|
|
117
152
|
},
|
|
118
153
|
)
|
|
119
154
|
|
|
155
|
+
const strings = useLabels<ChartText>(
|
|
156
|
+
"chart",
|
|
157
|
+
{ empty: "No data", rightAxis: "Measured on the right axis", point: "Point", xColumn: "Label" },
|
|
158
|
+
() => props.text,
|
|
159
|
+
)
|
|
160
|
+
|
|
120
161
|
const emit = defineEmits<{
|
|
121
162
|
/** Click on a point/bar. */
|
|
122
163
|
(e: "pointClick", payload: ChartPointEvent): void
|
|
@@ -139,12 +180,16 @@ let ro: ResizeObserver | null = null
|
|
|
139
180
|
* two combine into one question: should this draw itself right now?
|
|
140
181
|
*/
|
|
141
182
|
const { el: revealAnchor, revealed } = useReveal({ immediate: () => !props.animate })
|
|
142
|
-
|
|
183
|
+
// SMIL ignores the reduced-motion media query, so the wipe is skipped here.
|
|
184
|
+
const reduced = ref(false)
|
|
185
|
+
const still = computed(() => !props.animate || reduced.value)
|
|
186
|
+
const drawing = computed(() => !still.value && revealed.value)
|
|
143
187
|
|
|
144
188
|
onMounted(() => {
|
|
145
189
|
// One element, two observers: hand the composable the same node rather than
|
|
146
190
|
// wrapping the chart in a spare div just to carry a second ref.
|
|
147
191
|
revealAnchor.value = root.value
|
|
192
|
+
reduced.value = prefersReducedMotion()
|
|
148
193
|
if (!root.value || typeof ResizeObserver === "undefined") return
|
|
149
194
|
ro = new ResizeObserver(([entry]) => {
|
|
150
195
|
width.value = entry.contentRect.width
|
|
@@ -165,16 +210,19 @@ onBeforeUnmount(() => {
|
|
|
165
210
|
// ── data ───────────────────────────────────────
|
|
166
211
|
const hidden = ref(new Set<string>())
|
|
167
212
|
|
|
168
|
-
const toggleSeries = (
|
|
213
|
+
const toggleSeries = (id: string) => {
|
|
169
214
|
const next = new Set(hidden.value)
|
|
170
|
-
if (next.has(
|
|
171
|
-
else next.add(
|
|
215
|
+
if (next.has(id)) next.delete(id)
|
|
216
|
+
else next.add(id)
|
|
172
217
|
// Never hide the last visible series — an empty plot reads as broken.
|
|
173
218
|
if (next.size < props.series.length) hidden.value = next
|
|
174
219
|
}
|
|
175
220
|
|
|
176
|
-
|
|
177
|
-
const
|
|
221
|
+
// Identity is position plus name, so two series sharing a name stay separate.
|
|
222
|
+
const allSeries = computed(() =>
|
|
223
|
+
normalise(props.series).map((s, i) => ({ ...s, id: `${i}:${s.name}` })),
|
|
224
|
+
)
|
|
225
|
+
const visible = computed(() => allSeries.value.filter((s) => !hidden.value.has(s.id)))
|
|
178
226
|
|
|
179
227
|
/*
|
|
180
228
|
* How present each series is, 1 to 0, driven per frame rather than by CSS.
|
|
@@ -206,7 +254,7 @@ let presenceFrame: number | undefined
|
|
|
206
254
|
* reported the end state — so a series being hidden jumped to the floor while
|
|
207
255
|
* one being shown eased up from it.
|
|
208
256
|
*/
|
|
209
|
-
const presenceOf = (
|
|
257
|
+
const presenceOf = (id: string) => presence.value.get(id) ?? 1
|
|
210
258
|
|
|
211
259
|
watch(
|
|
212
260
|
// A string, not the Set: `hidden` is replaced wholesale on every toggle, and a
|
|
@@ -214,15 +262,15 @@ watch(
|
|
|
214
262
|
// and sees a change it cannot describe — or, with a stale copy in the array,
|
|
215
263
|
// no change at all. The names that are currently off say it exactly.
|
|
216
264
|
() =>
|
|
217
|
-
allSeries.value.map((s) => `${s.
|
|
265
|
+
allSeries.value.map((s) => `${s.id}:${hidden.value.has(s.id) ? 0 : 1}`).join(","),
|
|
218
266
|
() => {
|
|
219
|
-
const targets = new Map(allSeries.value.map((s) => [s.
|
|
267
|
+
const targets = new Map(allSeries.value.map((s) => [s.id, hidden.value.has(s.id) ? 0 : 1]))
|
|
220
268
|
if (!props.animate || prefersReducedMotion()) {
|
|
221
269
|
presence.value = targets
|
|
222
270
|
return
|
|
223
271
|
}
|
|
224
272
|
|
|
225
|
-
const from = new Map(allSeries.value.map((s) => [s.
|
|
273
|
+
const from = new Map(allSeries.value.map((s) => [s.id, presenceOf(s.id)]))
|
|
226
274
|
if ([...targets].every(([name, to]) => from.get(name) === to)) return
|
|
227
275
|
|
|
228
276
|
const start = performance.now()
|
|
@@ -299,12 +347,27 @@ const hasRightAxis = computed(
|
|
|
299
347
|
() => declaredRight.value.length > 0 && declaredLeft.value.length > 0,
|
|
300
348
|
)
|
|
301
349
|
|
|
350
|
+
/** A reference level must be on the plot, or it says nothing. */
|
|
351
|
+
const withThresholds = (raw: { min: number; max: number }, axis: "left" | "right") => {
|
|
352
|
+
let { min, max } = raw
|
|
353
|
+
for (const t of props.thresholds) {
|
|
354
|
+
if ((t.axis ?? "left") !== axis) continue
|
|
355
|
+
for (const v of [t.value, t.to]) {
|
|
356
|
+
if (v == null || !Number.isFinite(v)) continue
|
|
357
|
+
min = Math.min(min, v)
|
|
358
|
+
max = Math.max(max, v)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return { min, max }
|
|
362
|
+
}
|
|
363
|
+
|
|
302
364
|
const scaleFor = (members: typeof visible.value) => {
|
|
303
365
|
const totals = stackTotals.value
|
|
304
366
|
const raw = totals
|
|
305
367
|
? { min: Math.min(0, ...totals.neg), max: Math.max(0, ...totals.pos) }
|
|
306
368
|
: extent(members, zeroBased.value)
|
|
307
|
-
|
|
369
|
+
const wide = hasData.value ? withThresholds(raw, "left") : raw
|
|
370
|
+
return niceScale(wide.min, wide.max, props.yTicks)
|
|
308
371
|
}
|
|
309
372
|
|
|
310
373
|
const targetScale = computed(() =>
|
|
@@ -367,7 +430,7 @@ const targetRightScale = computed(() => {
|
|
|
367
430
|
axis holds the range it had — an axis that keeps its place while its curve
|
|
368
431
|
leaves, rather than collapsing to a degenerate 0..1 behind it. */
|
|
369
432
|
const members = onRight.value.length ? onRight.value : declaredRight.value
|
|
370
|
-
const raw = extent(members, zeroBased.value)
|
|
433
|
+
const raw = withThresholds(extent(members, zeroBased.value), "right")
|
|
371
434
|
/* Steps come from the TARGET left scale. Reading the animated one rebuilt this
|
|
372
435
|
axis from a fractional range every frame, so a right-hand series jittered
|
|
373
436
|
against a scale being redrawn underneath it. */
|
|
@@ -588,7 +651,7 @@ const baseY = computed(() =>
|
|
|
588
651
|
const lines = computed(() =>
|
|
589
652
|
allSeries.value.map((s) => {
|
|
590
653
|
const g = geoOf(s)
|
|
591
|
-
const p = presenceOf(s.
|
|
654
|
+
const p = presenceOf(s.id)
|
|
592
655
|
// Values pulled toward the axis floor, so the curve flattens into it.
|
|
593
656
|
const base = Math.min(Math.max(0, g.min), g.max)
|
|
594
657
|
const values = p === 1 ? s.values : s.values.map((v) => (v == null ? null : base + (v - base) * p))
|
|
@@ -660,7 +723,7 @@ const bars = computed(() => {
|
|
|
660
723
|
h = Math.abs(top - base)
|
|
661
724
|
}
|
|
662
725
|
out.push({
|
|
663
|
-
key: `${s.
|
|
726
|
+
key: `${s.id}-${i}`,
|
|
664
727
|
x,
|
|
665
728
|
y,
|
|
666
729
|
w: Math.max(1, slot - (props.stacked ? 0 : 2)),
|
|
@@ -711,6 +774,7 @@ const active = ref(-1)
|
|
|
711
774
|
const pointer = ref({ x: 0, y: 0 })
|
|
712
775
|
|
|
713
776
|
const onMove = (e: PointerEvent) => {
|
|
777
|
+
keyboard.value = false
|
|
714
778
|
const el = e.currentTarget as SVGElement
|
|
715
779
|
const rect = el.getBoundingClientRect()
|
|
716
780
|
const x = e.clientX - rect.left
|
|
@@ -729,7 +793,11 @@ const pointEvent = (originalEvent: MouseEvent): ChartPointEvent => ({
|
|
|
729
793
|
originalEvent,
|
|
730
794
|
})
|
|
731
795
|
|
|
796
|
+
/* A tap fires pointerleave before click, which has already cleared the hover,
|
|
797
|
+
so the index comes from the click itself. */
|
|
732
798
|
const onClick = (e: MouseEvent) => {
|
|
799
|
+
const rect = (e.currentTarget as SVGElement).getBoundingClientRect()
|
|
800
|
+
active.value = nearestIndex(e.clientX - rect.left, geo.value)
|
|
733
801
|
if (active.value < 0) return
|
|
734
802
|
emit("pointClick", pointEvent(e))
|
|
735
803
|
}
|
|
@@ -772,6 +840,7 @@ const activePoints = computed(() => {
|
|
|
772
840
|
const sc = scaleOf(s)
|
|
773
841
|
return {
|
|
774
842
|
color: s.color,
|
|
843
|
+
id: s.id,
|
|
775
844
|
name: s.name,
|
|
776
845
|
value: v,
|
|
777
846
|
y: yAt(v, sc.min, sc.max, box.value),
|
|
@@ -783,6 +852,7 @@ const tooltipRows = computed(() =>
|
|
|
783
852
|
active.value < 0
|
|
784
853
|
? []
|
|
785
854
|
: visible.value.map((s) => ({
|
|
855
|
+
id: s.id,
|
|
786
856
|
name: s.name,
|
|
787
857
|
color: s.color,
|
|
788
858
|
text:
|
|
@@ -825,12 +895,13 @@ const floatTransition = usePillTransition("float")
|
|
|
825
895
|
// One clip rect wipes across the plot; re-keyed on data change so an update
|
|
826
896
|
// replays the reveal instead of snapping.
|
|
827
897
|
const revealKey = shallowRef(0)
|
|
898
|
+
/* Keyed on the chart's SHAPE, not its values: a polled dashboard refreshing
|
|
899
|
+
its numbers every few seconds must not re-wipe on every tick. */
|
|
828
900
|
watch(
|
|
829
|
-
() =>
|
|
901
|
+
() => `${props.type}|${count.value}|${allSeries.value.map((s) => s.id).join(",")}`,
|
|
830
902
|
() => {
|
|
831
903
|
revealKey.value++
|
|
832
904
|
},
|
|
833
|
-
{ deep: true },
|
|
834
905
|
)
|
|
835
906
|
|
|
836
907
|
const clipId = `lp-chart-${Math.random().toString(36).slice(2, 9)}`
|
|
@@ -854,6 +925,171 @@ const clipId = `lp-chart-${Math.random().toString(36).slice(2, 9)}`
|
|
|
854
925
|
const gradientId = (name: string, index: number) =>
|
|
855
926
|
`${clipId}-g-${index}-${name.replace(/[^a-zA-Z0-9_-]/g, "-")}`
|
|
856
927
|
|
|
928
|
+
// ── overlays ───────────────────────────────────
|
|
929
|
+
const thresholdMarks = computed(() =>
|
|
930
|
+
props.thresholds.flatMap((t, i) => {
|
|
931
|
+
if (!Number.isFinite(t.value) || width.value <= 0) return []
|
|
932
|
+
const sc = t.axis === "right" && rightScale.value ? rightScale.value : scale.value
|
|
933
|
+
const y = yAt(t.value, sc.min, sc.max, box.value)
|
|
934
|
+
const to = t.to != null && Number.isFinite(t.to) ? yAt(t.to, sc.min, sc.max, box.value) : null
|
|
935
|
+
return [
|
|
936
|
+
{
|
|
937
|
+
key: i,
|
|
938
|
+
y,
|
|
939
|
+
top: to == null ? y : Math.min(y, to),
|
|
940
|
+
height: to == null ? 0 : Math.abs(to - y),
|
|
941
|
+
label: t.label,
|
|
942
|
+
color: t.color ?? "var(--color-line-strong)",
|
|
943
|
+
solid: !!t.solid,
|
|
944
|
+
},
|
|
945
|
+
]
|
|
946
|
+
}),
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
const markerMarks = computed(() =>
|
|
950
|
+
props.markers.flatMap((m, i) => {
|
|
951
|
+
const index = markerIndex(m.at, labels.value, count.value)
|
|
952
|
+
if (index < 0 || width.value <= 0) return []
|
|
953
|
+
return [
|
|
954
|
+
{
|
|
955
|
+
key: i,
|
|
956
|
+
x: xAt(index, count.value, box.value, banded.value),
|
|
957
|
+
label: m.label,
|
|
958
|
+
color: m.color ?? "var(--color-line-strong)",
|
|
959
|
+
},
|
|
960
|
+
]
|
|
961
|
+
}),
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
// ── keyboard & assistive text ──────────────────
|
|
965
|
+
const summary = computed(
|
|
966
|
+
() => props.ariaLabel ?? chartSummary(visible.value, labels.value, fmtSeries, props.title),
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
/* Arrow keys walk the points the way the pointer does; the readout below is
|
|
970
|
+
announced politely so each step is heard without stealing focus. */
|
|
971
|
+
const keyboard = ref(false)
|
|
972
|
+
|
|
973
|
+
const onKeydown = (e: KeyboardEvent) => {
|
|
974
|
+
if (!count.value || !hasData.value) return
|
|
975
|
+
const last = count.value - 1
|
|
976
|
+
let next: number
|
|
977
|
+
switch (e.key) {
|
|
978
|
+
case "ArrowRight":
|
|
979
|
+
next = active.value < 0 ? 0 : Math.min(last, active.value + 1)
|
|
980
|
+
break
|
|
981
|
+
case "ArrowLeft":
|
|
982
|
+
next = active.value < 0 ? last : Math.max(0, active.value - 1)
|
|
983
|
+
break
|
|
984
|
+
case "Home":
|
|
985
|
+
next = 0
|
|
986
|
+
break
|
|
987
|
+
case "End":
|
|
988
|
+
next = last
|
|
989
|
+
break
|
|
990
|
+
case "Enter":
|
|
991
|
+
case " ":
|
|
992
|
+
if (active.value < 0) return
|
|
993
|
+
e.preventDefault()
|
|
994
|
+
emit("pointClick", pointEvent(new MouseEvent("click")))
|
|
995
|
+
return
|
|
996
|
+
case "Escape":
|
|
997
|
+
active.value = -1
|
|
998
|
+
return
|
|
999
|
+
default:
|
|
1000
|
+
return
|
|
1001
|
+
}
|
|
1002
|
+
e.preventDefault()
|
|
1003
|
+
keyboard.value = true
|
|
1004
|
+
active.value = next
|
|
1005
|
+
const first = activePoints.value[0]
|
|
1006
|
+
pointer.value = {
|
|
1007
|
+
x: activeX.value,
|
|
1008
|
+
y: first ? first.y : box.value.top + plotHeight(box.value) / 2,
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const onBlur = () => {
|
|
1013
|
+
if (keyboard.value) active.value = -1
|
|
1014
|
+
keyboard.value = false
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const readout = computed(() =>
|
|
1018
|
+
keyboard.value && active.value >= 0
|
|
1019
|
+
? `${labels.value[active.value] ?? `${strings.value.point} ${active.value + 1}`}: ${tooltipRows.value
|
|
1020
|
+
.map((r) => `${r.name} ${r.text}`)
|
|
1021
|
+
.join(", ")}`
|
|
1022
|
+
: "",
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
// ── export ─────────────────────────────────────
|
|
1026
|
+
// Every series, hidden ones included: what a reader toggled off for a closer
|
|
1027
|
+
// look is still part of the data they asked to take away.
|
|
1028
|
+
const svgEl = ref<SVGSVGElement | null>(null)
|
|
1029
|
+
|
|
1030
|
+
function exportCsv(options: ChartExportOptions & CsvOptions = {}): string {
|
|
1031
|
+
const rows = labels.value.map((label, i) => ({ label, i }))
|
|
1032
|
+
const csv = toCsv(rows, [
|
|
1033
|
+
{ key: "label", label: strings.value.xColumn },
|
|
1034
|
+
...allSeries.value.map((s) => ({ key: s.id, label: s.name, value: (r: { i: number }) => s.values[r.i] })),
|
|
1035
|
+
], options)
|
|
1036
|
+
if (options.download !== false) {
|
|
1037
|
+
downloadFile(`${fileStem(options.filename ?? props.title, "chart")}.csv`, csv, "text/csv;charset=utf-8")
|
|
1038
|
+
}
|
|
1039
|
+
return csv
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/*
|
|
1043
|
+
* The plot as a picture, in the theme it is shown in. The legend and title are
|
|
1044
|
+
* HTML, so they are painted onto the canvas separately, in the page's font; the
|
|
1045
|
+
* SVG travels with its computed colours inlined, because an SVG drawn as an
|
|
1046
|
+
* image cannot see the page's custom properties.
|
|
1047
|
+
*/
|
|
1048
|
+
async function exportPng(options: ChartExportOptions & { scale?: number; background?: string } = {}): Promise<Blob | null> {
|
|
1049
|
+
const svg = svgEl.value
|
|
1050
|
+
if (!svg || typeof document === "undefined") return null
|
|
1051
|
+
const style = getComputedStyle(root.value ?? svg)
|
|
1052
|
+
const font = style.fontFamily
|
|
1053
|
+
const ink = style.getPropertyValue("--color-ink").trim() || style.color
|
|
1054
|
+
const muted = style.getPropertyValue("--color-muted-strong").trim() || style.color
|
|
1055
|
+
const legend = props.showLegend && allSeries.value.length > 1 ? visible.value : []
|
|
1056
|
+
const blob = await svgToPng(svg, {
|
|
1057
|
+
scale: options.scale,
|
|
1058
|
+
padding: 16,
|
|
1059
|
+
background: options.background ?? backgroundOf(root.value),
|
|
1060
|
+
header: (ctx) => {
|
|
1061
|
+
let y = 0
|
|
1062
|
+
if (props.title) {
|
|
1063
|
+
ctx.font = `500 14px ${font}`
|
|
1064
|
+
ctx.fillStyle = ink
|
|
1065
|
+
ctx.textBaseline = "top"
|
|
1066
|
+
ctx.fillText(props.title, 0, y)
|
|
1067
|
+
y += 22
|
|
1068
|
+
}
|
|
1069
|
+
if (legend.length) {
|
|
1070
|
+
ctx.font = `12px ${font}`
|
|
1071
|
+
ctx.textBaseline = "middle"
|
|
1072
|
+
let x = 0
|
|
1073
|
+
for (const s of legend) {
|
|
1074
|
+
ctx.fillStyle = resolveCssColor(s.color, root.value ?? svg)
|
|
1075
|
+
ctx.beginPath()
|
|
1076
|
+
ctx.arc(x + 4, y + 8, 4, 0, Math.PI * 2)
|
|
1077
|
+
ctx.fill()
|
|
1078
|
+
ctx.fillStyle = muted
|
|
1079
|
+
ctx.fillText(s.name, x + 14, y + 8)
|
|
1080
|
+
x += 14 + ctx.measureText(s.name).width + 16
|
|
1081
|
+
}
|
|
1082
|
+
y += 24
|
|
1083
|
+
}
|
|
1084
|
+
return y ? y + 8 : 0
|
|
1085
|
+
},
|
|
1086
|
+
})
|
|
1087
|
+
if (options.download !== false) downloadFile(`${fileStem(options.filename ?? props.title, "chart")}.png`, blob)
|
|
1088
|
+
return blob
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
defineExpose({ exportCsv, exportPng })
|
|
1092
|
+
|
|
857
1093
|
// The chart fills its container's width and sets its own gap between plot and legend — both are layout decisions the page makes, not the chart.
|
|
858
1094
|
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
859
1095
|
"flex w-full flex-col gap-3",
|
|
@@ -878,11 +1114,12 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
878
1114
|
>
|
|
879
1115
|
<button
|
|
880
1116
|
v-for="s in allSeries"
|
|
881
|
-
:key="s.
|
|
1117
|
+
:key="s.id"
|
|
882
1118
|
type="button"
|
|
883
1119
|
class="group flex cursor-pointer items-center gap-1.5 rounded-md text-xs outline-none transition-opacity duration-fast focus-visible:ring-2 focus-visible:ring-ring"
|
|
884
|
-
:class="hidden.has(s.
|
|
885
|
-
|
|
1120
|
+
:class="hidden.has(s.id) ? 'opacity-40' : ''"
|
|
1121
|
+
:aria-pressed="!hidden.has(s.id)"
|
|
1122
|
+
@click="toggleSeries(s.id)"
|
|
886
1123
|
>
|
|
887
1124
|
<span
|
|
888
1125
|
class="size-2 shrink-0 rounded-full transition-transform duration-fast group-hover:scale-125"
|
|
@@ -892,7 +1129,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
892
1129
|
<span
|
|
893
1130
|
v-if="hasRightAxis && s.axis === 'right'"
|
|
894
1131
|
class="text-[10px] text-muted"
|
|
895
|
-
title="
|
|
1132
|
+
:title="strings.rightAxis"
|
|
896
1133
|
>
|
|
897
1134
|
↗
|
|
898
1135
|
</span>
|
|
@@ -904,20 +1141,31 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
904
1141
|
<LpContextMenu :items="menuEntries" :always="!!menuItems">
|
|
905
1142
|
<!-- `group/plot` + data-state lets the tooltip hide itself while the
|
|
906
1143
|
context menu is up: the two would otherwise stack on the same point. -->
|
|
907
|
-
<div
|
|
1144
|
+
<div
|
|
1145
|
+
class="group/plot relative rounded-control outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
1146
|
+
:style="{ height: `${height}px` }"
|
|
1147
|
+
:tabindex="hasData ? 0 : undefined"
|
|
1148
|
+
:role="hasData ? 'group' : undefined"
|
|
1149
|
+
aria-roledescription="chart"
|
|
1150
|
+
:aria-label="hasData ? summary : undefined"
|
|
1151
|
+
@keydown="onKeydown"
|
|
1152
|
+
@blur="onBlur"
|
|
1153
|
+
>
|
|
908
1154
|
<div
|
|
909
1155
|
v-if="!hasData"
|
|
910
1156
|
class="flex size-full flex-col items-center justify-center gap-2 rounded-control border border-dashed border-line text-muted"
|
|
911
1157
|
>
|
|
912
1158
|
<LpIcon name="lucide:chart-no-axes-column" :size="18" />
|
|
913
|
-
<span class="text-xs">{{ emptyLabel }}</span>
|
|
1159
|
+
<span class="text-xs">{{ emptyLabel ?? strings.empty }}</span>
|
|
914
1160
|
</div>
|
|
915
1161
|
|
|
916
1162
|
<svg
|
|
917
1163
|
v-else-if="width > 0"
|
|
1164
|
+
ref="svgEl"
|
|
918
1165
|
:width="width"
|
|
919
1166
|
:height="height"
|
|
920
1167
|
class="overflow-visible touch-none select-none"
|
|
1168
|
+
aria-hidden="true"
|
|
921
1169
|
@pointermove="onMove"
|
|
922
1170
|
@pointerleave="onLeave"
|
|
923
1171
|
@click="onClick"
|
|
@@ -927,7 +1175,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
927
1175
|
<linearGradient
|
|
928
1176
|
v-for="(s, i) in lines"
|
|
929
1177
|
:id="gradientId(s.name, i)"
|
|
930
|
-
:key="s.
|
|
1178
|
+
:key="s.id"
|
|
931
1179
|
x1="0"
|
|
932
1180
|
y1="0"
|
|
933
1181
|
x2="0"
|
|
@@ -945,7 +1193,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
945
1193
|
:x="box.left"
|
|
946
1194
|
:y="0"
|
|
947
1195
|
:height="height"
|
|
948
|
-
:width="
|
|
1196
|
+
:width="still ? plotWidth(box) : 0"
|
|
949
1197
|
>
|
|
950
1198
|
<!-- keySplines is --ease-travel written out: SMIL cannot read a
|
|
951
1199
|
CSS custom property, so the curve is duplicated here. Change
|
|
@@ -1022,6 +1270,18 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
1022
1270
|
opacity="0.5"
|
|
1023
1271
|
/>
|
|
1024
1272
|
|
|
1273
|
+
<!-- threshold bands sit under the data -->
|
|
1274
|
+
<rect
|
|
1275
|
+
v-for="t in thresholdMarks.filter((m) => m.height > 0)"
|
|
1276
|
+
:key="`band-${t.key}`"
|
|
1277
|
+
:x="box.left"
|
|
1278
|
+
:y="t.top"
|
|
1279
|
+
:width="plotWidth(box)"
|
|
1280
|
+
:height="t.height"
|
|
1281
|
+
:fill="t.color"
|
|
1282
|
+
opacity="0.08"
|
|
1283
|
+
/>
|
|
1284
|
+
|
|
1025
1285
|
<g :clip-path="`url(#${clipId}-reveal)`">
|
|
1026
1286
|
<!-- bars: paths, so a stack rounds as one shape (see barPath) -->
|
|
1027
1287
|
<path
|
|
@@ -1034,7 +1294,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
1034
1294
|
/>
|
|
1035
1295
|
|
|
1036
1296
|
<!-- one group per series, so hiding one drops only that curve -->
|
|
1037
|
-
<g v-for="(s, i) in lines" :key="`s-${s.
|
|
1297
|
+
<g v-for="(s, i) in lines" :key="`s-${s.id}`" :style="{ opacity: s.alpha }">
|
|
1038
1298
|
<path
|
|
1039
1299
|
v-show="type === 'area'"
|
|
1040
1300
|
:d="s.fill"
|
|
@@ -1064,6 +1324,49 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
1064
1324
|
</g>
|
|
1065
1325
|
</g>
|
|
1066
1326
|
|
|
1327
|
+
<!-- threshold lines and markers sit over the data, under the crosshair -->
|
|
1328
|
+
<g v-for="t in thresholdMarks" :key="`t-${t.key}`">
|
|
1329
|
+
<line
|
|
1330
|
+
:x1="box.left"
|
|
1331
|
+
:x2="box.width - box.right"
|
|
1332
|
+
:y1="t.y"
|
|
1333
|
+
:y2="t.y"
|
|
1334
|
+
:stroke="t.color"
|
|
1335
|
+
stroke-width="1"
|
|
1336
|
+
:stroke-dasharray="t.solid ? undefined : '4 3'"
|
|
1337
|
+
/>
|
|
1338
|
+
<text
|
|
1339
|
+
v-if="t.label"
|
|
1340
|
+
:x="box.width - box.right - 4"
|
|
1341
|
+
:y="t.top - 4"
|
|
1342
|
+
text-anchor="end"
|
|
1343
|
+
class="text-[10px]"
|
|
1344
|
+
:fill="t.color"
|
|
1345
|
+
>
|
|
1346
|
+
{{ t.label }}
|
|
1347
|
+
</text>
|
|
1348
|
+
</g>
|
|
1349
|
+
<g v-for="m in markerMarks" :key="`m-${m.key}`">
|
|
1350
|
+
<line
|
|
1351
|
+
:x1="m.x"
|
|
1352
|
+
:x2="m.x"
|
|
1353
|
+
:y1="box.top"
|
|
1354
|
+
:y2="box.top + plotHeight(box)"
|
|
1355
|
+
:stroke="m.color"
|
|
1356
|
+
stroke-width="1"
|
|
1357
|
+
stroke-dasharray="2 3"
|
|
1358
|
+
/>
|
|
1359
|
+
<text
|
|
1360
|
+
v-if="m.label"
|
|
1361
|
+
:x="m.x + 4"
|
|
1362
|
+
:y="box.top + 9"
|
|
1363
|
+
class="text-[10px]"
|
|
1364
|
+
:fill="m.color"
|
|
1365
|
+
>
|
|
1366
|
+
{{ m.label }}
|
|
1367
|
+
</text>
|
|
1368
|
+
</g>
|
|
1369
|
+
|
|
1067
1370
|
<!-- crosshair -->
|
|
1068
1371
|
<g v-if="active >= 0 && type !== 'bar'">
|
|
1069
1372
|
<line
|
|
@@ -1077,7 +1380,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
1077
1380
|
/>
|
|
1078
1381
|
<circle
|
|
1079
1382
|
v-for="p in activePoints"
|
|
1080
|
-
:key="p.
|
|
1383
|
+
:key="p.id"
|
|
1081
1384
|
:cx="activeX"
|
|
1082
1385
|
:cy="p.y"
|
|
1083
1386
|
r="4"
|
|
@@ -1125,7 +1428,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
1125
1428
|
<div class="flex flex-col gap-1">
|
|
1126
1429
|
<div
|
|
1127
1430
|
v-for="r in tooltipRows"
|
|
1128
|
-
:key="r.
|
|
1431
|
+
:key="r.id"
|
|
1129
1432
|
class="flex items-center justify-between gap-3 text-xs"
|
|
1130
1433
|
>
|
|
1131
1434
|
<span class="flex items-center gap-1.5 text-muted">
|
|
@@ -1137,8 +1440,28 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
1137
1440
|
</div>
|
|
1138
1441
|
</LpShift>
|
|
1139
1442
|
</Motion>
|
|
1443
|
+
|
|
1444
|
+
<span class="sr-only" aria-live="polite">{{ readout }}</span>
|
|
1140
1445
|
</div>
|
|
1141
1446
|
</LpContextMenu>
|
|
1447
|
+
|
|
1448
|
+
<table v-if="dataTable && hasData" class="sr-only">
|
|
1449
|
+
<caption v-if="title">{{ title }}</caption>
|
|
1450
|
+
<thead>
|
|
1451
|
+
<tr>
|
|
1452
|
+
<th scope="col"></th>
|
|
1453
|
+
<th v-for="s in allSeries" :key="s.id" scope="col">{{ s.name }}</th>
|
|
1454
|
+
</tr>
|
|
1455
|
+
</thead>
|
|
1456
|
+
<tbody>
|
|
1457
|
+
<tr v-for="(label, i) in labels" :key="i">
|
|
1458
|
+
<th scope="row">{{ label }}</th>
|
|
1459
|
+
<td v-for="s in allSeries" :key="s.id">
|
|
1460
|
+
{{ s.values[i] == null ? "—" : fmtSeries(s, s.values[i] as number) }}
|
|
1461
|
+
</td>
|
|
1462
|
+
</tr>
|
|
1463
|
+
</tbody>
|
|
1464
|
+
</table>
|
|
1142
1465
|
</div>
|
|
1143
1466
|
</template>
|
|
1144
1467
|
|
|
@@ -8,9 +8,10 @@ export default { inheritAttrs: false }
|
|
|
8
8
|
<script setup lang="ts">
|
|
9
9
|
import { CheckboxIndicator, CheckboxRoot } from "reka-ui"
|
|
10
10
|
import LpIcon from "./LpIcon.vue"
|
|
11
|
+
import { useFieldControl } from "../composables/useField"
|
|
11
12
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
12
13
|
|
|
13
|
-
defineProps<{
|
|
14
|
+
const props = defineProps<{
|
|
14
15
|
modelValue?: boolean
|
|
15
16
|
/** Mixed state (e.g. a "select all" with a partial selection). Shows a dash. */
|
|
16
17
|
indeterminate?: boolean
|
|
@@ -30,6 +31,8 @@ function onUpdate(v: boolean | "indeterminate") {
|
|
|
30
31
|
emit("change", checked)
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
const field = useFieldControl(() => ({ wrapped: false, id: props.id, disabled: props.disabled }))
|
|
35
|
+
|
|
33
36
|
// The label sits a fixed distance from the box; a dense list of options needs that closer.
|
|
34
37
|
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
35
38
|
"group inline-flex cursor-pointer items-center gap-2 text-sm text-ink",
|
|
@@ -41,10 +44,11 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
41
44
|
:class="rootClass"
|
|
42
45
|
v-bind="rest">
|
|
43
46
|
<CheckboxRoot
|
|
44
|
-
:id="id"
|
|
47
|
+
:id="field.id"
|
|
45
48
|
:name="name"
|
|
46
49
|
:model-value="indeterminate ? 'indeterminate' : modelValue"
|
|
47
|
-
:disabled="disabled"
|
|
50
|
+
:disabled="field.disabled"
|
|
51
|
+
:aria-describedby="field.describedBy"
|
|
48
52
|
class="flex size-[18px] items-center justify-center rounded-[5px] border border-line-strong bg-surface-soft outline-none transition-[color,background-color,border-color,box-shadow,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] not-data-[state=checked]:not-data-[state=indeterminate]:group-hover:border-brand active:scale-90 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-transparent data-[state=checked]:bg-brand data-[state=indeterminate]:border-transparent data-[state=indeterminate]:bg-brand disabled:cursor-not-allowed disabled:opacity-55"
|
|
49
53
|
@update:model-value="onUpdate"
|
|
50
54
|
>
|