@leavepulse/ui 0.43.4 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -7
- package/dist/assets/{LpCommandPalette-B-0Est4Z.css → LpCommandPalette-nGNnzXqQ.css} +21 -21
- package/dist/assets/{LpDropzone-BBoWvi-J.css → LpDropzone-i4BdMi1H.css} +12 -12
- package/dist/assets/{LpImageEditor-Ct7Y3aNu.css → LpImageEditor-CbMA82g8.css} +6 -6
- package/dist/assets/{LpRollingText-CR1mb0Vt.css → LpRollingText-BGnE6q9j.css} +3 -0
- package/dist/assets/{LpShortcutsDialog-C6YoQ8_M.css → LpShortcutsDialog-CqYX3yy6.css} +28 -7
- package/dist/assets/{LpSkeleton-D7xU8IPV.css → LpSkeleton-H3Z5dEIk.css} +4 -1
- package/dist/assets/{LpTable-BgPLJOXa.css → LpTable-CPwWxTJS.css} +49 -9
- package/dist/chunks/ContextMenuItems-BtKU7074.js +124 -0
- package/dist/chunks/{LayoutCanvas-Dd20boUG.js → LayoutCanvas-BkaGXTjx.js} +26 -9
- package/dist/chunks/{LayoutNode-DGpZA9LX.js → LayoutNode-B3_9E1Xa.js} +28 -12
- package/dist/chunks/{LpActionBar-BlvtGB2P.js → LpActionBar-CnfFVIwC.js} +26 -12
- package/dist/chunks/{LpAddressInput-B-Zvxsn7.js → LpAddressInput-BwX8uo4_.js} +54 -49
- package/dist/chunks/{LpAlert-BAYHDSS1.js → LpAlert-B6ilrM7T.js} +16 -9
- package/dist/chunks/{LpAppShell-BGQVIY-3.js → LpAppShell-C53_RaGF.js} +53 -26
- package/dist/chunks/{LpAutocomplete-DExlIZe_.js → LpAutocomplete-C8IgSMsi.js} +52 -24
- package/dist/chunks/{LpAvatar-B3-xAOH3.js → LpAvatar-C1XStdOo.js} +19 -10
- package/dist/chunks/LpBreadcrumbs-CQJd73Gm.js +95 -0
- package/dist/chunks/{LpButton-5VXXCjwF.js → LpButton-CKSwC6Mu.js} +45 -15
- package/dist/chunks/{LpCalendar-B7Ko5RdR.js → LpCalendar-DrA2McTU.js} +1 -1
- package/dist/chunks/{LpCard-BhI1N_vL.js → LpCard-C2IvucmX.js} +1 -1
- package/dist/chunks/{LpCheckbox-CVccKYaH.js → LpCheckbox-Bkj13CbW.js} +8 -4
- package/dist/chunks/{LpCodeBlock-Bo0B4TFR.js → LpCodeBlock-ca7ANFyW.js} +49 -17
- package/dist/chunks/{LpColorPicker-BlXea7LM.js → LpColorPicker-zz4VGkG4.js} +75 -26
- package/dist/chunks/{LpConfirmDialog-Q-bxff8z.js → LpConfirmDialog-DCw5jR1t.js} +59 -17
- package/dist/chunks/LpConfirmHost-CbywMpMP.js +49 -0
- package/dist/chunks/LpContextMenu-BqkAYa9X.js +69 -0
- package/dist/chunks/LpDatePicker-DuusgT69.js +195 -0
- package/dist/chunks/LpDateRangePicker-DHeuznfC.js +208 -0
- package/dist/chunks/{LpDisclosure-ChQYmjlR.js → LpDisclosure-Cc23-JOT.js} +1 -1
- package/dist/chunks/{LpDrawer-BZ6k3hvo.js → LpDrawer-Dt4aB4Fr.js} +65 -28
- package/dist/chunks/{LpDropdownMenu-DJpoqRCb.js → LpDropdownMenu-DdJGQzPn.js} +2 -1
- package/dist/chunks/{LpEmptyState-Dsdqz1Wj.js → LpEmptyState-BJbZXC8D.js} +1 -1
- package/dist/chunks/LpFieldNotice-wtieLUTj.js +64 -0
- package/dist/chunks/{LpFileTree-_5vFBgaK.js → LpFileTree-CNurOvkV.js} +25 -10
- package/dist/chunks/{LpFileTreeNode-BdcIYKIE.js → LpFileTreeNode-D_qRRdsH.js} +3 -3
- package/dist/chunks/LpFormField-CtVYj6tO.js +101 -0
- package/dist/chunks/{LpIcon-CCnX5_2j.js → LpIcon-DYtaid0K.js} +20 -6
- package/dist/chunks/{LpInput-9EXWUM9B.js → LpInput-D-YGQbEv.js} +58 -20
- package/dist/chunks/{LpLightbox-2sYVMY-S.js → LpLightbox-DSDahF23.js} +148 -61
- package/dist/chunks/{LpLink-DCND-zyu.js → LpLink-Krkjhe_y.js} +15 -6
- package/dist/chunks/{LpLogViewer-CaRy5MeS.js → LpLogViewer-KypbgOyo.js} +300 -72
- package/dist/chunks/{LpMapPicker-ul4oYno6.js → LpMapPicker-Cc84LaM3.js} +39 -14
- package/dist/chunks/{LpMediaGallery-FF-NquIo.js → LpMediaGallery-YKr-cQPN.js} +7 -4
- package/dist/chunks/{LpModal-Bt0pQCI_.js → LpModal-DMMDQHAu.js} +52 -29
- package/dist/chunks/{LpModalNav-YaifHrXC.js → LpModalNav-BVPDyIup.js} +10 -7
- package/dist/chunks/{LpNotificationBell-DyGGsMAM.js → LpNotificationBell-CCwyJP5C.js} +79 -33
- package/dist/chunks/{LpNumberField-CWuX08iX.js → LpNumberField-B77O-z28.js} +21 -8
- package/dist/chunks/{LpNumberFlow-amWYaeuA.js → LpNumberFlow-z4WPZcZu.js} +22 -12
- package/dist/chunks/{LpOtpInput-y2v-pmiN.js → LpOtpInput-CziPF3Oq.js} +37 -8
- package/dist/chunks/{LpPagination-5FxjmmB5.js → LpPagination-BcDlo3r_.js} +31 -20
- package/dist/chunks/{LpPasswordInput--sjYCkLS.js → LpPasswordInput-eKzQhbQP.js} +11 -8
- package/dist/chunks/LpPhoneInput-BCbrrhgE.js +336 -0
- package/dist/chunks/{LpPie-D6OnzwlJ.js → LpPie-BEJ2SfI6.js} +84 -28
- package/dist/chunks/{LpPopover-8268UmlC.js → LpPopover-DCKeqiVn.js} +5 -2
- package/dist/chunks/{LpProgress-Btq5W4Ut.js → LpProgress-CBU1iI69.js} +5 -4
- package/dist/chunks/{LpRadioGroup-xPIuVDO_.js → LpRadioGroup-DmEW0l78.js} +9 -2
- package/dist/chunks/LpRangeCalendar-DZuaRV0W.js +331 -0
- package/dist/chunks/{LpRollingText-CM5gkSeF.js → LpRollingText-DB7upf-5.js} +1 -1
- package/dist/chunks/{LpScrollArea-D8xyKXcF.js → LpScrollArea-Bry1fdOM.js} +14 -2
- package/dist/chunks/{LpSection-aM6J_u51.js → LpSection-tKEH0jYf.js} +1 -1
- package/dist/chunks/{LpSegmented-fV0Sk92p.js → LpSegmented-C09EEam2.js} +8 -4
- package/dist/chunks/{LpSelect-ANIVtrpM.js → LpSelect-DNIL6MB9.js} +50 -31
- package/dist/chunks/{LpSidebar-ClBUwoEz.js → LpSidebar-CQNB0-yU.js} +30 -22
- package/dist/chunks/{LpSidebarNav-llOcs_Fn.js → LpSidebarNav-kx-LdqNU.js} +16 -11
- package/dist/chunks/{LpSkeleton-CTI-vBgW.js → LpSkeleton-BB6XIV-w.js} +1 -1
- package/dist/chunks/{LpSlider-BwNIBkAK.js → LpSlider-Cuzmrd7p.js} +7 -2
- package/dist/chunks/{LpSparkline-C6xQ2dwv.js → LpSparkline-CB80N8SJ.js} +11 -4
- package/dist/chunks/{LpStat-CQaW8R--.js → LpStat-B-7udBRP.js} +2 -2
- package/dist/chunks/{LpStepper-Bk-XaQYU.js → LpStepper-C7XiVKZj.js} +34 -19
- package/dist/chunks/{LpSwitch-Blizvqyt.js → LpSwitch-OrcOJX9m.js} +41 -27
- package/dist/chunks/LpTableFilter-BV8PpLM9.js +158 -0
- package/dist/chunks/{LpTableOfContents-CjvxPMpU.js → LpTableOfContents-BUcAfGPQ.js} +36 -41
- package/dist/chunks/{LpTabs-Cefe_lys.js → LpTabs-29iW1XOx.js} +20 -6
- package/dist/chunks/LpTagsInput-D1SuqbhB.js +300 -0
- package/dist/chunks/LpTextarea-uuScWo6-.js +77 -0
- package/dist/chunks/LpThemeProvider-Be4r2TQX.js +35 -0
- package/dist/chunks/LpThemeSwitcher-B4aVPbNC.js +166 -0
- package/dist/chunks/{LpTilt-DuyKbpkA.js → LpTilt-B7KHX1zH.js} +2 -2
- package/dist/chunks/{LpTimeField-K6nAGboX.js → LpTimeField-Cqe7fXnh.js} +14 -8
- package/dist/chunks/{LpToaster-gDB1EaU3.js → LpToaster-DpJvoCOh.js} +94 -31
- package/dist/chunks/{LpTooltip-hCLPAqMG.js → LpTooltip-CXVKcOXT.js} +14 -6
- package/dist/chunks/LpTooltipProvider-CftjV7Xc.js +25 -0
- package/dist/chunks/{LpTopologyCanvas-Cm1L39uL.js → LpTopologyCanvas-6ozNrQKh.js} +8 -2
- package/dist/chunks/{LpUptimeBar-DvAMno0S.js → LpUptimeBar-ugcv25xj.js} +44 -15
- package/dist/chunks/icons-CkA0njzm.js +43 -0
- package/dist/chunks/{LpThemeSwitcher-Cue-myST.js → useTheme-DJm1JSsY.js} +215 -260
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.d.ts +8 -0
- package/dist/components/ContextMenuItems.vue.js +4 -0
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.d.ts +4 -0
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.d.ts +3 -0
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.d.ts +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.d.ts +9 -0
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +10 -0
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.d.ts +31 -6
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.d.ts +3 -3
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.d.ts +2 -2
- package/dist/components/LpBreadcrumbs.vue.d.ts +13 -9
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +35 -98
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.d.ts +47 -3
- package/dist/components/LpChart.vue.js +377 -97
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.d.ts +2 -0
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.d.ts +2 -2
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +14 -3
- package/dist/components/LpCommandPalette.vue.js +142 -77
- package/dist/components/LpConfirmDialog.vue.d.ts +15 -3
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.d.ts +9 -0
- package/dist/components/LpConfirmHost.vue.js +4 -0
- package/dist/components/LpContextMenu.vue.d.ts +3 -5
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.d.ts +24 -6
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.d.ts +60 -0
- package/dist/components/LpDateRangePicker.vue.js +4 -0
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDrawer.vue.d.ts +10 -0
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +23 -7
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFieldNotice.vue.d.ts +8 -0
- package/dist/components/LpFieldNotice.vue.js +4 -0
- package/dist/components/LpFileTree.vue.d.ts +11 -2
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.d.ts +13 -1
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpIcon.vue.d.ts +0 -1
- package/dist/components/LpIcon.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +93 -58
- package/dist/components/LpInput.vue.d.ts +22 -3
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.d.ts +17 -0
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLink.vue.d.ts +11 -11
- package/dist/components/LpLink.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +72 -3
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.d.ts +4 -4
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.d.ts +16 -6
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.d.ts +0 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +11 -2
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.d.ts +2 -0
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPagination.vue.d.ts +2 -0
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.d.ts +13 -0
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.d.ts +21 -2
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPopover.vue.d.ts +2 -0
- package/dist/components/LpPopover.vue.js +1 -1
- package/dist/components/LpProgress.vue.d.ts +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeCalendar.vue.d.ts +51 -0
- package/dist/components/LpRangeCalendar.vue.js +4 -0
- package/dist/components/LpRollingText.vue.d.ts +1 -1
- package/dist/components/LpRollingText.vue.js +1 -1
- package/dist/components/LpScrollArea.vue.d.ts +2 -2
- package/dist/components/LpScrollArea.vue.js +1 -1
- package/dist/components/LpSection.vue.d.ts +1 -1
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSegmented.vue.d.ts +5 -0
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.d.ts +1 -0
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.d.ts +7 -1
- package/dist/components/LpShortcutsDialog.vue.js +60 -15
- package/dist/components/LpSidebar.vue.d.ts +6 -2
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSkeleton.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpSparkline.vue.d.ts +1 -0
- package/dist/components/LpSparkline.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpStepper.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.d.ts +107 -3
- package/dist/components/LpTable.vue.js +540 -128
- package/dist/components/LpTableFilter.vue.d.ts +31 -0
- package/dist/components/LpTableFilter.vue.js +4 -0
- package/dist/components/LpTableOfContents.vue.d.ts +4 -2
- package/dist/components/LpTableOfContents.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +2 -0
- package/dist/components/LpTabs.vue.js +1 -1
- package/dist/components/LpTagsInput.vue.d.ts +77 -0
- package/dist/components/LpTagsInput.vue.js +4 -0
- package/dist/components/LpTextarea.vue.d.ts +19 -6
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.d.ts +22 -0
- package/dist/components/LpThemeProvider.vue.js +4 -0
- package/dist/components/LpThemeSwitcher.vue.d.ts +15 -2
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTilt.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.d.ts +6 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.d.ts +1 -2
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTooltipProvider.vue.d.ts +22 -0
- package/dist/components/LpTooltipProvider.vue.js +4 -0
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.d.ts +4 -0
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/chart.d.ts +32 -1
- package/dist/components/chart.js +42 -6
- package/dist/components/codeHighlight.d.ts +6 -0
- package/dist/components/codeHighlight.js +92 -20
- package/dist/components/color.d.ts +1 -1
- package/dist/components/color.js +20 -9
- package/dist/components/commandSearch.js +3 -0
- package/dist/components/confirm.d.ts +25 -0
- package/dist/components/confirm.js +26 -0
- package/dist/components/countries.d.ts +6 -0
- package/dist/components/countries.js +7 -0
- package/dist/components/dateRange.d.ts +43 -0
- package/dist/components/dateRange.js +76 -0
- package/dist/components/exportData.d.ts +58 -0
- package/dist/components/exportData.js +165 -0
- package/dist/components/fileTree.js +16 -7
- package/dist/components/imageCrop.d.ts +42 -0
- package/dist/components/imageCrop.js +47 -0
- package/dist/components/link.d.ts +12 -0
- package/dist/components/link.js +19 -0
- package/dist/components/logSearch.d.ts +18 -0
- package/dist/components/logSearch.js +36 -0
- package/dist/components/modalDepth.d.ts +18 -2
- package/dist/components/modalDepth.js +17 -5
- package/dist/components/otp.d.ts +5 -0
- package/dist/components/otp.js +10 -0
- package/dist/components/phone.d.ts +41 -0
- package/dist/components/phone.js +80 -0
- package/dist/components/sidebar.d.ts +7 -1
- package/dist/components/tableFilter.d.ts +36 -0
- package/dist/components/tableFilter.js +84 -0
- package/dist/components/tableSort.d.ts +8 -0
- package/dist/components/tableSort.js +31 -0
- package/dist/components/tags.d.ts +20 -0
- package/dist/components/tags.js +37 -0
- package/dist/composables/useField.d.ts +38 -0
- package/dist/composables/useField.js +45 -0
- package/dist/composables/useFieldNotice.d.ts +17 -0
- package/dist/composables/useFieldNotice.js +28 -0
- package/dist/composables/useForm.d.ts +53 -0
- package/dist/composables/useForm.js +207 -0
- package/dist/composables/useFormDraft.d.ts +10 -3
- package/dist/composables/useFormDraft.js +9 -14
- package/dist/composables/useHotkeys.d.ts +81 -5
- package/dist/composables/useHotkeys.js +223 -25
- package/dist/composables/useInputFilter.d.ts +17 -0
- package/dist/composables/useInputFilter.js +47 -10
- package/dist/composables/useInputMask.d.ts +59 -0
- package/dist/composables/useInputMask.js +170 -0
- package/dist/composables/useLpConfig.d.ts +24 -0
- package/dist/composables/useLpConfig.js +44 -0
- package/dist/composables/usePaintSelect.d.ts +22 -0
- package/dist/composables/usePaintSelect.js +169 -0
- package/dist/composables/useRovingFocus.js +7 -0
- package/dist/composables/useShift.js +7 -2
- package/dist/composables/useTilt.d.ts +2 -2
- package/dist/composables/useTilt.js +5 -6
- package/dist/composables/useToast.d.ts +20 -0
- package/dist/composables/useToast.js +28 -4
- package/dist/composables/useZoomPan.d.ts +3 -3
- package/dist/composables/useZoomPan.js +4 -4
- package/dist/icons-all.d.ts +1 -0
- package/dist/icons-all.js +3 -0
- package/dist/icons.d.ts +14 -1
- package/dist/index.d.ts +60 -4
- package/dist/index.js +245 -141
- package/dist/kit-icons.d.ts +2 -0
- package/dist/layout/tree.d.ts +2 -0
- package/dist/layout/tree.js +16 -1
- package/dist/layout/useLayout.d.ts +15 -0
- package/dist/layout/useLayout.js +41 -14
- package/dist/theme/backdrop.d.ts +12 -0
- package/dist/theme/presets.d.ts +0 -2
- package/dist/theme/useTheme.d.ts +32 -9
- package/package.json +18 -8
- package/src/component-names.ts +9 -7
- package/src/components/ContextMenuItems.vue +88 -0
- package/src/components/LayoutCanvas.vue +26 -6
- package/src/components/LayoutNode.vue +30 -4
- package/src/components/LpActionBar.vue +31 -8
- package/src/components/LpAddressInput.vue +57 -41
- package/src/components/LpAlert.vue +16 -3
- package/src/components/LpAppShell.vue +45 -12
- package/src/components/LpAutocomplete.vue +47 -10
- package/src/components/LpAvatar.vue +20 -6
- package/src/components/LpBreadcrumbs.vue +65 -41
- package/src/components/LpButton.vue +60 -14
- package/src/components/LpChart.vue +351 -28
- package/src/components/LpCheckbox.vue +7 -3
- package/src/components/LpCodeBlock.vue +50 -14
- package/src/components/LpColorPicker.vue +73 -10
- package/src/components/LpCommandPalette.vue +115 -20
- package/src/components/LpConfirmDialog.vue +68 -17
- package/src/components/LpConfirmHost.vue +59 -0
- package/src/components/LpContextMenu.vue +15 -116
- package/src/components/LpDatePicker.vue +104 -68
- package/src/components/LpDateRangePicker.vue +224 -0
- package/src/components/LpDrawer.vue +56 -19
- package/src/components/LpDropdownMenu.vue +1 -0
- package/src/components/LpDropzone.vue +21 -4
- package/src/components/LpFieldNotice.vue +65 -0
- package/src/components/LpFileTree.vue +33 -7
- package/src/components/LpFormField.vue +59 -7
- package/src/components/LpIcon.vue +22 -4
- package/src/components/LpImageEditor.vue +96 -57
- package/src/components/LpInput.vue +62 -11
- package/src/components/LpLightbox.vue +140 -29
- package/src/components/LpLink.vue +24 -13
- package/src/components/LpLogViewer.vue +311 -39
- package/src/components/LpMapPicker.vue +36 -13
- package/src/components/LpMediaGallery.vue +8 -2
- package/src/components/LpModal.vue +54 -28
- package/src/components/LpModalNav.vue +5 -2
- package/src/components/LpNotificationBell.vue +77 -23
- package/src/components/LpNumberField.vue +21 -6
- package/src/components/LpNumberFlow.vue +26 -10
- package/src/components/LpOtpInput.vue +43 -8
- package/src/components/LpPagination.vue +23 -7
- package/src/components/LpPasswordInput.vue +4 -1
- package/src/components/LpPhoneInput.vue +224 -39
- package/src/components/LpPie.vue +102 -20
- package/src/components/LpPopover.vue +4 -0
- package/src/components/LpProgress.vue +5 -3
- package/src/components/LpRadioGroup.vue +9 -2
- package/src/components/LpRangeCalendar.vue +365 -0
- package/src/components/LpRollingText.vue +3 -0
- package/src/components/LpScrollArea.vue +22 -2
- package/src/components/LpSegmented.vue +12 -2
- package/src/components/LpSelect.vue +0 -0
- package/src/components/LpShortcutsDialog.vue +83 -9
- package/src/components/LpSidebar.vue +14 -9
- package/src/components/LpSidebarNav.vue +17 -5
- package/src/components/LpSkeleton.vue +4 -1
- package/src/components/LpSlider.vue +7 -1
- package/src/components/LpSparkline.vue +11 -3
- package/src/components/LpStepper.vue +23 -10
- package/src/components/LpSwitch.vue +31 -8
- package/src/components/LpTable.vue +668 -90
- package/src/components/LpTableFilter.vue +175 -0
- package/src/components/LpTableOfContents.vue +50 -32
- package/src/components/LpTabs.vue +23 -6
- package/src/components/LpTagsInput.vue +364 -0
- package/src/components/LpTextarea.vue +44 -6
- package/src/components/LpThemeProvider.vue +41 -0
- package/src/components/LpThemeSwitcher.vue +76 -33
- package/src/components/LpTilt.vue +2 -2
- package/src/components/LpTimeField.vue +14 -3
- package/src/components/LpToaster.vue +115 -27
- package/src/components/LpTooltip.vue +29 -5
- package/src/components/LpTooltipProvider.vue +24 -0
- package/src/components/LpTopologyCanvas.vue +19 -2
- package/src/components/LpUptimeBar.vue +46 -8
- package/src/components/chart.ts +82 -8
- package/src/components/codeHighlight.ts +111 -15
- package/src/components/color.ts +30 -9
- package/src/components/commandSearch.ts +6 -0
- package/src/components/confirm.ts +58 -0
- package/src/components/countries.ts +12 -0
- package/src/components/dateRange.ts +134 -0
- package/src/components/exportData.ts +228 -0
- package/src/components/fileTree.ts +19 -7
- package/src/components/imageCrop.ts +107 -0
- package/src/components/link.ts +34 -0
- package/src/components/logSearch.ts +46 -0
- package/src/components/modalDepth.ts +48 -6
- package/src/components/otp.ts +16 -0
- package/src/components/phone.ts +140 -0
- package/src/components/sidebar.ts +7 -1
- package/src/components/tableFilter.ts +122 -0
- package/src/components/tableSort.ts +37 -0
- package/src/components/tags.ts +62 -0
- package/src/composables/useField.ts +83 -0
- package/src/composables/useFieldNotice.ts +59 -0
- package/src/composables/useForm.ts +314 -0
- package/src/composables/useFormDraft.ts +25 -20
- package/src/composables/useHotkeys.ts +355 -28
- package/src/composables/useInputFilter.ts +77 -14
- package/src/composables/useInputMask.ts +254 -0
- package/src/composables/useLpConfig.ts +87 -0
- package/src/composables/usePaintSelect.ts +243 -0
- package/src/composables/useRovingFocus.ts +11 -0
- package/src/composables/useShift.ts +13 -2
- package/src/composables/useTilt.ts +5 -6
- package/src/composables/useToast.ts +56 -4
- package/src/composables/useZoomPan.ts +6 -6
- package/src/iconSubset.d.mts +10 -0
- package/src/iconSubset.mjs +51 -0
- package/src/icons-all.ts +10 -0
- package/src/icons.ts +59 -8
- package/src/index.ts +73 -4
- package/src/kit-icons.ts +5 -0
- package/src/layout/tree.ts +30 -0
- package/src/layout/useLayout.ts +68 -18
- package/src/nuxt.ts +87 -17
- package/src/runtime/config.plugin.ts +14 -0
- package/src/runtime/theme.plugin.ts +43 -23
- package/src/theme/backdrop.ts +69 -0
- package/src/theme/presets.ts +6 -64
- package/src/theme/useTheme.ts +206 -100
- package/src/tokens/tokens.css +46 -0
- package/dist/chunks/LpBreadcrumbs-p4jHe_V-.js +0 -62
- package/dist/chunks/LpContextMenu-CwW1QXe_.js +0 -217
- package/dist/chunks/LpDatePicker-CwOToxGq.js +0 -164
- package/dist/chunks/LpFormField-CazBc2-a.js +0 -62
- package/dist/chunks/LpPhoneInput-BDSKVl_z.js +0 -209
- package/dist/chunks/LpTextarea-t7F6gv8E.js +0 -47
|
@@ -126,7 +126,8 @@ export function formatSize(bytes: number): string {
|
|
|
126
126
|
const units = ["KB", "MB", "GB", "TB", "PB"]
|
|
127
127
|
let value = bytes / 1024
|
|
128
128
|
let i = 0
|
|
129
|
-
|
|
129
|
+
// Compare the value as it will print, so 1 048 575 B is "1.0 MB", not "1024 KB".
|
|
130
|
+
while (Math.round(value) >= 1024 && i < units.length - 1) {
|
|
130
131
|
value /= 1024
|
|
131
132
|
i++
|
|
132
133
|
}
|
|
@@ -191,14 +192,20 @@ export function checkStateOf(node: FileNode, checked: ReadonlySet<string>): Chec
|
|
|
191
192
|
}
|
|
192
193
|
let all = true
|
|
193
194
|
let any = false
|
|
195
|
+
let considered = 0
|
|
194
196
|
for (const child of node.children) {
|
|
195
197
|
const state = checkStateOf(child, checked)
|
|
198
|
+
// A locked, unticked entry can never be ticked, so it must not hold its
|
|
199
|
+
// folder at "indeterminate" forever.
|
|
200
|
+
if (child.disabled && state === "unchecked") continue
|
|
201
|
+
considered++
|
|
196
202
|
if (state === "checked") any = true
|
|
197
203
|
else if (state === "indeterminate") {
|
|
198
204
|
any = true
|
|
199
205
|
all = false
|
|
200
206
|
} else all = false
|
|
201
207
|
}
|
|
208
|
+
if (!considered) return checked.has(node.id) ? "checked" : "unchecked"
|
|
202
209
|
if (all) return "checked"
|
|
203
210
|
return any ? "indeterminate" : "unchecked"
|
|
204
211
|
}
|
|
@@ -271,14 +278,18 @@ export function computeStats(
|
|
|
271
278
|
let total = 0
|
|
272
279
|
let bytes = 0
|
|
273
280
|
let sized = node.size !== undefined
|
|
281
|
+
let considered = 0
|
|
274
282
|
|
|
275
283
|
for (const child of node.children) {
|
|
276
284
|
const childStats = visit(child)
|
|
277
|
-
if (childStats.state
|
|
278
|
-
|
|
279
|
-
any = true
|
|
280
|
-
|
|
281
|
-
|
|
285
|
+
if (!child.disabled || childStats.state !== "unchecked") {
|
|
286
|
+
considered++
|
|
287
|
+
if (childStats.state === "checked") any = true
|
|
288
|
+
else if (childStats.state === "indeterminate") {
|
|
289
|
+
any = true
|
|
290
|
+
all = false
|
|
291
|
+
} else all = false
|
|
292
|
+
}
|
|
282
293
|
count += childStats.checked
|
|
283
294
|
total += childStats.total
|
|
284
295
|
if (childStats.size !== undefined) {
|
|
@@ -287,8 +298,9 @@ export function computeStats(
|
|
|
287
298
|
}
|
|
288
299
|
}
|
|
289
300
|
|
|
301
|
+
const own: CheckState = checked.has(node.id) ? "checked" : "unchecked"
|
|
290
302
|
const self: NodeStats = {
|
|
291
|
-
state: all ? "checked" : any ? "indeterminate" : "unchecked",
|
|
303
|
+
state: !considered ? own : all ? "checked" : any ? "indeterminate" : "unchecked",
|
|
292
304
|
checked: count,
|
|
293
305
|
total,
|
|
294
306
|
// An explicit size on the directory wins: a server-side listing usually
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Geometry shared by LpImageEditor's preview and its export, so what is drawn
|
|
3
|
+
* on screen and what is written to the Blob come from one calculation.
|
|
4
|
+
*
|
|
5
|
+
* Three spaces are involved:
|
|
6
|
+
* - source: the picture's own pixels, origin at its centre;
|
|
7
|
+
* - upright: the source after the rotation, as the person sees it, origin at
|
|
8
|
+
* its centre, still in source-pixel units;
|
|
9
|
+
* - frame: the crop box on screen, origin at its centre, in CSS pixels.
|
|
10
|
+
*
|
|
11
|
+
* A turn of θ (clockwise, as CSS and canvas both rotate in a y-down space)
|
|
12
|
+
* maps source to upright by u = R(θ)·q. The picture is drawn at `cover` CSS
|
|
13
|
+
* pixels per source pixel, then panned by `offset` and zoomed by `scale`, so a
|
|
14
|
+
* frame point f shows the upright point (f / scale − offset) / cover.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface Point {
|
|
18
|
+
x: number
|
|
19
|
+
y: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CropInput {
|
|
23
|
+
sourceWidth: number
|
|
24
|
+
sourceHeight: number
|
|
25
|
+
frameWidth: number
|
|
26
|
+
frameHeight: number
|
|
27
|
+
/** Accumulated rotation in degrees; any angle, not only quarter turns. */
|
|
28
|
+
rotation: number
|
|
29
|
+
/** Zoom factor, 1 = the picture just covers the frame. */
|
|
30
|
+
scale: number
|
|
31
|
+
/** Pan in frame pixels before the zoom, as useZoomPan reports it. */
|
|
32
|
+
offset: Point
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CropGeometry {
|
|
36
|
+
/** CSS pixels per source pixel at zoom 1. */
|
|
37
|
+
cover: number
|
|
38
|
+
/** Crop size in upright (source-pixel) units. */
|
|
39
|
+
width: number
|
|
40
|
+
height: number
|
|
41
|
+
/** Crop centre in upright space. */
|
|
42
|
+
centre: Point
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const radians = (deg: number) => (deg * Math.PI) / 180
|
|
46
|
+
|
|
47
|
+
/** Rotate a point clockwise by `deg` in a y-down space. */
|
|
48
|
+
export function rotatePoint(p: Point, deg: number): Point {
|
|
49
|
+
const a = radians(deg)
|
|
50
|
+
const cos = Math.cos(a)
|
|
51
|
+
const sin = Math.sin(a)
|
|
52
|
+
return { x: p.x * cos - p.y * sin, y: p.x * sin + p.y * cos }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The source pixel (from the source's top-left) under an upright-space point. */
|
|
56
|
+
export function uprightToSource(u: Point, rotation: number, sourceWidth: number, sourceHeight: number): Point {
|
|
57
|
+
const q = rotatePoint(u, -rotation)
|
|
58
|
+
return { x: q.x + sourceWidth / 2, y: q.y + sourceHeight / 2 }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Smallest scale at which the turned picture still covers the whole frame.
|
|
63
|
+
*
|
|
64
|
+
* Covering the bounding box of a turned picture is not enough at a free angle:
|
|
65
|
+
* its corners are empty. The frame, turned back by the same angle, has to fit
|
|
66
|
+
* inside the unturned picture; for a quarter turn this reduces to the usual
|
|
67
|
+
* object-cover of the swapped dimensions.
|
|
68
|
+
*/
|
|
69
|
+
export function coverScale(
|
|
70
|
+
sourceWidth: number,
|
|
71
|
+
sourceHeight: number,
|
|
72
|
+
frameWidth: number,
|
|
73
|
+
frameHeight: number,
|
|
74
|
+
rotation: number,
|
|
75
|
+
): number {
|
|
76
|
+
const a = radians(rotation)
|
|
77
|
+
const cos = Math.abs(Math.cos(a))
|
|
78
|
+
const sin = Math.abs(Math.sin(a))
|
|
79
|
+
return Math.max(
|
|
80
|
+
(frameWidth * cos + frameHeight * sin) / sourceWidth,
|
|
81
|
+
(frameWidth * sin + frameHeight * cos) / sourceHeight,
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** What the frame holds, in upright space. */
|
|
86
|
+
export function cropGeometry(input: CropInput): CropGeometry {
|
|
87
|
+
const cover = coverScale(
|
|
88
|
+
input.sourceWidth,
|
|
89
|
+
input.sourceHeight,
|
|
90
|
+
input.frameWidth,
|
|
91
|
+
input.frameHeight,
|
|
92
|
+
input.rotation,
|
|
93
|
+
)
|
|
94
|
+
const shown = cover * input.scale
|
|
95
|
+
return {
|
|
96
|
+
cover,
|
|
97
|
+
width: input.frameWidth / shown,
|
|
98
|
+
height: input.frameHeight / shown,
|
|
99
|
+
centre: { x: -input.offset.x / cover, y: -input.offset.y / cover },
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Whether the turn is closer to sideways than to upright. */
|
|
104
|
+
export function isSideways(rotation: number): boolean {
|
|
105
|
+
const quarters = Math.round(rotation / 90)
|
|
106
|
+
return ((quarters % 2) + 2) % 2 === 1
|
|
107
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Component } from "vue"
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* How a navigation target renders: an internal destination goes through the
|
|
5
|
+
* configured link component (RouterLink, NuxtLink) so it navigates client-side,
|
|
6
|
+
* anything else is a plain anchor. Shared by every kit piece that links.
|
|
7
|
+
*/
|
|
8
|
+
export type LinkTarget = string | Record<string, unknown>
|
|
9
|
+
|
|
10
|
+
export interface ResolvedLink {
|
|
11
|
+
is: Component | string
|
|
12
|
+
props: Record<string, unknown>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** An app-relative path: "/x", not "//host" or "https://…". */
|
|
16
|
+
export function isInternalHref(href: string): boolean {
|
|
17
|
+
return href.startsWith("/") && !href.startsWith("//")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveLink(
|
|
21
|
+
linkComponent: Component | string,
|
|
22
|
+
target: { to?: LinkTarget; href?: string },
|
|
23
|
+
): ResolvedLink | null {
|
|
24
|
+
const router = linkComponent !== "a"
|
|
25
|
+
if (target.to != null) {
|
|
26
|
+
if (router) return { is: linkComponent, props: { to: target.to } }
|
|
27
|
+
return typeof target.to === "string" ? { is: "a", props: { href: target.to } } : null
|
|
28
|
+
}
|
|
29
|
+
if (target.href != null) {
|
|
30
|
+
if (router && isInternalHref(target.href)) return { is: linkComponent, props: { to: target.href } }
|
|
31
|
+
return { is: "a", props: { href: target.href } }
|
|
32
|
+
}
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The log viewer's search and filtering, kept free of the component so the
|
|
3
|
+
* rules (what counts as a match, how the cursor wraps) can be tested alone.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** The last `max` lines, and how many were cut from the front. */
|
|
7
|
+
export function ringWindow<T>(lines: readonly T[], max: number | undefined): { lines: readonly T[]; offset: number } {
|
|
8
|
+
if (!max || max <= 0 || lines.length <= max) return { lines, offset: 0 }
|
|
9
|
+
return { lines: lines.slice(lines.length - max), offset: lines.length - max }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Lines per level, a line with no level counting as `fallback`. */
|
|
13
|
+
export function countLevels<L extends string>(
|
|
14
|
+
lines: readonly { level?: L }[],
|
|
15
|
+
fallback: L,
|
|
16
|
+
): Map<L, number> {
|
|
17
|
+
const counts = new Map<L, number>()
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
const level = line.level ?? fallback
|
|
20
|
+
counts.set(level, (counts.get(level) ?? 0) + 1)
|
|
21
|
+
}
|
|
22
|
+
return counts
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Case-insensitive substring test; an empty term matches nothing. */
|
|
26
|
+
export function matchesTerm(message: string, term: string): boolean {
|
|
27
|
+
const needle = term.trim().toLowerCase()
|
|
28
|
+
return needle.length > 0 && message.toLowerCase().includes(needle)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Positions (into `rows`) of the rows whose message or source holds the term. */
|
|
32
|
+
export function matchPositions(rows: readonly { message: string; source?: string }[], term: string): number[] {
|
|
33
|
+
if (!term.trim()) return []
|
|
34
|
+
const out: number[] = []
|
|
35
|
+
rows.forEach((row, i) => {
|
|
36
|
+
if (matchesTerm(row.message, term) || (row.source !== undefined && matchesTerm(row.source, term))) out.push(i)
|
|
37
|
+
})
|
|
38
|
+
return out
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The next match cursor, wrapping at both ends; -1 when there are none. */
|
|
42
|
+
export function stepMatch(current: number, total: number, direction: 1 | -1): number {
|
|
43
|
+
if (total <= 0) return -1
|
|
44
|
+
if (current < 0 || current >= total) return direction === 1 ? 0 : total - 1
|
|
45
|
+
return (current + direction + total) % total
|
|
46
|
+
}
|
|
@@ -1,16 +1,47 @@
|
|
|
1
|
-
import { computed, ref, type ComputedRef } from "vue"
|
|
1
|
+
import { computed, ref, type ComputedRef, type Ref } from "vue"
|
|
2
2
|
|
|
3
3
|
const STEP = 20
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/*
|
|
6
|
+
* One opening-order stack for every modal surface. Pinned to a global symbol
|
|
7
|
+
* like the toast queue: a barrel import and a deep import can load this module
|
|
8
|
+
* twice, and two stacks would put a deep-imported dialog's scrim under a
|
|
9
|
+
* barrel-imported drawer's panel.
|
|
10
|
+
*/
|
|
11
|
+
const STATE_KEY = Symbol.for("@leavepulse/ui:modal-stack")
|
|
12
|
+
type GlobalWithStack = typeof globalThis & {
|
|
13
|
+
[STATE_KEY]?: {
|
|
14
|
+
stack: Ref<number[]>
|
|
15
|
+
nextId: number
|
|
16
|
+
elements: Map<number, () => HTMLElement | null | undefined>
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const g = globalThis as GlobalWithStack
|
|
20
|
+
const state = (g[STATE_KEY] ??= { stack: ref<number[]>([]), nextId: 0, elements: new Map() })
|
|
21
|
+
const stack = state.stack
|
|
7
22
|
|
|
8
|
-
export
|
|
23
|
+
export interface ModalLayer {
|
|
24
|
+
/** Join the stack on top. Ignored on the server, where nothing would ever release it. */
|
|
9
25
|
claim: () => void
|
|
10
26
|
release: () => void
|
|
27
|
+
/** z-index values for this surface's scrim and panel, and whether another modal is under it. */
|
|
11
28
|
layer: ComputedRef<{ scrim: string; panel: string; nested: boolean }>
|
|
12
|
-
|
|
13
|
-
|
|
29
|
+
/** Whether this surface is the topmost open one. */
|
|
30
|
+
top: ComputedRef<boolean>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ModalLayerOptions {
|
|
34
|
+
/** The surface's panel, so keyboard handling can tell what sits inside the top modal. */
|
|
35
|
+
element?: () => HTMLElement | null | undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Put an overlay on the shared modal stack, so a surface opened from another
|
|
40
|
+
* one (a dialog from a drawer, a lightbox from a dialog) lands above it with
|
|
41
|
+
* its scrim, whatever component either of them is.
|
|
42
|
+
*/
|
|
43
|
+
export function useModalLayer(options: ModalLayerOptions = {}): ModalLayer {
|
|
44
|
+
const id = state.nextId++
|
|
14
45
|
|
|
15
46
|
const fromTop = computed(() => {
|
|
16
47
|
const at = stack.value.indexOf(id)
|
|
@@ -19,9 +50,12 @@ export function useModalLayer(): {
|
|
|
19
50
|
|
|
20
51
|
return {
|
|
21
52
|
claim: () => {
|
|
53
|
+
if (typeof window === "undefined") return
|
|
54
|
+
if (options.element) state.elements.set(id, options.element)
|
|
22
55
|
if (!stack.value.includes(id)) stack.value = [...stack.value, id]
|
|
23
56
|
},
|
|
24
57
|
release: () => {
|
|
58
|
+
state.elements.delete(id)
|
|
25
59
|
stack.value = stack.value.filter((x) => x !== id)
|
|
26
60
|
},
|
|
27
61
|
layer: computed(() => {
|
|
@@ -32,9 +66,17 @@ export function useModalLayer(): {
|
|
|
32
66
|
nested: stack.value.indexOf(id) > 0,
|
|
33
67
|
}
|
|
34
68
|
}),
|
|
69
|
+
top: computed(() => stack.value[stack.value.length - 1] === id),
|
|
35
70
|
}
|
|
36
71
|
}
|
|
37
72
|
|
|
38
73
|
export function anyModalOpen(): boolean {
|
|
39
74
|
return stack.value.length > 0
|
|
40
75
|
}
|
|
76
|
+
|
|
77
|
+
/** The panel of the topmost modal, when it registered one. */
|
|
78
|
+
export function topModalElement(): HTMLElement | null {
|
|
79
|
+
const id = stack.value[stack.value.length - 1]
|
|
80
|
+
if (id === undefined) return null
|
|
81
|
+
return state.elements.get(id)?.() ?? null
|
|
82
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The bridge between reka PinInput's per-cell array and LpOtpInput's string
|
|
3
|
+
* model. reka empties a cell by deleting its slot (numeric mode) or setting it
|
|
4
|
+
* to "", so the array can have holes in the middle.
|
|
5
|
+
*/
|
|
6
|
+
export type OtpCells = (string | number | undefined)[]
|
|
7
|
+
|
|
8
|
+
/** The code typed so far: the filled cells in order. */
|
|
9
|
+
export function joinCells(cells: OtpCells): string {
|
|
10
|
+
return Array.from(cells, (c) => (c === undefined ? "" : String(c))).join("")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Cells for a code given from outside, one character each. */
|
|
14
|
+
export function splitCode(code: string | undefined, length: number): string[] {
|
|
15
|
+
return (code ?? "").split("").slice(0, length)
|
|
16
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The phone-number engine behind LpPhoneInput, split from the component so its
|
|
3
|
+
* rules are testable. libphonenumber-js (the `min` metadata build) is loaded
|
|
4
|
+
* on first use, the way the country table is: an app that never renders a
|
|
5
|
+
* phone field never downloads it, and until it arrives the field still takes
|
|
6
|
+
* digits, just without formatting or checks.
|
|
7
|
+
*/
|
|
8
|
+
import type { CountryCode } from "libphonenumber-js/min"
|
|
9
|
+
|
|
10
|
+
export type PhoneLib = typeof import("libphonenumber-js/min")
|
|
11
|
+
type Examples = typeof import("libphonenumber-js/examples.mobile.json")["default"]
|
|
12
|
+
|
|
13
|
+
let lib: Promise<PhoneLib> | undefined
|
|
14
|
+
let examples: Promise<Examples> | undefined
|
|
15
|
+
|
|
16
|
+
export function loadPhoneLib(): Promise<PhoneLib> {
|
|
17
|
+
lib ??= import("libphonenumber-js/min")
|
|
18
|
+
return lib
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function loadPhoneExamples(): Promise<Examples> {
|
|
22
|
+
examples ??= import("libphonenumber-js/examples.mobile.json").then((m) => m.default)
|
|
23
|
+
return examples
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** E.164 allows at most 15 digits, country code included. */
|
|
27
|
+
export const E164_MAX_DIGITS = 15
|
|
28
|
+
|
|
29
|
+
export function digitsOf(text: string): string {
|
|
30
|
+
return text.replace(/\D/g, "")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** How many digits sit before `caret` in `text`. */
|
|
34
|
+
export function digitsBefore(text: string, caret: number): number {
|
|
35
|
+
return digitsOf(text.slice(0, caret)).length
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The caret position in `formatted` that has `count` digits before it, placed
|
|
40
|
+
* right after the last of them so typing continues from there. Zero digits
|
|
41
|
+
* puts it after a leading "+".
|
|
42
|
+
*/
|
|
43
|
+
export function caretAfterDigits(formatted: string, count: number): number {
|
|
44
|
+
if (count <= 0) return formatted.startsWith("+") ? 1 : 0
|
|
45
|
+
let seen = 0
|
|
46
|
+
for (let i = 0; i < formatted.length; i++) {
|
|
47
|
+
if (/\d/.test(formatted[i]!)) {
|
|
48
|
+
seen++
|
|
49
|
+
if (seen === count) return i + 1
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return formatted.length
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type PhoneProblem = "too-short" | "too-long" | "invalid" | "invalid-country"
|
|
56
|
+
|
|
57
|
+
export interface FormattedPhone {
|
|
58
|
+
/** What the field shows: "+380 50 123 4567". */
|
|
59
|
+
display: string
|
|
60
|
+
/** Normalised "+380501234567", or "" when there are no digits. */
|
|
61
|
+
e164: string
|
|
62
|
+
/** Digits that were typed past the longest number the country allows. */
|
|
63
|
+
dropped: number
|
|
64
|
+
/** The calling code the number starts with ("380"), once recognised. */
|
|
65
|
+
callingCode?: string
|
|
66
|
+
/** Country libphonenumber recognised from the number, when it did. */
|
|
67
|
+
country?: CountryCode
|
|
68
|
+
possible: boolean
|
|
69
|
+
valid: boolean
|
|
70
|
+
type?: string
|
|
71
|
+
problem?: PhoneProblem
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Format what was typed, dropping digits beyond what the number may hold:
|
|
76
|
+
* the country's own maximum once libphonenumber knows the country, the E.164
|
|
77
|
+
* cap otherwise. The value always starts with "+", since the field holds a
|
|
78
|
+
* full international number.
|
|
79
|
+
*/
|
|
80
|
+
export function formatPhone(input: string, phone: PhoneLib | undefined): FormattedPhone {
|
|
81
|
+
let digits = digitsOf(input)
|
|
82
|
+
let dropped = 0
|
|
83
|
+
if (digits.length > E164_MAX_DIGITS) {
|
|
84
|
+
dropped += digits.length - E164_MAX_DIGITS
|
|
85
|
+
digits = digits.slice(0, E164_MAX_DIGITS)
|
|
86
|
+
}
|
|
87
|
+
if (!digits) return { display: input.trim().startsWith("+") ? "+" : "", e164: "", dropped, possible: false, valid: false }
|
|
88
|
+
if (!phone) return { display: `+${digits}`, e164: `+${digits}`, dropped, possible: false, valid: false }
|
|
89
|
+
|
|
90
|
+
while (digits.length > 1 && phone.validatePhoneNumberLength(`+${digits}`) === "TOO_LONG") {
|
|
91
|
+
digits = digits.slice(0, -1)
|
|
92
|
+
dropped++
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// A number that was already complete and valid does not take one more
|
|
96
|
+
// digit that would make it neither: a Ukrainian mobile ends at nine digits
|
|
97
|
+
// after +380 although the country's longest number type allows ten, and
|
|
98
|
+
// that tenth digit is almost always a slip rather than another kind of line.
|
|
99
|
+
const isValid = (d: string) => phone.parsePhoneNumberFromString(`+${d}`)?.isValid() ?? false
|
|
100
|
+
while (digits.length > 1 && !isValid(digits) && isValid(digits.slice(0, -1))) {
|
|
101
|
+
digits = digits.slice(0, -1)
|
|
102
|
+
dropped++
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const typer = new phone.AsYouType()
|
|
106
|
+
let display = typer.input(`+${digits}`)
|
|
107
|
+
// A length the country allows but no format covers (a 10-digit Ukrainian
|
|
108
|
+
// national number) comes back as one run of digits; group it by threes after
|
|
109
|
+
// the calling code so it still reads as a phone number.
|
|
110
|
+
const code = typer.getCallingCode()
|
|
111
|
+
if (code && !/\s/.test(display.slice(code.length + 2))) {
|
|
112
|
+
const national = digits.slice(code.length)
|
|
113
|
+
display = `+${code}${national ? " " + (national.match(/.{1,3}/g) ?? []).join(" ") : ""}`
|
|
114
|
+
}
|
|
115
|
+
const parsed = phone.parsePhoneNumberFromString(`+${digits}`)
|
|
116
|
+
const country = typer.getCountry() ?? parsed?.country
|
|
117
|
+
const possible = parsed?.isPossible() ?? false
|
|
118
|
+
const valid = parsed?.isValid() ?? false
|
|
119
|
+
const length = phone.validatePhoneNumberLength(`+${digits}`)
|
|
120
|
+
const problem: PhoneProblem | undefined = valid
|
|
121
|
+
? undefined
|
|
122
|
+
: length === "TOO_SHORT" || length === "NOT_A_NUMBER"
|
|
123
|
+
? "too-short"
|
|
124
|
+
: length === "INVALID_COUNTRY"
|
|
125
|
+
? "invalid-country"
|
|
126
|
+
: length === "TOO_LONG"
|
|
127
|
+
? "too-long"
|
|
128
|
+
: "invalid"
|
|
129
|
+
return {
|
|
130
|
+
display,
|
|
131
|
+
e164: `+${digits}`,
|
|
132
|
+
dropped,
|
|
133
|
+
callingCode: code,
|
|
134
|
+
country,
|
|
135
|
+
possible,
|
|
136
|
+
valid,
|
|
137
|
+
type: valid ? parsed?.getType() : undefined,
|
|
138
|
+
problem,
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -10,8 +10,14 @@ export interface SidebarItem {
|
|
|
10
10
|
id: string
|
|
11
11
|
label: string
|
|
12
12
|
icon?: string
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Render as a link instead of a button. An app-relative path goes through
|
|
15
|
+
* the link component from provideLpConfig (NuxtLink under the Nuxt module),
|
|
16
|
+
* so it navigates client-side; other URLs render a plain anchor.
|
|
17
|
+
*/
|
|
14
18
|
href?: string
|
|
19
|
+
/** Router destination (rendered through the configured link component). */
|
|
20
|
+
to?: string | Record<string, unknown>
|
|
15
21
|
/** Count chip on the right (number or short string). */
|
|
16
22
|
badge?: number | string
|
|
17
23
|
disabled?: boolean
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The rules behind LpTable's value filters and remembered state, kept pure so
|
|
3
|
+
* they can be tested. A column's filter is the set of HIDDEN values (see
|
|
4
|
+
* LpTable): a value nobody has seen before shows up rather than staying out.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Hide everything except `keep`: "show only this" in one step. */
|
|
8
|
+
export function onlyValues(values: readonly unknown[], keep: readonly unknown[]): Set<unknown> {
|
|
9
|
+
const kept = new Set(keep)
|
|
10
|
+
return new Set(values.filter((v) => !kept.has(v)))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Hide every value: the "None" step before ticking the few that matter. */
|
|
14
|
+
export function hideAllValues(values: readonly unknown[]): Set<unknown> {
|
|
15
|
+
return new Set(values)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Swap shown and hidden among the values the column holds. */
|
|
19
|
+
export function invertHidden(values: readonly unknown[], hidden: ReadonlySet<unknown>): Set<unknown> {
|
|
20
|
+
return new Set(values.filter((v) => !hidden.has(v)))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Show these values again, leaving the rest of the filter as it was. */
|
|
24
|
+
export function unhideValues(hidden: ReadonlySet<unknown>, values: readonly unknown[]): Set<unknown> {
|
|
25
|
+
const next = new Set(hidden)
|
|
26
|
+
for (const v of values) next.delete(v)
|
|
27
|
+
return next
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Lower-cased and stripped of accents, so "cafe" finds "Café". */
|
|
31
|
+
export function foldText(text: string): string {
|
|
32
|
+
return text.normalize("NFKD").replace(/\p{M}/gu, "").toLocaleLowerCase()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Every word of the query appears somewhere in the label, in any order. */
|
|
36
|
+
export function matchesQuery(label: string, query: string): boolean {
|
|
37
|
+
const words = foldText(query).split(/\s+/).filter(Boolean)
|
|
38
|
+
if (!words.length) return true
|
|
39
|
+
const hay = foldText(label)
|
|
40
|
+
return words.every((w) => hay.includes(w))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** How many rows hold each value of a column. */
|
|
44
|
+
export function countValues<Row extends Record<string, unknown>>(
|
|
45
|
+
rows: readonly Row[],
|
|
46
|
+
key: string,
|
|
47
|
+
): Map<unknown, number> {
|
|
48
|
+
const counts = new Map<unknown, number>()
|
|
49
|
+
for (const row of rows) counts.set(row[key], (counts.get(row[key]) ?? 0) + 1)
|
|
50
|
+
return counts
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A chip's text for one column's filter: the values still shown, the first
|
|
55
|
+
* `listed` by name and the rest as "+N", which says more than "3 of 12".
|
|
56
|
+
*/
|
|
57
|
+
export function summarizeShown(shownLabels: readonly string[], listed = 2): string {
|
|
58
|
+
if (shownLabels.length <= listed) return shownLabels.join(", ")
|
|
59
|
+
return `${shownLabels.slice(0, listed).join(", ")} +${shownLabels.length - listed}`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── remembered state ───────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
export interface PersistedTableState {
|
|
65
|
+
sort?: { key: string; dir: "asc" | "desc" } | null
|
|
66
|
+
hidden?: Record<string, unknown[]>
|
|
67
|
+
columns?: string[]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type Plain = string | number | boolean | null
|
|
71
|
+
|
|
72
|
+
function isPlain(value: unknown): value is Plain {
|
|
73
|
+
return value === null || ["string", "number", "boolean"].includes(typeof value)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The state worth keeping across visits, as JSON. Only plain values survive:
|
|
78
|
+
* a Date or an object hidden in a filter cannot come back as the same value
|
|
79
|
+
* (the filter compares by identity), so it is left out rather than restored
|
|
80
|
+
* as something that matches nothing.
|
|
81
|
+
*/
|
|
82
|
+
export function serializeTableState(state: PersistedTableState): string {
|
|
83
|
+
const hidden: Record<string, Plain[]> = {}
|
|
84
|
+
for (const [key, values] of Object.entries(state.hidden ?? {})) {
|
|
85
|
+
const plain = values.filter(isPlain)
|
|
86
|
+
if (plain.length) hidden[key] = plain
|
|
87
|
+
}
|
|
88
|
+
return JSON.stringify({ sort: state.sort ?? null, hidden, columns: state.columns ?? [] })
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Read it back, dropping anything about columns the table no longer has. */
|
|
92
|
+
export function parseTableState(raw: string | null, columnKeys: readonly string[]): PersistedTableState | null {
|
|
93
|
+
if (!raw) return null
|
|
94
|
+
let data: unknown
|
|
95
|
+
try {
|
|
96
|
+
data = JSON.parse(raw)
|
|
97
|
+
} catch {
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
if (!data || typeof data !== "object") return null
|
|
101
|
+
const known = new Set(columnKeys)
|
|
102
|
+
const record = data as Record<string, unknown>
|
|
103
|
+
const out: PersistedTableState = {}
|
|
104
|
+
|
|
105
|
+
const sort = record.sort as { key?: unknown; dir?: unknown } | null | undefined
|
|
106
|
+
if (sort === null) out.sort = null
|
|
107
|
+
else if (sort && typeof sort.key === "string" && known.has(sort.key) && (sort.dir === "asc" || sort.dir === "desc")) {
|
|
108
|
+
out.sort = { key: sort.key, dir: sort.dir }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (record.hidden && typeof record.hidden === "object") {
|
|
112
|
+
out.hidden = {}
|
|
113
|
+
for (const [key, values] of Object.entries(record.hidden as Record<string, unknown>)) {
|
|
114
|
+
if (known.has(key) && Array.isArray(values)) out.hidden[key] = values.filter(isPlain)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (Array.isArray(record.columns)) {
|
|
119
|
+
out.columns = record.columns.filter((c): c is string => typeof c === "string" && known.has(c))
|
|
120
|
+
}
|
|
121
|
+
return out
|
|
122
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const COLLATOR = new Intl.Collator(undefined, { numeric: true })
|
|
2
|
+
|
|
3
|
+
const rank = (v: unknown): number => {
|
|
4
|
+
if (v == null || (typeof v === "number" && Number.isNaN(v))) return 0
|
|
5
|
+
if (typeof v === "boolean") return 1
|
|
6
|
+
if (typeof v === "number" || typeof v === "bigint") return 2
|
|
7
|
+
if (v instanceof Date) return Number.isNaN(v.getTime()) ? 0 : 3
|
|
8
|
+
return 4
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Ascending order for raw cell values. Empty values (null, undefined, NaN, an
|
|
13
|
+
* invalid Date) come first; numbers, bigints and Dates compare by magnitude,
|
|
14
|
+
* booleans false before true, everything else as text with numeric runs read as
|
|
15
|
+
* numbers ("file 2" before "file 10"). Mixed kinds group by kind so the order
|
|
16
|
+
* is total and a sort is stable from one run to the next.
|
|
17
|
+
*/
|
|
18
|
+
export function compareValues(a: unknown, b: unknown): number {
|
|
19
|
+
const ra = rank(a)
|
|
20
|
+
const rb = rank(b)
|
|
21
|
+
if (ra !== rb) return ra - rb
|
|
22
|
+
switch (ra) {
|
|
23
|
+
case 0:
|
|
24
|
+
return 0
|
|
25
|
+
case 1:
|
|
26
|
+
return Number(a) - Number(b)
|
|
27
|
+
case 2: {
|
|
28
|
+
const x = a as number | bigint
|
|
29
|
+
const y = b as number | bigint
|
|
30
|
+
return x < y ? -1 : x > y ? 1 : 0
|
|
31
|
+
}
|
|
32
|
+
case 3:
|
|
33
|
+
return (a as Date).getTime() - (b as Date).getTime()
|
|
34
|
+
default:
|
|
35
|
+
return COLLATOR.compare(String(a), String(b))
|
|
36
|
+
}
|
|
37
|
+
}
|