@leavepulse/ui 0.43.4 → 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-B-0Est4Z.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 -3
- package/dist/components/LpCommandPalette.vue.js +86 -72
- 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/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 +32 -2
- package/dist/index.js +184 -137
- 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 +34 -13
- 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/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 +37 -2
- 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
|
@@ -12,37 +12,47 @@ import {
|
|
|
12
12
|
ToastViewport,
|
|
13
13
|
} from "reka-ui"
|
|
14
14
|
import { onBeforeUnmount, ref, watch } from "vue"
|
|
15
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
15
16
|
import { useToast, type ToastItem } from "../composables/useToast"
|
|
16
17
|
import { CLOSE_ICON } from "./dropdown"
|
|
17
18
|
import LpIcon from "./LpIcon.vue"
|
|
18
19
|
|
|
20
|
+
const props = defineProps<{ labels?: Partial<{ dismiss: string }> }>()
|
|
21
|
+
const text = useLabels("toast", { dismiss: "Dismiss" }, () => props.labels)
|
|
22
|
+
|
|
19
23
|
const { queue, dismiss } = useToast()
|
|
20
24
|
|
|
25
|
+
// A toast replaced through its `key` keeps its id and gets a new revision, so
|
|
26
|
+
// everything tied to one rendered toast goes by both.
|
|
27
|
+
const tag = (t: ToastItem) => `${t.id}:${t.rev}`
|
|
28
|
+
|
|
21
29
|
// Toasts that are playing their exit animation. We don't splice them out of the
|
|
22
30
|
// queue immediately on close — that would unmount before `toast-out` can run.
|
|
23
31
|
// Instead we flip `open` to false (reka sets data-state=closed → the keyframe
|
|
24
32
|
// plays), then drop the toast for real once `animationend` fires. A safety
|
|
25
33
|
// timer removes it even if the animationend event is missed (e.g. tab hidden).
|
|
26
|
-
const closing = ref<Set<
|
|
34
|
+
const closing = ref<Set<string>>(new Set())
|
|
27
35
|
|
|
28
|
-
function beginClose(
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
function beginClose(t: ToastItem) {
|
|
37
|
+
const at = tag(t)
|
|
38
|
+
if (closing.value.has(at)) return
|
|
39
|
+
closing.value = new Set(closing.value).add(at)
|
|
31
40
|
// Fallback slightly longer than the 140ms toast-out duration.
|
|
32
|
-
window.setTimeout(() => finishClose(id), 220)
|
|
41
|
+
window.setTimeout(() => finishClose(t.id, at), 220)
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
function finishClose(id: number) {
|
|
36
|
-
if (!closing.value.has(
|
|
44
|
+
function finishClose(id: number, at: string) {
|
|
45
|
+
if (!closing.value.has(at)) return
|
|
37
46
|
const next = new Set(closing.value)
|
|
38
|
-
next.delete(
|
|
47
|
+
next.delete(at)
|
|
39
48
|
closing.value = next
|
|
40
|
-
|
|
49
|
+
// Replaced while it was leaving: the new revision stays.
|
|
50
|
+
if (queue.some((t) => tag(t) === at)) dismiss(id)
|
|
41
51
|
}
|
|
42
52
|
|
|
43
|
-
function onAnimEnd(e: AnimationEvent,
|
|
53
|
+
function onAnimEnd(e: AnimationEvent, t: ToastItem) {
|
|
44
54
|
// Only the exit keyframe should trigger removal, not toast-in.
|
|
45
|
-
if (e.animationName === "toast-out") finishClose(id)
|
|
55
|
+
if (e.animationName === "toast-out") finishClose(t.id, tag(t))
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
const variantIcon: Record<ToastItem["variant"], string> = {
|
|
@@ -97,7 +107,7 @@ watch(
|
|
|
97
107
|
// Same for the closing set — a toast evicted by the queue cap or clear()
|
|
98
108
|
// leaves without an animationend, so prune dead ids here.
|
|
99
109
|
if (closing.value.size) {
|
|
100
|
-
const live = new Set([...closing.value].filter((
|
|
110
|
+
const live = new Set([...closing.value].filter((at) => queue.some((t) => tag(t) === at)))
|
|
101
111
|
if (live.size !== closing.value.size) closing.value = live
|
|
102
112
|
}
|
|
103
113
|
if (queue.some((t) => t.dismissAt !== null)) startTimer()
|
|
@@ -126,16 +136,41 @@ function progress(t: ToastItem): number {
|
|
|
126
136
|
return Math.max(0, Math.min(100, (remaining / t.duration) * 100))
|
|
127
137
|
}
|
|
128
138
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
139
|
+
/*
|
|
140
|
+
* reka cancels the click that ends a swipe by adding a listener after ours, so
|
|
141
|
+
* by the time it runs our handler has already acted on it: a swiped-away
|
|
142
|
+
* "View details" toast navigated. The swipe marks the toast instead, and the
|
|
143
|
+
* mark lasts only until that trailing click has had its chance.
|
|
144
|
+
*/
|
|
145
|
+
let swiped: string | null = null
|
|
146
|
+
function onSwipeDone(t: ToastItem) {
|
|
147
|
+
swiped = tag(t)
|
|
148
|
+
window.setTimeout(() => (swiped = null), 0)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const busy = ref<Set<string>>(new Set())
|
|
152
|
+
|
|
153
|
+
async function settle(t: ToastItem, run: () => void | Promise<void>) {
|
|
154
|
+
const at = tag(t)
|
|
155
|
+
if (busy.value.has(at)) return
|
|
156
|
+
busy.value = new Set(busy.value).add(at)
|
|
157
|
+
try {
|
|
158
|
+
await run()
|
|
159
|
+
beginClose(t)
|
|
160
|
+
} finally {
|
|
161
|
+
const next = new Set(busy.value)
|
|
162
|
+
next.delete(at)
|
|
163
|
+
busy.value = next
|
|
133
164
|
}
|
|
134
165
|
}
|
|
135
166
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
167
|
+
function runClick(t: ToastItem) {
|
|
168
|
+
if (swiped === tag(t)) return
|
|
169
|
+
if (t.onClick) return settle(t, t.onClick)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>[number]) {
|
|
173
|
+
return settle(t, action.onClick)
|
|
139
174
|
}
|
|
140
175
|
</script>
|
|
141
176
|
|
|
@@ -143,13 +178,16 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
|
|
|
143
178
|
<ToastProvider>
|
|
144
179
|
<ToastRoot
|
|
145
180
|
v-for="t in queue"
|
|
146
|
-
:key="t
|
|
147
|
-
:open="!closing.has(t
|
|
181
|
+
:key="tag(t)"
|
|
182
|
+
:open="!closing.has(tag(t))"
|
|
148
183
|
:duration="t.duration"
|
|
184
|
+
:type="t.variant === 'error' || t.variant === 'warning' ? 'foreground' : 'background'"
|
|
149
185
|
class="pointer-events-auto relative overflow-hidden rounded-card border border-line bg-surface-raised shadow-panel data-[state=open]:animate-[toast-in_200ms_var(--ease-emphasized)] data-[state=closed]:animate-[toast-out_140ms_ease]"
|
|
150
186
|
:class="{ 'cursor-pointer': t.onClick }"
|
|
151
|
-
@update:open="(open) => !open && beginClose(t
|
|
152
|
-
@animationend="(e: AnimationEvent) => onAnimEnd(e, t
|
|
187
|
+
@update:open="(open) => !open && beginClose(t)"
|
|
188
|
+
@animationend="(e: AnimationEvent) => onAnimEnd(e, t)"
|
|
189
|
+
@swipe-end="onSwipeDone(t)"
|
|
190
|
+
@swipe-cancel="onSwipeDone(t)"
|
|
153
191
|
@pause="onPause(t)"
|
|
154
192
|
@resume="onResume(t)"
|
|
155
193
|
@click="runClick(t)"
|
|
@@ -178,7 +216,9 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
|
|
|
178
216
|
v-for="(action, i) in t.actions"
|
|
179
217
|
:key="i"
|
|
180
218
|
type="button"
|
|
181
|
-
|
|
219
|
+
:disabled="busy.has(tag(t))"
|
|
220
|
+
:aria-busy="busy.has(tag(t)) || undefined"
|
|
221
|
+
class="inline-flex items-center gap-1.5 rounded-control disabled:opacity-60 border border-line bg-surface-soft px-2.5 py-1 text-xs font-medium text-ink outline-none transition-[border-color,background-color,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] hover:border-line-strong hover:bg-surface-soft/70 active:scale-95 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring"
|
|
182
222
|
@click="runAction(t, action)"
|
|
183
223
|
>
|
|
184
224
|
<LpIcon v-if="action.icon" :name="action.icon" :size="14" />
|
|
@@ -190,8 +230,8 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
|
|
|
190
230
|
<button
|
|
191
231
|
type="button"
|
|
192
232
|
class="group -mr-1 -mt-1 rounded-md p-1 text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink"
|
|
193
|
-
aria-label="
|
|
194
|
-
@click.stop="beginClose(t
|
|
233
|
+
:aria-label="text.dismiss"
|
|
234
|
+
@click.stop="beginClose(t)"
|
|
195
235
|
>
|
|
196
236
|
<LpIcon
|
|
197
237
|
name="lucide:x"
|
|
@@ -28,13 +28,14 @@
|
|
|
28
28
|
* them and the control stops opening. Put `aria-label` on the trigger instead.
|
|
29
29
|
*/
|
|
30
30
|
import {
|
|
31
|
+
injectTooltipProviderContext,
|
|
31
32
|
TooltipContent,
|
|
32
33
|
TooltipPortal,
|
|
33
34
|
TooltipProvider,
|
|
34
35
|
TooltipRoot,
|
|
35
36
|
TooltipTrigger,
|
|
36
37
|
} from "reka-ui"
|
|
37
|
-
import { onBeforeUnmount, ref, watch } from "vue"
|
|
38
|
+
import { Fragment, onBeforeUnmount, ref, watch } from "vue"
|
|
38
39
|
import {
|
|
39
40
|
springSettled,
|
|
40
41
|
springStep,
|
|
@@ -54,7 +55,7 @@ const props = withDefaults(
|
|
|
54
55
|
* adding a description.
|
|
55
56
|
*/
|
|
56
57
|
label?: boolean
|
|
57
|
-
/** Milliseconds before the tip appears on hover. */
|
|
58
|
+
/** Milliseconds before the tip appears on hover. Unset follows the provider, else 200. */
|
|
58
59
|
delay?: number
|
|
59
60
|
/**
|
|
60
61
|
* Show on any focus, not just keyboard focus. Off by default: a tip that
|
|
@@ -83,7 +84,7 @@ const props = withDefaults(
|
|
|
83
84
|
*/
|
|
84
85
|
disabled?: boolean
|
|
85
86
|
}>(),
|
|
86
|
-
{
|
|
87
|
+
{ side: "top" },
|
|
87
88
|
)
|
|
88
89
|
|
|
89
90
|
/*
|
|
@@ -101,6 +102,16 @@ const props = withDefaults(
|
|
|
101
102
|
* the pointer exactly reads as jitter, while a slight lag reads as following.
|
|
102
103
|
* Same reasoning, and the same shared "float" feel, as LpChart's readout.
|
|
103
104
|
*/
|
|
105
|
+
/*
|
|
106
|
+
* A provider of its own only when none is above. Each tip in its own provider
|
|
107
|
+
* meant moving along a toolbar waited the full delay at every button; under a
|
|
108
|
+
* shared one (LpTooltipProvider at the root) the next tip opens at once.
|
|
109
|
+
*/
|
|
110
|
+
const shared = injectTooltipProviderContext(null)
|
|
111
|
+
|
|
112
|
+
const reducedMotion = () =>
|
|
113
|
+
typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
|
114
|
+
|
|
104
115
|
const target = { x: 0, y: 0 }
|
|
105
116
|
const offset = ref({ x: 0, y: 0 })
|
|
106
117
|
|
|
@@ -155,6 +166,11 @@ const onPointerMove = (e: PointerEvent) => {
|
|
|
155
166
|
const half = props.side === "top" || props.side === "bottom" ? rect.height / 2 : 0
|
|
156
167
|
const dir = props.side === "bottom" ? -1 : 1
|
|
157
168
|
target.y = e.clientY - (rect.top + rect.height / 2) + half * dir
|
|
169
|
+
// Without motion the tip goes straight to the reading instead of trailing.
|
|
170
|
+
if (reducedMotion()) {
|
|
171
|
+
offset.value = { x: target.x, y: target.y }
|
|
172
|
+
return
|
|
173
|
+
}
|
|
158
174
|
if (!frame) frame = requestAnimationFrame(settle)
|
|
159
175
|
}
|
|
160
176
|
|
|
@@ -205,7 +221,7 @@ onBeforeUnmount(() => {
|
|
|
205
221
|
</script>
|
|
206
222
|
|
|
207
223
|
<template>
|
|
208
|
-
<TooltipProvider
|
|
224
|
+
<component :is="shared ? Fragment : TooltipProvider" v-bind="shared ? {} : { delayDuration: delay ?? 200 }">
|
|
209
225
|
<!--
|
|
210
226
|
`disable-closing-trigger` in follow mode: reka dismisses a tip when its
|
|
211
227
|
trigger is clicked, which is right for a button (the tip explained the
|
|
@@ -214,6 +230,7 @@ onBeforeUnmount(() => {
|
|
|
214
230
|
-->
|
|
215
231
|
<TooltipRoot
|
|
216
232
|
:open="open"
|
|
233
|
+
:delay-duration="delay"
|
|
217
234
|
:ignore-non-keyboard-focus="!showOnPointerFocus"
|
|
218
235
|
:disable-hoverable-content="followCursor"
|
|
219
236
|
:disable-closing-trigger="followCursor"
|
|
@@ -245,7 +262,7 @@ onBeforeUnmount(() => {
|
|
|
245
262
|
</TooltipContent>
|
|
246
263
|
</TooltipPortal>
|
|
247
264
|
</TooltipRoot>
|
|
248
|
-
</
|
|
265
|
+
</component>
|
|
249
266
|
</template>
|
|
250
267
|
|
|
251
268
|
<style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* Shared timing for every LpTooltip below it: once one tip is showing, moving
|
|
4
|
+
* to the next opens it without waiting the delay again. Mount once near the
|
|
5
|
+
* app root; tooltips outside any provider keep working on their own.
|
|
6
|
+
*/
|
|
7
|
+
import { TooltipProvider } from "reka-ui"
|
|
8
|
+
|
|
9
|
+
withDefaults(
|
|
10
|
+
defineProps<{
|
|
11
|
+
/** Milliseconds before the first tip appears on hover. */
|
|
12
|
+
delay?: number
|
|
13
|
+
/** How long after a tip closes the next one still opens at once. */
|
|
14
|
+
skipDelay?: number
|
|
15
|
+
}>(),
|
|
16
|
+
{ delay: 200, skipDelay: 300 },
|
|
17
|
+
)
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<TooltipProvider :delay-duration="delay" :skip-delay-duration="skipDelay">
|
|
22
|
+
<slot />
|
|
23
|
+
</TooltipProvider>
|
|
24
|
+
</template>
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
type NodeTypesObject,
|
|
23
23
|
} from "@vue-flow/core"
|
|
24
24
|
import { MiniMap } from "@vue-flow/minimap"
|
|
25
|
-
import { computed, markRaw, watch, type Component } from "vue"
|
|
25
|
+
import { computed, defineComponent, h, markRaw, watch, type Component } from "vue"
|
|
26
26
|
import LpInfraNode, { type InfraNodeData } from "./LpInfraNode.vue"
|
|
27
27
|
import LpLaneNode from "./LpLaneNode.vue"
|
|
28
28
|
import LpServiceNode, { type ServiceNodeData } from "./LpServiceNode.vue"
|
|
@@ -193,7 +193,18 @@ const emit = defineEmits<{
|
|
|
193
193
|
// their own props (data/selected) which Vue Flow supplies at runtime as part of
|
|
194
194
|
// NodeProps. Widen to `Component` (not the concrete DefineComponent) so the
|
|
195
195
|
// registry types cleanly without a cast.
|
|
196
|
+
// Vue Flow ships no "group" renderer, so a host frame without one warns and
|
|
197
|
+
// draws nothing inside its styled box. The frame only needs its label.
|
|
198
|
+
const GroupNode = defineComponent({
|
|
199
|
+
props: { label: { type: [String, Object], default: undefined } },
|
|
200
|
+
setup: (props) => () =>
|
|
201
|
+
typeof props.label === "string"
|
|
202
|
+
? h("div", { class: "px-3 pt-2 text-xs font-medium text-muted" }, props.label)
|
|
203
|
+
: null,
|
|
204
|
+
})
|
|
205
|
+
|
|
196
206
|
const nodeTypes: NodeTypesObject = {
|
|
207
|
+
group: markRaw(GroupNode) as Component,
|
|
197
208
|
infra: markRaw(LpInfraNode) as Component,
|
|
198
209
|
service: markRaw(LpServiceNode) as Component,
|
|
199
210
|
lane: markRaw(LpLaneNode) as Component,
|
|
@@ -11,6 +11,7 @@ import LpNumberFlow from "./LpNumberFlow.vue"
|
|
|
11
11
|
import LpRollingText from "./LpRollingText.vue"
|
|
12
12
|
import LpTooltip from "./LpTooltip.vue"
|
|
13
13
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
14
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
14
15
|
|
|
15
16
|
export type UptimeStatus = "operational" | "degraded" | "down" | "maintenance" | "empty"
|
|
16
17
|
|
|
@@ -39,6 +40,8 @@ const props = withDefaults(
|
|
|
39
40
|
* `{ degraded: '#ffb84d' }` to recolour just one state.
|
|
40
41
|
*/
|
|
41
42
|
colors?: Partial<Record<UptimeStatus, string>>
|
|
43
|
+
/** Status names, for the tooltip and screen readers. */
|
|
44
|
+
labels?: Partial<Record<UptimeStatus, string>>
|
|
42
45
|
}>(),
|
|
43
46
|
{ showUptime: true },
|
|
44
47
|
)
|
|
@@ -64,13 +67,17 @@ const opacityFor = (status: UptimeStatus): number =>
|
|
|
64
67
|
const colorFor = (status: UptimeStatus): string =>
|
|
65
68
|
props.colors?.[status] ?? defaultColor[status]
|
|
66
69
|
|
|
67
|
-
const statusLabel
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
const statusLabel = useLabels(
|
|
71
|
+
"uptimeBar",
|
|
72
|
+
{
|
|
73
|
+
operational: "Operational",
|
|
74
|
+
degraded: "Degraded",
|
|
75
|
+
down: "Down",
|
|
76
|
+
maintenance: "Maintenance",
|
|
77
|
+
empty: "No data",
|
|
78
|
+
},
|
|
79
|
+
() => props.labels,
|
|
80
|
+
)
|
|
74
81
|
|
|
75
82
|
// Uptime counts only slices with data; operational + maintenance are "up".
|
|
76
83
|
const uptimePct = computed(() => {
|
|
@@ -114,9 +121,35 @@ const tip = computed(() => {
|
|
|
114
121
|
if (i == null) return null
|
|
115
122
|
const seg = props.segments[i]
|
|
116
123
|
if (!seg) return null
|
|
117
|
-
return { status: seg.status, text: seg.label ?? statusLabel[seg.status] }
|
|
124
|
+
return { status: seg.status, text: seg.label ?? statusLabel.value[seg.status] }
|
|
118
125
|
})
|
|
119
126
|
|
|
127
|
+
/*
|
|
128
|
+
* One Tab stop for the whole bar, arrows to walk it. A quarter's worth of
|
|
129
|
+
* slices as separate stops made the bar ninety Tab presses to get past.
|
|
130
|
+
*/
|
|
131
|
+
const focusIndex = ref(-1)
|
|
132
|
+
const slots = ref<HTMLElement[]>([])
|
|
133
|
+
const tabStop = computed(() =>
|
|
134
|
+
focusIndex.value >= 0 && focusIndex.value < props.segments.length
|
|
135
|
+
? focusIndex.value
|
|
136
|
+
: props.segments.length - 1,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
function onSlotKeydown(e: KeyboardEvent, i: number) {
|
|
140
|
+
const last = props.segments.length - 1
|
|
141
|
+
const next =
|
|
142
|
+
e.key === "ArrowRight" ? Math.min(last, i + 1)
|
|
143
|
+
: e.key === "ArrowLeft" ? Math.max(0, i - 1)
|
|
144
|
+
: e.key === "Home" ? 0
|
|
145
|
+
: e.key === "End" ? last
|
|
146
|
+
: -1
|
|
147
|
+
if (next < 0) return
|
|
148
|
+
e.preventDefault()
|
|
149
|
+
focusIndex.value = next
|
|
150
|
+
slots.value[next]?.focus()
|
|
151
|
+
}
|
|
152
|
+
|
|
120
153
|
const uptimeDecimals = computed(() => {
|
|
121
154
|
const v = uptimePct.value
|
|
122
155
|
if (v == null) return 0
|
|
@@ -168,10 +201,15 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
168
201
|
<button
|
|
169
202
|
v-for="(seg, i) in segments"
|
|
170
203
|
:key="i"
|
|
204
|
+
ref="slots"
|
|
171
205
|
type="button"
|
|
172
206
|
class="lp-uptime__slot min-w-0 flex-1 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
207
|
+
:tabindex="i === tabStop ? 0 : -1"
|
|
173
208
|
:aria-label="seg.label ?? statusLabel[seg.status]"
|
|
174
209
|
@pointerenter="hovered = i"
|
|
210
|
+
@focus="focusIndex = i; hovered = i"
|
|
211
|
+
@blur="hovered === i && (hovered = null)"
|
|
212
|
+
@keydown="onSlotKeydown($event, i)"
|
|
175
213
|
>
|
|
176
214
|
<span
|
|
177
215
|
class="lp-uptime__mark block h-full rounded-[1px] transition-[opacity,scale] duration-[var(--duration-fast)] motion-reduce:transition-none"
|
package/src/components/chart.ts
CHANGED
|
@@ -30,6 +30,62 @@ export interface ChartSeries {
|
|
|
30
30
|
|
|
31
31
|
export type ChartAxis = "left" | "right"
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* A reference level drawn across the plot: a line at `value`, or a band from
|
|
35
|
+
* `value` to `to`. SLOs, budgets, alert thresholds.
|
|
36
|
+
*/
|
|
37
|
+
export interface ChartThreshold {
|
|
38
|
+
value: number
|
|
39
|
+
/** Upper (or lower) edge of a band. Omit for a single line. */
|
|
40
|
+
to?: number
|
|
41
|
+
label?: string
|
|
42
|
+
/** Any CSS colour. Defaults to the strong line token. */
|
|
43
|
+
color?: string
|
|
44
|
+
axis?: ChartAxis
|
|
45
|
+
/** Solid instead of the default dashed line. */
|
|
46
|
+
solid?: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A vertical marker at one x position: a deploy, an incident, a release.
|
|
51
|
+
* `at` is a point index or one of the x labels.
|
|
52
|
+
*/
|
|
53
|
+
export interface ChartMarker {
|
|
54
|
+
at: number | string
|
|
55
|
+
label?: string
|
|
56
|
+
color?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The x index a marker points at, or -1 when it names nothing on the axis. */
|
|
60
|
+
export const markerIndex = (at: number | string, labels: string[], count: number): number => {
|
|
61
|
+
if (typeof at === "number") return Number.isInteger(at) && at >= 0 && at < count ? at : -1
|
|
62
|
+
return labels.indexOf(at)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A one-sentence reading of the chart for assistive technology: what is
|
|
67
|
+
* plotted, over how many points, and each series' range and latest value.
|
|
68
|
+
*/
|
|
69
|
+
export const chartSummary = (
|
|
70
|
+
series: NormalSeries[],
|
|
71
|
+
labels: string[],
|
|
72
|
+
fmt: (s: NormalSeries, v: number) => string,
|
|
73
|
+
title?: string,
|
|
74
|
+
): string => {
|
|
75
|
+
const parts: string[] = []
|
|
76
|
+
const span = labels.length ? `${labels.length} points, ${labels[0]} to ${labels[labels.length - 1]}` : ""
|
|
77
|
+
parts.push([title, span].filter(Boolean).join(", "))
|
|
78
|
+
for (const s of series) {
|
|
79
|
+
const values = s.values.filter((v): v is number => v != null)
|
|
80
|
+
if (!values.length) continue
|
|
81
|
+
const last = values[values.length - 1]
|
|
82
|
+
parts.push(
|
|
83
|
+
`${s.name}: from ${fmt(s, Math.min(...values))} to ${fmt(s, Math.max(...values))}, latest ${fmt(s, last)}`,
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
return parts.filter(Boolean).join(". ")
|
|
87
|
+
}
|
|
88
|
+
|
|
33
89
|
export interface NormalSeries {
|
|
34
90
|
name: string
|
|
35
91
|
color: string
|
|
@@ -592,6 +648,7 @@ export const calloutLabels = (
|
|
|
592
648
|
return side === "right" ? a : -a
|
|
593
649
|
}
|
|
594
650
|
const list = sides[side].sort((p, q) => rank(p.mid) - rank(q.mid))
|
|
651
|
+
if (!list.length) continue
|
|
595
652
|
|
|
596
653
|
/*
|
|
597
654
|
* Resolve collisions by NUDGING, not by stacking.
|
|
@@ -676,11 +733,26 @@ export const calloutLabels = (
|
|
|
676
733
|
return out
|
|
677
734
|
}
|
|
678
735
|
|
|
679
|
-
/** Compact default tick formatting: 1.2k, 3.4M — never 1200.0000001. */
|
|
736
|
+
/** Compact default tick formatting: 1.2k, 3.4M — never 1200.0000001 or 1000k. */
|
|
680
737
|
export const formatTick = (v: number): string => {
|
|
738
|
+
if (!Number.isFinite(v)) return String(v)
|
|
739
|
+
const units: [number, string][] = [
|
|
740
|
+
[1e3, "k"],
|
|
741
|
+
[1e6, "M"],
|
|
742
|
+
[1e9, "B"],
|
|
743
|
+
[1e12, "T"],
|
|
744
|
+
]
|
|
681
745
|
const abs = Math.abs(v)
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if (
|
|
685
|
-
|
|
746
|
+
let i = units.length - 1
|
|
747
|
+
while (i >= 0 && abs < units[i][0]) i--
|
|
748
|
+
if (i < 0) {
|
|
749
|
+
const plain = Number(v.toFixed(2))
|
|
750
|
+
return Math.abs(plain) >= 1000 ? formatTick(Math.sign(v) * 1000) : String(plain)
|
|
751
|
+
}
|
|
752
|
+
const scaled = Number((v / units[i][0]).toFixed(1))
|
|
753
|
+
// 999 950 rounds to "1000k"; it reads as the next unit up.
|
|
754
|
+
if (Math.abs(scaled) >= 1000 && i < units.length - 1) {
|
|
755
|
+
return `${Number((v / units[i + 1][0]).toFixed(1))}${units[i + 1][1]}`
|
|
756
|
+
}
|
|
757
|
+
return `${scaled}${units[i][1]}`
|
|
686
758
|
}
|
|
@@ -79,19 +79,67 @@ const BOOLEANS = new Set([
|
|
|
79
79
|
"Some", "Ok", "Err", // rust common variants read as constants
|
|
80
80
|
])
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"
|
|
94
|
-
|
|
82
|
+
/*
|
|
83
|
+
* Comment syntax is the one thing the shared tokenizer cannot guess: `#` opens
|
|
84
|
+
* a comment in shell, Python, YAML and TOML but is a private field in
|
|
85
|
+
* TypeScript, and `//` is a comment in C-family code but part of every URL in a
|
|
86
|
+
* YAML file. So each language names its own comment forms.
|
|
87
|
+
*/
|
|
88
|
+
type CommentStyle = "c" | "hash" | "html" | "none"
|
|
89
|
+
|
|
90
|
+
const COMMENTS: Record<CodeLang, CommentStyle> = {
|
|
91
|
+
ts: "c",
|
|
92
|
+
js: "c",
|
|
93
|
+
json: "none",
|
|
94
|
+
bash: "hash",
|
|
95
|
+
html: "html",
|
|
96
|
+
python: "hash",
|
|
97
|
+
rust: "c",
|
|
98
|
+
yaml: "hash",
|
|
99
|
+
toml: "hash",
|
|
100
|
+
kotlin: "c",
|
|
101
|
+
java: "c",
|
|
102
|
+
groovy: "c",
|
|
103
|
+
plain: "none",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const COMMENT_PATTERN: Record<CommentStyle, string> = {
|
|
107
|
+
c: "\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\/",
|
|
108
|
+
// A `#` only starts a comment at the start of a word, so `a#b` in a URL or
|
|
109
|
+
// `${#arr[@]}` in shell stay code.
|
|
110
|
+
hash: "(?<![\\w$])#[^\\n]*",
|
|
111
|
+
html: "<!--[\\s\\S]*?-->",
|
|
112
|
+
none: "(?!)",
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Where an unterminated block comment opens on a line, and what closes it. */
|
|
116
|
+
const BLOCK: Partial<Record<CommentStyle, { open: string; close: string }>> = {
|
|
117
|
+
c: { open: "/*", close: "*/" },
|
|
118
|
+
html: { open: "<!--", close: "-->" },
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const masters = new Map<CommentStyle, RegExp>()
|
|
122
|
+
|
|
123
|
+
// One master regex per comment style, alternation ordered so longer/greedier
|
|
124
|
+
// rules win first. Order: comments → strings → numbers → identifiers → punctuation.
|
|
125
|
+
function masterFor(style: CommentStyle): RegExp {
|
|
126
|
+
let re = masters.get(style)
|
|
127
|
+
if (!re) {
|
|
128
|
+
re = new RegExp(
|
|
129
|
+
[
|
|
130
|
+
`(${COMMENT_PATTERN[style]})`, // 1 comment
|
|
131
|
+
"(\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|`(?:\\\\.|[^`\\\\])*`)", // 2 string
|
|
132
|
+
"(@[A-Za-z_$][\\w$]*)", // 3 annotation (@Override, @ConfigSerializable — JVM)
|
|
133
|
+
"(\\b\\d[\\d_]*(?:\\.\\d+)?(?:e[+-]?\\d+)?[fFlLdD]?\\b)", // 4 number (JVM suffixes)
|
|
134
|
+
"(#?[A-Za-z_$][\\w$]*)", // 5 identifier (keyword/bool/func/prop/plain), incl. #private
|
|
135
|
+
"([{}()\\[\\];:,.<>=+\\-*/%!&|?]+)", // 6 punctuation
|
|
136
|
+
].join("|"),
|
|
137
|
+
"g",
|
|
138
|
+
)
|
|
139
|
+
masters.set(style, re)
|
|
140
|
+
}
|
|
141
|
+
return re
|
|
142
|
+
}
|
|
95
143
|
|
|
96
144
|
function classifyIdent(name: string, before: string, after: string): string {
|
|
97
145
|
if (KEYWORDS.has(name)) return C.keyword
|
|
@@ -103,11 +151,13 @@ function classifyIdent(name: string, before: string, after: string): string {
|
|
|
103
151
|
|
|
104
152
|
/** Tokenize one line into coloured spans. JSON keys get the property colour. */
|
|
105
153
|
export function tokenizeLine(line: string, lang: CodeLang): Token[] {
|
|
154
|
+
if (lang === "plain") return line ? [{ text: line, cls: "" }] : []
|
|
106
155
|
const out: Token[] = []
|
|
107
156
|
let last = 0
|
|
108
157
|
let m: RegExpExecArray | null
|
|
109
|
-
|
|
110
|
-
|
|
158
|
+
const master = masterFor(COMMENTS[lang])
|
|
159
|
+
master.lastIndex = 0
|
|
160
|
+
while ((m = master.exec(line)) !== null) {
|
|
111
161
|
if (m.index > last) out.push({ text: line.slice(last, m.index), cls: "" })
|
|
112
162
|
const [full, comment, str, annotation, num, ident, punct] = m
|
|
113
163
|
if (comment != null) out.push({ text: full, cls: C.comment })
|
|
@@ -135,3 +185,49 @@ export function tokenizeLine(line: string, lang: CodeLang): Token[] {
|
|
|
135
185
|
if (last < line.length) out.push({ text: line.slice(last), cls: "" })
|
|
136
186
|
return out
|
|
137
187
|
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Tokenize a whole snippet, carrying a block comment (`/* … *\/`, `<!-- … -->`)
|
|
191
|
+
* across the lines it spans. `tokenizeLine` alone sees each line in isolation
|
|
192
|
+
* and coloured the middle of a doc comment as code.
|
|
193
|
+
*/
|
|
194
|
+
export function tokenizeLines(lines: string[], lang: CodeLang): Token[][] {
|
|
195
|
+
const block = BLOCK[COMMENTS[lang]]
|
|
196
|
+
if (!block) return lines.map((line) => tokenizeLine(line, lang))
|
|
197
|
+
const out: Token[][] = []
|
|
198
|
+
let open = false
|
|
199
|
+
for (const line of lines) {
|
|
200
|
+
let rest = line
|
|
201
|
+
const tokens: Token[] = []
|
|
202
|
+
if (open) {
|
|
203
|
+
const end = rest.indexOf(block.close)
|
|
204
|
+
if (end === -1) {
|
|
205
|
+
out.push(rest ? [{ text: rest, cls: C.comment }] : [])
|
|
206
|
+
continue
|
|
207
|
+
}
|
|
208
|
+
tokens.push({ text: rest.slice(0, end + block.close.length), cls: C.comment })
|
|
209
|
+
rest = rest.slice(end + block.close.length)
|
|
210
|
+
open = false
|
|
211
|
+
}
|
|
212
|
+
const lineTokens = tokenizeLine(rest, lang)
|
|
213
|
+
// A closed comment is already a comment token, so an opener left in plain
|
|
214
|
+
// code is one the line never closes: from there on, the line is comment.
|
|
215
|
+
const at = lineTokens.findIndex(
|
|
216
|
+
(t) => t.cls !== C.comment && t.cls !== C.string && t.text.includes(block.open),
|
|
217
|
+
)
|
|
218
|
+
if (at >= 0) {
|
|
219
|
+
const cut = lineTokens[at].text.indexOf(block.open)
|
|
220
|
+
const tail = lineTokens
|
|
221
|
+
.slice(at)
|
|
222
|
+
.map((t) => t.text)
|
|
223
|
+
.join("")
|
|
224
|
+
.slice(cut)
|
|
225
|
+
tokens.push(...lineTokens.slice(0, at))
|
|
226
|
+
if (cut > 0) tokens.push({ text: lineTokens[at].text.slice(0, cut), cls: lineTokens[at].cls })
|
|
227
|
+
tokens.push({ text: tail, cls: C.comment })
|
|
228
|
+
open = true
|
|
229
|
+
} else tokens.push(...lineTokens)
|
|
230
|
+
out.push(tokens)
|
|
231
|
+
}
|
|
232
|
+
return out
|
|
233
|
+
}
|