@leavepulse/ui 0.69.0 → 0.70.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 +37 -0
- package/dist/assets/LoaderMark-DxBV2iDJ.css +43 -0
- package/dist/assets/LpActivityLine-B3EwNQEB.css +33 -0
- package/dist/assets/{LpCommandPalette-DxyjhYn_.css → LpCommandPalette-u-zOxKWa.css} +21 -31
- package/dist/assets/LpDock-Cbh0wQUv.css +17 -0
- package/dist/assets/{LpDropzone-Qb8k7cg4.css → LpDropzone-Bb1vBTeU.css} +12 -17
- package/dist/assets/{LpImageEditor-CElPDzpF.css → LpImageEditor-XM5bAziW.css} +6 -6
- package/dist/assets/LpLogViewer-BDNW6P52.css +38 -0
- package/dist/assets/{LpPalettePreview-CJmAKgYW.css → LpPalettePreview-B9VKDrvA.css} +2 -7
- package/dist/assets/LpShinyText-D69cBYDR.css +61 -0
- package/dist/assets/LpStatus-430OGTZY.css +17 -0
- package/dist/assets/LpStepMark-Drvx33Vg.css +10 -0
- package/dist/assets/{LpTable-BPERfJIk.css → LpTable-DHxEQ_-3.css} +19 -19
- package/dist/assets/LpTabs-BJ-nrJuI.css +18 -0
- package/dist/assets/LpTextReveal-DjI1MsDS.css +35 -0
- package/dist/assets/LpTypewriter-CXAm9Nca.css +27 -0
- package/dist/chunks/AttentionDot-DCBf4aWg.js +25 -0
- package/dist/chunks/{ContextMenuItems-B7XFHksm.js → ContextMenuItems-B5uE8Znw.js} +1 -1
- package/dist/chunks/{DropzoneLinkField-CjkrpDqO.js → DropzoneLinkField-Bw8Flkhk.js} +2 -2
- package/dist/chunks/FieldWrapper-BiennROB.js +43 -0
- package/dist/chunks/{LayoutCanvas-DF9CovaK.js → LayoutCanvas-BsJIzhdd.js} +1 -1
- package/dist/chunks/{LayoutNode-D9bIFzk0.js → LayoutNode-DJVhkSGk.js} +6 -8
- package/dist/chunks/LegendSwatch-BhRjrtzv.js +18 -0
- package/dist/chunks/LoaderMark-C1dubiKn.js +85 -0
- package/dist/chunks/{LpActionBar-MPKMu3pz.js → LpActionBar-CzOFar5t.js} +14 -20
- package/dist/chunks/LpActivityLine-CJvzrNpj.js +95 -0
- package/dist/chunks/{LpAddressInput-DG0BTqdH.js → LpAddressInput-CNnLTDZg.js} +7 -7
- package/dist/chunks/LpAlert-C5igj802.js +119 -0
- package/dist/chunks/{LpAppShell-DPh1756q.js → LpAppShell-CEUAB8kK.js} +15 -16
- package/dist/chunks/{LpAutocomplete-Dgn6hw3p.js → LpAutocomplete-D92niDHE.js} +15 -32
- package/dist/chunks/{LpAvatar-Bs0Fb1lC.js → LpAvatar-CLPl-qOF.js} +18 -6
- package/dist/chunks/LpBadge-HOEPBYmi.js +126 -0
- package/dist/chunks/LpBreadcrumbs-DYoPc3P4.js +144 -0
- package/dist/chunks/{LpButton-DNR-YAq5.js → LpButton-CVxBfGjd.js} +12 -4
- package/dist/chunks/{LpCalendar-BLya39Fh.js → LpCalendar-pVL91-Pl.js} +1 -1
- package/dist/chunks/{LpCard-Bf71NXZq.js → LpCard-CqKLnEhl.js} +18 -8
- package/dist/chunks/{LpCheckbox-Da5oZPHo.js → LpCheckbox-DHc2bHDe.js} +9 -6
- package/dist/chunks/{LpCodeBlock-lLbmslfs.js → LpCodeBlock-BGsQhxw8.js} +24 -32
- package/dist/chunks/LpCodeDisplay-CIKKT2iW.js +189 -0
- package/dist/chunks/LpCollapse-CWS9M_lm.js +115 -0
- package/dist/chunks/{LpColorPicker-DtEfBAcK.js → LpColorPicker-DFugXNUi.js} +10 -5
- package/dist/chunks/{LpConfirmDialog-D7s4jakB.js → LpConfirmDialog-CfGCux8m.js} +3 -3
- package/dist/chunks/{LpConfirmHost-DrYI5y1K.js → LpConfirmHost-exB7JmcO.js} +1 -1
- package/dist/chunks/{LpContextMenu-CRnjoqSt.js → LpContextMenu-COwOwRgI.js} +13 -5
- package/dist/chunks/LpCopyButton-DzHjnFs0.js +133 -0
- package/dist/chunks/LpCountdown-Dtq1ihlJ.js +231 -0
- package/dist/chunks/LpDataList-B-5nHfNw.js +120 -0
- package/dist/chunks/LpDatePicker-BUe1VUC0.js +168 -0
- package/dist/chunks/LpDateRangePicker-C_k65vGR.js +126 -0
- package/dist/chunks/{LpDisclosure-6AplYBub.js → LpDisclosure-DOarzlAG.js} +22 -11
- package/dist/chunks/{LpDrawer-Q4SOxM4s.js → LpDrawer-DGNKRsGq.js} +9 -4
- package/dist/chunks/{LpDropdownMenu-DWTt0ooM.js → LpDropdownMenu-CRgrhdAH.js} +10 -5
- package/dist/chunks/LpEditable-DTqLt2lT.js +236 -0
- package/dist/chunks/{LpEmptyState-om1UnFjV.js → LpEmptyState-KTKipws2.js} +1 -1
- package/dist/chunks/{LpFieldNotice-CHuDeLjc.js → LpFieldNotice-CurbNuXI.js} +1 -1
- package/dist/chunks/{LpFileTree-CW24YsVX.js → LpFileTree-DMISXVHC.js} +5 -3
- package/dist/chunks/{LpFileTreeNode-BXhTHaKW.js → LpFileTreeNode-CPXB_Yy3.js} +42 -55
- package/dist/chunks/{LpFormField-DPb47HEm.js → LpFormField-BWwgm1E4.js} +23 -9
- package/dist/chunks/LpFrame-BVQCQLDI.js +155 -0
- package/dist/chunks/LpGauge-Bk8TwJj0.js +204 -0
- package/dist/chunks/LpGlowBorder-B8V-QzIl.js +40 -0
- package/dist/chunks/{LpHintExample-C1eKYdeL.js → LpHintExample-9Py0wc-L.js} +11 -34
- package/dist/chunks/{LpHintLayer-7UOdWQG3.js → LpHintLayer-p4hqzdyA.js} +27 -14
- package/dist/chunks/{LpHints-BUtBp9-w.js → LpHints-CCrhRfy9.js} +3 -3
- package/dist/chunks/LpHoverCard-spGxyw7T.js +130 -0
- package/dist/chunks/{LpIcon-6tSrZ3Zy.js → LpIcon-HJP5xMz4.js} +1 -1
- package/dist/chunks/LpIdenticon-BzdC_qz9.js +63 -0
- package/dist/chunks/{LpInfraNode-Ds_8SZgN.js → LpInfraNode-CUC3t4na.js} +39 -25
- package/dist/chunks/{LpInput-CUyr6PIE.js → LpInput-CDuwODJG.js} +44 -18
- package/dist/chunks/{LpLightbox-B9J8rzsj.js → LpLightbox-XJ0LpTcZ.js} +69 -69
- package/dist/chunks/LpLoader-DjJEX8hL.js +92 -0
- package/dist/chunks/{LpLogViewer-4QFc-sVN.js → LpLogViewer-elZHbj85.js} +225 -129
- package/dist/chunks/LpMagnetic-CtXKLa2Y.js +62 -0
- package/dist/chunks/{LpMapPicker-BTbkFeSG.js → LpMapPicker-BtIl7NOM.js} +34 -31
- package/dist/chunks/LpMasonry--k0571qG.js +185 -0
- package/dist/chunks/{LpMediaGallery-CuPcscx3.js → LpMediaGallery-D5swlmZ_.js} +3 -3
- package/dist/chunks/{LpModal-hk1noDcr.js → LpModal-eBMt2Fqn.js} +8 -3
- package/dist/chunks/{LpModalNav-DQd2keZm.js → LpModalNav-D4Z0wVHR.js} +4 -4
- package/dist/chunks/{LpNotificationBell-X84PJIxD.js → LpNotificationBell-DPZmP5dM.js} +32 -48
- package/dist/chunks/LpNumberField-B9k-h2HE.js +140 -0
- package/dist/chunks/{LpNumberFlow-z4WPZcZu.js → LpNumberFlow-Cfyh033Q.js} +8 -28
- package/dist/chunks/{LpOtpInput-PbeNJ5In.js → LpOtpInput-CeBPsM7D.js} +2 -1
- package/dist/chunks/{LpPagination-Bx-VG4Ag.js → LpPagination-VNCX6pFC.js} +37 -6
- package/dist/chunks/{LpPalettePreview-CFav_7co.js → LpPalettePreview-BNB8T_MY.js} +1 -1
- package/dist/chunks/{LpPasswordInput-CbnXitAS.js → LpPasswordInput-DTV18FIh.js} +20 -20
- package/dist/chunks/{LpPhoneInput-DP69HONS.js → LpPhoneInput-C60tx1tC.js} +12 -7
- package/dist/chunks/{LpPie-yLF9-aDR.js → LpPie-CFuV7pZv.js} +158 -47
- package/dist/chunks/LpPipeline-D81yh_NX.js +329 -0
- package/dist/chunks/{LpPopover-DVnkHaOu.js → LpPopover-BEIcqb9p.js} +8 -3
- package/dist/chunks/{LpProgress-CA86wxXu.js → LpProgress-CE-m5OpD.js} +23 -63
- package/dist/chunks/LpQuota-2Sw_gqcJ.js +162 -0
- package/dist/chunks/{LpRadioGroup-8S5hf8lE.js → LpRadioGroup-CGBr5nIp.js} +1 -1
- package/dist/chunks/{LpRangeCalendar-ByQi-WUz.js → LpRangeCalendar-c5qj7YcC.js} +1 -1
- package/dist/chunks/{LpRangeSlider-C1yF9-nP.js → LpRangeSlider-Dv6lNvHY.js} +6 -2
- package/dist/chunks/LpRelativeTime-CKzVqAo9.js +73 -0
- package/dist/chunks/LpRepeater-D0VjmD6y.js +140 -0
- package/dist/chunks/LpRevealOnScroll-DcQjexLy.js +76 -0
- package/dist/chunks/LpScramble-TDO5Kdou.js +85 -0
- package/dist/chunks/LpSecretField-DMfdJ1cQ.js +307 -0
- package/dist/chunks/{LpSection-BmRXe6ZP.js → LpSection-BW4xJ-Li.js} +1 -1
- package/dist/chunks/{LpSegmented-CW3bFHM4.js → LpSegmented-BO99Sr4m.js} +11 -14
- package/dist/chunks/{LpSelect-BCyDYoih.js → LpSelect-B4f_yRi5.js} +93 -108
- package/dist/chunks/LpShaderBackdrop-Bote0iuy.js +332 -0
- package/dist/chunks/LpShinyText-DX2ajtWq.js +48 -0
- package/dist/chunks/{LpSidebar-DfIiS-lB.js → LpSidebar-D-M0Y_7U.js} +5 -3
- package/dist/chunks/{LpSidebarNav-C5FDNI0t.js → LpSidebarNav-DF-TYxOV.js} +3 -3
- package/dist/chunks/{LpSlider-Cuzmrd7p.js → LpSlider-DuoRpOrH.js} +6 -2
- package/dist/chunks/{LpSparkline-DXT4KrSi.js → LpSparkline-CExIRDb3.js} +32 -27
- package/dist/chunks/LpSpotlight-4F3NeUMY.js +41 -0
- package/dist/chunks/{LpStat-iBMF5QNk.js → LpStat-B2ix1Q7t.js} +38 -35
- package/dist/chunks/LpStatus-C7kx5PKE.js +86 -0
- package/dist/chunks/LpStepMark-DK-5BKAt.js +101 -0
- package/dist/chunks/LpStepper-DRFfdJaq.js +76 -0
- package/dist/chunks/{LpSwap-D82jiecV.js → LpSwap-DzE6M3Mg.js} +1 -1
- package/dist/chunks/LpSwitch-DrzKrZZV.js +112 -0
- package/dist/chunks/{LpTableFilter-CuIbgdW0.js → LpTableFilter-DyvHGbOk.js} +3 -2
- package/dist/chunks/{LpTableOfContents-C_4VBktT.js → LpTableOfContents-rYvo8z5T.js} +34 -21
- package/dist/chunks/{LpTagsInput-DiXrpYeE.js → LpTagsInput-DgpBFRV2.js} +68 -71
- package/dist/chunks/LpTextReveal-CtAnOUkp.js +140 -0
- package/dist/chunks/{LpTextarea-xoQd5Lnp.js → LpTextarea-DiWjeVoJ.js} +12 -11
- package/dist/chunks/{LpThemeProvider-CQ2jC9TA.js → LpThemeProvider-DcbfOxsU.js} +1 -1
- package/dist/chunks/LpThemeStudio-DguwHdGt.js +416 -0
- package/dist/chunks/LpThemeStudioSection-BwhqYDLf.js +20 -0
- package/dist/chunks/LpThemeSwitcher-D5uz0MhK.js +278 -0
- package/dist/chunks/{LpTimeField-MFgAsNc5.js → LpTimeField-BFQVf_PW.js} +13 -5
- package/dist/chunks/{LpToaster-C4zE9zB7.js → LpToaster-Wv9ksj4q.js} +49 -82
- package/dist/chunks/{LpTooltip-suFCM34F.js → LpTooltip-Cz8SQwRt.js} +8 -3
- package/dist/chunks/{LpTopologyCanvas-6ozNrQKh.js → LpTopologyCanvas-ZhKthfgz.js} +1 -1
- package/dist/chunks/{LpTour-BUp3Jt2e.js → LpTour-NfRJNEIP.js} +24 -18
- package/dist/chunks/LpTruncate-D_5Pfv5B.js +112 -0
- package/dist/chunks/LpTypewriter-BCL_a6wE.js +159 -0
- package/dist/chunks/{LpUptimeBar-B6kHw4Cj.js → LpUptimeBar-DWegV2p6.js} +9 -8
- package/dist/chunks/{LpVideoBackdrop-qWRYBOTQ.js → LpVideoBackdrop-DS1wGv-X.js} +7 -8
- package/dist/chunks/{MenuTitle-obgek0B7.js → MenuTitle-BjQZX8tQ.js} +1 -1
- package/dist/chunks/OptionContent-CmYUUeWj.js +40 -0
- package/dist/chunks/PickerField-CHRS1h7K.js +116 -0
- package/dist/chunks/RevealToggle-C-KzaQGJ.js +65 -0
- package/dist/chunks/StepConnector-37OgEOXA.js +41 -0
- package/dist/chunks/TourCard-BXuVHsfn.js +123 -0
- package/dist/chunks/backdrop-Dkf2vM81.js +59 -0
- package/dist/chunks/{icons-DVhZ_plB.js → icons-4IywzkHk.js} +1 -1
- package/dist/chunks/{useTheme-2qHG8NEU.js → useTheme-BOV_-AiY.js} +9 -65
- package/dist/component-names.d.ts +1 -1
- package/dist/components/AttentionDot.vue.d.ts +9 -0
- package/dist/components/AttentionDot.vue.js +4 -0
- package/dist/components/ContextMenuItems.vue.js +1 -1
- package/dist/components/DropzoneLinkField.vue.js +1 -1
- package/dist/components/FieldWrapper.vue.d.ts +25 -0
- package/dist/components/FieldWrapper.vue.js +4 -0
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.d.ts +4 -4
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LegendSwatch.vue.d.ts +6 -0
- package/dist/components/LegendSwatch.vue.js +4 -0
- package/dist/components/LoaderMark.vue.d.ts +12 -0
- package/dist/components/LoaderMark.vue.js +4 -0
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpActivityLine.vue.d.ts +58 -0
- package/dist/components/LpActivityLine.vue.js +4 -0
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.d.ts +2 -2
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +8 -7
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.d.ts +16 -0
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.d.ts +19 -5
- package/dist/components/LpBadge.vue.js +1 -1
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +2 -2
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpCard.vue.d.ts +24 -0
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.js +107 -138
- package/dist/components/LpCheckbox.vue.d.ts +2 -2
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpCodeDisplay.vue.d.ts +89 -0
- package/dist/components/LpCodeDisplay.vue.js +4 -0
- package/dist/components/LpCollapse.vue.d.ts +58 -0
- package/dist/components/LpCollapse.vue.js +4 -0
- package/dist/components/LpColorPicker.vue.d.ts +2 -2
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +34 -23
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.js +1 -1
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpCopyButton.vue.d.ts +82 -0
- package/dist/components/LpCopyButton.vue.js +4 -0
- package/dist/components/LpCountdown.vue.d.ts +100 -0
- package/dist/components/LpCountdown.vue.js +4 -0
- package/dist/components/LpDataList.vue.d.ts +66 -0
- package/dist/components/LpDataList.vue.js +4 -0
- package/dist/components/LpDatePicker.vue.d.ts +1 -1
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.d.ts +1 -1
- package/dist/components/LpDateRangePicker.vue.js +1 -1
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDock.vue.d.ts +74 -0
- package/dist/components/LpDock.vue.js +204 -0
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.d.ts +8 -1
- package/dist/components/LpDropzone.vue.js +36 -15
- package/dist/components/LpEditable.vue.d.ts +85 -0
- package/dist/components/LpEditable.vue.js +4 -0
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFieldNotice.vue.js +1 -1
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpFrame.vue.d.ts +22 -0
- package/dist/components/LpFrame.vue.js +4 -0
- package/dist/components/LpGauge.vue.d.ts +116 -0
- package/dist/components/LpGauge.vue.js +4 -0
- package/dist/components/LpGlowBorder.vue.d.ts +47 -0
- package/dist/components/LpGlowBorder.vue.js +4 -0
- package/dist/components/LpHintExample.vue.d.ts +2 -2
- package/dist/components/LpHintExample.vue.js +1 -1
- package/dist/components/LpHintLayer.vue.js +1 -1
- package/dist/components/LpHints.vue.js +1 -1
- package/dist/components/LpHoverCard.vue.d.ts +60 -0
- package/dist/components/LpHoverCard.vue.js +4 -0
- package/dist/components/LpIcon.vue.js +1 -1
- package/dist/components/LpIdenticon.vue.d.ts +37 -0
- package/dist/components/LpIdenticon.vue.js +4 -0
- package/dist/components/LpImageEditor.vue.js +13 -19
- package/dist/components/LpInfraNode.vue.js +1 -1
- package/dist/components/LpInput.vue.d.ts +10 -0
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLoader.vue.d.ts +106 -0
- package/dist/components/LpLoader.vue.js +4 -0
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMagnetic.vue.d.ts +28 -0
- package/dist/components/LpMagnetic.vue.js +4 -0
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMasonry.vue.d.ts +42 -0
- package/dist/components/LpMasonry.vue.js +4 -0
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +15 -6
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.d.ts +57 -0
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.d.ts +12 -4
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPaletteFacts.vue.js +1 -1
- package/dist/components/LpPaletteHeader.vue.js +2 -2
- package/dist/components/LpPalettePreview.vue.js +1 -1
- package/dist/components/LpPaletteStats.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.d.ts +12 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.d.ts +3 -1
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPipeline.vue.d.ts +91 -0
- package/dist/components/LpPipeline.vue.js +4 -0
- package/dist/components/LpPopover.vue.d.ts +2 -2
- package/dist/components/LpPopover.vue.js +1 -1
- package/dist/components/LpProgress.vue.d.ts +11 -4
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpQuota.vue.d.ts +68 -0
- package/dist/components/LpQuota.vue.js +4 -0
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeCalendar.vue.js +1 -1
- package/dist/components/LpRangeSlider.vue.js +1 -1
- package/dist/components/LpRelativeTime.vue.d.ts +41 -0
- package/dist/components/LpRelativeTime.vue.js +4 -0
- package/dist/components/LpRepeater.vue.d.ts +48 -0
- package/dist/components/LpRepeater.vue.js +4 -0
- package/dist/components/LpRevealOnScroll.vue.d.ts +67 -0
- package/dist/components/LpRevealOnScroll.vue.js +4 -0
- package/dist/components/LpRollingText.vue.d.ts +1 -1
- package/dist/components/LpScramble.vue.d.ts +55 -0
- package/dist/components/LpScramble.vue.js +4 -0
- package/dist/components/LpScrollArea.vue.d.ts +1 -1
- package/dist/components/LpSecretField.vue.d.ts +108 -0
- package/dist/components/LpSecretField.vue.js +4 -0
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.d.ts +2 -0
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShaderBackdrop.vue.d.ts +39 -0
- package/dist/components/LpShaderBackdrop.vue.js +4 -0
- package/dist/components/LpShinyText.vue.d.ts +39 -0
- package/dist/components/LpShinyText.vue.js +4 -0
- package/dist/components/LpShortcutsDialog.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpSparkline.vue.d.ts +4 -3
- package/dist/components/LpSparkline.vue.js +1 -1
- package/dist/components/LpSpotlight.vue.d.ts +37 -0
- package/dist/components/LpSpotlight.vue.js +4 -0
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpStatus.vue.d.ts +65 -0
- package/dist/components/LpStatus.vue.js +4 -0
- package/dist/components/LpStepMark.vue.d.ts +31 -0
- package/dist/components/LpStepMark.vue.js +4 -0
- package/dist/components/LpStepper.vue.js +1 -1
- package/dist/components/LpSwap.vue.js +1 -1
- package/dist/components/LpSwitch.vue.d.ts +2 -2
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.js +104 -120
- package/dist/components/LpTableFilter.vue.js +1 -1
- package/dist/components/LpTableOfContents.vue.d.ts +6 -1
- package/dist/components/LpTableOfContents.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +5 -5
- package/dist/components/LpTabs.vue.js +25 -33
- package/dist/components/LpTagsInput.vue.js +1 -1
- package/dist/components/LpTextReveal.vue.d.ts +58 -0
- package/dist/components/LpTextReveal.vue.js +4 -0
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.js +1 -1
- package/dist/components/LpThemeStudio.vue.d.ts +54 -0
- package/dist/components/LpThemeStudio.vue.js +4 -0
- package/dist/components/LpThemeStudioSection.vue.d.ts +16 -0
- package/dist/components/LpThemeStudioSection.vue.js +4 -0
- package/dist/components/LpThemeSwitcher.vue.d.ts +32 -1
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTimeField.vue.d.ts +2 -0
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.d.ts +2 -2
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpTour.vue.js +1 -1
- package/dist/components/LpTruncate.vue.d.ts +34 -0
- package/dist/components/LpTruncate.vue.js +4 -0
- package/dist/components/LpTypewriter.vue.d.ts +61 -0
- package/dist/components/LpTypewriter.vue.js +4 -0
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/LpVideoBackdrop.vue.js +1 -1
- package/dist/components/MenuTitle.vue.js +1 -1
- package/dist/components/OptionContent.vue.d.ts +7 -0
- package/dist/components/OptionContent.vue.js +4 -0
- package/dist/components/PickerField.vue.d.ts +43 -0
- package/dist/components/PickerField.vue.js +4 -0
- package/dist/components/RevealToggle.vue.d.ts +21 -0
- package/dist/components/RevealToggle.vue.js +4 -0
- package/dist/components/StepConnector.vue.d.ts +19 -0
- package/dist/components/StepConnector.vue.js +4 -0
- package/dist/components/TourCard.vue.js +1 -1
- package/dist/components/badge.d.ts +1 -0
- package/dist/components/badge.js +4 -0
- package/dist/components/chart.d.ts +5 -1
- package/dist/components/chart.js +2 -21
- package/dist/components/dateRange.d.ts +11 -0
- package/dist/components/dateRange.js +12 -0
- package/dist/components/dock.d.ts +19 -0
- package/dist/components/dock.js +16 -0
- package/dist/components/dropdown.d.ts +10 -1
- package/dist/components/dropdown.js +7 -2
- package/dist/components/fieldText.d.ts +38 -0
- package/dist/components/fieldText.js +53 -0
- package/dist/components/fileTree.d.ts +7 -3
- package/dist/components/fileTree.js +12 -18
- package/dist/components/format.d.ts +41 -0
- package/dist/components/format.js +67 -0
- package/dist/components/identicon.d.ts +38 -0
- package/dist/components/identicon.js +85 -0
- package/dist/components/masonry.d.ts +32 -0
- package/dist/components/masonry.js +40 -0
- package/dist/components/menuWhen.js +1 -1
- package/dist/components/meter.d.ts +64 -0
- package/dist/components/meter.js +53 -0
- package/dist/components/otp.d.ts +1 -1
- package/dist/components/otp.js +7 -2
- package/dist/components/pipeline.d.ts +55 -0
- package/dist/components/pipeline.js +51 -0
- package/dist/components/quota.d.ts +57 -0
- package/dist/components/quota.js +40 -0
- package/dist/components/relativeTime.d.ts +24 -0
- package/dist/components/relativeTime.js +56 -0
- package/dist/components/reveal.d.ts +18 -0
- package/dist/components/reveal.js +32 -0
- package/dist/components/scramble.d.ts +68 -0
- package/dist/components/scramble.js +58 -0
- package/dist/components/shaderBackdrop.d.ts +20 -0
- package/dist/components/shaderBackdrop.js +156 -0
- package/dist/components/slider.d.ts +5 -0
- package/dist/components/slider.js +24 -0
- package/dist/components/slotText.d.ts +2 -0
- package/dist/components/slotText.js +9 -0
- package/dist/components/status.d.ts +14 -0
- package/dist/components/status.js +22 -0
- package/dist/components/tableFacets.d.ts +8 -0
- package/dist/components/tableFacets.js +19 -0
- package/dist/components/textSplit.d.ts +17 -0
- package/dist/components/textSplit.js +24 -0
- package/dist/components/timeSpan.d.ts +78 -0
- package/dist/components/timeSpan.js +85 -0
- package/dist/components/tone.d.ts +43 -0
- package/dist/components/tone.js +82 -0
- package/dist/components/tour.js +3 -2
- package/dist/components/typewriter.d.ts +36 -0
- package/dist/components/typewriter.js +53 -0
- package/dist/components/videoBackdrop.js +5 -6
- package/dist/composables/easing.d.ts +22 -1
- package/dist/composables/easing.js +42 -1
- package/dist/composables/pseudoLocale.d.ts +15 -0
- package/dist/composables/pseudoLocale.js +96 -0
- package/dist/composables/scroll.js +5 -3
- package/dist/composables/useClipboard.d.ts +4 -1
- package/dist/composables/useClipboard.js +12 -2
- package/dist/composables/useField.d.ts +8 -0
- package/dist/composables/useField.js +5 -0
- package/dist/composables/useForm.js +19 -5
- package/dist/composables/useInView.d.ts +11 -0
- package/dist/composables/useInView.js +39 -0
- package/dist/composables/useLoadingGate.d.ts +8 -0
- package/dist/composables/useLoadingGate.js +38 -0
- package/dist/composables/useLpConfig.d.ts +10 -0
- package/dist/composables/useLpConfig.js +11 -1
- package/dist/composables/useMediaQuery.js +3 -1
- package/dist/composables/useNow.d.ts +16 -0
- package/dist/composables/useNow.js +39 -0
- package/dist/composables/usePersisted.js +14 -7
- package/dist/composables/usePillTransition.d.ts +5 -2
- package/dist/composables/usePillTransition.js +4 -3
- package/dist/composables/usePointerFrame.d.ts +18 -0
- package/dist/composables/usePointerFrame.js +31 -0
- package/dist/composables/useReducedMotion.d.ts +2 -0
- package/dist/composables/useReducedMotion.js +7 -0
- package/dist/composables/useRepeater.d.ts +17 -0
- package/dist/composables/useRepeater.js +31 -0
- package/dist/composables/useResizeObserver.d.ts +8 -0
- package/dist/composables/useResizeObserver.js +33 -0
- package/dist/composables/useRevealedValue.d.ts +31 -0
- package/dist/composables/useRevealedValue.js +26 -0
- package/dist/composables/useScramble.d.ts +42 -0
- package/dist/composables/useScramble.js +76 -0
- package/dist/composables/useShift.js +2 -10
- package/dist/composables/useSpotlight.d.ts +18 -0
- package/dist/composables/useSpotlight.js +53 -0
- package/dist/composables/useTilt.js +26 -18
- package/dist/composables/useTween.d.ts +43 -0
- package/dist/composables/useTween.js +69 -0
- package/dist/composables/useView.d.ts +18 -0
- package/dist/composables/useView.js +61 -0
- package/dist/icons-all.js +1 -1
- package/dist/index.d.ts +98 -1
- package/dist/index.js +262 -104
- package/dist/theme/define.d.ts +12 -0
- package/dist/theme/studio.d.ts +90 -0
- package/package.json +5 -2
- package/src/component-names.ts +33 -0
- package/src/components/AttentionDot.vue +27 -0
- package/src/components/FieldWrapper.vue +38 -0
- package/src/components/LayoutNode.vue +5 -9
- package/src/components/LegendSwatch.vue +15 -0
- package/src/components/LoaderMark.vue +90 -0
- package/src/components/LpActionBar.vue +12 -20
- package/src/components/LpActivityLine.vue +186 -0
- package/src/components/LpAlert.vue +54 -24
- package/src/components/LpAppShell.vue +7 -11
- package/src/components/LpAutocomplete.vue +16 -14
- package/src/components/LpAvatar.vue +17 -2
- package/src/components/LpBadge.vue +77 -13
- package/src/components/LpBreadcrumbs.vue +101 -35
- package/src/components/LpButton.vue +15 -3
- package/src/components/LpCard.vue +30 -7
- package/src/components/LpChart.vue +143 -154
- package/src/components/LpCheckbox.vue +10 -4
- package/src/components/LpCodeBlock.vue +11 -19
- package/src/components/LpCodeDisplay.vue +228 -0
- package/src/components/LpCollapse.vue +162 -0
- package/src/components/LpColorPicker.vue +10 -1
- package/src/components/LpCommandPalette.vue +38 -24
- package/src/components/LpContextMenu.vue +10 -1
- package/src/components/LpCopyButton.vue +185 -0
- package/src/components/LpCountdown.vue +308 -0
- package/src/components/LpDataList.vue +154 -0
- package/src/components/LpDatePicker.vue +53 -104
- package/src/components/LpDateRangePicker.vue +49 -95
- package/src/components/LpDisclosure.vue +58 -36
- package/src/components/LpDock.vue +274 -0
- package/src/components/LpDrawer.vue +10 -1
- package/src/components/LpDropdownMenu.vue +10 -1
- package/src/components/LpDropzone.vue +34 -13
- package/src/components/LpEditable.vue +293 -0
- package/src/components/LpFileTree.vue +5 -1
- package/src/components/LpFileTreeNode.vue +25 -50
- package/src/components/LpFormField.vue +22 -3
- package/src/components/LpFrame.vue +200 -0
- package/src/components/LpGauge.vue +287 -0
- package/src/components/LpGlowBorder.vue +60 -0
- package/src/components/LpHintExample.vue +9 -32
- package/src/components/LpHintLayer.vue +13 -3
- package/src/components/LpHoverCard.vue +175 -0
- package/src/components/LpIdenticon.vue +69 -0
- package/src/components/LpImageEditor.vue +15 -18
- package/src/components/LpInfraNode.vue +27 -16
- package/src/components/LpInput.vue +30 -16
- package/src/components/LpLightbox.vue +44 -23
- package/src/components/LpLoader.vue +145 -0
- package/src/components/LpLogViewer.vue +311 -82
- package/src/components/LpMagnetic.vue +97 -0
- package/src/components/LpMapPicker.vue +26 -28
- package/src/components/LpMasonry.vue +247 -0
- package/src/components/LpModal.vue +10 -1
- package/src/components/LpNotificationBell.vue +33 -49
- package/src/components/LpNumberField.vue +79 -14
- package/src/components/LpNumberFlow.vue +10 -35
- package/src/components/LpOtpInput.vue +7 -2
- package/src/components/LpPagination.vue +38 -4
- package/src/components/LpPalettePreview.vue +2 -7
- package/src/components/LpPasswordInput.vue +16 -14
- package/src/components/LpPhoneInput.vue +12 -3
- package/src/components/LpPie.vue +197 -36
- package/src/components/LpPipeline.vue +366 -0
- package/src/components/LpPopover.vue +10 -1
- package/src/components/LpProgress.vue +22 -84
- package/src/components/LpQuota.vue +197 -0
- package/src/components/LpRangeSlider.vue +5 -1
- package/src/components/LpRelativeTime.vue +90 -0
- package/src/components/LpRepeater.vue +80 -0
- package/src/components/LpRevealOnScroll.vue +125 -0
- package/src/components/LpScramble.vue +107 -0
- package/src/components/LpSecretField.vue +313 -0
- package/src/components/LpSegmented.vue +3 -10
- package/src/components/LpSelect.vue +78 -64
- package/src/components/LpShaderBackdrop.vue +434 -0
- package/src/components/LpShinyText.vue +139 -0
- package/src/components/LpSidebar.vue +2 -0
- package/src/components/LpSlider.vue +5 -1
- package/src/components/LpSparkline.vue +55 -37
- package/src/components/LpSpotlight.vue +51 -0
- package/src/components/LpStat.vue +6 -2
- package/src/components/LpStatus.vue +127 -0
- package/src/components/LpStepMark.vue +138 -0
- package/src/components/LpStepper.vue +37 -43
- package/src/components/LpSwap.vue +2 -2
- package/src/components/LpSwitch.vue +15 -22
- package/src/components/LpTable.vue +47 -63
- package/src/components/LpTableFilter.vue +1 -0
- package/src/components/LpTableOfContents.vue +53 -29
- package/src/components/LpTabs.vue +10 -21
- package/src/components/LpTagsInput.vue +53 -37
- package/src/components/LpTextReveal.vue +236 -0
- package/src/components/LpTextarea.vue +11 -11
- package/src/components/LpThemeStudio.vue +216 -0
- package/src/components/LpThemeStudioSection.vue +16 -0
- package/src/components/LpThemeSwitcher.vue +156 -47
- package/src/components/LpTimeField.vue +9 -1
- package/src/components/LpToaster.vue +63 -83
- package/src/components/LpTooltip.vue +10 -1
- package/src/components/LpTour.vue +24 -13
- package/src/components/LpTruncate.vue +135 -0
- package/src/components/LpTypewriter.vue +236 -0
- package/src/components/LpUptimeBar.vue +6 -5
- package/src/components/LpVideoBackdrop.vue +15 -13
- package/src/components/OptionContent.vue +17 -0
- package/src/components/PickerField.vue +58 -0
- package/src/components/RevealToggle.vue +51 -0
- package/src/components/StepConnector.vue +54 -0
- package/src/components/TourCard.vue +29 -16
- package/src/components/badge.ts +8 -0
- package/src/components/chart.ts +8 -23
- package/src/components/dateRange.ts +25 -0
- package/src/components/dock.ts +31 -0
- package/src/components/dropdown.ts +15 -3
- package/src/components/fieldText.ts +95 -0
- package/src/components/fileTree.ts +19 -21
- package/src/components/format.ts +136 -0
- package/src/components/identicon.ts +160 -0
- package/src/components/masonry.ts +73 -0
- package/src/components/meter.ts +122 -0
- package/src/components/otp.ts +14 -3
- package/src/components/pipeline.ts +101 -0
- package/src/components/quota.ts +111 -0
- package/src/components/relativeTime.ts +118 -0
- package/src/components/reveal.ts +56 -0
- package/src/components/scramble.ts +138 -0
- package/src/components/shaderBackdrop.ts +208 -0
- package/src/components/slider.ts +30 -0
- package/src/components/slotText.ts +13 -0
- package/src/components/status.ts +46 -0
- package/src/components/tableFacets.ts +25 -0
- package/src/components/textSplit.ts +55 -0
- package/src/components/timeSpan.ts +183 -0
- package/src/components/tone.ts +144 -0
- package/src/components/tour.ts +3 -2
- package/src/components/typewriter.ts +104 -0
- package/src/components/videoBackdrop.ts +7 -6
- package/src/composables/easing.ts +66 -1
- package/src/composables/pseudoLocale.ts +105 -0
- package/src/composables/scroll.ts +6 -3
- package/src/composables/useClipboard.ts +25 -3
- package/src/composables/useField.ts +12 -0
- package/src/composables/useForm.ts +27 -6
- package/src/composables/useInView.ts +61 -0
- package/src/composables/useLoadingGate.ts +63 -0
- package/src/composables/useLpConfig.ts +20 -1
- package/src/composables/useMediaQuery.ts +6 -2
- package/src/composables/useNow.ts +84 -0
- package/src/composables/usePersisted.ts +19 -8
- package/src/composables/usePillTransition.ts +21 -13
- package/src/composables/usePointerFrame.ts +72 -0
- package/src/composables/useReducedMotion.ts +22 -0
- package/src/composables/useRepeater.ts +44 -0
- package/src/composables/useResizeObserver.ts +59 -0
- package/src/composables/useRevealedValue.ts +84 -0
- package/src/composables/useScramble.ts +152 -0
- package/src/composables/useShift.ts +10 -16
- package/src/composables/useSpotlight.ts +82 -0
- package/src/composables/useTilt.ts +40 -23
- package/src/composables/useTween.ts +149 -0
- package/src/composables/useView.ts +93 -0
- package/src/index.css +1 -0
- package/src/index.ts +143 -1
- package/src/kit-icons.ts +1 -1
- package/src/theme/define.ts +65 -0
- package/src/theme/studio.ts +202 -0
- package/src/tokens/effects.css +151 -0
- package/src/tokens/tokens.css +131 -39
- package/src/tokens/utilities.css +81 -0
- package/dist/assets/LpTabs-Bk7v4bZz.css +0 -18
- package/dist/chunks/LpAlert-BdvxvcWE.js +0 -85
- package/dist/chunks/LpBadge-Fe0D1bYx.js +0 -62
- package/dist/chunks/LpBreadcrumbs-rzOKkLiI.js +0 -98
- package/dist/chunks/LpDatePicker-BMfM454Y.js +0 -243
- package/dist/chunks/LpDateRangePicker-CNb9X3eL.js +0 -195
- package/dist/chunks/LpNumberField-DO1ufKvE.js +0 -107
- package/dist/chunks/LpStepper-C2slwObu.js +0 -90
- package/dist/chunks/LpSwitch-CZy9_IoA.js +0 -132
- package/dist/chunks/LpThemeSwitcher-CDoziW0z.js +0 -166
- package/dist/chunks/TourCard-BKI7X9-r.js +0 -105
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Pseudo-localisation: English text made to look like a translation, so a
|
|
3
|
+
* screen can be checked for translation problems before any translation exists.
|
|
4
|
+
*
|
|
5
|
+
* "Save changes" → "[Šååṽéé çĥååñĝééš]"
|
|
6
|
+
*
|
|
7
|
+
* What each part catches:
|
|
8
|
+
*
|
|
9
|
+
* - Accents. A string that still reads as plain ASCII on a pseudo screen was
|
|
10
|
+
* never routed through the label system: it is hard-coded and would stay
|
|
11
|
+
* English in every language.
|
|
12
|
+
* - Length. Translations run longer than English, German and Finnish by a
|
|
13
|
+
* third and more. Vowels are stretched by `expand`, so a label that only fits
|
|
14
|
+
* in English overflows, wraps or truncates right here.
|
|
15
|
+
* - Brackets. A clipped end bracket is truncation that would otherwise go
|
|
16
|
+
* unnoticed; a bracket in the middle of a sentence is two strings glued
|
|
17
|
+
* together, which no language with a different word order can translate.
|
|
18
|
+
*
|
|
19
|
+
* Placeholders (`{name}`, `{{ name }}`, `%s`, `%1$d`), tags and entities are
|
|
20
|
+
* left exactly as they are: they are code the label is filled through, and an
|
|
21
|
+
* accented `{ñåmé}` would no longer be replaced.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export interface PseudoOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Extra length as a share of the text's letters: 0.35 adds about a third.
|
|
27
|
+
* Spread over the vowels, so the text grows the way a longer word does
|
|
28
|
+
* rather than gaining a tail that wraps somewhere convenient.
|
|
29
|
+
*/
|
|
30
|
+
expand?: number
|
|
31
|
+
/**
|
|
32
|
+
* Markers around the whole string. `true` uses square brackets, a pair of
|
|
33
|
+
* strings picks others, `false` drops them.
|
|
34
|
+
*/
|
|
35
|
+
brackets?: boolean | readonly [string, string]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ACCENTED: Record<string, string> = {
|
|
39
|
+
a: "å", b: "ƀ", c: "ç", d: "ð", e: "é", f: "ƒ", g: "ĝ", h: "ĥ", i: "î", j: "ĵ", k: "ķ", l: "ļ", m: "ɱ",
|
|
40
|
+
n: "ñ", o: "ö", p: "þ", q: "ǫ", r: "ŕ", s: "š", t: "ţ", u: "û", v: "ṽ", w: "ŵ", x: "ẋ", y: "ý", z: "ž",
|
|
41
|
+
A: "Å", B: "Ɓ", C: "Ç", D: "Ð", E: "É", F: "Ƒ", G: "Ĝ", H: "Ĥ", I: "Î", J: "Ĵ", K: "Ķ", L: "Ļ", M: "Ṁ",
|
|
42
|
+
N: "Ñ", O: "Ö", P: "Þ", Q: "Ǫ", R: "Ŕ", S: "Š", T: "Ţ", U: "Û", V: "Ṽ", W: "Ŵ", X: "Ẋ", Y: "Ý", Z: "Ž",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const VOWEL = /[aeiouyAEIOUY]/
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
* Everything that is code rather than text. Order matters only in that the
|
|
49
|
+
* double-brace form must be tried before the single one, or `{{ x }}` would
|
|
50
|
+
* be read as `{` + `{ x }` + `}`.
|
|
51
|
+
*/
|
|
52
|
+
const PROTECTED = /\{\{[^}]*\}\}|\{[^{}]*\}|%(?:\d+\$)?[-+ 0#]*\d*(?:\.\d+)?[sdifoxXeEgGc%]|<[^>]+>|&[a-zA-Z#0-9]+;/g
|
|
53
|
+
|
|
54
|
+
/** English text made to look translated and longer; see the module comment. */
|
|
55
|
+
export function pseudoLocalize(text: string, options: PseudoOptions = {}): string {
|
|
56
|
+
if (!text.trim()) return text
|
|
57
|
+
const expand = Math.max(0, options.expand ?? 0.35)
|
|
58
|
+
const brackets = options.brackets ?? true
|
|
59
|
+
const [open, close] = brackets === true ? ["[", "]"] : brackets === false ? ["", ""] : brackets
|
|
60
|
+
|
|
61
|
+
// Split into text runs and protected runs, keeping both.
|
|
62
|
+
const runs: { text: string; code: boolean }[] = []
|
|
63
|
+
let last = 0
|
|
64
|
+
for (const match of text.matchAll(PROTECTED)) {
|
|
65
|
+
const at = match.index ?? 0
|
|
66
|
+
if (at > last) runs.push({ text: text.slice(last, at), code: false })
|
|
67
|
+
runs.push({ text: match[0], code: true })
|
|
68
|
+
last = at + match[0].length
|
|
69
|
+
}
|
|
70
|
+
if (last < text.length) runs.push({ text: text.slice(last), code: false })
|
|
71
|
+
|
|
72
|
+
const letters = runs.reduce((n, run) => (run.code ? n : n + (run.text.match(/[a-zA-Z]/g)?.length ?? 0)), 0)
|
|
73
|
+
const vowels = runs.reduce((n, run) => (run.code ? n : n + (run.text.match(/[aeiouyAEIOUY]/g)?.length ?? 0)), 0)
|
|
74
|
+
let extra = Math.round(letters * expand)
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* Every vowel repeats `per` more times, and the first `rest` once more on
|
|
78
|
+
* top, so the growth is spread over the whole string instead of piling up
|
|
79
|
+
* in its first word.
|
|
80
|
+
*/
|
|
81
|
+
const per = vowels ? Math.floor(extra / vowels) : 0
|
|
82
|
+
let rest = vowels ? extra % vowels : 0
|
|
83
|
+
if (vowels) extra = 0
|
|
84
|
+
|
|
85
|
+
const out = runs
|
|
86
|
+
.map((run) => {
|
|
87
|
+
if (run.code) return run.text
|
|
88
|
+
let s = ""
|
|
89
|
+
for (const ch of run.text) {
|
|
90
|
+
const mapped = ACCENTED[ch] ?? ch
|
|
91
|
+
s += mapped
|
|
92
|
+
if (VOWEL.test(ch)) {
|
|
93
|
+
const times = per + (rest > 0 ? 1 : 0)
|
|
94
|
+
if (rest > 0) rest--
|
|
95
|
+
s += mapped.toLowerCase().repeat(times)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return s
|
|
99
|
+
})
|
|
100
|
+
.join("")
|
|
101
|
+
|
|
102
|
+
// No vowels to stretch (an abbreviation, a code): the length goes at the end.
|
|
103
|
+
const tail = extra > 0 ? "·".repeat(extra) : ""
|
|
104
|
+
return `${open}${out}${tail}${close}`
|
|
105
|
+
}
|
|
@@ -17,7 +17,7 @@ export interface ScrollParentOptions {
|
|
|
17
17
|
/** The nearest ancestor that scrolls along `axis` and has something to scroll, or null. */
|
|
18
18
|
export function scrollParent(el: Element | null | undefined, { axis = "y", self = false }: ScrollParentOptions = {}): HTMLElement | null {
|
|
19
19
|
for (let node = self ? (el as HTMLElement | null | undefined) : el?.parentElement; node; node = node.parentElement) {
|
|
20
|
-
if (node ===
|
|
20
|
+
if (node === node.ownerDocument.body || node === node.ownerDocument.documentElement) return null
|
|
21
21
|
const style = getComputedStyle(node)
|
|
22
22
|
const y = axis !== "x" && SCROLLS.test(style.overflowY) && node.scrollHeight > node.clientHeight
|
|
23
23
|
const x = axis !== "y" && SCROLLS.test(style.overflowX) && node.scrollWidth > node.clientWidth
|
|
@@ -40,9 +40,12 @@ export function scrollToElement(
|
|
|
40
40
|
{ block = "nearest", behavior = "auto", margin = 0 }: ScrollToElementOptions = {},
|
|
41
41
|
): void {
|
|
42
42
|
if (!el) return
|
|
43
|
-
|
|
43
|
+
// The element's own document and window: inside an LpFrame they are the frame's.
|
|
44
|
+
const doc = el.ownerDocument
|
|
45
|
+
const win = doc.defaultView ?? window
|
|
46
|
+
const box = scrollParent(el) ?? (doc.scrollingElement as HTMLElement | null)
|
|
44
47
|
if (!box) return
|
|
45
|
-
const outer = box ===
|
|
48
|
+
const outer = box === doc.scrollingElement ? new DOMRect(0, 0, win.innerWidth, win.innerHeight) : box.getBoundingClientRect()
|
|
46
49
|
const inner = el.getBoundingClientRect()
|
|
47
50
|
const top = inner.top - outer.top + box.scrollTop
|
|
48
51
|
let target = box.scrollTop
|
|
@@ -70,6 +70,23 @@ function writeLegacy(text: string): boolean {
|
|
|
70
70
|
return ok
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/*
|
|
74
|
+
* A blob (an image, mostly) goes through `clipboard.write`. There is no old
|
|
75
|
+
* command to fall back on here: the copy command only knows the selection,
|
|
76
|
+
* and an image cannot be selected into a text field. Browsers take a narrow
|
|
77
|
+
* set of types (PNG reliably, text/html and text/plain), so the caller
|
|
78
|
+
* converts first; a type the browser refuses fails like any other refusal.
|
|
79
|
+
*/
|
|
80
|
+
async function writeBlob(blob: Blob): Promise<boolean> {
|
|
81
|
+
if (typeof navigator === "undefined" || !navigator.clipboard?.write || typeof ClipboardItem === "undefined") return false
|
|
82
|
+
try {
|
|
83
|
+
await navigator.clipboard.write([new ClipboardItem({ [blob.type || "image/png"]: blob })])
|
|
84
|
+
return true
|
|
85
|
+
} catch {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
73
90
|
/** How long `copied` stays true — long enough to read, short enough to not linger. */
|
|
74
91
|
const COPIED_MS = 1400
|
|
75
92
|
|
|
@@ -102,8 +119,11 @@ export interface UseClipboard {
|
|
|
102
119
|
* Writes `text` to the clipboard. Resolves true on success, false when the
|
|
103
120
|
* clipboard was unavailable or refused — it never rejects, so `await copy(x)`
|
|
104
121
|
* is safe to call bare from an event handler.
|
|
122
|
+
*
|
|
123
|
+
* A `Blob` is written as itself under its own type (an image as a PNG blob),
|
|
124
|
+
* for "copy image" next to "copy link".
|
|
105
125
|
*/
|
|
106
|
-
copy: (
|
|
126
|
+
copy: (data: string | Blob, options?: CopyOptions) => Promise<boolean>
|
|
107
127
|
}
|
|
108
128
|
|
|
109
129
|
/**
|
|
@@ -125,10 +145,12 @@ export function useClipboard(): UseClipboard {
|
|
|
125
145
|
const { success, error } = useToast()
|
|
126
146
|
let timer: ReturnType<typeof setTimeout> | undefined
|
|
127
147
|
|
|
128
|
-
async function copy(
|
|
148
|
+
async function copy(data: string | Blob, options: CopyOptions = {}): Promise<boolean> {
|
|
129
149
|
const { toast = false, errorToast = false } = options
|
|
130
150
|
try {
|
|
131
|
-
|
|
151
|
+
const written =
|
|
152
|
+
typeof data === "string" ? (await writeAsync(data)) || writeLegacy(data) : await writeBlob(data)
|
|
153
|
+
if (!written) throw new Error("clipboard unavailable")
|
|
132
154
|
|
|
133
155
|
copied.value = true
|
|
134
156
|
clearTimeout(timer)
|
|
@@ -81,3 +81,15 @@ export function useFieldControl(
|
|
|
81
81
|
}
|
|
82
82
|
})
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Whether a focusout leaves the whole control, not just moves between its own
|
|
87
|
+
* parts (a trigger and a clear button, the segments of a time field, an input
|
|
88
|
+
* and its stepper). Composite controls emit their `blur` only then: focus
|
|
89
|
+
* events do not bubble, so a consumer's `@blur` on the wrapper never fired, and
|
|
90
|
+
* `useForm().field()` could not mark such a field touched.
|
|
91
|
+
*/
|
|
92
|
+
export function focusLeft(event: FocusEvent): boolean {
|
|
93
|
+
const next = event.relatedTarget
|
|
94
|
+
return !(next instanceof Node && (event.currentTarget as Node | null)?.contains(next))
|
|
95
|
+
}
|
|
@@ -120,6 +120,9 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
|
|
|
120
120
|
const submitting = ref(false)
|
|
121
121
|
const submitCount = ref(0)
|
|
122
122
|
const runs = new Map<keyof T | "form", number>()
|
|
123
|
+
// What the form-level check set last, so its next run can take back what no
|
|
124
|
+
// longer holds without touching an error a field rule set on the same key.
|
|
125
|
+
const fromForm = new Map<keyof T | "form", string>()
|
|
123
126
|
const baseVersion = ref(0)
|
|
124
127
|
|
|
125
128
|
const keys = (): (keyof T)[] => Object.keys(plain()) as (keyof T)[]
|
|
@@ -160,11 +163,15 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
|
|
|
160
163
|
runs.set("form", run)
|
|
161
164
|
const found = (await options.validate(plain())) ?? {}
|
|
162
165
|
if (runs.get("form") !== run) return true
|
|
166
|
+
for (const [name, text] of fromForm) if (errors[name] === text) delete errors[name]
|
|
167
|
+
fromForm.clear()
|
|
163
168
|
let ok = true
|
|
164
169
|
for (const [name, text] of Object.entries(found) as [keyof T | "form", string | undefined][]) {
|
|
165
170
|
if (!text) continue
|
|
166
171
|
ok = false
|
|
167
|
-
if (
|
|
172
|
+
if (errors[name]) continue
|
|
173
|
+
errors[name] = text
|
|
174
|
+
fromForm.set(name, text)
|
|
168
175
|
}
|
|
169
176
|
return ok
|
|
170
177
|
}
|
|
@@ -173,7 +180,6 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
|
|
|
173
180
|
validating.value = true
|
|
174
181
|
try {
|
|
175
182
|
if (name !== undefined) return await runField(name)
|
|
176
|
-
if (errors.form) delete errors.form
|
|
177
183
|
const fields = await Promise.all(keys().map(runField))
|
|
178
184
|
const form = await runForm()
|
|
179
185
|
return fields.every(Boolean) && form && !Object.values(external).some(Boolean)
|
|
@@ -200,6 +206,7 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
|
|
|
200
206
|
for (const k of Object.keys(external)) delete external[k as keyof T]
|
|
201
207
|
for (const k of Object.keys(touched)) delete touched[k as keyof T]
|
|
202
208
|
runs.clear()
|
|
209
|
+
fromForm.clear()
|
|
203
210
|
submitCount.value = 0
|
|
204
211
|
}
|
|
205
212
|
|
|
@@ -231,18 +238,27 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
|
|
|
231
238
|
{ deep: true },
|
|
232
239
|
)
|
|
233
240
|
|
|
234
|
-
// An edit re-checks a field already touched,
|
|
241
|
+
// An edit re-checks a field already touched, drops an external error on it,
|
|
242
|
+
// and runs the form-level check again once it has run: a cross-field error
|
|
243
|
+
// ("passwords differ") must clear when either field is fixed.
|
|
244
|
+
async function recheck(names: (keyof T)[]): Promise<void> {
|
|
245
|
+
await Promise.all(names.map((name) => validate(name)))
|
|
246
|
+
if (fromForm.size || submitCount.value) await runForm()
|
|
247
|
+
}
|
|
248
|
+
|
|
235
249
|
let snapshot = clone(plain())
|
|
236
250
|
watch(
|
|
237
251
|
plain,
|
|
238
252
|
() => {
|
|
239
253
|
const now = clone(plain())
|
|
254
|
+
const changed: (keyof T)[] = []
|
|
240
255
|
for (const name of Object.keys(now) as (keyof T)[]) {
|
|
241
256
|
if (deepEqual(now[name], snapshot[name])) continue
|
|
242
257
|
if (external[name]) delete external[name]
|
|
243
|
-
if (touched[name] || submitCount.value)
|
|
258
|
+
if (touched[name] || submitCount.value) changed.push(name)
|
|
244
259
|
}
|
|
245
260
|
snapshot = now
|
|
261
|
+
if (changed.length) void recheck(changed)
|
|
246
262
|
},
|
|
247
263
|
{ deep: true },
|
|
248
264
|
)
|
|
@@ -278,8 +294,13 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
|
|
|
278
294
|
try {
|
|
279
295
|
submitCount.value++
|
|
280
296
|
for (const name of keys()) touched[name] = true
|
|
281
|
-
|
|
282
|
-
|
|
297
|
+
// The values that passed are the values sent: an edit made while an
|
|
298
|
+
// async rule was running is checked again before anything goes out.
|
|
299
|
+
let submitted: T
|
|
300
|
+
do {
|
|
301
|
+
submitted = clone(plain())
|
|
302
|
+
if (!(await validate())) return
|
|
303
|
+
} while (!deepEqual(plain(), submitted))
|
|
283
304
|
await fn(submitted)
|
|
284
305
|
if (deepEqual(plain(), submitted)) {
|
|
285
306
|
baseline = submitted
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { onBeforeUnmount, ref, watch, type Ref } from "vue"
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* "Is this element on screen RIGHT NOW, in a tab someone is looking at?"
|
|
5
|
+
*
|
|
6
|
+
* useReveal answers a one-way question (has it ever been seen) for entrances.
|
|
7
|
+
* A looping effect needs the live answer: a sheen sweeping text below the fold
|
|
8
|
+
* or a typewriter cycling in a background tab spends frames and battery on
|
|
9
|
+
* nothing, and on a dashboard of them that adds up. So this flips both ways,
|
|
10
|
+
* and the loop pauses while it is false.
|
|
11
|
+
*
|
|
12
|
+
* Without IntersectionObserver (old WebKitGTK, tests) the element counts as on
|
|
13
|
+
* screen: better a loop that runs than one that never starts.
|
|
14
|
+
*/
|
|
15
|
+
export interface UseInView {
|
|
16
|
+
/** Attach to the element to watch. */
|
|
17
|
+
el: Ref<Element | null>
|
|
18
|
+
/** On screen and the page visible. */
|
|
19
|
+
inView: Ref<boolean>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useInView(options: { rootMargin?: string; threshold?: number } = {}): UseInView {
|
|
23
|
+
const el = ref<Element | null>(null)
|
|
24
|
+
const intersecting = ref(typeof IntersectionObserver === "undefined")
|
|
25
|
+
const pageVisible = ref(typeof document === "undefined" || document.visibilityState === "visible")
|
|
26
|
+
const inView = ref(false)
|
|
27
|
+
let observer: IntersectionObserver | null = null
|
|
28
|
+
|
|
29
|
+
const sync = () => (inView.value = intersecting.value && pageVisible.value)
|
|
30
|
+
const onVisibility = () => {
|
|
31
|
+
pageVisible.value = document.visibilityState === "visible"
|
|
32
|
+
sync()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (typeof document !== "undefined") document.addEventListener("visibilitychange", onVisibility)
|
|
36
|
+
|
|
37
|
+
watch(
|
|
38
|
+
el,
|
|
39
|
+
(node) => {
|
|
40
|
+
observer?.disconnect()
|
|
41
|
+
observer = null
|
|
42
|
+
if (!node || typeof IntersectionObserver === "undefined") return sync()
|
|
43
|
+
observer = new IntersectionObserver(
|
|
44
|
+
(entries) => {
|
|
45
|
+
intersecting.value = entries.some((entry) => entry.isIntersecting)
|
|
46
|
+
sync()
|
|
47
|
+
},
|
|
48
|
+
{ rootMargin: options.rootMargin ?? "0px", threshold: options.threshold ?? 0 },
|
|
49
|
+
)
|
|
50
|
+
observer.observe(node)
|
|
51
|
+
},
|
|
52
|
+
{ immediate: true, flush: "post" },
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
onBeforeUnmount(() => {
|
|
56
|
+
observer?.disconnect()
|
|
57
|
+
if (typeof document !== "undefined") document.removeEventListener("visibilitychange", onVisibility)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return { el, inView }
|
|
61
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { onScopeDispose, ref, toValue, watch, type MaybeRefOrGetter, type Ref } from "vue"
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* "Should a loading indicator be on screen right now?" — which is not the same
|
|
5
|
+
* question as "is something loading?".
|
|
6
|
+
*
|
|
7
|
+
* Shown the moment work starts, a spinner flashes for 80 ms on every fast
|
|
8
|
+
* request: the page blinks, and blinking reads as instability rather than
|
|
9
|
+
* speed. Hidden the moment work ends, one that did appear can vanish a frame
|
|
10
|
+
* after it arrived — the same blink from the other side. So the indicator
|
|
11
|
+
* waits `delay` before appearing (most loads finish inside it and show
|
|
12
|
+
* nothing), and once up it stays at least `minVisible`, long enough to be seen
|
|
13
|
+
* as a deliberate state rather than a glitch.
|
|
14
|
+
*
|
|
15
|
+
* LpLoader runs on this; it is exported for anything else that gates a
|
|
16
|
+
* placeholder the same way (a skeleton, a dimmed table).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface LoadingGateOptions {
|
|
20
|
+
/** Ms of loading before the indicator appears. */
|
|
21
|
+
delay?: MaybeRefOrGetter<number>
|
|
22
|
+
/** Ms the indicator stays once shown, even if loading ends sooner. */
|
|
23
|
+
minVisible?: MaybeRefOrGetter<number>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useLoadingGate(
|
|
27
|
+
active: MaybeRefOrGetter<boolean>,
|
|
28
|
+
options: LoadingGateOptions = {},
|
|
29
|
+
): Ref<boolean> {
|
|
30
|
+
const shown = ref(false)
|
|
31
|
+
let shownAt = 0
|
|
32
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
33
|
+
|
|
34
|
+
const clear = () => {
|
|
35
|
+
if (timer !== undefined) clearTimeout(timer)
|
|
36
|
+
timer = undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const show = () => {
|
|
40
|
+
clear()
|
|
41
|
+
shown.value = true
|
|
42
|
+
shownAt = Date.now()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const settle = (on: boolean) => {
|
|
46
|
+
clear()
|
|
47
|
+
if (on) {
|
|
48
|
+
if (shown.value) return
|
|
49
|
+
const wait = Math.max(0, toValue(options.delay) ?? 0)
|
|
50
|
+
if (wait === 0) show()
|
|
51
|
+
else timer = setTimeout(show, wait)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
if (!shown.value) return
|
|
55
|
+
const left = Math.max(0, toValue(options.minVisible) ?? 0) - (Date.now() - shownAt)
|
|
56
|
+
if (left <= 0) shown.value = false
|
|
57
|
+
else timer = setTimeout(() => { timer = undefined; shown.value = false }, left)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
watch(() => !!toValue(active), settle, { immediate: true })
|
|
61
|
+
onScopeDispose(clear)
|
|
62
|
+
return shown
|
|
63
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type InjectionKey,
|
|
10
10
|
type MaybeRefOrGetter,
|
|
11
11
|
} from "vue"
|
|
12
|
+
import { pseudoLocalize, type PseudoOptions } from "./pseudoLocale"
|
|
12
13
|
|
|
13
14
|
/*
|
|
14
15
|
* App-wide kit configuration, said once at the root instead of on every call
|
|
@@ -33,6 +34,15 @@ export interface LpConfig {
|
|
|
33
34
|
* desktop shell can hand in its own store. See `usePersisted`.
|
|
34
35
|
*/
|
|
35
36
|
storage?: KitStorage
|
|
37
|
+
/**
|
|
38
|
+
* Show every kit label pseudo-localised ("Next" → "[Ñééẋţ]"): accented,
|
|
39
|
+
* about a third longer, and bracketed. A development switch for checking a
|
|
40
|
+
* screen before any translation exists: plain English left on it is text
|
|
41
|
+
* that never went through the labels, and anything clipped or overflowing
|
|
42
|
+
* will be in a real language too. `true` uses the defaults; an object tunes
|
|
43
|
+
* them (see `pseudoLocalize`).
|
|
44
|
+
*/
|
|
45
|
+
pseudoLocale?: boolean | PseudoOptions
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
/**
|
|
@@ -82,6 +92,7 @@ export function mergeConfig(outer: LpConfig, inner: LpConfig): LpConfig {
|
|
|
82
92
|
...inner,
|
|
83
93
|
linkComponent: inner.linkComponent ?? outer.linkComponent,
|
|
84
94
|
storage: inner.storage ?? outer.storage,
|
|
95
|
+
pseudoLocale: inner.pseudoLocale ?? outer.pseudoLocale,
|
|
85
96
|
labels,
|
|
86
97
|
}
|
|
87
98
|
}
|
|
@@ -101,7 +112,15 @@ export function useLabels<T extends LabelScope>(
|
|
|
101
112
|
own?: () => Partial<T> | undefined,
|
|
102
113
|
): ComputedRef<T> {
|
|
103
114
|
const config = useLpConfig()
|
|
104
|
-
return computed(() =>
|
|
115
|
+
return computed(() => {
|
|
116
|
+
const labels = { ...defaults, ...config.value.labels?.[scope], ...own?.() } as T
|
|
117
|
+
const pseudo = config.value.pseudoLocale
|
|
118
|
+
if (!pseudo) return labels
|
|
119
|
+
const options = pseudo === true ? undefined : pseudo
|
|
120
|
+
return Object.fromEntries(
|
|
121
|
+
Object.entries(labels).map(([key, text]) => [key, typeof text === "string" ? pseudoLocalize(text, options) : text]),
|
|
122
|
+
) as T
|
|
123
|
+
})
|
|
105
124
|
}
|
|
106
125
|
|
|
107
126
|
/** The link component to render for an internal destination. */
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { onBeforeUnmount, onMounted, ref, type Ref } from "vue"
|
|
2
|
+
import { useView, viewWindow } from "./useView"
|
|
2
3
|
|
|
3
4
|
/*
|
|
4
5
|
* A media query as a ref. False until mounted, so the server and the hydrating
|
|
5
|
-
* client render the same thing and the client switches right after.
|
|
6
|
+
* client render the same thing and the client switches right after. Asked of
|
|
7
|
+
* the window the component renders in, so inside an LpFrame narrowed to a
|
|
8
|
+
* phone a width query answers for the phone.
|
|
6
9
|
*/
|
|
7
10
|
export function useMediaQuery(query: string): Ref<boolean> {
|
|
11
|
+
const view = useView()
|
|
8
12
|
const matches = ref(false)
|
|
9
13
|
let list: MediaQueryList | undefined
|
|
10
14
|
const update = () => {
|
|
11
15
|
matches.value = !!list?.matches
|
|
12
16
|
}
|
|
13
17
|
onMounted(() => {
|
|
14
|
-
list =
|
|
18
|
+
list = viewWindow(view.value).matchMedia?.(query)
|
|
15
19
|
update()
|
|
16
20
|
list?.addEventListener("change", update)
|
|
17
21
|
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { onScopeDispose, ref, toValue, watch, type MaybeRefOrGetter, type Ref } from "vue"
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* The current time, as a ref that ticks only while something shows it.
|
|
5
|
+
*
|
|
6
|
+
* A clock on screen (a running job's elapsed time, a countdown) needs `now`
|
|
7
|
+
* once per visible change and not otherwise. Two things go wrong with the
|
|
8
|
+
* obvious `setInterval(1000)`:
|
|
9
|
+
*
|
|
10
|
+
* - drift: an interval started mid-second shows each value late by that
|
|
11
|
+
* fraction, and timers fire late under load, so the display slips further
|
|
12
|
+
* behind the real time the longer it runs. Here every tick is scheduled
|
|
13
|
+
* for the moment the display next changes (`nextIn`), read from the clock,
|
|
14
|
+
* so a late tick corrects itself on the next one;
|
|
15
|
+
* - background tabs: a hidden page gets its timers throttled to once a
|
|
16
|
+
* minute or worse, then shows a stale value for a frame on return. Here the
|
|
17
|
+
* ticking stops while the tab is hidden (nothing is looking) and `now` is
|
|
18
|
+
* re-read the moment it is visible again.
|
|
19
|
+
*
|
|
20
|
+
* Time itself never pauses — it is always read from Date.now() — so a
|
|
21
|
+
* countdown that was hidden for ten minutes comes back ten minutes on.
|
|
22
|
+
*/
|
|
23
|
+
export interface UseNowOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Ms from `now` until the next tick. Defaults to the next wall-clock
|
|
26
|
+
* second, which is when an elapsed "1m 12s" changes.
|
|
27
|
+
*/
|
|
28
|
+
nextIn?: (now: number) => number
|
|
29
|
+
/**
|
|
30
|
+
* Re-read the clock and reschedule whenever this changes. For a `nextIn`
|
|
31
|
+
* that depends on more than the time: a relative date that was "last month"
|
|
32
|
+
* sleeps an hour, and if it is replaced by a date a few seconds old, the
|
|
33
|
+
* wait it scheduled for the old one would leave "now" on screen for an hour.
|
|
34
|
+
*/
|
|
35
|
+
restartOn?: MaybeRefOrGetter<unknown>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useNow(
|
|
39
|
+
active: MaybeRefOrGetter<boolean> = true,
|
|
40
|
+
options: UseNowOptions = {},
|
|
41
|
+
): Ref<number> {
|
|
42
|
+
const now = ref(Date.now())
|
|
43
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
44
|
+
const hasDocument = typeof document !== "undefined"
|
|
45
|
+
|
|
46
|
+
const stop = () => {
|
|
47
|
+
if (timer !== undefined) clearTimeout(timer)
|
|
48
|
+
timer = undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const delay = (at: number) => {
|
|
52
|
+
const next = options.nextIn ? options.nextIn(at) : 1000 - (at % 1000)
|
|
53
|
+
// Never zero, never negative: a tick that lands on the boundary would
|
|
54
|
+
// otherwise reschedule itself in a tight loop.
|
|
55
|
+
return Math.max(16, Number.isFinite(next) ? next : 1000)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const tick = () => {
|
|
59
|
+
stop()
|
|
60
|
+
now.value = Date.now()
|
|
61
|
+
if (!toValue(active) || (hasDocument && document.hidden)) return
|
|
62
|
+
// Waking a hair after the boundary rather than before it: a tick that
|
|
63
|
+
// fires 2 ms early would read the old value and show it for another second.
|
|
64
|
+
timer = setTimeout(tick, delay(now.value) + 4)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const onVisibility = () => {
|
|
68
|
+
if (document.hidden) stop()
|
|
69
|
+
else tick()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
watch(() => !!toValue(active), (on) => (on ? tick() : stop()), { immediate: true })
|
|
73
|
+
if (options.restartOn !== undefined) {
|
|
74
|
+
watch(() => toValue(options.restartOn), () => {
|
|
75
|
+
if (toValue(active)) tick()
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
if (hasDocument) document.addEventListener("visibilitychange", onVisibility)
|
|
79
|
+
onScopeDispose(() => {
|
|
80
|
+
stop()
|
|
81
|
+
if (hasDocument) document.removeEventListener("visibilitychange", onVisibility)
|
|
82
|
+
})
|
|
83
|
+
return now
|
|
84
|
+
}
|
|
@@ -66,22 +66,33 @@ export function usePersisted<T>(
|
|
|
66
66
|
options: PersistedOptions<T> = {},
|
|
67
67
|
): Ref<T> {
|
|
68
68
|
const storage = useKitStorage()
|
|
69
|
-
|
|
69
|
+
// A copy taken now: an object initial edited in place under one key must
|
|
70
|
+
// not come back edited as the starting value of the next.
|
|
71
|
+
const pristine = initial === undefined ? undefined : JSON.stringify(initial)
|
|
72
|
+
const fresh = (): T => (pristine === undefined ? initial : (JSON.parse(pristine) as T))
|
|
73
|
+
const value = ref(fresh()) as Ref<T>
|
|
70
74
|
|
|
71
|
-
function
|
|
72
|
-
if (!name) return
|
|
75
|
+
function stored(name: string): { ok: true; value: T } | { ok: false } {
|
|
73
76
|
const raw = storage.get(name)
|
|
74
|
-
if (raw == null) return
|
|
77
|
+
if (raw == null) return { ok: false }
|
|
75
78
|
try {
|
|
76
79
|
const parsed: unknown = JSON.parse(raw)
|
|
77
|
-
|
|
78
|
-
value.value = parsed as T
|
|
80
|
+
return options.validate && !options.validate(parsed) ? { ok: false } : { ok: true, value: parsed as T }
|
|
79
81
|
} catch {
|
|
80
|
-
|
|
82
|
+
return { ok: false }
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
|
|
84
|
-
|
|
86
|
+
// Nothing usable under the new key (never written, broken, rejected) starts
|
|
87
|
+
// from the initial value, not from what the previous key held.
|
|
88
|
+
function read(name: string | false | null | undefined, previous?: string | false | null) {
|
|
89
|
+
if (!name) return
|
|
90
|
+
const found = stored(name)
|
|
91
|
+
if (found.ok) value.value = found.value
|
|
92
|
+
else if (previous) value.value = fresh()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
watch(() => toValue(key), (name, previous) => read(name, previous), { immediate: true })
|
|
85
96
|
watch(
|
|
86
97
|
value,
|
|
87
98
|
(next) => {
|