@leavepulse/ui 0.43.3 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -7
- package/dist/assets/{LpCommandPalette-DH7sPcKP.css → LpCommandPalette-DCP5g2ik.css} +21 -21
- package/dist/assets/{LpDropzone-BBoWvi-J.css → LpDropzone-i4BdMi1H.css} +12 -12
- package/dist/assets/{LpImageEditor-Ct7Y3aNu.css → LpImageEditor-CbMA82g8.css} +6 -6
- package/dist/assets/{LpShortcutsDialog-C6YoQ8_M.css → LpShortcutsDialog-ChUyU7oV.css} +7 -7
- package/dist/assets/{LpSkeleton-D7xU8IPV.css → LpSkeleton-H3Z5dEIk.css} +4 -1
- package/dist/assets/{LpTable-BgPLJOXa.css → LpTable-DDQNdf86.css} +9 -9
- package/dist/chunks/ContextMenuItems-CrfeNPWT.js +123 -0
- package/dist/chunks/{LayoutCanvas-Dd20boUG.js → LayoutCanvas-D-jPOAvg.js} +26 -9
- package/dist/chunks/{LayoutNode-DGpZA9LX.js → LayoutNode-D8Xy9N-o.js} +20 -8
- package/dist/chunks/{LpActionBar-BlvtGB2P.js → LpActionBar-BbwCF6l5.js} +24 -10
- package/dist/chunks/{LpAddressInput-B-Zvxsn7.js → LpAddressInput-BrOqkZqg.js} +53 -48
- package/dist/chunks/{LpAlert-BAYHDSS1.js → LpAlert-DkOIjsbq.js} +15 -8
- package/dist/chunks/{LpAppShell-BGQVIY-3.js → LpAppShell-BUKPcHco.js} +51 -24
- package/dist/chunks/{LpAutocomplete-DExlIZe_.js → LpAutocomplete-DxV3Y9h4.js} +50 -23
- package/dist/chunks/{LpAvatar-B3-xAOH3.js → LpAvatar-ClBbEYZN.js} +17 -8
- package/dist/chunks/LpBreadcrumbs-B8IJgFIc.js +95 -0
- package/dist/chunks/{LpButton-5VXXCjwF.js → LpButton-kqJBHIIb.js} +44 -14
- package/dist/chunks/{LpCard-BhI1N_vL.js → LpCard-CpRByR0q.js} +1 -1
- package/dist/chunks/{LpCheckbox-CVccKYaH.js → LpCheckbox-DpqchzH4.js} +7 -3
- package/dist/chunks/{LpCodeBlock-Bo0B4TFR.js → LpCodeBlock-DeHJPq7p.js} +48 -16
- package/dist/chunks/{LpColorPicker-BlXea7LM.js → LpColorPicker-Bhf1qxhB.js} +32 -9
- package/dist/chunks/{LpConfirmDialog-Q-bxff8z.js → LpConfirmDialog-CIUOSnWo.js} +58 -16
- package/dist/chunks/LpConfirmHost-CDm0Wtpv.js +49 -0
- package/dist/chunks/LpContextMenu-DQw5dGfG.js +68 -0
- package/dist/chunks/LpDatePicker-Drirx1AK.js +191 -0
- package/dist/chunks/{LpDrawer-BZ6k3hvo.js → LpDrawer-bScx-KMO.js} +63 -26
- package/dist/chunks/{LpFileTree-_5vFBgaK.js → LpFileTree-CSI_5SK2.js} +23 -8
- package/dist/chunks/{LpFileTreeNode-BdcIYKIE.js → LpFileTreeNode-B4MiSQX2.js} +2 -2
- package/dist/chunks/LpFormField-CIk5qosh.js +87 -0
- package/dist/chunks/{LpInput-9EXWUM9B.js → LpInput-C0e8y8_d.js} +26 -11
- package/dist/chunks/{LpLightbox-2sYVMY-S.js → LpLightbox-Mr0W_6HK.js} +147 -60
- package/dist/chunks/{LpLink-DCND-zyu.js → LpLink-Krkjhe_y.js} +15 -6
- package/dist/chunks/{LpLogViewer-CaRy5MeS.js → LpLogViewer-YBOaFnc_.js} +68 -34
- package/dist/chunks/{LpMapPicker-ul4oYno6.js → LpMapPicker-NUzS9e37.js} +38 -13
- package/dist/chunks/{LpMediaGallery-FF-NquIo.js → LpMediaGallery-DfJmMW0I.js} +6 -3
- package/dist/chunks/{LpModal-Bt0pQCI_.js → LpModal-DPwGKBPv.js} +49 -26
- package/dist/chunks/{LpModalNav-YaifHrXC.js → LpModalNav-DPg5H9f3.js} +9 -6
- package/dist/chunks/{LpNotificationBell-DyGGsMAM.js → LpNotificationBell-t5yn-siX.js} +58 -24
- package/dist/chunks/{LpNumberField-CWuX08iX.js → LpNumberField-DWnQKktS.js} +20 -7
- package/dist/chunks/{LpNumberFlow-amWYaeuA.js → LpNumberFlow-z4WPZcZu.js} +22 -12
- package/dist/chunks/{LpOtpInput-y2v-pmiN.js → LpOtpInput-CziPF3Oq.js} +37 -8
- package/dist/chunks/{LpPagination-5FxjmmB5.js → LpPagination-DFngX7Xo.js} +30 -19
- package/dist/chunks/{LpPasswordInput--sjYCkLS.js → LpPasswordInput-CWVDuBJ5.js} +10 -7
- package/dist/chunks/{LpPhoneInput-BDSKVl_z.js → LpPhoneInput-CD-ZTeSd.js} +57 -33
- package/dist/chunks/{LpPie-D6OnzwlJ.js → LpPie-BLco31Nx.js} +78 -21
- package/dist/chunks/{LpPopover-8268UmlC.js → LpPopover-p_e_shN3.js} +4 -2
- package/dist/chunks/{LpProgress-Btq5W4Ut.js → LpProgress-CBU1iI69.js} +5 -4
- package/dist/chunks/{LpRadioGroup-xPIuVDO_.js → LpRadioGroup-DmEW0l78.js} +9 -2
- package/dist/chunks/{LpSegmented-fV0Sk92p.js → LpSegmented-Dejd7cIx.js} +7 -3
- package/dist/chunks/{LpSelect-ANIVtrpM.js → LpSelect-DrRD9I7L.js} +40 -18
- package/dist/chunks/{LpSidebar-ClBUwoEz.js → LpSidebar-BgwUNWpJ.js} +30 -22
- package/dist/chunks/{LpSidebarNav-llOcs_Fn.js → LpSidebarNav-Bqxz8aWT.js} +14 -9
- package/dist/chunks/{LpSkeleton-CTI-vBgW.js → LpSkeleton-BB6XIV-w.js} +1 -1
- package/dist/chunks/{LpSlider-BwNIBkAK.js → LpSlider-Cuzmrd7p.js} +7 -2
- package/dist/chunks/{LpSparkline-C6xQ2dwv.js → LpSparkline-CB80N8SJ.js} +11 -4
- package/dist/chunks/{LpStat-CQaW8R--.js → LpStat-dXtyrdX9.js} +1 -1
- package/dist/chunks/{LpSwitch-Blizvqyt.js → LpSwitch-B1aLKulj.js} +40 -26
- package/dist/chunks/{LpTableOfContents-CjvxPMpU.js → LpTableOfContents-BUcAfGPQ.js} +36 -41
- package/dist/chunks/{LpTabs-Cefe_lys.js → LpTabs-_qMVaZJ6.js} +19 -5
- package/dist/chunks/LpTextarea-yP8hGedR.js +77 -0
- package/dist/chunks/LpThemeProvider-Wjm7Tuit.js +35 -0
- package/dist/chunks/LpThemeSwitcher-CKLc10KZ.js +169 -0
- package/dist/chunks/{LpTilt-DuyKbpkA.js → LpTilt-B7KHX1zH.js} +2 -2
- package/dist/chunks/{LpTimeField-K6nAGboX.js → LpTimeField-C5HRgS5H.js} +13 -7
- package/dist/chunks/{LpToaster-gDB1EaU3.js → LpToaster--oT6lfhL.js} +57 -28
- package/dist/chunks/{LpTooltip-hCLPAqMG.js → LpTooltip-CBdzeKNf.js} +13 -6
- package/dist/chunks/LpTooltipProvider-CftjV7Xc.js +25 -0
- package/dist/chunks/{LpTopologyCanvas-Cm1L39uL.js → LpTopologyCanvas-DocsLal3.js} +6 -1
- package/dist/chunks/{LpUptimeBar-DvAMno0S.js → LpUptimeBar-DLtxOMRy.js} +43 -14
- package/dist/chunks/{LpThemeSwitcher-Cue-myST.js → useTheme-C8JHUlPM.js} +173 -215
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.d.ts +8 -0
- package/dist/components/ContextMenuItems.vue.js +4 -0
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.d.ts +4 -0
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.d.ts +3 -0
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.d.ts +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.d.ts +9 -0
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +10 -0
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.d.ts +31 -6
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.d.ts +3 -3
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.d.ts +2 -2
- package/dist/components/LpBreadcrumbs.vue.d.ts +13 -9
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +35 -98
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.d.ts +31 -2
- package/dist/components/LpChart.vue.js +317 -95
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.d.ts +2 -0
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +8 -10
- package/dist/components/LpCommandPalette.vue.js +88 -78
- package/dist/components/LpConfirmDialog.vue.d.ts +15 -3
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.d.ts +9 -0
- package/dist/components/LpConfirmHost.vue.js +4 -0
- package/dist/components/LpContextMenu.vue.d.ts +3 -5
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.d.ts +24 -6
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDrawer.vue.d.ts +10 -0
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +22 -6
- package/dist/components/LpFileTree.vue.d.ts +11 -2
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.d.ts +9 -0
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +92 -57
- package/dist/components/LpInput.vue.d.ts +3 -3
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.d.ts +17 -0
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLink.vue.d.ts +11 -11
- package/dist/components/LpLink.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +20 -2
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.d.ts +4 -4
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.d.ts +16 -6
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.d.ts +0 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +11 -2
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.d.ts +2 -0
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPagination.vue.d.ts +2 -0
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.d.ts +21 -2
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPopover.vue.d.ts +2 -0
- package/dist/components/LpPopover.vue.js +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpSection.vue.d.ts +1 -1
- package/dist/components/LpSegmented.vue.d.ts +5 -0
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.d.ts +1 -0
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.d.ts +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +11 -8
- package/dist/components/LpSidebar.vue.d.ts +6 -2
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSkeleton.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpSparkline.vue.d.ts +1 -0
- package/dist/components/LpSparkline.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.d.ts +23 -1
- package/dist/components/LpTable.vue.js +74 -36
- package/dist/components/LpTableOfContents.vue.d.ts +4 -2
- package/dist/components/LpTableOfContents.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +2 -0
- package/dist/components/LpTabs.vue.js +1 -1
- package/dist/components/LpTextarea.vue.d.ts +19 -6
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.d.ts +22 -0
- package/dist/components/LpThemeProvider.vue.js +4 -0
- package/dist/components/LpThemeSwitcher.vue.d.ts +13 -0
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTilt.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.d.ts +6 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.d.ts +1 -2
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTooltipProvider.vue.d.ts +22 -0
- package/dist/components/LpTooltipProvider.vue.js +4 -0
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.d.ts +4 -0
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/chart.d.ts +32 -1
- package/dist/components/chart.js +39 -4
- package/dist/components/codeHighlight.d.ts +6 -0
- package/dist/components/codeHighlight.js +92 -20
- package/dist/components/color.d.ts +1 -1
- package/dist/components/color.js +20 -9
- package/dist/components/commandSearch.d.ts +0 -1
- package/dist/components/commandSearch.js +1 -17
- package/dist/components/confirm.d.ts +25 -0
- package/dist/components/confirm.js +26 -0
- package/dist/components/countries.d.ts +6 -0
- package/dist/components/countries.js +7 -0
- package/dist/components/fileTree.js +16 -7
- package/dist/components/imageCrop.d.ts +42 -0
- package/dist/components/imageCrop.js +47 -0
- package/dist/components/link.d.ts +12 -0
- package/dist/components/link.js +19 -0
- package/dist/components/modalDepth.d.ts +18 -2
- package/dist/components/modalDepth.js +17 -5
- package/dist/components/otp.d.ts +5 -0
- package/dist/components/otp.js +10 -0
- package/dist/components/sidebar.d.ts +7 -1
- package/dist/components/tableSort.d.ts +8 -0
- package/dist/components/tableSort.js +31 -0
- package/dist/composables/useField.d.ts +34 -0
- package/dist/composables/useField.js +45 -0
- package/dist/composables/useFormDraft.d.ts +10 -3
- package/dist/composables/useFormDraft.js +9 -14
- package/dist/composables/useHotkeys.d.ts +17 -1
- package/dist/composables/useHotkeys.js +55 -18
- package/dist/composables/useInputFilter.d.ts +17 -0
- package/dist/composables/useInputFilter.js +47 -10
- package/dist/composables/useLpConfig.d.ts +24 -0
- package/dist/composables/useLpConfig.js +44 -0
- package/dist/composables/useRovingFocus.js +7 -0
- package/dist/composables/useTilt.d.ts +2 -2
- package/dist/composables/useTilt.js +5 -6
- package/dist/composables/useToast.d.ts +20 -0
- package/dist/composables/useToast.js +28 -4
- package/dist/composables/useZoomPan.d.ts +3 -3
- package/dist/composables/useZoomPan.js +4 -4
- package/dist/index.d.ts +33 -3
- package/dist/index.js +185 -139
- package/dist/layout/tree.d.ts +2 -0
- package/dist/layout/tree.js +16 -1
- package/dist/layout/useLayout.d.ts +15 -0
- package/dist/layout/useLayout.js +41 -14
- package/dist/theme/useTheme.d.ts +32 -9
- package/package.json +8 -7
- package/src/component-names.ts +5 -7
- package/src/components/ContextMenuItems.vue +88 -0
- package/src/components/LayoutCanvas.vue +26 -6
- package/src/components/LayoutNode.vue +19 -2
- package/src/components/LpActionBar.vue +30 -7
- package/src/components/LpAddressInput.vue +57 -41
- package/src/components/LpAlert.vue +16 -3
- package/src/components/LpAppShell.vue +45 -12
- package/src/components/LpAutocomplete.vue +46 -10
- package/src/components/LpAvatar.vue +19 -5
- package/src/components/LpBreadcrumbs.vue +65 -41
- package/src/components/LpButton.vue +59 -13
- package/src/components/LpChart.vue +272 -28
- package/src/components/LpCheckbox.vue +7 -3
- package/src/components/LpCodeBlock.vue +50 -14
- package/src/components/LpColorPicker.vue +39 -9
- package/src/components/LpCommandPalette.vue +36 -25
- package/src/components/LpConfirmDialog.vue +68 -17
- package/src/components/LpConfirmHost.vue +59 -0
- package/src/components/LpContextMenu.vue +14 -115
- package/src/components/LpDatePicker.vue +99 -68
- package/src/components/LpDrawer.vue +56 -19
- package/src/components/LpDropzone.vue +21 -4
- package/src/components/LpFileTree.vue +33 -7
- package/src/components/LpFormField.vue +30 -7
- package/src/components/LpImageEditor.vue +96 -57
- package/src/components/LpInput.vue +18 -7
- package/src/components/LpLightbox.vue +140 -29
- package/src/components/LpLink.vue +24 -13
- package/src/components/LpLogViewer.vue +86 -17
- package/src/components/LpMapPicker.vue +36 -13
- package/src/components/LpMediaGallery.vue +8 -2
- package/src/components/LpModal.vue +53 -27
- package/src/components/LpModalNav.vue +5 -2
- package/src/components/LpNotificationBell.vue +63 -17
- package/src/components/LpNumberField.vue +21 -6
- package/src/components/LpNumberFlow.vue +26 -10
- package/src/components/LpOtpInput.vue +43 -8
- package/src/components/LpPagination.vue +23 -7
- package/src/components/LpPasswordInput.vue +4 -1
- package/src/components/LpPhoneInput.vue +53 -27
- package/src/components/LpPie.vue +94 -15
- package/src/components/LpPopover.vue +3 -0
- package/src/components/LpProgress.vue +5 -3
- package/src/components/LpRadioGroup.vue +9 -2
- package/src/components/LpSegmented.vue +12 -2
- package/src/components/LpSelect.vue +0 -0
- package/src/components/LpShortcutsDialog.vue +8 -3
- package/src/components/LpSidebar.vue +14 -9
- package/src/components/LpSidebarNav.vue +17 -5
- package/src/components/LpSkeleton.vue +4 -1
- package/src/components/LpSlider.vue +7 -1
- package/src/components/LpSparkline.vue +11 -3
- package/src/components/LpSwitch.vue +30 -7
- package/src/components/LpTable.vue +91 -32
- package/src/components/LpTableOfContents.vue +50 -32
- package/src/components/LpTabs.vue +23 -6
- package/src/components/LpTextarea.vue +44 -6
- package/src/components/LpThemeProvider.vue +41 -0
- package/src/components/LpThemeSwitcher.vue +64 -17
- package/src/components/LpTilt.vue +2 -2
- package/src/components/LpTimeField.vue +14 -3
- package/src/components/LpToaster.vue +66 -26
- package/src/components/LpTooltip.vue +22 -5
- package/src/components/LpTooltipProvider.vue +24 -0
- package/src/components/LpTopologyCanvas.vue +12 -1
- package/src/components/LpUptimeBar.vue +46 -8
- package/src/components/chart.ts +77 -5
- package/src/components/codeHighlight.ts +111 -15
- package/src/components/color.ts +30 -9
- package/src/components/commandSearch.ts +0 -17
- package/src/components/confirm.ts +58 -0
- package/src/components/countries.ts +12 -0
- package/src/components/fileTree.ts +19 -7
- package/src/components/imageCrop.ts +107 -0
- package/src/components/link.ts +34 -0
- package/src/components/modalDepth.ts +48 -6
- package/src/components/otp.ts +16 -0
- package/src/components/sidebar.ts +7 -1
- package/src/components/tableSort.ts +37 -0
- package/src/composables/useField.ts +79 -0
- package/src/composables/useFormDraft.ts +25 -20
- package/src/composables/useHotkeys.ts +89 -18
- package/src/composables/useInputFilter.ts +77 -14
- package/src/composables/useLpConfig.ts +87 -0
- package/src/composables/useRovingFocus.ts +11 -0
- package/src/composables/useTilt.ts +5 -6
- package/src/composables/useToast.ts +56 -4
- package/src/composables/useZoomPan.ts +6 -6
- package/src/index.ts +38 -3
- package/src/layout/tree.ts +30 -0
- package/src/layout/useLayout.ts +68 -18
- package/src/nuxt.ts +59 -17
- package/src/runtime/config.plugin.ts +14 -0
- package/src/runtime/theme.plugin.ts +42 -22
- package/src/theme/useTheme.ts +206 -100
- package/dist/chunks/LpBreadcrumbs-p4jHe_V-.js +0 -62
- package/dist/chunks/LpContextMenu-CwW1QXe_.js +0 -217
- package/dist/chunks/LpDatePicker-CwOToxGq.js +0 -164
- package/dist/chunks/LpFormField-CazBc2-a.js +0 -62
- package/dist/chunks/LpTextarea-t7F6gv8E.js +0 -47
package/src/components/color.ts
CHANGED
|
@@ -50,7 +50,15 @@ export function clampHsv(c: Hsv): Hsv {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
// NaN and Infinity read as 0: one bad channel must not throw out of a public
|
|
54
|
+
// conversion or poison the others.
|
|
55
|
+
const finite = (n: number) => (Number.isFinite(n) ? n : 0)
|
|
56
|
+
|
|
57
|
+
export function hsvToRgb(color: Hsv): Rgb {
|
|
58
|
+
const h = finite(color.h)
|
|
59
|
+
const s = clamp(finite(color.s), 0, 1)
|
|
60
|
+
const v = clamp(finite(color.v), 0, 1)
|
|
61
|
+
const a = Number.isFinite(color.a) ? color.a : 1
|
|
54
62
|
const hue = normalizeHue(h) / 60
|
|
55
63
|
const c = v * s
|
|
56
64
|
const x = c * (1 - Math.abs((hue % 2) - 1))
|
|
@@ -99,10 +107,20 @@ export function rgbToHsv({ r, g, b, a }: Rgb): Hsv {
|
|
|
99
107
|
|
|
100
108
|
const hex2 = (n: number) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0")
|
|
101
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Alpha quantised to the one precision every format can carry, a byte, so hex,
|
|
112
|
+
* rgb and hsl agree on whether a colour is opaque: 0.996 used to print as an
|
|
113
|
+
* opaque rgb() and as "#…fe" in hex.
|
|
114
|
+
*/
|
|
115
|
+
function alphaByte(a: number): number {
|
|
116
|
+
return Math.round(clamp(a, 0, 1) * 255)
|
|
117
|
+
}
|
|
118
|
+
|
|
102
119
|
/** "#rrggbb", or "#rrggbbaa" when the colour is not fully opaque. */
|
|
103
120
|
export function rgbToHex({ r, g, b, a }: Rgb): string {
|
|
104
121
|
const base = `#${hex2(r)}${hex2(g)}${hex2(b)}`
|
|
105
|
-
|
|
122
|
+
const byte = alphaByte(a)
|
|
123
|
+
return byte >= 255 ? base : base + hex2(byte)
|
|
106
124
|
}
|
|
107
125
|
|
|
108
126
|
/**
|
|
@@ -199,20 +217,23 @@ export function hsvToHsl({ h, s, v }: Hsv): { h: number; s: number; l: number }
|
|
|
199
217
|
/** The colour as a CSS string in the requested format. */
|
|
200
218
|
export function formatColor(c: Hsv, format: ColorFormat): string {
|
|
201
219
|
const rgb = hsvToRgb(c)
|
|
202
|
-
const
|
|
220
|
+
const byte = alphaByte(rgb.a)
|
|
221
|
+
const a = Math.round((byte / 255) * 1000) / 1000
|
|
203
222
|
|
|
204
223
|
if (format === "hex") return rgbToHex(rgb)
|
|
205
224
|
if (format === "rgb") {
|
|
206
|
-
return
|
|
225
|
+
return byte >= 255
|
|
207
226
|
? `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`
|
|
208
227
|
: `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a})`
|
|
209
228
|
}
|
|
210
229
|
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
|
|
230
|
+
// One decimal: whole percentages drift up to 4 per channel on the way back.
|
|
231
|
+
const { h, s, l } = hsvToHsl({ ...c, h: finite(c.h), s: clamp(finite(c.s), 0, 1), v: clamp(finite(c.v), 0, 1) })
|
|
232
|
+
const dec = (n: number) => Math.round(n * 10) / 10
|
|
233
|
+
const hh = dec(h) % 360
|
|
234
|
+
const ss = dec(s * 100)
|
|
235
|
+
const ll = dec(l * 100)
|
|
236
|
+
return byte >= 255 ? `hsl(${hh}, ${ss}%, ${ll}%)` : `hsla(${hh}, ${ss}%, ${ll}%, ${a})`
|
|
216
237
|
}
|
|
217
238
|
|
|
218
239
|
/** Always opaque — for the swatch behind an alpha checkerboard, and for rails. */
|
|
@@ -12,14 +12,6 @@ export interface CommandSearchTerms {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const QUERY_TOKEN = /(-?)(?:(in):)?(?:"([^"]*)"|(\S+))/giu
|
|
15
|
-
const LATIN = "qwertyuiop[]asdfghjkl;'zxcvbnm,.`"
|
|
16
|
-
const CYRILLIC = "\u0439\u0446\u0443\u043A\u0435\u043D\u0433\u0448\u0449\u0437\u0445\u044A\u0444\u044B\u0432\u0430\u043F\u0440\u043E\u043B\u0434\u0436\u044D\u044F\u0447\u0441\u043C\u0438\u0442\u044C\u0431\u044E\u0451"
|
|
17
|
-
const SWAP = new Map<string, string>()
|
|
18
|
-
|
|
19
|
-
for (let i = 0; i < LATIN.length; i++) {
|
|
20
|
-
SWAP.set(LATIN[i]!, CYRILLIC[i]!)
|
|
21
|
-
SWAP.set(CYRILLIC[i]!, LATIN[i]!)
|
|
22
|
-
}
|
|
23
15
|
|
|
24
16
|
export function parseCommandQuery(value: string): CommandSearchTerms {
|
|
25
17
|
const out: CommandSearchTerms = { include: [], exclude: [], groups: [] }
|
|
@@ -34,15 +26,6 @@ export function parseCommandQuery(value: string): CommandSearchTerms {
|
|
|
34
26
|
return out
|
|
35
27
|
}
|
|
36
28
|
|
|
37
|
-
export function swapCommandTerms(terms: CommandSearchTerms): CommandSearchTerms {
|
|
38
|
-
const swap = (term: string) => [...term].map((ch) => SWAP.get(ch) ?? ch).join("")
|
|
39
|
-
return {
|
|
40
|
-
include: terms.include.map(swap),
|
|
41
|
-
exclude: terms.exclude.map(swap),
|
|
42
|
-
groups: terms.groups.map(swap),
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
29
|
function matches(command: SearchableCommand, term: string): boolean {
|
|
47
30
|
return (
|
|
48
31
|
command.label.toLowerCase().includes(term)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { reactive } from "vue"
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Imperative confirmation: `if (await confirm({ title: "Delete?" })) ...`.
|
|
5
|
+
* Requests queue here and the one mounted LpConfirmHost shows them one at a
|
|
6
|
+
* time. Pinned to a global symbol like the toast queue, so a barrel and a deep
|
|
7
|
+
* import share one host.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface ConfirmOptions {
|
|
11
|
+
title: string
|
|
12
|
+
description?: string
|
|
13
|
+
danger?: boolean
|
|
14
|
+
icon?: string
|
|
15
|
+
confirmLabel?: string
|
|
16
|
+
cancelLabel?: string
|
|
17
|
+
loadingLabel?: string
|
|
18
|
+
/**
|
|
19
|
+
* Work to run on confirm. The dialog stays open with a loading state while it
|
|
20
|
+
* runs and shows the error's message if it rejects; the promise from
|
|
21
|
+
* `confirm()` resolves true only once it succeeded.
|
|
22
|
+
*/
|
|
23
|
+
action?: () => unknown
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ConfirmRequest extends ConfirmOptions {
|
|
27
|
+
id: number
|
|
28
|
+
resolve: (confirmed: boolean) => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const STATE_KEY = Symbol.for("@leavepulse/ui:confirm-queue")
|
|
32
|
+
type GlobalWithConfirm = typeof globalThis & {
|
|
33
|
+
[STATE_KEY]?: { queue: ConfirmRequest[]; nextId: number }
|
|
34
|
+
}
|
|
35
|
+
const g = globalThis as GlobalWithConfirm
|
|
36
|
+
const state = (g[STATE_KEY] ??= { queue: reactive<ConfirmRequest[]>([]), nextId: 0 })
|
|
37
|
+
|
|
38
|
+
export const confirmQueue = state.queue
|
|
39
|
+
|
|
40
|
+
/** Ask for confirmation. Resolves false when the user cancels or dismisses. */
|
|
41
|
+
export function confirm(options: ConfirmOptions): Promise<boolean> {
|
|
42
|
+
if (typeof window === "undefined") return Promise.resolve(false)
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
state.queue.push({ ...options, id: state.nextId++, resolve })
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Settle a request and drop it from the queue. Used by the host. */
|
|
49
|
+
export function settleConfirm(id: number, confirmed: boolean): void {
|
|
50
|
+
const i = state.queue.findIndex((r) => r.id === id)
|
|
51
|
+
if (i === -1) return
|
|
52
|
+
const [request] = state.queue.splice(i, 1)
|
|
53
|
+
request?.resolve(confirmed)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function useConfirm(): typeof confirm {
|
|
57
|
+
return confirm
|
|
58
|
+
}
|
|
@@ -65,3 +65,15 @@ export function matchCountryByValue(
|
|
|
65
65
|
}
|
|
66
66
|
return best
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Countries matching a search query: by name, or by dial code when the query
|
|
71
|
+
* carries digits ("+44", "44"). A query without digits must not match on the
|
|
72
|
+
* dial code at all, or every country would match the empty string.
|
|
73
|
+
*/
|
|
74
|
+
export function filterCountries(list: Country[], query: string): Country[] {
|
|
75
|
+
const q = query.trim().toLowerCase()
|
|
76
|
+
if (!q) return list
|
|
77
|
+
const digits = q.replace(/\D/g, "")
|
|
78
|
+
return list.filter((c) => c.name.toLowerCase().includes(q) || (digits !== "" && c.dial.includes(digits)))
|
|
79
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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,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
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { computed, inject, provide, useId, type ComputedRef, type InjectionKey } from "vue"
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* What a form field tells the control inside it: the id its label points at,
|
|
5
|
+
* the id of the hint/error line the control should be described by, and the
|
|
6
|
+
* states the control should reflect. LpFormField provides it; every kit control
|
|
7
|
+
* consumes it, and so can a consumer's own control placed inside a field.
|
|
8
|
+
*/
|
|
9
|
+
export interface FieldContext {
|
|
10
|
+
id: string
|
|
11
|
+
/** Id of the visible label, for controls a `<label for>` cannot name (groups). */
|
|
12
|
+
labelId?: string
|
|
13
|
+
describedBy?: string
|
|
14
|
+
invalid: boolean
|
|
15
|
+
required: boolean
|
|
16
|
+
disabled: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const LP_FIELD: InjectionKey<ComputedRef<FieldContext>> = Symbol.for("lp-field")
|
|
20
|
+
|
|
21
|
+
export function provideField(context: ComputedRef<FieldContext>): void {
|
|
22
|
+
provide(LP_FIELD, context)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The nearest LpFormField's context, or null outside one. */
|
|
26
|
+
export function useField(): ComputedRef<FieldContext> | null {
|
|
27
|
+
return inject(LP_FIELD, null)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function fieldMessageId(id: string): string {
|
|
31
|
+
return `${id}-message`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface FieldControlState {
|
|
35
|
+
/** The control wraps itself in its own LpFormField (it has a label, hint or error prop). */
|
|
36
|
+
wrapped: boolean
|
|
37
|
+
/** That own field renders a hint or error line. */
|
|
38
|
+
hasMessage?: boolean
|
|
39
|
+
/** An id the consumer set on the control, which must win over a generated one. */
|
|
40
|
+
id?: string
|
|
41
|
+
invalid?: boolean
|
|
42
|
+
required?: boolean
|
|
43
|
+
disabled?: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The field state a control renders with: its own wrapping field when it has
|
|
48
|
+
* one, otherwise the field around it, with the control's own flags or-ed in.
|
|
49
|
+
* `id` is undefined only for a bare control outside any field.
|
|
50
|
+
*/
|
|
51
|
+
export function useFieldControl(
|
|
52
|
+
own: () => FieldControlState,
|
|
53
|
+
): ComputedRef<Omit<FieldContext, "id"> & { id?: string }> {
|
|
54
|
+
const outer = useField()
|
|
55
|
+
const generated = useId()
|
|
56
|
+
return computed(() => {
|
|
57
|
+
const state = own()
|
|
58
|
+
if (state.wrapped) {
|
|
59
|
+
const id = state.id ?? generated
|
|
60
|
+
return {
|
|
61
|
+
id,
|
|
62
|
+
labelId: undefined,
|
|
63
|
+
describedBy: state.hasMessage ? fieldMessageId(id) : undefined,
|
|
64
|
+
invalid: !!state.invalid,
|
|
65
|
+
required: !!state.required,
|
|
66
|
+
disabled: !!state.disabled,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const field = outer?.value
|
|
70
|
+
return {
|
|
71
|
+
id: state.id ?? field?.id,
|
|
72
|
+
labelId: field?.labelId,
|
|
73
|
+
describedBy: field?.describedBy,
|
|
74
|
+
invalid: !!state.invalid || !!field?.invalid,
|
|
75
|
+
required: !!state.required || !!field?.required,
|
|
76
|
+
disabled: !!state.disabled || !!field?.disabled,
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
}
|