@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
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { computed, toValue, type MaybeRefOrGetter } from "vue"
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Format-as-you-type for a native <input> without losing the caret.
|
|
5
|
+
*
|
|
6
|
+
* A mask is a string of tokens and literals: "+0 (000) 000-00-00",
|
|
7
|
+
* "0000 0000 0000 0000", "AA-0000". Each token accepts one character; anything
|
|
8
|
+
* else is a literal the mask inserts itself. A backslash makes the next mask
|
|
9
|
+
* character literal ("\\0" is a literal zero). A list of masks picks the first
|
|
10
|
+
* one the typed characters fit, and a function derives the mask from the raw
|
|
11
|
+
* value typed so far (card networks, phone plans).
|
|
12
|
+
*
|
|
13
|
+
* The caret is tracked by how many accepted characters sit before it, not by
|
|
14
|
+
* its offset, so inserting a literal ahead of it never shoves it backwards.
|
|
15
|
+
* Literals are only written once a character follows them, so Backspace walks
|
|
16
|
+
* back naturally instead of hitting an auto-inserted separator.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export type MaskToken = RegExp | { pattern: RegExp; transform?: (ch: string) => string }
|
|
20
|
+
export type MaskTokens = Record<string, MaskToken>
|
|
21
|
+
export type MaskSpec = string | readonly string[] | ((raw: string) => string)
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_MASK_TOKENS: MaskTokens = {
|
|
24
|
+
"0": /\d/,
|
|
25
|
+
a: /\p{L}/u,
|
|
26
|
+
A: { pattern: /\p{L}/u, transform: (ch) => ch.toLocaleUpperCase() },
|
|
27
|
+
"*": /[\p{L}\d]/u,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type Slot = { literal: string } | { test: (ch: string) => boolean; transform: (ch: string) => string }
|
|
31
|
+
|
|
32
|
+
export interface MaskResult {
|
|
33
|
+
masked: string
|
|
34
|
+
raw: string
|
|
35
|
+
/** Caret offset in `masked`. */
|
|
36
|
+
caret: number
|
|
37
|
+
/** Every token of the mask is filled. */
|
|
38
|
+
complete: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface MaskOptions {
|
|
42
|
+
mask: MaskSpec
|
|
43
|
+
tokens?: MaskTokens
|
|
44
|
+
/**
|
|
45
|
+
* Caret position: an offset into `input`, or with `fromRaw` the number of raw
|
|
46
|
+
* characters before it. Defaults to the end.
|
|
47
|
+
*/
|
|
48
|
+
caret?: number
|
|
49
|
+
/** `input` holds raw characters only; literals in it are never consumed as mask literals. */
|
|
50
|
+
fromRaw?: boolean
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function tester(token: MaskToken): { test: (ch: string) => boolean; transform: (ch: string) => string } {
|
|
54
|
+
const pattern = token instanceof RegExp ? token : token.pattern
|
|
55
|
+
const re = new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""))
|
|
56
|
+
const transform = token instanceof RegExp ? undefined : token.transform
|
|
57
|
+
return { test: (ch) => re.test(ch), transform: transform ?? ((ch) => ch) }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function parseMask(mask: string, tokens: MaskTokens = DEFAULT_MASK_TOKENS): Slot[] {
|
|
61
|
+
const slots: Slot[] = []
|
|
62
|
+
const chars = Array.from(mask)
|
|
63
|
+
for (let i = 0; i < chars.length; i++) {
|
|
64
|
+
const ch = chars[i]!
|
|
65
|
+
if (ch === "\\" && i + 1 < chars.length) {
|
|
66
|
+
slots.push({ literal: chars[++i]! })
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
const token = tokens[ch]
|
|
70
|
+
slots.push(token ? tester(token) : { literal: ch })
|
|
71
|
+
}
|
|
72
|
+
return slots
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface Run extends MaskResult {
|
|
76
|
+
overflow: boolean
|
|
77
|
+
/** Offsets in the input of the characters consumed as tokens. */
|
|
78
|
+
tokenOffsets: number[]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function run(slots: Slot[], input: string, caret: number, fromRaw: boolean): Run {
|
|
82
|
+
const tokenStarts: number[] = []
|
|
83
|
+
const tokenEnds: number[] = []
|
|
84
|
+
const tokenOffsets: number[] = []
|
|
85
|
+
let out = ""
|
|
86
|
+
let raw = ""
|
|
87
|
+
let pending = ""
|
|
88
|
+
let si = 0
|
|
89
|
+
let offset = 0
|
|
90
|
+
let before = 0
|
|
91
|
+
let overflow = false
|
|
92
|
+
for (const ch of input) {
|
|
93
|
+
const at = offset
|
|
94
|
+
offset += ch.length
|
|
95
|
+
if (si >= slots.length) {
|
|
96
|
+
if (slots.some((s) => "test" in s && s.test(ch))) overflow = true
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
while (si < slots.length) {
|
|
100
|
+
const slot = slots[si]!
|
|
101
|
+
if ("literal" in slot) {
|
|
102
|
+
pending += slot.literal
|
|
103
|
+
si++
|
|
104
|
+
if (!fromRaw && slot.literal === ch) break
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
if (slot.test(ch)) {
|
|
108
|
+
const value = slot.transform(ch)
|
|
109
|
+
out += pending
|
|
110
|
+
pending = ""
|
|
111
|
+
tokenStarts.push(out.length)
|
|
112
|
+
out += value
|
|
113
|
+
tokenEnds.push(out.length)
|
|
114
|
+
raw += value
|
|
115
|
+
tokenOffsets.push(at)
|
|
116
|
+
si++
|
|
117
|
+
if (fromRaw ? raw.length <= caret : at < caret) before++
|
|
118
|
+
}
|
|
119
|
+
break
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const rest = slots.slice(si)
|
|
123
|
+
const complete = !rest.some((s) => "test" in s)
|
|
124
|
+
// A fully typed mask also shows its closing literals ("(000)" ends with ")").
|
|
125
|
+
if (complete && out) out += pending + rest.map((s) => ("literal" in s ? s.literal : "")).join("")
|
|
126
|
+
const at = before === 0 ? (tokenStarts[0] ?? 0) : tokenEnds[before - 1]!
|
|
127
|
+
return {
|
|
128
|
+
masked: out,
|
|
129
|
+
raw,
|
|
130
|
+
caret: complete && before === tokenEnds.length ? out.length : at,
|
|
131
|
+
complete,
|
|
132
|
+
overflow,
|
|
133
|
+
tokenOffsets,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function resolve(options: MaskOptions, input: string, caret: number): Run {
|
|
138
|
+
const tokens = options.tokens ?? DEFAULT_MASK_TOKENS
|
|
139
|
+
const fromRaw = !!options.fromRaw
|
|
140
|
+
const { mask } = options
|
|
141
|
+
if (typeof mask === "string") return run(parseMask(mask, tokens), input, caret, fromRaw)
|
|
142
|
+
if (typeof mask === "function") {
|
|
143
|
+
let current = mask("")
|
|
144
|
+
let result = run(parseMask(current, tokens), input, caret, fromRaw)
|
|
145
|
+
for (let i = 0; i < 4; i++) {
|
|
146
|
+
const next = mask(result.raw)
|
|
147
|
+
if (next === current) break
|
|
148
|
+
current = next
|
|
149
|
+
result = run(parseMask(current, tokens), input, caret, fromRaw)
|
|
150
|
+
}
|
|
151
|
+
return result
|
|
152
|
+
}
|
|
153
|
+
let last: Run | undefined
|
|
154
|
+
for (const m of mask) {
|
|
155
|
+
last = run(parseMask(m, tokens), input, caret, fromRaw)
|
|
156
|
+
if (!last.overflow) return last
|
|
157
|
+
}
|
|
158
|
+
return last ?? run([], input, caret, fromRaw)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Format `input` through a mask and map the caret into the result. */
|
|
162
|
+
export function applyMask(input: string, options: MaskOptions): MaskResult {
|
|
163
|
+
const caret = options.caret ?? (options.fromRaw ? Number.POSITIVE_INFINITY : input.length)
|
|
164
|
+
const { masked, raw, caret: at, complete } = resolve(options, input, caret)
|
|
165
|
+
return { masked, raw, caret: at, complete }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The value after deleting one accepted character next to a collapsed caret,
|
|
170
|
+
* skipping over literals, or null when there is nothing to delete that way.
|
|
171
|
+
*/
|
|
172
|
+
export function maskDelete(
|
|
173
|
+
value: string,
|
|
174
|
+
caret: number,
|
|
175
|
+
direction: "backward" | "forward",
|
|
176
|
+
options: Omit<MaskOptions, "caret" | "fromRaw">,
|
|
177
|
+
): MaskResult | null {
|
|
178
|
+
const { tokenOffsets, raw } = resolve(options, value, caret)
|
|
179
|
+
const index =
|
|
180
|
+
direction === "backward"
|
|
181
|
+
? tokenOffsets.filter((o) => o < caret).length - 1
|
|
182
|
+
: tokenOffsets.findIndex((o) => o >= caret)
|
|
183
|
+
if (index < 0) return null
|
|
184
|
+
const chars = Array.from(raw)
|
|
185
|
+
chars.splice(index, 1)
|
|
186
|
+
return applyMask(chars.join(""), { ...options, caret: index, fromRaw: true })
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface UseInputMaskOptions {
|
|
190
|
+
mask: MaybeRefOrGetter<MaskSpec | undefined>
|
|
191
|
+
tokens?: MaybeRefOrGetter<MaskTokens | undefined>
|
|
192
|
+
/** Called with every value the user produces. */
|
|
193
|
+
onChange?: (masked: string, raw: string, complete: boolean) => void
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Bind `onBeforeInput`, `onInput` and `onCompositionEnd` to the input, and show
|
|
198
|
+
* `format(model).masked` as its value.
|
|
199
|
+
*/
|
|
200
|
+
export function useInputMask(options: UseInputMaskOptions) {
|
|
201
|
+
const spec = computed(() => {
|
|
202
|
+
const mask = toValue(options.mask)
|
|
203
|
+
return mask === undefined || mask === "" ? undefined : { mask, tokens: toValue(options.tokens) }
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
function format(value: string | null | undefined): MaskResult {
|
|
207
|
+
const s = spec.value
|
|
208
|
+
const text = value ?? ""
|
|
209
|
+
return s ? applyMask(text, s) : { masked: text, raw: text, caret: text.length, complete: true }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function commit(el: HTMLInputElement, result: MaskResult): void {
|
|
213
|
+
if (el.value !== result.masked) el.value = result.masked
|
|
214
|
+
if (el.ownerDocument.activeElement === el) {
|
|
215
|
+
try {
|
|
216
|
+
el.setSelectionRange(result.caret, result.caret)
|
|
217
|
+
} catch {
|
|
218
|
+
// Input types without a selection cannot place a caret.
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
options.onChange?.(result.masked, result.raw, result.complete)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function onBeforeInput(event: InputEvent): void {
|
|
225
|
+
const s = spec.value
|
|
226
|
+
const el = event.target
|
|
227
|
+
if (!s || event.isComposing || !(el instanceof HTMLInputElement)) return
|
|
228
|
+
const backward = event.inputType === "deleteContentBackward"
|
|
229
|
+
if (!backward && event.inputType !== "deleteContentForward") return
|
|
230
|
+
const start = el.selectionStart
|
|
231
|
+
if (start === null || start !== el.selectionEnd) return
|
|
232
|
+
event.preventDefault()
|
|
233
|
+
const result = maskDelete(el.value, start, backward ? "backward" : "forward", s)
|
|
234
|
+
if (result) commit(el, result)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function reformat(el: HTMLInputElement): void {
|
|
238
|
+
const s = spec.value
|
|
239
|
+
if (!s) return
|
|
240
|
+
commit(el, applyMask(el.value, { ...s, caret: el.selectionStart ?? el.value.length }))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Attach to `input`. Composition is left alone until it ends. */
|
|
244
|
+
function onInput(event: Event): void {
|
|
245
|
+
if ((event as InputEvent).isComposing || !(event.target instanceof HTMLInputElement)) return
|
|
246
|
+
reformat(event.target)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function onCompositionEnd(event: CompositionEvent): void {
|
|
250
|
+
if (event.target instanceof HTMLInputElement) reformat(event.target)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return { active: computed(() => !!spec.value), format, onBeforeInput, onInput, onCompositionEnd }
|
|
254
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
inject,
|
|
4
|
+
provide,
|
|
5
|
+
toValue,
|
|
6
|
+
type App,
|
|
7
|
+
type Component,
|
|
8
|
+
type ComputedRef,
|
|
9
|
+
type InjectionKey,
|
|
10
|
+
type MaybeRefOrGetter,
|
|
11
|
+
} from "vue"
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* App-wide kit configuration, said once at the root instead of on every call
|
|
15
|
+
* site: the router link component that navigation pieces render, and the text
|
|
16
|
+
* the kit would otherwise hard-code in English.
|
|
17
|
+
*
|
|
18
|
+
* Labels are grouped by scope (one scope per component, e.g. "lightbox",
|
|
19
|
+
* "toast") so each component owns its own keys and defaults. The precedence is
|
|
20
|
+
* always: the component's own `labels` prop, then the nearest config, then the
|
|
21
|
+
* component's English defaults.
|
|
22
|
+
*/
|
|
23
|
+
export type LabelScope = Record<string, string>
|
|
24
|
+
|
|
25
|
+
export interface LpConfig {
|
|
26
|
+
/** Component rendered for internal links (RouterLink, NuxtLink). Plain `<a>` when unset. */
|
|
27
|
+
linkComponent?: Component | string
|
|
28
|
+
/** Text overrides by scope: `{ lightbox: { previous: "Back" } }`. */
|
|
29
|
+
labels?: Partial<Record<string, Partial<LabelScope>>>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const LP_CONFIG: InjectionKey<MaybeRefOrGetter<LpConfig>> = Symbol.for("lp-config")
|
|
33
|
+
|
|
34
|
+
/** Provide kit configuration to every component below the caller. */
|
|
35
|
+
export function provideLpConfig(config: MaybeRefOrGetter<LpConfig>): void {
|
|
36
|
+
provide(LP_CONFIG, layered(inject(LP_CONFIG, {}), config))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Install the configuration app-wide: `app.use(lpConfig({ ... }))`. */
|
|
40
|
+
export function lpConfig(config: MaybeRefOrGetter<LpConfig>) {
|
|
41
|
+
return {
|
|
42
|
+
install: (app: App) => {
|
|
43
|
+
const outer = app.runWithContext(() => inject(LP_CONFIG, {}))
|
|
44
|
+
app.provide(LP_CONFIG, layered(outer, config))
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
* A nested config refines the one above it rather than replacing it: an app's
|
|
51
|
+
* own labels must not drop the link component a framework plugin provided.
|
|
52
|
+
*/
|
|
53
|
+
function layered(outer: MaybeRefOrGetter<LpConfig>, inner: MaybeRefOrGetter<LpConfig>): () => LpConfig {
|
|
54
|
+
return () => mergeConfig(toValue(outer), toValue(inner))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function mergeConfig(outer: LpConfig, inner: LpConfig): LpConfig {
|
|
58
|
+
const labels: Partial<Record<string, Partial<LabelScope>>> = { ...outer.labels }
|
|
59
|
+
for (const [scope, values] of Object.entries(inner.labels ?? {})) {
|
|
60
|
+
labels[scope] = { ...labels[scope], ...values }
|
|
61
|
+
}
|
|
62
|
+
return { ...outer, ...inner, linkComponent: inner.linkComponent ?? outer.linkComponent, labels }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function useLpConfig(): ComputedRef<LpConfig> {
|
|
66
|
+
const config = inject(LP_CONFIG, {})
|
|
67
|
+
return computed(() => toValue(config))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The labels a component renders: its own `labels` prop over the configured
|
|
72
|
+
* scope over its defaults.
|
|
73
|
+
*/
|
|
74
|
+
export function useLabels<T extends LabelScope>(
|
|
75
|
+
scope: string,
|
|
76
|
+
defaults: T,
|
|
77
|
+
own?: () => Partial<T> | undefined,
|
|
78
|
+
): ComputedRef<T> {
|
|
79
|
+
const config = useLpConfig()
|
|
80
|
+
return computed(() => ({ ...defaults, ...config.value.labels?.[scope], ...own?.() }) as T)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The link component to render for an internal destination. */
|
|
84
|
+
export function useLinkComponent(): ComputedRef<Component | string> {
|
|
85
|
+
const config = useLpConfig()
|
|
86
|
+
return computed(() => config.value.linkComponent ?? "a")
|
|
87
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { onBeforeUnmount, onMounted, readonly, ref, toValue, type MaybeRefOrGetter, type Ref } from "vue"
|
|
2
|
+
import { useHotkeys, type HotkeyHelp } from "./useHotkeys"
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Paint selection: press on one item's tick and sweep across the others, and
|
|
6
|
+
* every item the pointer passes takes the state the first one is switching
|
|
7
|
+
* to. Starting on an unticked item ticks the run, starting on a ticked one
|
|
8
|
+
* clears it, and sweeping back un-paints what the stroke covered, restoring
|
|
9
|
+
* the items as they were before it. Near the edge of the scrolling box the list
|
|
10
|
+
* scrolls on its own, so a stroke can run past what is visible.
|
|
11
|
+
*
|
|
12
|
+
* Items are found in the DOM by `data-paint-key`, in document order, so the
|
|
13
|
+
* run follows what the user sees (sorted, filtered, collapsed groups skipped).
|
|
14
|
+
* A mouse or pen paints at once. A finger holds still for HOLD_MS first, so a
|
|
15
|
+
* swipe through the list still scrolls it.
|
|
16
|
+
*
|
|
17
|
+
* The press itself ticks the first item, so the click the browser sends after
|
|
18
|
+
* the stroke is swallowed before the item's own handler sees it; otherwise the
|
|
19
|
+
* first item would toggle back.
|
|
20
|
+
*
|
|
21
|
+
* `onPointerDown(event, key, { hold })` makes any pointer hold first. That is
|
|
22
|
+
* how a list without visible ticks enters selection: a quick click on the row
|
|
23
|
+
* stays the row's click, a long press selects it and the same press goes on
|
|
24
|
+
* painting.
|
|
25
|
+
*/
|
|
26
|
+
export interface UsePaintSelectOptions<K> {
|
|
27
|
+
/** The element that holds the items. */
|
|
28
|
+
root: Ref<HTMLElement | null>
|
|
29
|
+
selection: () => readonly K[]
|
|
30
|
+
set: (keys: K[]) => void
|
|
31
|
+
/** Turn painting off without unmounting (e.g. selection mode is off). */
|
|
32
|
+
enabled?: MaybeRefOrGetter<boolean>
|
|
33
|
+
/** Map an item's `data-paint-key` back to its key. Defaults to the string itself. */
|
|
34
|
+
resolve?: (raw: string) => K | undefined
|
|
35
|
+
/** Every key, for Ctrl+A while the focus is inside `root`. Omit to leave Ctrl+A alone. */
|
|
36
|
+
selectAll?: () => K[]
|
|
37
|
+
/** How Ctrl+A is listed in the shortcuts reference. */
|
|
38
|
+
help?: MaybeRefOrGetter<HotkeyHelp | undefined>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const HOLD_MS = 320
|
|
42
|
+
const SLOP_PX = 8
|
|
43
|
+
const EDGE_PX = 40
|
|
44
|
+
const SCROLL_STEP = 14
|
|
45
|
+
|
|
46
|
+
interface Stroke<K> {
|
|
47
|
+
keys: K[]
|
|
48
|
+
anchor: number
|
|
49
|
+
paint: boolean
|
|
50
|
+
before: Set<K>
|
|
51
|
+
x: number
|
|
52
|
+
y: number
|
|
53
|
+
scroller: HTMLElement | null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function scrollParent(el: HTMLElement | null): HTMLElement | null {
|
|
57
|
+
for (let node = el?.parentElement; node; node = node.parentElement) {
|
|
58
|
+
const { overflowY } = getComputedStyle(node)
|
|
59
|
+
if ((overflowY === "auto" || overflowY === "scroll") && node.scrollHeight > node.clientHeight) return node
|
|
60
|
+
}
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function usePaintSelect<K = string>(options: UsePaintSelectOptions<K>) {
|
|
65
|
+
const resolve = options.resolve ?? ((raw: string) => raw as unknown as K)
|
|
66
|
+
let stroke: Stroke<K> | null = null
|
|
67
|
+
let pending: { key: K; pointerId: number; x: number; y: number; timer: ReturnType<typeof setTimeout> } | null = null
|
|
68
|
+
let swallowClick = false
|
|
69
|
+
let frame = 0
|
|
70
|
+
/** True while a stroke is painting, for UI that reacts to it (a tick column revealing). */
|
|
71
|
+
const painting = ref(false)
|
|
72
|
+
|
|
73
|
+
const enabled = () => toValue(options.enabled) ?? true
|
|
74
|
+
|
|
75
|
+
function keysInView(): K[] {
|
|
76
|
+
const items = options.root.value?.querySelectorAll<HTMLElement>("[data-paint-key]") ?? []
|
|
77
|
+
const out: K[] = []
|
|
78
|
+
for (const item of items) {
|
|
79
|
+
if (item.offsetParent === null) continue
|
|
80
|
+
const key = resolve(item.dataset.paintKey ?? "")
|
|
81
|
+
if (key !== undefined) out.push(key)
|
|
82
|
+
}
|
|
83
|
+
return out
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function keyAt(x: number, y: number): K | undefined {
|
|
87
|
+
const item = document.elementFromPoint(x, y)?.closest<HTMLElement>("[data-paint-key]")
|
|
88
|
+
if (!item || !options.root.value?.contains(item)) return undefined
|
|
89
|
+
return resolve(item.dataset.paintKey ?? "")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function extend() {
|
|
93
|
+
if (!stroke) return
|
|
94
|
+
const key = keyAt(stroke.x, stroke.y)
|
|
95
|
+
const at = key === undefined ? -1 : stroke.keys.indexOf(key)
|
|
96
|
+
if (at < 0) return
|
|
97
|
+
const [lo, hi] = at < stroke.anchor ? [at, stroke.anchor] : [stroke.anchor, at]
|
|
98
|
+
const next = new Set(stroke.before)
|
|
99
|
+
for (let i = lo; i <= hi; i++) {
|
|
100
|
+
const k = stroke.keys[i]!
|
|
101
|
+
if (stroke.paint) next.add(k)
|
|
102
|
+
else next.delete(k)
|
|
103
|
+
}
|
|
104
|
+
options.set([...next])
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function tick() {
|
|
108
|
+
frame = 0
|
|
109
|
+
if (!stroke) return
|
|
110
|
+
const box = stroke.scroller?.getBoundingClientRect() ?? { top: 0, bottom: window.innerHeight }
|
|
111
|
+
const delta = stroke.y > box.bottom - EDGE_PX ? SCROLL_STEP : stroke.y < box.top + EDGE_PX ? -SCROLL_STEP : 0
|
|
112
|
+
if (delta) {
|
|
113
|
+
if (stroke.scroller) stroke.scroller.scrollTop += delta
|
|
114
|
+
else window.scrollBy(0, delta)
|
|
115
|
+
extend()
|
|
116
|
+
}
|
|
117
|
+
frame = requestAnimationFrame(tick)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function begin(key: K, x: number, y: number) {
|
|
121
|
+
const keys = keysInView()
|
|
122
|
+
const anchor = keys.indexOf(key)
|
|
123
|
+
if (anchor < 0) return
|
|
124
|
+
const before = new Set(options.selection())
|
|
125
|
+
stroke = { keys, anchor, paint: !before.has(key), before, x, y, scroller: scrollParent(options.root.value) }
|
|
126
|
+
painting.value = true
|
|
127
|
+
// A sweep over text would otherwise select it, and a hold that started
|
|
128
|
+
// before the stroke may already have.
|
|
129
|
+
window.getSelection()?.removeAllRanges()
|
|
130
|
+
if (options.root.value) options.root.value.style.userSelect = "none"
|
|
131
|
+
extend()
|
|
132
|
+
if (!frame) frame = requestAnimationFrame(tick)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function cancelPending() {
|
|
136
|
+
if (pending) clearTimeout(pending.timer)
|
|
137
|
+
pending = null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Bind to `pointerdown` on the item's tick (or the whole item). */
|
|
141
|
+
function onPointerDown(event: PointerEvent, key: K, behaviour: { hold?: number } = {}) {
|
|
142
|
+
if (!enabled() || event.button !== 0) return
|
|
143
|
+
cancelPending()
|
|
144
|
+
if (behaviour.hold === undefined && event.pointerType !== "touch") {
|
|
145
|
+
// Keeps the press from starting a text selection or moving focus mid-stroke.
|
|
146
|
+
event.preventDefault()
|
|
147
|
+
begin(key, event.clientX, event.clientY)
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
const { pointerId, clientX: x, clientY: y } = event
|
|
151
|
+
pending = {
|
|
152
|
+
key,
|
|
153
|
+
pointerId,
|
|
154
|
+
x,
|
|
155
|
+
y,
|
|
156
|
+
timer: setTimeout(() => {
|
|
157
|
+
pending = null
|
|
158
|
+
begin(key, x, y)
|
|
159
|
+
navigator.vibrate?.(8)
|
|
160
|
+
}, behaviour.hold ?? HOLD_MS),
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function onMove(event: PointerEvent) {
|
|
165
|
+
if (pending && event.pointerId === pending.pointerId) {
|
|
166
|
+
if (Math.hypot(event.clientX - pending.x, event.clientY - pending.y) > SLOP_PX) cancelPending()
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
if (!stroke) return
|
|
170
|
+
stroke.x = event.clientX
|
|
171
|
+
stroke.y = event.clientY
|
|
172
|
+
extend()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function onUp() {
|
|
176
|
+
cancelPending()
|
|
177
|
+
if (!stroke) return
|
|
178
|
+
stroke = null
|
|
179
|
+
painting.value = false
|
|
180
|
+
if (options.root.value) options.root.value.style.userSelect = ""
|
|
181
|
+
if (frame) cancelAnimationFrame(frame)
|
|
182
|
+
frame = 0
|
|
183
|
+
swallowClick = true
|
|
184
|
+
setTimeout(() => (swallowClick = false), 0)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function onClickCapture(event: MouseEvent) {
|
|
188
|
+
if (!swallowClick) return
|
|
189
|
+
swallowClick = false
|
|
190
|
+
event.stopPropagation()
|
|
191
|
+
event.preventDefault()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// A long press is also how a finger asks for the context menu; once it has
|
|
195
|
+
// turned into painting, that menu would open over the stroke.
|
|
196
|
+
function onContextMenu(event: Event) {
|
|
197
|
+
if (!stroke && !swallowClick) return
|
|
198
|
+
event.preventDefault()
|
|
199
|
+
event.stopPropagation()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// A finger that has started painting must not scroll the page under it.
|
|
203
|
+
function onTouchMove(event: TouchEvent) {
|
|
204
|
+
if (stroke) event.preventDefault()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
useHotkeys(
|
|
208
|
+
() =>
|
|
209
|
+
options.selectAll && enabled()
|
|
210
|
+
? [
|
|
211
|
+
{
|
|
212
|
+
key: "a",
|
|
213
|
+
code: "KeyA",
|
|
214
|
+
ctrl: true,
|
|
215
|
+
help: toValue(options.help),
|
|
216
|
+
handler: () => options.set(options.selectAll!()),
|
|
217
|
+
},
|
|
218
|
+
]
|
|
219
|
+
: [],
|
|
220
|
+
{ scope: options.root },
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
onMounted(() => {
|
|
224
|
+
window.addEventListener("pointermove", onMove)
|
|
225
|
+
window.addEventListener("pointerup", onUp)
|
|
226
|
+
window.addEventListener("pointercancel", onUp)
|
|
227
|
+
window.addEventListener("click", onClickCapture, true)
|
|
228
|
+
window.addEventListener("contextmenu", onContextMenu, true)
|
|
229
|
+
options.root.value?.addEventListener("touchmove", onTouchMove, { passive: false })
|
|
230
|
+
})
|
|
231
|
+
onBeforeUnmount(() => {
|
|
232
|
+
onUp()
|
|
233
|
+
swallowClick = false
|
|
234
|
+
window.removeEventListener("pointermove", onMove)
|
|
235
|
+
window.removeEventListener("pointerup", onUp)
|
|
236
|
+
window.removeEventListener("pointercancel", onUp)
|
|
237
|
+
window.removeEventListener("click", onClickCapture, true)
|
|
238
|
+
window.removeEventListener("contextmenu", onContextMenu, true)
|
|
239
|
+
options.root.value?.removeEventListener("touchmove", onTouchMove)
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
return { onPointerDown, painting: readonly(painting) }
|
|
243
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { computed, ref, watch, type MaybeRefOrGetter, toValue } from "vue"
|
|
2
2
|
|
|
3
|
+
const NON_TEXT_INPUTS = new Set(["checkbox", "radio", "button", "submit", "reset", "range", "color", "file", "image"])
|
|
4
|
+
|
|
3
5
|
/*
|
|
4
6
|
* Arrow-key navigation over a list, with a roving tabindex.
|
|
5
7
|
*
|
|
@@ -152,7 +154,16 @@ export function useRovingFocus(
|
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
function isTextEntry(target: EventTarget | null): boolean {
|
|
158
|
+
if (!(target instanceof HTMLElement)) return false
|
|
159
|
+
if (target.isContentEditable || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) return true
|
|
160
|
+
return target instanceof HTMLInputElement && !NON_TEXT_INPUTS.has(target.type)
|
|
161
|
+
}
|
|
162
|
+
|
|
155
163
|
function onKeydown(event: KeyboardEvent) {
|
|
164
|
+
// A field inside a row (an inline rename, a filter) owns its keys: the list
|
|
165
|
+
// taking letters for typeahead or Space for activation made it untypeable.
|
|
166
|
+
if (event.isComposing || isTextEntry(event.target)) return
|
|
156
167
|
const vertical = orientation === "vertical" || orientation === "both"
|
|
157
168
|
const horizontal = orientation === "horizontal" || orientation === "both"
|
|
158
169
|
|
|
@@ -87,6 +87,13 @@ export function useShift(
|
|
|
87
87
|
/** Where the box last settled, so a retune has a size to ease FROM. */
|
|
88
88
|
let lastHeight = 0
|
|
89
89
|
let lastWidth = 0
|
|
90
|
+
/**
|
|
91
|
+
* Whether the last measurement was of a box actually laid out. An empty box
|
|
92
|
+
* in the flow measures 0 and is a real size to grow from (a bar or message
|
|
93
|
+
* appearing in it); a box under `display: none` also measures 0 but has no
|
|
94
|
+
* layout yet, and growing from that would animate content in from nothing.
|
|
95
|
+
*/
|
|
96
|
+
let laidOut = false
|
|
90
97
|
/** True while a tween owns the size, so observers don't cancel it midway. */
|
|
91
98
|
let inFlight = false
|
|
92
99
|
/** When the in-flight tween started, to tell a duplicate from a follow-up. */
|
|
@@ -192,6 +199,8 @@ export function useShift(
|
|
|
192
199
|
restoreScroll(tops)
|
|
193
200
|
lastHeight = toHeight
|
|
194
201
|
lastWidth = toWidth
|
|
202
|
+
const wasLaidOut = laidOut
|
|
203
|
+
laidOut = node.getClientRects().length > 0
|
|
195
204
|
|
|
196
205
|
// Nothing moved on either axis, so there is nothing to ease — and leaving
|
|
197
206
|
// the size unset keeps the box free to size itself.
|
|
@@ -203,8 +212,8 @@ export function useShift(
|
|
|
203
212
|
// different thing it was meant to catch: the box was not in the layout yet,
|
|
204
213
|
// so there is no size to ease out of.
|
|
205
214
|
const movesHeight =
|
|
206
|
-
wantsHeight() && fromHeight !== 0 && toHeight !== fromHeight
|
|
207
|
-
const movesWidth = wantsWidth() && fromWidth !== 0 && toWidth !== fromWidth
|
|
215
|
+
wantsHeight() && (fromHeight !== 0 || wasLaidOut) && toHeight !== fromHeight
|
|
216
|
+
const movesWidth = wantsWidth() && (fromWidth !== 0 || wasLaidOut) && toWidth !== fromWidth
|
|
208
217
|
if (!movesHeight && !movesWidth) return
|
|
209
218
|
|
|
210
219
|
if (movesHeight) node.style.height = `${fromHeight}px`
|
|
@@ -250,6 +259,7 @@ export function useShift(
|
|
|
250
259
|
resizing.value = false
|
|
251
260
|
lastHeight = 0
|
|
252
261
|
lastWidth = 0
|
|
262
|
+
laidOut = false
|
|
253
263
|
inFlight = false
|
|
254
264
|
}
|
|
255
265
|
|
|
@@ -263,6 +273,7 @@ export function useShift(
|
|
|
263
273
|
|
|
264
274
|
lastHeight = node.offsetHeight
|
|
265
275
|
lastWidth = node.offsetWidth
|
|
276
|
+
laidOut = node.getClientRects().length > 0
|
|
266
277
|
scrollHost = node
|
|
267
278
|
node.addEventListener("scroll", rememberScroll, { capture: true, passive: true })
|
|
268
279
|
if (wantsHeight()) node.style.height = `${lastHeight}px`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useReducedMotion } from "motion-v"
|
|
2
|
-
import { computed, ref, type ComputedRef, type CSSProperties, type Ref } from "vue"
|
|
2
|
+
import { computed, ref, toValue, type ComputedRef, type CSSProperties, type MaybeRefOrGetter, type Ref } from "vue"
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
5
|
* useTilt — pointer-reactive 3D tilt for a "tactile" surface (a card, a tile, a
|
|
@@ -35,10 +35,8 @@ export interface UseTilt {
|
|
|
35
35
|
active: Ref<boolean>
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export function useTilt(
|
|
39
|
-
const
|
|
40
|
-
const lift = options.lift ?? 8
|
|
41
|
-
const scale = options.scale ?? 1.03
|
|
38
|
+
export function useTilt(source: MaybeRefOrGetter<UseTiltOptions> = {}): UseTilt {
|
|
39
|
+
const options = computed(() => toValue(source))
|
|
42
40
|
const reduceMotion = useReducedMotion()
|
|
43
41
|
|
|
44
42
|
const active = ref(false)
|
|
@@ -72,6 +70,7 @@ export function useTilt(options: UseTiltOptions = {}): UseTilt {
|
|
|
72
70
|
// rotateX tips away from the pointer's vertical position; rotateY toward its
|
|
73
71
|
// horizontal one. Only tilt while active so it rests flat when the pointer
|
|
74
72
|
// leaves (nx/ny are already zeroed on leave).
|
|
73
|
+
const { max = 10, lift = 8, scale = 1.03 } = options.value
|
|
75
74
|
const rx = active.value ? -ny.value * max : 0
|
|
76
75
|
const ry = active.value ? nx.value * max : 0
|
|
77
76
|
const z = active.value ? lift : 0
|
|
@@ -86,7 +85,7 @@ export function useTilt(options: UseTiltOptions = {}): UseTilt {
|
|
|
86
85
|
})
|
|
87
86
|
|
|
88
87
|
const glareStyle = computed<CSSProperties>(() => {
|
|
89
|
-
if (!options.glare || reduceMotion.value || !active.value) {
|
|
88
|
+
if (!options.value.glare || reduceMotion.value || !active.value) {
|
|
90
89
|
return { opacity: 0 }
|
|
91
90
|
}
|
|
92
91
|
// Bright spot tracks the pointer; map [-1,1] → [0%,100%].
|