@leavepulse/ui 0.43.3 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -7
- package/dist/assets/{LpCommandPalette-DH7sPcKP.css → LpCommandPalette-DCP5g2ik.css} +21 -21
- package/dist/assets/{LpDropzone-BBoWvi-J.css → LpDropzone-i4BdMi1H.css} +12 -12
- package/dist/assets/{LpImageEditor-Ct7Y3aNu.css → LpImageEditor-CbMA82g8.css} +6 -6
- package/dist/assets/{LpShortcutsDialog-C6YoQ8_M.css → LpShortcutsDialog-ChUyU7oV.css} +7 -7
- package/dist/assets/{LpSkeleton-D7xU8IPV.css → LpSkeleton-H3Z5dEIk.css} +4 -1
- package/dist/assets/{LpTable-BgPLJOXa.css → LpTable-DDQNdf86.css} +9 -9
- package/dist/chunks/ContextMenuItems-CrfeNPWT.js +123 -0
- package/dist/chunks/{LayoutCanvas-Dd20boUG.js → LayoutCanvas-D-jPOAvg.js} +26 -9
- package/dist/chunks/{LayoutNode-DGpZA9LX.js → LayoutNode-D8Xy9N-o.js} +20 -8
- package/dist/chunks/{LpActionBar-BlvtGB2P.js → LpActionBar-BbwCF6l5.js} +24 -10
- package/dist/chunks/{LpAddressInput-B-Zvxsn7.js → LpAddressInput-BrOqkZqg.js} +53 -48
- package/dist/chunks/{LpAlert-BAYHDSS1.js → LpAlert-DkOIjsbq.js} +15 -8
- package/dist/chunks/{LpAppShell-BGQVIY-3.js → LpAppShell-BUKPcHco.js} +51 -24
- package/dist/chunks/{LpAutocomplete-DExlIZe_.js → LpAutocomplete-DxV3Y9h4.js} +50 -23
- package/dist/chunks/{LpAvatar-B3-xAOH3.js → LpAvatar-ClBbEYZN.js} +17 -8
- package/dist/chunks/LpBreadcrumbs-B8IJgFIc.js +95 -0
- package/dist/chunks/{LpButton-5VXXCjwF.js → LpButton-kqJBHIIb.js} +44 -14
- package/dist/chunks/{LpCard-BhI1N_vL.js → LpCard-CpRByR0q.js} +1 -1
- package/dist/chunks/{LpCheckbox-CVccKYaH.js → LpCheckbox-DpqchzH4.js} +7 -3
- package/dist/chunks/{LpCodeBlock-Bo0B4TFR.js → LpCodeBlock-DeHJPq7p.js} +48 -16
- package/dist/chunks/{LpColorPicker-BlXea7LM.js → LpColorPicker-Bhf1qxhB.js} +32 -9
- package/dist/chunks/{LpConfirmDialog-Q-bxff8z.js → LpConfirmDialog-CIUOSnWo.js} +58 -16
- package/dist/chunks/LpConfirmHost-CDm0Wtpv.js +49 -0
- package/dist/chunks/LpContextMenu-DQw5dGfG.js +68 -0
- package/dist/chunks/LpDatePicker-Drirx1AK.js +191 -0
- package/dist/chunks/{LpDrawer-BZ6k3hvo.js → LpDrawer-bScx-KMO.js} +63 -26
- package/dist/chunks/{LpFileTree-_5vFBgaK.js → LpFileTree-CSI_5SK2.js} +23 -8
- package/dist/chunks/{LpFileTreeNode-BdcIYKIE.js → LpFileTreeNode-B4MiSQX2.js} +2 -2
- package/dist/chunks/LpFormField-CIk5qosh.js +87 -0
- package/dist/chunks/{LpInput-9EXWUM9B.js → LpInput-C0e8y8_d.js} +26 -11
- package/dist/chunks/{LpLightbox-2sYVMY-S.js → LpLightbox-Mr0W_6HK.js} +147 -60
- package/dist/chunks/{LpLink-DCND-zyu.js → LpLink-Krkjhe_y.js} +15 -6
- package/dist/chunks/{LpLogViewer-CaRy5MeS.js → LpLogViewer-YBOaFnc_.js} +68 -34
- package/dist/chunks/{LpMapPicker-ul4oYno6.js → LpMapPicker-NUzS9e37.js} +38 -13
- package/dist/chunks/{LpMediaGallery-FF-NquIo.js → LpMediaGallery-DfJmMW0I.js} +6 -3
- package/dist/chunks/{LpModal-Bt0pQCI_.js → LpModal-DPwGKBPv.js} +49 -26
- package/dist/chunks/{LpModalNav-YaifHrXC.js → LpModalNav-DPg5H9f3.js} +9 -6
- package/dist/chunks/{LpNotificationBell-DyGGsMAM.js → LpNotificationBell-t5yn-siX.js} +58 -24
- package/dist/chunks/{LpNumberField-CWuX08iX.js → LpNumberField-DWnQKktS.js} +20 -7
- package/dist/chunks/{LpNumberFlow-amWYaeuA.js → LpNumberFlow-z4WPZcZu.js} +22 -12
- package/dist/chunks/{LpOtpInput-y2v-pmiN.js → LpOtpInput-CziPF3Oq.js} +37 -8
- package/dist/chunks/{LpPagination-5FxjmmB5.js → LpPagination-DFngX7Xo.js} +30 -19
- package/dist/chunks/{LpPasswordInput--sjYCkLS.js → LpPasswordInput-CWVDuBJ5.js} +10 -7
- package/dist/chunks/{LpPhoneInput-BDSKVl_z.js → LpPhoneInput-CD-ZTeSd.js} +57 -33
- package/dist/chunks/{LpPie-D6OnzwlJ.js → LpPie-BLco31Nx.js} +78 -21
- package/dist/chunks/{LpPopover-8268UmlC.js → LpPopover-p_e_shN3.js} +4 -2
- package/dist/chunks/{LpProgress-Btq5W4Ut.js → LpProgress-CBU1iI69.js} +5 -4
- package/dist/chunks/{LpRadioGroup-xPIuVDO_.js → LpRadioGroup-DmEW0l78.js} +9 -2
- package/dist/chunks/{LpSegmented-fV0Sk92p.js → LpSegmented-Dejd7cIx.js} +7 -3
- package/dist/chunks/{LpSelect-ANIVtrpM.js → LpSelect-DrRD9I7L.js} +40 -18
- package/dist/chunks/{LpSidebar-ClBUwoEz.js → LpSidebar-BgwUNWpJ.js} +30 -22
- package/dist/chunks/{LpSidebarNav-llOcs_Fn.js → LpSidebarNav-Bqxz8aWT.js} +14 -9
- package/dist/chunks/{LpSkeleton-CTI-vBgW.js → LpSkeleton-BB6XIV-w.js} +1 -1
- package/dist/chunks/{LpSlider-BwNIBkAK.js → LpSlider-Cuzmrd7p.js} +7 -2
- package/dist/chunks/{LpSparkline-C6xQ2dwv.js → LpSparkline-CB80N8SJ.js} +11 -4
- package/dist/chunks/{LpStat-CQaW8R--.js → LpStat-dXtyrdX9.js} +1 -1
- package/dist/chunks/{LpSwitch-Blizvqyt.js → LpSwitch-B1aLKulj.js} +40 -26
- package/dist/chunks/{LpTableOfContents-CjvxPMpU.js → LpTableOfContents-BUcAfGPQ.js} +36 -41
- package/dist/chunks/{LpTabs-Cefe_lys.js → LpTabs-_qMVaZJ6.js} +19 -5
- package/dist/chunks/LpTextarea-yP8hGedR.js +77 -0
- package/dist/chunks/LpThemeProvider-Wjm7Tuit.js +35 -0
- package/dist/chunks/LpThemeSwitcher-CKLc10KZ.js +169 -0
- package/dist/chunks/{LpTilt-DuyKbpkA.js → LpTilt-B7KHX1zH.js} +2 -2
- package/dist/chunks/{LpTimeField-K6nAGboX.js → LpTimeField-C5HRgS5H.js} +13 -7
- package/dist/chunks/{LpToaster-gDB1EaU3.js → LpToaster--oT6lfhL.js} +57 -28
- package/dist/chunks/{LpTooltip-hCLPAqMG.js → LpTooltip-CBdzeKNf.js} +13 -6
- package/dist/chunks/LpTooltipProvider-CftjV7Xc.js +25 -0
- package/dist/chunks/{LpTopologyCanvas-Cm1L39uL.js → LpTopologyCanvas-DocsLal3.js} +6 -1
- package/dist/chunks/{LpUptimeBar-DvAMno0S.js → LpUptimeBar-DLtxOMRy.js} +43 -14
- package/dist/chunks/{LpThemeSwitcher-Cue-myST.js → useTheme-C8JHUlPM.js} +173 -215
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.d.ts +8 -0
- package/dist/components/ContextMenuItems.vue.js +4 -0
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.d.ts +4 -0
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.d.ts +3 -0
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.d.ts +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.d.ts +9 -0
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +10 -0
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.d.ts +31 -6
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.d.ts +3 -3
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.d.ts +2 -2
- package/dist/components/LpBreadcrumbs.vue.d.ts +13 -9
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +35 -98
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.d.ts +31 -2
- package/dist/components/LpChart.vue.js +317 -95
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.d.ts +2 -0
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +8 -10
- package/dist/components/LpCommandPalette.vue.js +88 -78
- package/dist/components/LpConfirmDialog.vue.d.ts +15 -3
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.d.ts +9 -0
- package/dist/components/LpConfirmHost.vue.js +4 -0
- package/dist/components/LpContextMenu.vue.d.ts +3 -5
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.d.ts +24 -6
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDrawer.vue.d.ts +10 -0
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +22 -6
- package/dist/components/LpFileTree.vue.d.ts +11 -2
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.d.ts +9 -0
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +92 -57
- package/dist/components/LpInput.vue.d.ts +3 -3
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.d.ts +17 -0
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLink.vue.d.ts +11 -11
- package/dist/components/LpLink.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +20 -2
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.d.ts +4 -4
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.d.ts +16 -6
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.d.ts +0 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +11 -2
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.d.ts +2 -0
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPagination.vue.d.ts +2 -0
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.d.ts +21 -2
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPopover.vue.d.ts +2 -0
- package/dist/components/LpPopover.vue.js +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpSection.vue.d.ts +1 -1
- package/dist/components/LpSegmented.vue.d.ts +5 -0
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.d.ts +1 -0
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.d.ts +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +11 -8
- package/dist/components/LpSidebar.vue.d.ts +6 -2
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSkeleton.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpSparkline.vue.d.ts +1 -0
- package/dist/components/LpSparkline.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.d.ts +23 -1
- package/dist/components/LpTable.vue.js +74 -36
- package/dist/components/LpTableOfContents.vue.d.ts +4 -2
- package/dist/components/LpTableOfContents.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +2 -0
- package/dist/components/LpTabs.vue.js +1 -1
- package/dist/components/LpTextarea.vue.d.ts +19 -6
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.d.ts +22 -0
- package/dist/components/LpThemeProvider.vue.js +4 -0
- package/dist/components/LpThemeSwitcher.vue.d.ts +13 -0
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTilt.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.d.ts +6 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.d.ts +1 -2
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTooltipProvider.vue.d.ts +22 -0
- package/dist/components/LpTooltipProvider.vue.js +4 -0
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.d.ts +4 -0
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/chart.d.ts +32 -1
- package/dist/components/chart.js +39 -4
- package/dist/components/codeHighlight.d.ts +6 -0
- package/dist/components/codeHighlight.js +92 -20
- package/dist/components/color.d.ts +1 -1
- package/dist/components/color.js +20 -9
- package/dist/components/commandSearch.d.ts +0 -1
- package/dist/components/commandSearch.js +1 -17
- package/dist/components/confirm.d.ts +25 -0
- package/dist/components/confirm.js +26 -0
- package/dist/components/countries.d.ts +6 -0
- package/dist/components/countries.js +7 -0
- package/dist/components/fileTree.js +16 -7
- package/dist/components/imageCrop.d.ts +42 -0
- package/dist/components/imageCrop.js +47 -0
- package/dist/components/link.d.ts +12 -0
- package/dist/components/link.js +19 -0
- package/dist/components/modalDepth.d.ts +18 -2
- package/dist/components/modalDepth.js +17 -5
- package/dist/components/otp.d.ts +5 -0
- package/dist/components/otp.js +10 -0
- package/dist/components/sidebar.d.ts +7 -1
- package/dist/components/tableSort.d.ts +8 -0
- package/dist/components/tableSort.js +31 -0
- package/dist/composables/useField.d.ts +34 -0
- package/dist/composables/useField.js +45 -0
- package/dist/composables/useFormDraft.d.ts +10 -3
- package/dist/composables/useFormDraft.js +9 -14
- package/dist/composables/useHotkeys.d.ts +17 -1
- package/dist/composables/useHotkeys.js +55 -18
- package/dist/composables/useInputFilter.d.ts +17 -0
- package/dist/composables/useInputFilter.js +47 -10
- package/dist/composables/useLpConfig.d.ts +24 -0
- package/dist/composables/useLpConfig.js +44 -0
- package/dist/composables/useRovingFocus.js +7 -0
- package/dist/composables/useTilt.d.ts +2 -2
- package/dist/composables/useTilt.js +5 -6
- package/dist/composables/useToast.d.ts +20 -0
- package/dist/composables/useToast.js +28 -4
- package/dist/composables/useZoomPan.d.ts +3 -3
- package/dist/composables/useZoomPan.js +4 -4
- package/dist/index.d.ts +33 -3
- package/dist/index.js +185 -139
- package/dist/layout/tree.d.ts +2 -0
- package/dist/layout/tree.js +16 -1
- package/dist/layout/useLayout.d.ts +15 -0
- package/dist/layout/useLayout.js +41 -14
- package/dist/theme/useTheme.d.ts +32 -9
- package/package.json +8 -7
- package/src/component-names.ts +5 -7
- package/src/components/ContextMenuItems.vue +88 -0
- package/src/components/LayoutCanvas.vue +26 -6
- package/src/components/LayoutNode.vue +19 -2
- package/src/components/LpActionBar.vue +30 -7
- package/src/components/LpAddressInput.vue +57 -41
- package/src/components/LpAlert.vue +16 -3
- package/src/components/LpAppShell.vue +45 -12
- package/src/components/LpAutocomplete.vue +46 -10
- package/src/components/LpAvatar.vue +19 -5
- package/src/components/LpBreadcrumbs.vue +65 -41
- package/src/components/LpButton.vue +59 -13
- package/src/components/LpChart.vue +272 -28
- package/src/components/LpCheckbox.vue +7 -3
- package/src/components/LpCodeBlock.vue +50 -14
- package/src/components/LpColorPicker.vue +39 -9
- package/src/components/LpCommandPalette.vue +36 -25
- package/src/components/LpConfirmDialog.vue +68 -17
- package/src/components/LpConfirmHost.vue +59 -0
- package/src/components/LpContextMenu.vue +14 -115
- package/src/components/LpDatePicker.vue +99 -68
- package/src/components/LpDrawer.vue +56 -19
- package/src/components/LpDropzone.vue +21 -4
- package/src/components/LpFileTree.vue +33 -7
- package/src/components/LpFormField.vue +30 -7
- package/src/components/LpImageEditor.vue +96 -57
- package/src/components/LpInput.vue +18 -7
- package/src/components/LpLightbox.vue +140 -29
- package/src/components/LpLink.vue +24 -13
- package/src/components/LpLogViewer.vue +86 -17
- package/src/components/LpMapPicker.vue +36 -13
- package/src/components/LpMediaGallery.vue +8 -2
- package/src/components/LpModal.vue +53 -27
- package/src/components/LpModalNav.vue +5 -2
- package/src/components/LpNotificationBell.vue +63 -17
- package/src/components/LpNumberField.vue +21 -6
- package/src/components/LpNumberFlow.vue +26 -10
- package/src/components/LpOtpInput.vue +43 -8
- package/src/components/LpPagination.vue +23 -7
- package/src/components/LpPasswordInput.vue +4 -1
- package/src/components/LpPhoneInput.vue +53 -27
- package/src/components/LpPie.vue +94 -15
- package/src/components/LpPopover.vue +3 -0
- package/src/components/LpProgress.vue +5 -3
- package/src/components/LpRadioGroup.vue +9 -2
- package/src/components/LpSegmented.vue +12 -2
- package/src/components/LpSelect.vue +0 -0
- package/src/components/LpShortcutsDialog.vue +8 -3
- package/src/components/LpSidebar.vue +14 -9
- package/src/components/LpSidebarNav.vue +17 -5
- package/src/components/LpSkeleton.vue +4 -1
- package/src/components/LpSlider.vue +7 -1
- package/src/components/LpSparkline.vue +11 -3
- package/src/components/LpSwitch.vue +30 -7
- package/src/components/LpTable.vue +91 -32
- package/src/components/LpTableOfContents.vue +50 -32
- package/src/components/LpTabs.vue +23 -6
- package/src/components/LpTextarea.vue +44 -6
- package/src/components/LpThemeProvider.vue +41 -0
- package/src/components/LpThemeSwitcher.vue +64 -17
- package/src/components/LpTilt.vue +2 -2
- package/src/components/LpTimeField.vue +14 -3
- package/src/components/LpToaster.vue +66 -26
- package/src/components/LpTooltip.vue +22 -5
- package/src/components/LpTooltipProvider.vue +24 -0
- package/src/components/LpTopologyCanvas.vue +12 -1
- package/src/components/LpUptimeBar.vue +46 -8
- package/src/components/chart.ts +77 -5
- package/src/components/codeHighlight.ts +111 -15
- package/src/components/color.ts +30 -9
- package/src/components/commandSearch.ts +0 -17
- package/src/components/confirm.ts +58 -0
- package/src/components/countries.ts +12 -0
- package/src/components/fileTree.ts +19 -7
- package/src/components/imageCrop.ts +107 -0
- package/src/components/link.ts +34 -0
- package/src/components/modalDepth.ts +48 -6
- package/src/components/otp.ts +16 -0
- package/src/components/sidebar.ts +7 -1
- package/src/components/tableSort.ts +37 -0
- package/src/composables/useField.ts +79 -0
- package/src/composables/useFormDraft.ts +25 -20
- package/src/composables/useHotkeys.ts +89 -18
- package/src/composables/useInputFilter.ts +77 -14
- package/src/composables/useLpConfig.ts +87 -0
- package/src/composables/useRovingFocus.ts +11 -0
- package/src/composables/useTilt.ts +5 -6
- package/src/composables/useToast.ts +56 -4
- package/src/composables/useZoomPan.ts +6 -6
- package/src/index.ts +38 -3
- package/src/layout/tree.ts +30 -0
- package/src/layout/useLayout.ts +68 -18
- package/src/nuxt.ts +59 -17
- package/src/runtime/config.plugin.ts +14 -0
- package/src/runtime/theme.plugin.ts +42 -22
- package/src/theme/useTheme.ts +206 -100
- package/dist/chunks/LpBreadcrumbs-p4jHe_V-.js +0 -62
- package/dist/chunks/LpContextMenu-CwW1QXe_.js +0 -217
- package/dist/chunks/LpDatePicker-CwOToxGq.js +0 -164
- package/dist/chunks/LpFormField-CazBc2-a.js +0 -62
- package/dist/chunks/LpTextarea-t7F6gv8E.js +0 -47
|
@@ -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,13 +41,23 @@ 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"
|
|
47
52
|
|
|
53
|
+
export interface ChartText {
|
|
54
|
+
[key: string]: string
|
|
55
|
+
empty: string
|
|
56
|
+
rightAxis: string
|
|
57
|
+
/** Prefix of the keyboard readout, before the point's label. */
|
|
58
|
+
point: string
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
/** What was under the pointer when a point was clicked or right-clicked. */
|
|
49
62
|
export interface ChartPointEvent {
|
|
50
63
|
index: number
|
|
@@ -94,6 +107,16 @@ const props = withDefaults(
|
|
|
94
107
|
animate?: boolean
|
|
95
108
|
/** Shown when every series is empty. */
|
|
96
109
|
emptyLabel?: string
|
|
110
|
+
/** Reference lines and bands (SLOs, budgets, alert levels). */
|
|
111
|
+
thresholds?: ChartThreshold[]
|
|
112
|
+
/** Vertical markers at x positions (deploys, incidents). */
|
|
113
|
+
markers?: ChartMarker[]
|
|
114
|
+
/** Accessible name for the plot. Defaults to a generated summary. */
|
|
115
|
+
ariaLabel?: string
|
|
116
|
+
/** Render the data as a visually hidden table for screen readers. */
|
|
117
|
+
dataTable?: boolean
|
|
118
|
+
/** Text overrides (`labels` is taken by the x axis). */
|
|
119
|
+
text?: Partial<ChartText>
|
|
97
120
|
/**
|
|
98
121
|
* Right-click menu for a point, built from whatever was hit. Return [] to
|
|
99
122
|
* leave the browser's own menu alone for that point. Which actions a point
|
|
@@ -113,10 +136,18 @@ const props = withDefaults(
|
|
|
113
136
|
showLegend: true,
|
|
114
137
|
yTicks: 4,
|
|
115
138
|
animate: true,
|
|
116
|
-
|
|
139
|
+
thresholds: () => [],
|
|
140
|
+
markers: () => [],
|
|
141
|
+
dataTable: false,
|
|
117
142
|
},
|
|
118
143
|
)
|
|
119
144
|
|
|
145
|
+
const strings = useLabels<ChartText>(
|
|
146
|
+
"chart",
|
|
147
|
+
{ empty: "No data", rightAxis: "Measured on the right axis", point: "Point" },
|
|
148
|
+
() => props.text,
|
|
149
|
+
)
|
|
150
|
+
|
|
120
151
|
const emit = defineEmits<{
|
|
121
152
|
/** Click on a point/bar. */
|
|
122
153
|
(e: "pointClick", payload: ChartPointEvent): void
|
|
@@ -139,12 +170,16 @@ let ro: ResizeObserver | null = null
|
|
|
139
170
|
* two combine into one question: should this draw itself right now?
|
|
140
171
|
*/
|
|
141
172
|
const { el: revealAnchor, revealed } = useReveal({ immediate: () => !props.animate })
|
|
142
|
-
|
|
173
|
+
// SMIL ignores the reduced-motion media query, so the wipe is skipped here.
|
|
174
|
+
const reduced = ref(false)
|
|
175
|
+
const still = computed(() => !props.animate || reduced.value)
|
|
176
|
+
const drawing = computed(() => !still.value && revealed.value)
|
|
143
177
|
|
|
144
178
|
onMounted(() => {
|
|
145
179
|
// One element, two observers: hand the composable the same node rather than
|
|
146
180
|
// wrapping the chart in a spare div just to carry a second ref.
|
|
147
181
|
revealAnchor.value = root.value
|
|
182
|
+
reduced.value = prefersReducedMotion()
|
|
148
183
|
if (!root.value || typeof ResizeObserver === "undefined") return
|
|
149
184
|
ro = new ResizeObserver(([entry]) => {
|
|
150
185
|
width.value = entry.contentRect.width
|
|
@@ -165,16 +200,19 @@ onBeforeUnmount(() => {
|
|
|
165
200
|
// ── data ───────────────────────────────────────
|
|
166
201
|
const hidden = ref(new Set<string>())
|
|
167
202
|
|
|
168
|
-
const toggleSeries = (
|
|
203
|
+
const toggleSeries = (id: string) => {
|
|
169
204
|
const next = new Set(hidden.value)
|
|
170
|
-
if (next.has(
|
|
171
|
-
else next.add(
|
|
205
|
+
if (next.has(id)) next.delete(id)
|
|
206
|
+
else next.add(id)
|
|
172
207
|
// Never hide the last visible series — an empty plot reads as broken.
|
|
173
208
|
if (next.size < props.series.length) hidden.value = next
|
|
174
209
|
}
|
|
175
210
|
|
|
176
|
-
|
|
177
|
-
const
|
|
211
|
+
// Identity is position plus name, so two series sharing a name stay separate.
|
|
212
|
+
const allSeries = computed(() =>
|
|
213
|
+
normalise(props.series).map((s, i) => ({ ...s, id: `${i}:${s.name}` })),
|
|
214
|
+
)
|
|
215
|
+
const visible = computed(() => allSeries.value.filter((s) => !hidden.value.has(s.id)))
|
|
178
216
|
|
|
179
217
|
/*
|
|
180
218
|
* How present each series is, 1 to 0, driven per frame rather than by CSS.
|
|
@@ -206,7 +244,7 @@ let presenceFrame: number | undefined
|
|
|
206
244
|
* reported the end state — so a series being hidden jumped to the floor while
|
|
207
245
|
* one being shown eased up from it.
|
|
208
246
|
*/
|
|
209
|
-
const presenceOf = (
|
|
247
|
+
const presenceOf = (id: string) => presence.value.get(id) ?? 1
|
|
210
248
|
|
|
211
249
|
watch(
|
|
212
250
|
// A string, not the Set: `hidden` is replaced wholesale on every toggle, and a
|
|
@@ -214,15 +252,15 @@ watch(
|
|
|
214
252
|
// and sees a change it cannot describe — or, with a stale copy in the array,
|
|
215
253
|
// no change at all. The names that are currently off say it exactly.
|
|
216
254
|
() =>
|
|
217
|
-
allSeries.value.map((s) => `${s.
|
|
255
|
+
allSeries.value.map((s) => `${s.id}:${hidden.value.has(s.id) ? 0 : 1}`).join(","),
|
|
218
256
|
() => {
|
|
219
|
-
const targets = new Map(allSeries.value.map((s) => [s.
|
|
257
|
+
const targets = new Map(allSeries.value.map((s) => [s.id, hidden.value.has(s.id) ? 0 : 1]))
|
|
220
258
|
if (!props.animate || prefersReducedMotion()) {
|
|
221
259
|
presence.value = targets
|
|
222
260
|
return
|
|
223
261
|
}
|
|
224
262
|
|
|
225
|
-
const from = new Map(allSeries.value.map((s) => [s.
|
|
263
|
+
const from = new Map(allSeries.value.map((s) => [s.id, presenceOf(s.id)]))
|
|
226
264
|
if ([...targets].every(([name, to]) => from.get(name) === to)) return
|
|
227
265
|
|
|
228
266
|
const start = performance.now()
|
|
@@ -299,12 +337,27 @@ const hasRightAxis = computed(
|
|
|
299
337
|
() => declaredRight.value.length > 0 && declaredLeft.value.length > 0,
|
|
300
338
|
)
|
|
301
339
|
|
|
340
|
+
/** A reference level must be on the plot, or it says nothing. */
|
|
341
|
+
const withThresholds = (raw: { min: number; max: number }, axis: "left" | "right") => {
|
|
342
|
+
let { min, max } = raw
|
|
343
|
+
for (const t of props.thresholds) {
|
|
344
|
+
if ((t.axis ?? "left") !== axis) continue
|
|
345
|
+
for (const v of [t.value, t.to]) {
|
|
346
|
+
if (v == null || !Number.isFinite(v)) continue
|
|
347
|
+
min = Math.min(min, v)
|
|
348
|
+
max = Math.max(max, v)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return { min, max }
|
|
352
|
+
}
|
|
353
|
+
|
|
302
354
|
const scaleFor = (members: typeof visible.value) => {
|
|
303
355
|
const totals = stackTotals.value
|
|
304
356
|
const raw = totals
|
|
305
357
|
? { min: Math.min(0, ...totals.neg), max: Math.max(0, ...totals.pos) }
|
|
306
358
|
: extent(members, zeroBased.value)
|
|
307
|
-
|
|
359
|
+
const wide = hasData.value ? withThresholds(raw, "left") : raw
|
|
360
|
+
return niceScale(wide.min, wide.max, props.yTicks)
|
|
308
361
|
}
|
|
309
362
|
|
|
310
363
|
const targetScale = computed(() =>
|
|
@@ -367,7 +420,7 @@ const targetRightScale = computed(() => {
|
|
|
367
420
|
axis holds the range it had — an axis that keeps its place while its curve
|
|
368
421
|
leaves, rather than collapsing to a degenerate 0..1 behind it. */
|
|
369
422
|
const members = onRight.value.length ? onRight.value : declaredRight.value
|
|
370
|
-
const raw = extent(members, zeroBased.value)
|
|
423
|
+
const raw = withThresholds(extent(members, zeroBased.value), "right")
|
|
371
424
|
/* Steps come from the TARGET left scale. Reading the animated one rebuilt this
|
|
372
425
|
axis from a fractional range every frame, so a right-hand series jittered
|
|
373
426
|
against a scale being redrawn underneath it. */
|
|
@@ -588,7 +641,7 @@ const baseY = computed(() =>
|
|
|
588
641
|
const lines = computed(() =>
|
|
589
642
|
allSeries.value.map((s) => {
|
|
590
643
|
const g = geoOf(s)
|
|
591
|
-
const p = presenceOf(s.
|
|
644
|
+
const p = presenceOf(s.id)
|
|
592
645
|
// Values pulled toward the axis floor, so the curve flattens into it.
|
|
593
646
|
const base = Math.min(Math.max(0, g.min), g.max)
|
|
594
647
|
const values = p === 1 ? s.values : s.values.map((v) => (v == null ? null : base + (v - base) * p))
|
|
@@ -660,7 +713,7 @@ const bars = computed(() => {
|
|
|
660
713
|
h = Math.abs(top - base)
|
|
661
714
|
}
|
|
662
715
|
out.push({
|
|
663
|
-
key: `${s.
|
|
716
|
+
key: `${s.id}-${i}`,
|
|
664
717
|
x,
|
|
665
718
|
y,
|
|
666
719
|
w: Math.max(1, slot - (props.stacked ? 0 : 2)),
|
|
@@ -711,6 +764,7 @@ const active = ref(-1)
|
|
|
711
764
|
const pointer = ref({ x: 0, y: 0 })
|
|
712
765
|
|
|
713
766
|
const onMove = (e: PointerEvent) => {
|
|
767
|
+
keyboard.value = false
|
|
714
768
|
const el = e.currentTarget as SVGElement
|
|
715
769
|
const rect = el.getBoundingClientRect()
|
|
716
770
|
const x = e.clientX - rect.left
|
|
@@ -729,7 +783,11 @@ const pointEvent = (originalEvent: MouseEvent): ChartPointEvent => ({
|
|
|
729
783
|
originalEvent,
|
|
730
784
|
})
|
|
731
785
|
|
|
786
|
+
/* A tap fires pointerleave before click, which has already cleared the hover,
|
|
787
|
+
so the index comes from the click itself. */
|
|
732
788
|
const onClick = (e: MouseEvent) => {
|
|
789
|
+
const rect = (e.currentTarget as SVGElement).getBoundingClientRect()
|
|
790
|
+
active.value = nearestIndex(e.clientX - rect.left, geo.value)
|
|
733
791
|
if (active.value < 0) return
|
|
734
792
|
emit("pointClick", pointEvent(e))
|
|
735
793
|
}
|
|
@@ -772,6 +830,7 @@ const activePoints = computed(() => {
|
|
|
772
830
|
const sc = scaleOf(s)
|
|
773
831
|
return {
|
|
774
832
|
color: s.color,
|
|
833
|
+
id: s.id,
|
|
775
834
|
name: s.name,
|
|
776
835
|
value: v,
|
|
777
836
|
y: yAt(v, sc.min, sc.max, box.value),
|
|
@@ -783,6 +842,7 @@ const tooltipRows = computed(() =>
|
|
|
783
842
|
active.value < 0
|
|
784
843
|
? []
|
|
785
844
|
: visible.value.map((s) => ({
|
|
845
|
+
id: s.id,
|
|
786
846
|
name: s.name,
|
|
787
847
|
color: s.color,
|
|
788
848
|
text:
|
|
@@ -825,12 +885,13 @@ const floatTransition = usePillTransition("float")
|
|
|
825
885
|
// One clip rect wipes across the plot; re-keyed on data change so an update
|
|
826
886
|
// replays the reveal instead of snapping.
|
|
827
887
|
const revealKey = shallowRef(0)
|
|
888
|
+
/* Keyed on the chart's SHAPE, not its values: a polled dashboard refreshing
|
|
889
|
+
its numbers every few seconds must not re-wipe on every tick. */
|
|
828
890
|
watch(
|
|
829
|
-
() =>
|
|
891
|
+
() => `${props.type}|${count.value}|${allSeries.value.map((s) => s.id).join(",")}`,
|
|
830
892
|
() => {
|
|
831
893
|
revealKey.value++
|
|
832
894
|
},
|
|
833
|
-
{ deep: true },
|
|
834
895
|
)
|
|
835
896
|
|
|
836
897
|
const clipId = `lp-chart-${Math.random().toString(36).slice(2, 9)}`
|
|
@@ -854,6 +915,103 @@ const clipId = `lp-chart-${Math.random().toString(36).slice(2, 9)}`
|
|
|
854
915
|
const gradientId = (name: string, index: number) =>
|
|
855
916
|
`${clipId}-g-${index}-${name.replace(/[^a-zA-Z0-9_-]/g, "-")}`
|
|
856
917
|
|
|
918
|
+
// ── overlays ───────────────────────────────────
|
|
919
|
+
const thresholdMarks = computed(() =>
|
|
920
|
+
props.thresholds.flatMap((t, i) => {
|
|
921
|
+
if (!Number.isFinite(t.value) || width.value <= 0) return []
|
|
922
|
+
const sc = t.axis === "right" && rightScale.value ? rightScale.value : scale.value
|
|
923
|
+
const y = yAt(t.value, sc.min, sc.max, box.value)
|
|
924
|
+
const to = t.to != null && Number.isFinite(t.to) ? yAt(t.to, sc.min, sc.max, box.value) : null
|
|
925
|
+
return [
|
|
926
|
+
{
|
|
927
|
+
key: i,
|
|
928
|
+
y,
|
|
929
|
+
top: to == null ? y : Math.min(y, to),
|
|
930
|
+
height: to == null ? 0 : Math.abs(to - y),
|
|
931
|
+
label: t.label,
|
|
932
|
+
color: t.color ?? "var(--color-line-strong)",
|
|
933
|
+
solid: !!t.solid,
|
|
934
|
+
},
|
|
935
|
+
]
|
|
936
|
+
}),
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
const markerMarks = computed(() =>
|
|
940
|
+
props.markers.flatMap((m, i) => {
|
|
941
|
+
const index = markerIndex(m.at, labels.value, count.value)
|
|
942
|
+
if (index < 0 || width.value <= 0) return []
|
|
943
|
+
return [
|
|
944
|
+
{
|
|
945
|
+
key: i,
|
|
946
|
+
x: xAt(index, count.value, box.value, banded.value),
|
|
947
|
+
label: m.label,
|
|
948
|
+
color: m.color ?? "var(--color-line-strong)",
|
|
949
|
+
},
|
|
950
|
+
]
|
|
951
|
+
}),
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
// ── keyboard & assistive text ──────────────────
|
|
955
|
+
const summary = computed(
|
|
956
|
+
() => props.ariaLabel ?? chartSummary(visible.value, labels.value, fmtSeries, props.title),
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
/* Arrow keys walk the points the way the pointer does; the readout below is
|
|
960
|
+
announced politely so each step is heard without stealing focus. */
|
|
961
|
+
const keyboard = ref(false)
|
|
962
|
+
|
|
963
|
+
const onKeydown = (e: KeyboardEvent) => {
|
|
964
|
+
if (!count.value || !hasData.value) return
|
|
965
|
+
const last = count.value - 1
|
|
966
|
+
let next: number
|
|
967
|
+
switch (e.key) {
|
|
968
|
+
case "ArrowRight":
|
|
969
|
+
next = active.value < 0 ? 0 : Math.min(last, active.value + 1)
|
|
970
|
+
break
|
|
971
|
+
case "ArrowLeft":
|
|
972
|
+
next = active.value < 0 ? last : Math.max(0, active.value - 1)
|
|
973
|
+
break
|
|
974
|
+
case "Home":
|
|
975
|
+
next = 0
|
|
976
|
+
break
|
|
977
|
+
case "End":
|
|
978
|
+
next = last
|
|
979
|
+
break
|
|
980
|
+
case "Enter":
|
|
981
|
+
case " ":
|
|
982
|
+
if (active.value < 0) return
|
|
983
|
+
e.preventDefault()
|
|
984
|
+
emit("pointClick", pointEvent(new MouseEvent("click")))
|
|
985
|
+
return
|
|
986
|
+
case "Escape":
|
|
987
|
+
active.value = -1
|
|
988
|
+
return
|
|
989
|
+
default:
|
|
990
|
+
return
|
|
991
|
+
}
|
|
992
|
+
e.preventDefault()
|
|
993
|
+
keyboard.value = true
|
|
994
|
+
active.value = next
|
|
995
|
+
const first = activePoints.value[0]
|
|
996
|
+
pointer.value = {
|
|
997
|
+
x: activeX.value,
|
|
998
|
+
y: first ? first.y : box.value.top + plotHeight(box.value) / 2,
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const onBlur = () => {
|
|
1003
|
+
if (keyboard.value) active.value = -1
|
|
1004
|
+
keyboard.value = false
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const readout = computed(() =>
|
|
1008
|
+
keyboard.value && active.value >= 0
|
|
1009
|
+
? `${labels.value[active.value] ?? `${strings.value.point} ${active.value + 1}`}: ${tooltipRows.value
|
|
1010
|
+
.map((r) => `${r.name} ${r.text}`)
|
|
1011
|
+
.join(", ")}`
|
|
1012
|
+
: "",
|
|
1013
|
+
)
|
|
1014
|
+
|
|
857
1015
|
// 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
1016
|
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
859
1017
|
"flex w-full flex-col gap-3",
|
|
@@ -878,11 +1036,12 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
878
1036
|
>
|
|
879
1037
|
<button
|
|
880
1038
|
v-for="s in allSeries"
|
|
881
|
-
:key="s.
|
|
1039
|
+
:key="s.id"
|
|
882
1040
|
type="button"
|
|
883
1041
|
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
|
-
|
|
1042
|
+
:class="hidden.has(s.id) ? 'opacity-40' : ''"
|
|
1043
|
+
:aria-pressed="!hidden.has(s.id)"
|
|
1044
|
+
@click="toggleSeries(s.id)"
|
|
886
1045
|
>
|
|
887
1046
|
<span
|
|
888
1047
|
class="size-2 shrink-0 rounded-full transition-transform duration-fast group-hover:scale-125"
|
|
@@ -892,7 +1051,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
892
1051
|
<span
|
|
893
1052
|
v-if="hasRightAxis && s.axis === 'right'"
|
|
894
1053
|
class="text-[10px] text-muted"
|
|
895
|
-
title="
|
|
1054
|
+
:title="strings.rightAxis"
|
|
896
1055
|
>
|
|
897
1056
|
↗
|
|
898
1057
|
</span>
|
|
@@ -904,13 +1063,22 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
904
1063
|
<LpContextMenu :items="menuEntries" :always="!!menuItems">
|
|
905
1064
|
<!-- `group/plot` + data-state lets the tooltip hide itself while the
|
|
906
1065
|
context menu is up: the two would otherwise stack on the same point. -->
|
|
907
|
-
<div
|
|
1066
|
+
<div
|
|
1067
|
+
class="group/plot relative rounded-control outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
1068
|
+
:style="{ height: `${height}px` }"
|
|
1069
|
+
:tabindex="hasData ? 0 : undefined"
|
|
1070
|
+
:role="hasData ? 'group' : undefined"
|
|
1071
|
+
aria-roledescription="chart"
|
|
1072
|
+
:aria-label="hasData ? summary : undefined"
|
|
1073
|
+
@keydown="onKeydown"
|
|
1074
|
+
@blur="onBlur"
|
|
1075
|
+
>
|
|
908
1076
|
<div
|
|
909
1077
|
v-if="!hasData"
|
|
910
1078
|
class="flex size-full flex-col items-center justify-center gap-2 rounded-control border border-dashed border-line text-muted"
|
|
911
1079
|
>
|
|
912
1080
|
<LpIcon name="lucide:chart-no-axes-column" :size="18" />
|
|
913
|
-
<span class="text-xs">{{ emptyLabel }}</span>
|
|
1081
|
+
<span class="text-xs">{{ emptyLabel ?? strings.empty }}</span>
|
|
914
1082
|
</div>
|
|
915
1083
|
|
|
916
1084
|
<svg
|
|
@@ -918,6 +1086,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
918
1086
|
:width="width"
|
|
919
1087
|
:height="height"
|
|
920
1088
|
class="overflow-visible touch-none select-none"
|
|
1089
|
+
aria-hidden="true"
|
|
921
1090
|
@pointermove="onMove"
|
|
922
1091
|
@pointerleave="onLeave"
|
|
923
1092
|
@click="onClick"
|
|
@@ -927,7 +1096,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
927
1096
|
<linearGradient
|
|
928
1097
|
v-for="(s, i) in lines"
|
|
929
1098
|
:id="gradientId(s.name, i)"
|
|
930
|
-
:key="s.
|
|
1099
|
+
:key="s.id"
|
|
931
1100
|
x1="0"
|
|
932
1101
|
y1="0"
|
|
933
1102
|
x2="0"
|
|
@@ -945,7 +1114,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
945
1114
|
:x="box.left"
|
|
946
1115
|
:y="0"
|
|
947
1116
|
:height="height"
|
|
948
|
-
:width="
|
|
1117
|
+
:width="still ? plotWidth(box) : 0"
|
|
949
1118
|
>
|
|
950
1119
|
<!-- keySplines is --ease-travel written out: SMIL cannot read a
|
|
951
1120
|
CSS custom property, so the curve is duplicated here. Change
|
|
@@ -1022,6 +1191,18 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
1022
1191
|
opacity="0.5"
|
|
1023
1192
|
/>
|
|
1024
1193
|
|
|
1194
|
+
<!-- threshold bands sit under the data -->
|
|
1195
|
+
<rect
|
|
1196
|
+
v-for="t in thresholdMarks.filter((m) => m.height > 0)"
|
|
1197
|
+
:key="`band-${t.key}`"
|
|
1198
|
+
:x="box.left"
|
|
1199
|
+
:y="t.top"
|
|
1200
|
+
:width="plotWidth(box)"
|
|
1201
|
+
:height="t.height"
|
|
1202
|
+
:fill="t.color"
|
|
1203
|
+
opacity="0.08"
|
|
1204
|
+
/>
|
|
1205
|
+
|
|
1025
1206
|
<g :clip-path="`url(#${clipId}-reveal)`">
|
|
1026
1207
|
<!-- bars: paths, so a stack rounds as one shape (see barPath) -->
|
|
1027
1208
|
<path
|
|
@@ -1034,7 +1215,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
1034
1215
|
/>
|
|
1035
1216
|
|
|
1036
1217
|
<!-- one group per series, so hiding one drops only that curve -->
|
|
1037
|
-
<g v-for="(s, i) in lines" :key="`s-${s.
|
|
1218
|
+
<g v-for="(s, i) in lines" :key="`s-${s.id}`" :style="{ opacity: s.alpha }">
|
|
1038
1219
|
<path
|
|
1039
1220
|
v-show="type === 'area'"
|
|
1040
1221
|
:d="s.fill"
|
|
@@ -1064,6 +1245,49 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
1064
1245
|
</g>
|
|
1065
1246
|
</g>
|
|
1066
1247
|
|
|
1248
|
+
<!-- threshold lines and markers sit over the data, under the crosshair -->
|
|
1249
|
+
<g v-for="t in thresholdMarks" :key="`t-${t.key}`">
|
|
1250
|
+
<line
|
|
1251
|
+
:x1="box.left"
|
|
1252
|
+
:x2="box.width - box.right"
|
|
1253
|
+
:y1="t.y"
|
|
1254
|
+
:y2="t.y"
|
|
1255
|
+
:stroke="t.color"
|
|
1256
|
+
stroke-width="1"
|
|
1257
|
+
:stroke-dasharray="t.solid ? undefined : '4 3'"
|
|
1258
|
+
/>
|
|
1259
|
+
<text
|
|
1260
|
+
v-if="t.label"
|
|
1261
|
+
:x="box.width - box.right - 4"
|
|
1262
|
+
:y="t.top - 4"
|
|
1263
|
+
text-anchor="end"
|
|
1264
|
+
class="text-[10px]"
|
|
1265
|
+
:fill="t.color"
|
|
1266
|
+
>
|
|
1267
|
+
{{ t.label }}
|
|
1268
|
+
</text>
|
|
1269
|
+
</g>
|
|
1270
|
+
<g v-for="m in markerMarks" :key="`m-${m.key}`">
|
|
1271
|
+
<line
|
|
1272
|
+
:x1="m.x"
|
|
1273
|
+
:x2="m.x"
|
|
1274
|
+
:y1="box.top"
|
|
1275
|
+
:y2="box.top + plotHeight(box)"
|
|
1276
|
+
:stroke="m.color"
|
|
1277
|
+
stroke-width="1"
|
|
1278
|
+
stroke-dasharray="2 3"
|
|
1279
|
+
/>
|
|
1280
|
+
<text
|
|
1281
|
+
v-if="m.label"
|
|
1282
|
+
:x="m.x + 4"
|
|
1283
|
+
:y="box.top + 9"
|
|
1284
|
+
class="text-[10px]"
|
|
1285
|
+
:fill="m.color"
|
|
1286
|
+
>
|
|
1287
|
+
{{ m.label }}
|
|
1288
|
+
</text>
|
|
1289
|
+
</g>
|
|
1290
|
+
|
|
1067
1291
|
<!-- crosshair -->
|
|
1068
1292
|
<g v-if="active >= 0 && type !== 'bar'">
|
|
1069
1293
|
<line
|
|
@@ -1077,7 +1301,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
1077
1301
|
/>
|
|
1078
1302
|
<circle
|
|
1079
1303
|
v-for="p in activePoints"
|
|
1080
|
-
:key="p.
|
|
1304
|
+
:key="p.id"
|
|
1081
1305
|
:cx="activeX"
|
|
1082
1306
|
:cy="p.y"
|
|
1083
1307
|
r="4"
|
|
@@ -1125,7 +1349,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
1125
1349
|
<div class="flex flex-col gap-1">
|
|
1126
1350
|
<div
|
|
1127
1351
|
v-for="r in tooltipRows"
|
|
1128
|
-
:key="r.
|
|
1352
|
+
:key="r.id"
|
|
1129
1353
|
class="flex items-center justify-between gap-3 text-xs"
|
|
1130
1354
|
>
|
|
1131
1355
|
<span class="flex items-center gap-1.5 text-muted">
|
|
@@ -1137,8 +1361,28 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
1137
1361
|
</div>
|
|
1138
1362
|
</LpShift>
|
|
1139
1363
|
</Motion>
|
|
1364
|
+
|
|
1365
|
+
<span class="sr-only" aria-live="polite">{{ readout }}</span>
|
|
1140
1366
|
</div>
|
|
1141
1367
|
</LpContextMenu>
|
|
1368
|
+
|
|
1369
|
+
<table v-if="dataTable && hasData" class="sr-only">
|
|
1370
|
+
<caption v-if="title">{{ title }}</caption>
|
|
1371
|
+
<thead>
|
|
1372
|
+
<tr>
|
|
1373
|
+
<th scope="col"></th>
|
|
1374
|
+
<th v-for="s in allSeries" :key="s.id" scope="col">{{ s.name }}</th>
|
|
1375
|
+
</tr>
|
|
1376
|
+
</thead>
|
|
1377
|
+
<tbody>
|
|
1378
|
+
<tr v-for="(label, i) in labels" :key="i">
|
|
1379
|
+
<th scope="row">{{ label }}</th>
|
|
1380
|
+
<td v-for="s in allSeries" :key="s.id">
|
|
1381
|
+
{{ s.values[i] == null ? "—" : fmtSeries(s, s.values[i] as number) }}
|
|
1382
|
+
</td>
|
|
1383
|
+
</tr>
|
|
1384
|
+
</tbody>
|
|
1385
|
+
</table>
|
|
1142
1386
|
</div>
|
|
1143
1387
|
</template>
|
|
1144
1388
|
|
|
@@ -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
|
>
|