@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
|
@@ -16,8 +16,10 @@ export default { inheritAttrs: false }
|
|
|
16
16
|
* is filled. Digits-only by default (TOTP); flip `alphanumeric` for backup codes.
|
|
17
17
|
*/
|
|
18
18
|
import { PinInputInput, PinInputRoot } from "reka-ui"
|
|
19
|
-
import { computed } from "vue"
|
|
19
|
+
import { computed, ref, watch } from "vue"
|
|
20
|
+
import { useFieldControl } from "../composables/useField"
|
|
20
21
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
22
|
+
import { joinCells, splitCode, type OtpCells } from "./otp"
|
|
21
23
|
|
|
22
24
|
const props = withDefaults(
|
|
23
25
|
defineProps<{
|
|
@@ -45,13 +47,44 @@ const emit = defineEmits<{
|
|
|
45
47
|
|
|
46
48
|
// reka models the value as an array of per-cell strings; we bridge to a single
|
|
47
49
|
// string at the boundary so callers never touch the array shape.
|
|
50
|
+
//
|
|
51
|
+
// The cells are kept here, positions and all, rather than re-split from the
|
|
52
|
+
// string on every render: clearing a middle cell leaves a gap the string cannot
|
|
53
|
+
// express, and re-splitting it shifted every later digit one cell left. The
|
|
54
|
+
// cells are only rebuilt when the model changes from outside.
|
|
55
|
+
const local = ref<string[]>(splitCode(props.modelValue, props.length))
|
|
56
|
+
let emitted = props.modelValue ?? ""
|
|
57
|
+
watch(
|
|
58
|
+
() => props.modelValue ?? "",
|
|
59
|
+
(value) => {
|
|
60
|
+
if (value === emitted) return
|
|
61
|
+
emitted = value
|
|
62
|
+
local.value = splitCode(value, props.length)
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
watch(
|
|
66
|
+
() => props.length,
|
|
67
|
+
(length) => {
|
|
68
|
+
local.value = local.value.slice(0, length)
|
|
69
|
+
},
|
|
70
|
+
)
|
|
48
71
|
const cells = computed<string[]>({
|
|
49
|
-
get: () =>
|
|
50
|
-
set: (arr) =>
|
|
72
|
+
get: () => local.value,
|
|
73
|
+
set: (arr) => {
|
|
74
|
+
local.value = arr
|
|
75
|
+
emitted = joinCells(arr)
|
|
76
|
+
emit("update:modelValue", emitted)
|
|
77
|
+
},
|
|
51
78
|
})
|
|
52
79
|
|
|
53
|
-
|
|
54
|
-
|
|
80
|
+
const field = useFieldControl(() => ({
|
|
81
|
+
wrapped: false,
|
|
82
|
+
invalid: props.invalid,
|
|
83
|
+
disabled: props.disabled,
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
function onComplete(arr: OtpCells) {
|
|
87
|
+
emit("complete", joinCells(arr))
|
|
55
88
|
}
|
|
56
89
|
|
|
57
90
|
const cellSize = {
|
|
@@ -71,7 +104,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
71
104
|
v-model="cells"
|
|
72
105
|
:type="alphanumeric ? 'text' : 'number'"
|
|
73
106
|
:mask="mask"
|
|
74
|
-
:disabled="disabled"
|
|
107
|
+
:disabled="field.disabled"
|
|
75
108
|
otp
|
|
76
109
|
:class="rootClass"
|
|
77
110
|
v-bind="rest"
|
|
@@ -81,14 +114,16 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
81
114
|
v-for="i in length"
|
|
82
115
|
:key="i"
|
|
83
116
|
:index="i - 1"
|
|
84
|
-
:
|
|
117
|
+
:id="i === 1 ? field.id : undefined"
|
|
118
|
+
:aria-invalid="field.invalid || undefined"
|
|
119
|
+
:aria-describedby="field.describedBy"
|
|
85
120
|
:class="[
|
|
86
121
|
cellSize[size],
|
|
87
122
|
'rounded-control border bg-surface-soft text-center font-semibold text-ink caret-brand',
|
|
88
123
|
'outline-none transition-colors duration-[var(--duration-fast)]',
|
|
89
124
|
'focus:border-brand focus:ring-2 focus:ring-ring',
|
|
90
125
|
'disabled:cursor-not-allowed disabled:opacity-55',
|
|
91
|
-
invalid ? 'border-danger focus:border-danger focus:ring-danger-soft' : 'border-line',
|
|
126
|
+
field.invalid ? 'border-danger focus:border-danger focus:ring-danger-soft' : 'border-line',
|
|
92
127
|
]"
|
|
93
128
|
/>
|
|
94
129
|
</PinInputRoot>
|
|
@@ -22,6 +22,7 @@ import { computed, useId } from "vue"
|
|
|
22
22
|
import { usePillTransition } from "../composables/usePillTransition"
|
|
23
23
|
import LpIcon from "./LpIcon.vue"
|
|
24
24
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
25
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
25
26
|
|
|
26
27
|
const props = withDefaults(
|
|
27
28
|
defineProps<{
|
|
@@ -35,22 +36,37 @@ const props = withDefaults(
|
|
|
35
36
|
/** Pages shown on each side of the current one in the inner run. */
|
|
36
37
|
siblings?: number
|
|
37
38
|
disabled?: boolean
|
|
39
|
+
labels?: Partial<Record<"nav" | "previous" | "next", string>>
|
|
38
40
|
}>(),
|
|
39
41
|
{ page: 1, pageSize: 10, siblings: 1 },
|
|
40
42
|
)
|
|
41
43
|
|
|
44
|
+
const text = useLabels(
|
|
45
|
+
"pagination",
|
|
46
|
+
{ nav: "Pagination", previous: "Previous page", next: "Next page" },
|
|
47
|
+
() => props.labels,
|
|
48
|
+
)
|
|
49
|
+
|
|
42
50
|
const emit = defineEmits<{
|
|
43
51
|
(e: "update:page", value: number): void
|
|
44
52
|
(e: "change", value: number): void
|
|
45
53
|
}>()
|
|
46
54
|
|
|
47
55
|
const count = computed(() => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
// Guarded so a zero page size or a total still loading never yields an
|
|
57
|
+
// "Infinity" or "NaN" page.
|
|
58
|
+
const n =
|
|
59
|
+
props.pageCount != null
|
|
60
|
+
? props.pageCount
|
|
61
|
+
: props.total != null && props.pageSize > 0
|
|
62
|
+
? Math.ceil(props.total / props.pageSize)
|
|
63
|
+
: 1
|
|
64
|
+
return Number.isFinite(n) ? Math.max(1, Math.floor(n)) : 1
|
|
51
65
|
})
|
|
52
66
|
|
|
53
|
-
const current = computed(() =>
|
|
67
|
+
const current = computed(() =>
|
|
68
|
+
Number.isFinite(props.page) ? Math.min(Math.max(1, Math.floor(props.page)), count.value) : 1,
|
|
69
|
+
)
|
|
54
70
|
|
|
55
71
|
// A fixed run of page slots. Each slot is either a real page or a gap ("…").
|
|
56
72
|
// The number of slots is constant for a given `count`, so the row width is
|
|
@@ -122,12 +138,12 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
122
138
|
<template>
|
|
123
139
|
<nav
|
|
124
140
|
:class="rootClass"
|
|
125
|
-
v-bind="rest" aria-label="
|
|
141
|
+
v-bind="rest" :aria-label="text.nav">
|
|
126
142
|
<button
|
|
127
143
|
type="button"
|
|
128
144
|
:class="[navBase, navArrow]"
|
|
129
145
|
:disabled="disabled || current === 1"
|
|
130
|
-
aria-label="
|
|
146
|
+
:aria-label="text.previous"
|
|
131
147
|
@click="go(current - 1)"
|
|
132
148
|
>
|
|
133
149
|
<LpIcon name="lucide:chevron-left" :size="16" />
|
|
@@ -171,7 +187,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
171
187
|
type="button"
|
|
172
188
|
:class="[navBase, navArrow]"
|
|
173
189
|
:disabled="disabled || current === count"
|
|
174
|
-
aria-label="
|
|
190
|
+
:aria-label="text.next"
|
|
175
191
|
@click="go(current + 1)"
|
|
176
192
|
>
|
|
177
193
|
<LpIcon name="lucide:chevron-right" :size="16" />
|
|
@@ -6,6 +6,7 @@ export default { inheritAttrs: false }
|
|
|
6
6
|
// Password field = LpInput + a show/hide toggle in its trailing slot.
|
|
7
7
|
// No duplicated field styling — the shell lives in LpInput.
|
|
8
8
|
import { ref } from "vue"
|
|
9
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
9
10
|
import LpIcon from "./LpIcon.vue"
|
|
10
11
|
import LpInput from "./LpInput.vue"
|
|
11
12
|
|
|
@@ -23,6 +24,7 @@ defineProps<{
|
|
|
23
24
|
}>()
|
|
24
25
|
defineEmits<{ (e: "update:modelValue", value: string): void }>()
|
|
25
26
|
|
|
27
|
+
const text = useLabels("passwordInput", { show: "Show password", hide: "Hide password" })
|
|
26
28
|
const visible = ref(false)
|
|
27
29
|
const inner = ref<InstanceType<typeof LpInput> | null>(null)
|
|
28
30
|
defineExpose({
|
|
@@ -35,6 +37,7 @@ defineExpose({
|
|
|
35
37
|
<template>
|
|
36
38
|
<LpInput
|
|
37
39
|
ref="inner"
|
|
40
|
+
v-bind="$attrs"
|
|
38
41
|
:model-value="modelValue"
|
|
39
42
|
:type="visible ? 'text' : 'password'"
|
|
40
43
|
:placeholder="placeholder"
|
|
@@ -51,7 +54,7 @@ defineExpose({
|
|
|
51
54
|
<button
|
|
52
55
|
type="button"
|
|
53
56
|
class="group rounded-md p-1 text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:text-ink"
|
|
54
|
-
:aria-label="visible ?
|
|
57
|
+
:aria-label="visible ? text.hide : text.show"
|
|
55
58
|
@click="visible = !visible"
|
|
56
59
|
>
|
|
57
60
|
<LpIcon
|
|
@@ -27,9 +27,11 @@ import {
|
|
|
27
27
|
ComboboxTrigger,
|
|
28
28
|
ComboboxViewport,
|
|
29
29
|
} from "reka-ui"
|
|
30
|
-
import { computed, onMounted, ref } from "vue"
|
|
30
|
+
import { computed, onMounted, ref, useAttrs } from "vue"
|
|
31
|
+
import { useFieldControl } from "../composables/useField"
|
|
31
32
|
import { useInputFilter } from "../composables/useInputFilter"
|
|
32
|
-
import {
|
|
33
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
34
|
+
import { type Country, filterCountries, flagEmoji, loadCountries, matchCountryByValue } from "./countries"
|
|
33
35
|
import { OPTION_ITEM, POPOVER_PANEL } from "./dropdown"
|
|
34
36
|
import LpIcon from "./LpIcon.vue"
|
|
35
37
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
@@ -74,7 +76,9 @@ const emit = defineEmits<{
|
|
|
74
76
|
// Phone keys only: digits, "+", spaces and the usual separators. Free typing of
|
|
75
77
|
// "+code" stays possible; letters and junk are blocked at the source.
|
|
76
78
|
const PHONE_CHARS = /[\d+()\-\s]/
|
|
77
|
-
const { onBeforeInput, onPaste } = useInputFilter({
|
|
79
|
+
const { onBeforeInput, onInput: filterInput, onCompositionEnd, onPaste } = useInputFilter({
|
|
80
|
+
restrict: () => PHONE_CHARS,
|
|
81
|
+
})
|
|
78
82
|
|
|
79
83
|
// The dial-code table is code-split; it's empty until the lazy chunk resolves.
|
|
80
84
|
// Everything degrades gracefully meanwhile: digits still parse, the flag shows a
|
|
@@ -100,13 +104,7 @@ const activeCountry = computed<Country | undefined>(
|
|
|
100
104
|
const open = ref(false)
|
|
101
105
|
const query = ref("")
|
|
102
106
|
|
|
103
|
-
const filtered = computed(() =>
|
|
104
|
-
const q = query.value.trim().toLowerCase()
|
|
105
|
-
if (!q) return countries.value
|
|
106
|
-
return countries.value.filter(
|
|
107
|
-
(c) => c.name.toLowerCase().includes(q) || c.dial.includes(q.replace(/\D/g, "")),
|
|
108
|
-
)
|
|
109
|
-
})
|
|
107
|
+
const filtered = computed(() => filterCountries(countries.value, query.value))
|
|
110
108
|
|
|
111
109
|
function parse(value: string): PhoneDetail {
|
|
112
110
|
const country = matchCountryByValue(value, countries.value)
|
|
@@ -123,6 +121,7 @@ function setValue(value: string) {
|
|
|
123
121
|
}
|
|
124
122
|
|
|
125
123
|
function onInput(e: Event) {
|
|
124
|
+
filterInput(e)
|
|
126
125
|
setValue((e.target as HTMLInputElement).value)
|
|
127
126
|
}
|
|
128
127
|
|
|
@@ -139,16 +138,22 @@ function chooseCountry(c: Country) {
|
|
|
139
138
|
query.value = ""
|
|
140
139
|
}
|
|
141
140
|
|
|
142
|
-
// Load the dial-code table on mount (lazy chunk), then seed the default
|
|
143
|
-
// country's "+code" into an empty field so the user starts with the prefix in
|
|
144
|
-
// place — but never clobber a value the consumer already provided.
|
|
145
141
|
onMounted(async () => {
|
|
146
142
|
allCountries.value = await loadCountries()
|
|
147
|
-
if (!props.modelValue && fallback.value) {
|
|
148
|
-
emit("update:modelValue", `+${fallback.value.dial} `)
|
|
149
|
-
}
|
|
150
143
|
})
|
|
151
144
|
|
|
145
|
+
// The default country's "+code" is put in place when the user enters an empty
|
|
146
|
+
// field, and taken back out if they leave without typing a number. Seeding it
|
|
147
|
+
// on mount instead made every untouched optional field submit "+380 ", pass
|
|
148
|
+
// `required`, and read as edited.
|
|
149
|
+
const seeded = computed(() => (fallback.value ? `+${fallback.value.dial} ` : ""))
|
|
150
|
+
function onFocus() {
|
|
151
|
+
if (!props.modelValue && seeded.value) setValue(seeded.value)
|
|
152
|
+
}
|
|
153
|
+
function onBlur() {
|
|
154
|
+
if (seeded.value && props.modelValue?.trim() === seeded.value.trim()) setValue("")
|
|
155
|
+
}
|
|
156
|
+
|
|
152
157
|
const sizeClass = {
|
|
153
158
|
sm: "h-(--size-control-sm) text-xs",
|
|
154
159
|
md: "h-(--size-control-md) text-sm",
|
|
@@ -157,23 +162,37 @@ const sizeClass = {
|
|
|
157
162
|
|
|
158
163
|
// The control is full-width with its own frame — the two things a consumer
|
|
159
164
|
// placing it in a narrow column has to change, so `class="w-48"` must win.
|
|
165
|
+
// style lays out the frame; every other attr (name, autocomplete, @blur…)
|
|
166
|
+
// belongs to the number <input>.
|
|
167
|
+
const attrs = useAttrs()
|
|
168
|
+
const field = useFieldControl(() => ({
|
|
169
|
+
wrapped: false,
|
|
170
|
+
id: typeof attrs.id === "string" ? attrs.id : undefined,
|
|
171
|
+
invalid: props.invalid,
|
|
172
|
+
disabled: props.disabled,
|
|
173
|
+
}))
|
|
174
|
+
const text = useLabels("phoneInput", { selectCountry: "Select country" })
|
|
160
175
|
const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
161
176
|
[
|
|
162
177
|
"flex w-full items-stretch rounded-control border bg-surface-soft text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring",
|
|
163
178
|
sizeClass[props.size],
|
|
164
|
-
|
|
179
|
+
field.value.invalid
|
|
165
180
|
? "border-danger focus-within:border-danger focus-within:ring-danger-soft"
|
|
166
181
|
: "border-line focus-within:border-brand",
|
|
167
|
-
|
|
182
|
+
field.value.disabled ? "cursor-not-allowed opacity-55" : "",
|
|
168
183
|
].join(" "),
|
|
169
184
|
)
|
|
185
|
+
const inputAttrs = computed(() => {
|
|
186
|
+
const { style: _s, ...others } = rest.value
|
|
187
|
+
return others
|
|
188
|
+
})
|
|
170
189
|
</script>
|
|
171
190
|
|
|
172
191
|
<template>
|
|
173
192
|
<div
|
|
174
193
|
data-lp-ring-owner
|
|
175
194
|
:class="rootClass"
|
|
176
|
-
|
|
195
|
+
:style="attrs.style as never"
|
|
177
196
|
>
|
|
178
197
|
<!-- reset-search-term-on-{select,blur}: as in LpSelect, the trigger shows
|
|
179
198
|
the selection and the box only filters, so reka seeding the chosen value
|
|
@@ -184,15 +203,15 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
|
184
203
|
:ignore-filter="true"
|
|
185
204
|
:reset-search-term-on-select="false"
|
|
186
205
|
:reset-search-term-on-blur="false"
|
|
187
|
-
:disabled="disabled"
|
|
206
|
+
:disabled="field.disabled"
|
|
188
207
|
class="flex"
|
|
189
208
|
>
|
|
190
209
|
<ComboboxAnchor as-child>
|
|
191
210
|
<ComboboxTrigger
|
|
192
211
|
type="button"
|
|
193
|
-
:disabled="disabled"
|
|
194
|
-
class="flex shrink-0 items-center gap-1 rounded-l-control border-r border-line px-2.5 outline-none hover:bg-
|
|
195
|
-
aria-label="
|
|
212
|
+
:disabled="field.disabled"
|
|
213
|
+
class="flex shrink-0 items-center gap-1 rounded-l-control border-r border-line px-2.5 outline-none hover:bg-ink/5 data-[state=open]:bg-ink/5"
|
|
214
|
+
:aria-label="text.selectCountry"
|
|
196
215
|
>
|
|
197
216
|
<span class="text-base leading-none">{{ activeCountry ? flagEmoji(activeCountry.iso2) : "🌐" }}</span>
|
|
198
217
|
<LpIcon
|
|
@@ -247,17 +266,24 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
|
247
266
|
</ComboboxRoot>
|
|
248
267
|
|
|
249
268
|
<input
|
|
250
|
-
:value="modelValue"
|
|
251
|
-
:placeholder="placeholder"
|
|
252
|
-
:disabled="disabled"
|
|
253
|
-
:aria-invalid="invalid || undefined"
|
|
254
269
|
type="tel"
|
|
255
270
|
inputmode="tel"
|
|
256
271
|
autocomplete="tel"
|
|
272
|
+
v-bind="inputAttrs"
|
|
273
|
+
:id="field.id"
|
|
274
|
+
:value="modelValue"
|
|
275
|
+
:placeholder="placeholder"
|
|
276
|
+
:disabled="field.disabled"
|
|
277
|
+
:required="field.required || undefined"
|
|
278
|
+
:aria-invalid="field.invalid || undefined"
|
|
279
|
+
:aria-describedby="field.describedBy"
|
|
257
280
|
class="lp-autofill min-w-0 flex-1 self-stretch bg-transparent px-3 outline-none placeholder:text-muted"
|
|
258
281
|
@beforeinput="onBeforeInput"
|
|
259
282
|
@paste="onPaste"
|
|
283
|
+
@compositionend="onCompositionEnd"
|
|
260
284
|
@input="onInput"
|
|
285
|
+
@focus="onFocus"
|
|
286
|
+
@blur="onBlur"
|
|
261
287
|
/>
|
|
262
288
|
</div>
|
|
263
289
|
</template>
|
package/src/components/LpPie.vue
CHANGED
|
@@ -31,6 +31,16 @@ import LpRollingText from "./LpRollingText.vue"
|
|
|
31
31
|
import LpShift from "./LpShift.vue"
|
|
32
32
|
import LpTooltip from "./LpTooltip.vue"
|
|
33
33
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
34
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
35
|
+
|
|
36
|
+
export interface PieText {
|
|
37
|
+
[key: string]: string
|
|
38
|
+
empty: string
|
|
39
|
+
other: string
|
|
40
|
+
total: string
|
|
41
|
+
/** "{n} selected"; `{n}` is replaced by the count. */
|
|
42
|
+
selected: string
|
|
43
|
+
}
|
|
34
44
|
|
|
35
45
|
/** A slice, as reported by every interaction event. */
|
|
36
46
|
export interface SliceEvent {
|
|
@@ -107,21 +117,26 @@ const props = withDefaults(
|
|
|
107
117
|
*/
|
|
108
118
|
menuItems?: (slice: SliceEvent, selection: SliceEvent[]) => ContextMenuItemDef[]
|
|
109
119
|
emptyLabel?: string
|
|
120
|
+
/** Accessible name for the chart. Defaults to a generated summary. */
|
|
121
|
+
ariaLabel?: string
|
|
122
|
+
/** Render the slices as a visually hidden table for screen readers. */
|
|
123
|
+
dataTable?: boolean
|
|
124
|
+
/** Text overrides (`labels` is taken by the callout switch). */
|
|
125
|
+
text?: Partial<PieText>
|
|
110
126
|
}>(),
|
|
111
127
|
{
|
|
112
128
|
type: "donut",
|
|
113
129
|
size: 180,
|
|
114
130
|
innerRatio: 0.62,
|
|
115
131
|
maxSlices: 6,
|
|
116
|
-
otherLabel: "Other",
|
|
117
132
|
showPercent: true,
|
|
118
133
|
showTotal: true,
|
|
119
134
|
labelWidth: 140,
|
|
120
135
|
padAngle: 1.5,
|
|
121
136
|
cornerRadius: 4,
|
|
122
137
|
hoverOffset: 8,
|
|
123
|
-
emptyLabel: "No data",
|
|
124
138
|
animate: true,
|
|
139
|
+
dataTable: false,
|
|
125
140
|
duration: 900,
|
|
126
141
|
},
|
|
127
142
|
)
|
|
@@ -134,11 +149,17 @@ const emit = defineEmits<{
|
|
|
134
149
|
(e: "sliceContextMenu", payload: SliceEvent): void
|
|
135
150
|
}>()
|
|
136
151
|
|
|
152
|
+
const strings = useLabels<PieText>(
|
|
153
|
+
"pie",
|
|
154
|
+
{ empty: "No data", other: "Other", total: "total", selected: "{n} selected" },
|
|
155
|
+
() => props.text,
|
|
156
|
+
)
|
|
157
|
+
|
|
137
158
|
const resolved = computed(() =>
|
|
138
159
|
resolveSlices(
|
|
139
160
|
props.slices,
|
|
140
161
|
props.maxSlices || undefined,
|
|
141
|
-
props.otherLabel,
|
|
162
|
+
props.otherLabel ?? strings.value.other,
|
|
142
163
|
!props.sortBySize,
|
|
143
164
|
),
|
|
144
165
|
)
|
|
@@ -336,6 +357,23 @@ const dragging = ref(false)
|
|
|
336
357
|
|
|
337
358
|
const isSelected = (i: number) => selected.value.has(i)
|
|
338
359
|
|
|
360
|
+
/*
|
|
361
|
+
* Indices only mean something against the slices they were taken from. When
|
|
362
|
+
* the data changes shape, a selection or hover pointing past the end would read
|
|
363
|
+
* a slice that no longer exists, so anything out of range is dropped.
|
|
364
|
+
*/
|
|
365
|
+
watch(
|
|
366
|
+
() => arcs.value.length,
|
|
367
|
+
(n) => {
|
|
368
|
+
if (active.value >= n) active.value = -1
|
|
369
|
+
if (anchor.value >= n) anchor.value = -1
|
|
370
|
+
if ([...selected.value].some((i) => i >= n)) {
|
|
371
|
+
selected.value = new Set([...selected.value].filter((i) => i < n))
|
|
372
|
+
emitSelection()
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
)
|
|
376
|
+
|
|
339
377
|
const payloadFor = (i: number) => {
|
|
340
378
|
const a = arcs.value[i]
|
|
341
379
|
return { index: i, label: a.label, value: a.value, isOther: a.isOther }
|
|
@@ -354,7 +392,9 @@ const rangeTo = (i: number) => {
|
|
|
354
392
|
return next
|
|
355
393
|
}
|
|
356
394
|
|
|
357
|
-
|
|
395
|
+
type Modifiers = Pick<MouseEvent, "button" | "shiftKey" | "ctrlKey" | "metaKey">
|
|
396
|
+
|
|
397
|
+
const onSlicePointerDown = (i: number, e: Modifiers, drag = true) => {
|
|
358
398
|
// Right-click opens a menu; it must not disturb a selection the menu acts on.
|
|
359
399
|
if (e.button === 2) return
|
|
360
400
|
|
|
@@ -372,12 +412,18 @@ const onSlicePointerDown = (i: number, e: PointerEvent) => {
|
|
|
372
412
|
const only = selected.value.size === 1 && selected.value.has(i)
|
|
373
413
|
selected.value = only ? new Set() : new Set([i])
|
|
374
414
|
anchor.value = only ? -1 : i
|
|
375
|
-
if (!only) beginDrag()
|
|
415
|
+
if (!only && drag) beginDrag()
|
|
376
416
|
}
|
|
377
417
|
emitSelection()
|
|
378
418
|
emit("sliceClick", payloadFor(i))
|
|
379
419
|
}
|
|
380
420
|
|
|
421
|
+
/* Enter and Space on a legend entry arrive as a click with no pointer behind
|
|
422
|
+
it (detail 0); pointer clicks were already handled on pointerdown. */
|
|
423
|
+
const onLegendClick = (i: number, e: MouseEvent) => {
|
|
424
|
+
if (e.detail === 0) onSlicePointerDown(i, e, false)
|
|
425
|
+
}
|
|
426
|
+
|
|
381
427
|
/** Drag across slices to gather them — same gesture as a rubber-band in a list. */
|
|
382
428
|
const onSlicePointerEnter = (i: number) => {
|
|
383
429
|
clearTimeout(leaveTimer)
|
|
@@ -516,8 +562,10 @@ const onSliceContextMenu = (i: number) => {
|
|
|
516
562
|
|
|
517
563
|
/** The slice the centre reads out: the hovered one, else a lone selection. */
|
|
518
564
|
const focused = computed(() => {
|
|
519
|
-
|
|
520
|
-
|
|
565
|
+
const n = arcs.value.length
|
|
566
|
+
if (active.value >= 0 && active.value < n) return active.value
|
|
567
|
+
const only = selected.value.size === 1 ? [...selected.value][0] : -1
|
|
568
|
+
return only < n ? only : -1
|
|
521
569
|
})
|
|
522
570
|
|
|
523
571
|
/** Unselected slices only recede once something is actually selected. */
|
|
@@ -526,11 +574,14 @@ const dimmed = (i: number) => selected.value.size > 0 && !selected.value.has(i)
|
|
|
526
574
|
const total = computed(() => resolved.value.total)
|
|
527
575
|
|
|
528
576
|
const selectedArcs = computed(() =>
|
|
529
|
-
[...selected.value]
|
|
577
|
+
[...selected.value]
|
|
578
|
+
.sort((x, y) => x - y)
|
|
579
|
+
.filter((i) => i < arcs.value.length)
|
|
580
|
+
.map((i) => arcs.value[i]),
|
|
530
581
|
)
|
|
531
582
|
|
|
532
583
|
/** The slice under the pointer; null over the gaps, which disables the tip. */
|
|
533
|
-
const hovered = computed(() =>
|
|
584
|
+
const hovered = computed(() => arcs.value[active.value] ?? null)
|
|
534
585
|
|
|
535
586
|
/* Sticky copy for the tooltip's own contents: `hovered` drops to null the
|
|
536
587
|
instant the pointer leaves a slice, and reading it directly would blank the
|
|
@@ -550,10 +601,22 @@ const centreValue = computed(() => {
|
|
|
550
601
|
|
|
551
602
|
const centreLabel = computed(() => {
|
|
552
603
|
if (focused.value >= 0) return arcs.value[focused.value].label
|
|
553
|
-
if (selected.value.size > 1)
|
|
554
|
-
|
|
604
|
+
if (selected.value.size > 1)
|
|
605
|
+
return strings.value.selected.replace("{n}", String(selected.value.size))
|
|
606
|
+
return strings.value.total
|
|
555
607
|
})
|
|
556
608
|
|
|
609
|
+
const summary = computed(
|
|
610
|
+
() =>
|
|
611
|
+
props.ariaLabel ??
|
|
612
|
+
[
|
|
613
|
+
props.title,
|
|
614
|
+
...arcs.value.map((a) => `${a.label}: ${fmt(a.value)} (${pct(a.fraction)})`),
|
|
615
|
+
]
|
|
616
|
+
.filter(Boolean)
|
|
617
|
+
.join(", "),
|
|
618
|
+
)
|
|
619
|
+
|
|
557
620
|
// The pie sets its own gap between chart and legend, which a narrow card has to close up.
|
|
558
621
|
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
559
622
|
"flex flex-col gap-3",
|
|
@@ -575,7 +638,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
575
638
|
:style="{ width: `${size}px`, height: `${size}px` }"
|
|
576
639
|
>
|
|
577
640
|
<LpIcon name="lucide:chart-pie" :size="18" />
|
|
578
|
-
<span class="text-xs">{{ emptyLabel }}</span>
|
|
641
|
+
<span class="text-xs">{{ emptyLabel ?? strings.empty }}</span>
|
|
579
642
|
</div>
|
|
580
643
|
|
|
581
644
|
<!-- items-start, not items-center: an unfolded legend can be taller than
|
|
@@ -644,6 +707,8 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
644
707
|
:width="canvasWidth"
|
|
645
708
|
:height="canvasHeight"
|
|
646
709
|
class="touch-none select-none"
|
|
710
|
+
role="img"
|
|
711
|
+
:aria-label="summary"
|
|
647
712
|
@pointerleave="leaveChart"
|
|
648
713
|
@pointerup="endDrag"
|
|
649
714
|
@click.self="clearSelection"
|
|
@@ -672,7 +737,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
672
737
|
<g :clip-path="animate ? `url(#${sweepId}-sweep)` : undefined">
|
|
673
738
|
<path
|
|
674
739
|
v-for="(a, i) in arcs"
|
|
675
|
-
:key="a.label"
|
|
740
|
+
:key="`${i}-${a.label}`"
|
|
676
741
|
:d="a.d"
|
|
677
742
|
:fill="a.color"
|
|
678
743
|
class="pointer-events-none transition-[transform,opacity] duration-medium ease-[var(--ease-emphasized)] motion-reduce:transition-none"
|
|
@@ -684,7 +749,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
684
749
|
<!-- Invisible, unmoving, and on top: the actual hit area. -->
|
|
685
750
|
<path
|
|
686
751
|
v-for="(a, i) in arcs"
|
|
687
|
-
:key="`hit-${a.label}`"
|
|
752
|
+
:key="`hit-${i}-${a.label}`"
|
|
688
753
|
:d="a.d"
|
|
689
754
|
fill="transparent"
|
|
690
755
|
class="cursor-pointer"
|
|
@@ -765,7 +830,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
765
830
|
<div v-if="legendShown" class="flex min-w-0 flex-col gap-1.5" @pointerup="endDrag">
|
|
766
831
|
<button
|
|
767
832
|
v-for="(a, i) in arcs"
|
|
768
|
-
:key="a.label"
|
|
833
|
+
:key="`${i}-${a.label}`"
|
|
769
834
|
type="button"
|
|
770
835
|
class="group flex cursor-pointer items-center gap-2 rounded-md px-1 py-0.5 text-xs outline-none transition-[opacity,background-color] duration-fast focus-visible:ring-2 focus-visible:ring-ring"
|
|
771
836
|
:class="[dimmed(i) ? 'opacity-45' : '', isSelected(i) ? 'bg-surface-soft' : '']"
|
|
@@ -773,6 +838,9 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
773
838
|
@pointerenter="onSlicePointerEnter(i)"
|
|
774
839
|
@pointerleave="onSlicePointerLeave(i)"
|
|
775
840
|
@pointerdown="onSlicePointerDown(i, $event)"
|
|
841
|
+
@click="onLegendClick(i, $event)"
|
|
842
|
+
@focus="active = i"
|
|
843
|
+
@blur="active === i && (active = -1)"
|
|
776
844
|
@contextmenu="onSliceContextMenu(i)"
|
|
777
845
|
>
|
|
778
846
|
<span
|
|
@@ -789,5 +857,16 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
789
857
|
</div>
|
|
790
858
|
</div>
|
|
791
859
|
</LpContextMenu>
|
|
860
|
+
|
|
861
|
+
<table v-if="dataTable && hasData" class="sr-only">
|
|
862
|
+
<caption v-if="title">{{ title }}</caption>
|
|
863
|
+
<tbody>
|
|
864
|
+
<tr v-for="(a, i) in arcs" :key="`${i}-${a.label}`">
|
|
865
|
+
<th scope="row">{{ a.label }}</th>
|
|
866
|
+
<td>{{ fmt(a.value) }}</td>
|
|
867
|
+
<td>{{ pct(a.fraction) }}</td>
|
|
868
|
+
</tr>
|
|
869
|
+
</tbody>
|
|
870
|
+
</table>
|
|
792
871
|
</div>
|
|
793
872
|
</template>
|
|
@@ -14,6 +14,8 @@ withDefaults(
|
|
|
14
14
|
sideOffset?: number
|
|
15
15
|
/** Extra classes for the floating panel (width, padding, overflow). */
|
|
16
16
|
panelClass?: string
|
|
17
|
+
/** Accessible name of the panel, which reka exposes as a dialog. */
|
|
18
|
+
ariaLabel?: string
|
|
17
19
|
}>(),
|
|
18
20
|
{ side: "bottom", align: "center", sideOffset: 6 },
|
|
19
21
|
)
|
|
@@ -33,6 +35,7 @@ const open = defineModel<boolean>("open")
|
|
|
33
35
|
:side="side"
|
|
34
36
|
:align="align"
|
|
35
37
|
:side-offset="sideOffset"
|
|
38
|
+
:aria-label="ariaLabel"
|
|
36
39
|
:class="[POPOVER_PANEL, 'z-(--z-popover) w-64 rounded-card p-3 text-sm text-ink outline-none', panelClass]"
|
|
37
40
|
>
|
|
38
41
|
<slot />
|
|
@@ -166,9 +166,11 @@ watch(
|
|
|
166
166
|
() => runSweep(props.value, props.duration),
|
|
167
167
|
)
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
)
|
|
169
|
+
// A zero or missing max (no quota known yet) reads as empty, not as NaN%.
|
|
170
|
+
const pct = computed(() => {
|
|
171
|
+
const ratio = props.max > 0 ? (shown.value / props.max) * 100 : 0
|
|
172
|
+
return Number.isFinite(ratio) ? Math.max(0, Math.min(100, ratio)) : 0
|
|
173
|
+
})
|
|
172
174
|
|
|
173
175
|
/*
|
|
174
176
|
* The tone actually painted. `auto` reads the fill and crosses two thresholds;
|
|
@@ -7,6 +7,7 @@ export default { inheritAttrs: false }
|
|
|
7
7
|
|
|
8
8
|
<script setup lang="ts">
|
|
9
9
|
import { RadioGroupIndicator, RadioGroupItem, RadioGroupRoot } from "reka-ui"
|
|
10
|
+
import { useFieldControl } from "../composables/useField"
|
|
10
11
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
11
12
|
|
|
12
13
|
export interface RadioOption {
|
|
@@ -17,9 +18,11 @@ export interface RadioOption {
|
|
|
17
18
|
// `options` renders simple {value,label} rows. For rich items (icons, badges,
|
|
18
19
|
// multi-line) pass <LpRadio> children in the default slot instead — they share
|
|
19
20
|
// the same RadioGroupRoot, so selection still flows through v-model.
|
|
20
|
-
defineProps<{ modelValue?: string; options?: RadioOption[]; disabled?: boolean }>()
|
|
21
|
+
const props = defineProps<{ modelValue?: string; options?: RadioOption[]; disabled?: boolean }>()
|
|
21
22
|
defineEmits<{ (e: "update:modelValue", value: string): void }>()
|
|
22
23
|
|
|
24
|
+
const field = useFieldControl(() => ({ wrapped: false, disabled: props.disabled }))
|
|
25
|
+
|
|
23
26
|
// A radio group stacks with a fixed gap; a dense settings list needs it smaller, and a horizontal layout needs it gone.
|
|
24
27
|
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
25
28
|
"flex flex-col gap-2",
|
|
@@ -29,7 +32,11 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
29
32
|
<template>
|
|
30
33
|
<RadioGroupRoot
|
|
31
34
|
:model-value="modelValue"
|
|
32
|
-
:disabled="disabled"
|
|
35
|
+
:disabled="field.disabled"
|
|
36
|
+
:required="field.required"
|
|
37
|
+
:aria-labelledby="field.labelId"
|
|
38
|
+
:aria-describedby="field.describedBy"
|
|
39
|
+
:aria-invalid="field.invalid || undefined"
|
|
33
40
|
:class="rootClass"
|
|
34
41
|
v-bind="rest"
|
|
35
42
|
@update:model-value="(v) => $emit('update:modelValue', v as string)"
|