@leavepulse/ui 0.69.0 → 0.70.1
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 +49 -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/LpRepeater-C1Cw27hj.css +21 -0
- package/dist/assets/{LpRollingText-BGnE6q9j.css → LpRollingText-dyOXedGO.css} +7 -0
- 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-6kxEivBI.js} +11 -5
- 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-Chv_Xf8q.js} +7 -7
- package/dist/chunks/LpAlert-C5igj802.js +119 -0
- package/dist/chunks/{LpAppShell-DPh1756q.js → LpAppShell-BiWByLmt.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-BBq1Er9T.js +126 -0
- package/dist/chunks/LpBreadcrumbs-JKEi_yFF.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-CbJMs-IK.js +189 -0
- package/dist/chunks/LpCollapse-CWS9M_lm.js +115 -0
- package/dist/chunks/{LpColorPicker-DtEfBAcK.js → LpColorPicker-B7HSIFmn.js} +11 -6
- 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-Cj96ENDC.js} +16 -7
- package/dist/chunks/LpCopyButton-DzHjnFs0.js +133 -0
- package/dist/chunks/LpCountdown-BQnmB4gq.js +231 -0
- package/dist/chunks/LpDataList-CHNYQw7h.js +121 -0
- package/dist/chunks/LpDatePicker-ac1_6o5P.js +169 -0
- package/dist/chunks/LpDateRangePicker-B15gYrZJ.js +127 -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-R4aYVtVV.js} +14 -6
- package/dist/chunks/LpEditable-Ws3DNQHn.js +238 -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-DDNg34tJ.js} +5 -3
- package/dist/chunks/{LpFileTreeNode-BXhTHaKW.js → LpFileTreeNode-DmPuYNZ_.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-C14Z9503.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-HvwUA5Ec.js} +27 -14
- package/dist/chunks/{LpHints-BUtBp9-w.js → LpHints-DxbPKOzQ.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-D64hywJI.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-DKK4ySZl.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-rmI1DjKN.js} +3 -3
- package/dist/chunks/{LpModal-hk1noDcr.js → LpModal-eBMt2Fqn.js} +8 -3
- package/dist/chunks/{LpModalNav-DQd2keZm.js → LpModalNav-DiyFCHiZ.js} +4 -4
- package/dist/chunks/{LpNotificationBell-X84PJIxD.js → LpNotificationBell-D-WhE5m2.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-GL2KOFK8.js} +26 -22
- package/dist/chunks/{LpPhoneInput-DP69HONS.js → LpPhoneInput-C60tx1tC.js} +12 -7
- package/dist/chunks/{LpPie-yLF9-aDR.js → LpPie-BgXZ3xmm.js} +159 -48
- package/dist/chunks/LpPipeline-B31h41Mu.js +329 -0
- package/dist/chunks/{LpPopover-DVnkHaOu.js → LpPopover-DmZ3wUtP.js} +13 -7
- package/dist/chunks/{LpProgress-CA86wxXu.js → LpProgress-CE-m5OpD.js} +23 -63
- package/dist/chunks/LpQuota-DaL8TgNe.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-yin9uQP7.js +73 -0
- package/dist/chunks/LpRepeater-D0S7jjwr.js +180 -0
- package/dist/chunks/LpRevealOnScroll-DcQjexLy.js +76 -0
- package/dist/chunks/{LpRollingText-DB7upf-5.js → LpRollingText-DiL717LK.js} +4 -3
- package/dist/chunks/LpScramble-TDO5Kdou.js +85 -0
- package/dist/chunks/LpSecretField-CfWyHv9m.js +316 -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-C_8cJtj6.js} +91 -111
- package/dist/chunks/LpShaderBackdrop-Bote0iuy.js +332 -0
- package/dist/chunks/LpShinyText-DX2ajtWq.js +48 -0
- package/dist/chunks/{LpSidebar-DfIiS-lB.js → LpSidebar-CZRXtvem.js} +5 -3
- package/dist/chunks/{LpSidebarNav-C5FDNI0t.js → LpSidebarNav-DBg1U1oW.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-DFKlPJgy.js} +38 -35
- package/dist/chunks/LpStatus-BsGLrpor.js +86 -0
- package/dist/chunks/LpStepMark-TrzOioa9.js +101 -0
- package/dist/chunks/LpStepper-BbnS5jUr.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-DZg80W-B.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-DxVZMtzN.js +416 -0
- package/dist/chunks/LpThemeStudioSection-BwhqYDLf.js +20 -0
- package/dist/chunks/LpThemeSwitcher-CadVBTBn.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-C17A1XCb.js} +1 -1
- package/dist/chunks/{LpTour-BUp3Jt2e.js → LpTour-CY2bFXge.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-Tew1Ykx4.js} +10 -9
- 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-DoHv0WMH.js +125 -0
- package/dist/chunks/RevealToggle-C-KzaQGJ.js +65 -0
- package/dist/chunks/StepConnector-37OgEOXA.js +41 -0
- package/dist/chunks/TourCard-BJ8i7dk0.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 +131 -160
- 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 +3 -1
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.d.ts +3 -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 +2 -2
- package/dist/components/LpPaletteHeader.vue.js +2 -2
- package/dist/components/LpPalettePreview.vue.js +1 -1
- package/dist/components/LpPaletteStats.vue.js +2 -2
- package/dist/components/LpPasswordInput.vue.d.ts +19 -2
- 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 +3 -1
- package/dist/components/LpRollingText.vue.js +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 +45 -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 +13 -0
- package/dist/composables/useField.js +26 -1
- package/dist/composables/useForm.js +32 -9
- 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 +64 -0
- package/dist/icons-all.js +1 -1
- package/dist/index.d.ts +98 -1
- package/dist/index.js +263 -105
- package/dist/theme/define.d.ts +12 -0
- package/dist/theme/studio.d.ts +90 -0
- package/package.json +8 -2
- package/src/component-names.ts +33 -0
- package/src/components/AttentionDot.vue +27 -0
- package/src/components/ContextMenuItems.vue +5 -2
- 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 +162 -171
- 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 +13 -3
- 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 +55 -104
- package/src/components/LpDateRangePicker.vue +51 -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 +11 -2
- package/src/components/LpDropzone.vue +34 -13
- package/src/components/LpEditable.vue +295 -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 +25 -18
- 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 +14 -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 +131 -0
- package/src/components/LpRevealOnScroll.vue +125 -0
- package/src/components/LpRollingText.vue +12 -1
- package/src/components/LpScramble.vue +107 -0
- package/src/components/LpSecretField.vue +320 -0
- package/src/components/LpSegmented.vue +3 -10
- package/src/components/LpSelect.vue +74 -65
- 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 +59 -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 +38 -1
- package/src/composables/useForm.ts +41 -11
- 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 +96 -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)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, inject, provide, useId, type ComputedRef, type InjectionKey } from "vue"
|
|
1
|
+
import { computed, inject, onScopeDispose, provide, useId, type ComputedRef, type InjectionKey } from "vue"
|
|
2
2
|
import type { FieldNoticeOptions } from "./useFieldNotice"
|
|
3
3
|
|
|
4
4
|
/*
|
|
@@ -81,3 +81,40 @@ 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
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Trigger and portalled content share a boundary for the control's public blur. */
|
|
98
|
+
export function useFieldBlur(blur: (event: FocusEvent) => void) {
|
|
99
|
+
const partId = `lp-field-${useId()}`
|
|
100
|
+
let cancelPending: (() => void) | undefined
|
|
101
|
+
const inside = (target: EventTarget | null) =>
|
|
102
|
+
!!(target as Element | null)?.closest?.(`[data-lp-field-part="${partId}"]`)
|
|
103
|
+
|
|
104
|
+
function onFocusOut(event: FocusEvent) {
|
|
105
|
+
cancelPending?.()
|
|
106
|
+
if (inside(event.relatedTarget)) return
|
|
107
|
+
if (event.relatedTarget) return blur(event)
|
|
108
|
+
// A removed calendar cell can lose focus before its trigger gets it back.
|
|
109
|
+
const doc = (event.target as Node).ownerDocument!
|
|
110
|
+
const view = doc.defaultView!
|
|
111
|
+
const frame = view.requestAnimationFrame(() => {
|
|
112
|
+
cancelPending = undefined
|
|
113
|
+
if (!inside(doc.activeElement)) blur(event)
|
|
114
|
+
})
|
|
115
|
+
cancelPending = () => view.cancelAnimationFrame(frame)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
onScopeDispose(() => cancelPending?.())
|
|
119
|
+
return { partId, onFocusOut }
|
|
120
|
+
}
|
|
@@ -119,7 +119,11 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
|
|
|
119
119
|
const validating = ref(false)
|
|
120
120
|
const submitting = ref(false)
|
|
121
121
|
const submitCount = ref(0)
|
|
122
|
-
const runs = new Map<keyof T | "form",
|
|
122
|
+
const runs = new Map<keyof T | "form", symbol>()
|
|
123
|
+
let validationEpoch = 0
|
|
124
|
+
// What the form-level check set last, so its next run can take back what no
|
|
125
|
+
// longer holds without touching an error a field rule set on the same key.
|
|
126
|
+
const fromForm = new Map<keyof T | "form", string>()
|
|
123
127
|
const baseVersion = ref(0)
|
|
124
128
|
|
|
125
129
|
const keys = (): (keyof T)[] => Object.keys(plain()) as (keyof T)[]
|
|
@@ -145,7 +149,7 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
|
|
|
145
149
|
}
|
|
146
150
|
|
|
147
151
|
async function runField(name: keyof T): Promise<boolean> {
|
|
148
|
-
const run = (
|
|
152
|
+
const run = Symbol()
|
|
149
153
|
runs.set(name, run)
|
|
150
154
|
const found = await checkField(name)
|
|
151
155
|
if (runs.get(name) !== run) return !errors[name]
|
|
@@ -156,27 +160,35 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
|
|
|
156
160
|
|
|
157
161
|
async function runForm(): Promise<boolean> {
|
|
158
162
|
if (!options.validate) return true
|
|
159
|
-
const run = (
|
|
163
|
+
const run = Symbol()
|
|
160
164
|
runs.set("form", run)
|
|
161
165
|
const found = (await options.validate(plain())) ?? {}
|
|
162
166
|
if (runs.get("form") !== run) return true
|
|
167
|
+
for (const [name, text] of fromForm) if (errors[name] === text) delete errors[name]
|
|
168
|
+
fromForm.clear()
|
|
163
169
|
let ok = true
|
|
164
170
|
for (const [name, text] of Object.entries(found) as [keyof T | "form", string | undefined][]) {
|
|
165
171
|
if (!text) continue
|
|
166
172
|
ok = false
|
|
167
|
-
if (
|
|
173
|
+
if (errors[name]) continue
|
|
174
|
+
errors[name] = text
|
|
175
|
+
fromForm.set(name, text)
|
|
168
176
|
}
|
|
169
177
|
return ok
|
|
170
178
|
}
|
|
171
179
|
|
|
172
180
|
async function validate(name?: keyof T): Promise<boolean> {
|
|
181
|
+
const epoch = validationEpoch
|
|
173
182
|
validating.value = true
|
|
174
183
|
try {
|
|
175
|
-
if (name !== undefined)
|
|
176
|
-
|
|
184
|
+
if (name !== undefined) {
|
|
185
|
+
const valid = await runField(name)
|
|
186
|
+
return epoch === validationEpoch && valid
|
|
187
|
+
}
|
|
177
188
|
const fields = await Promise.all(keys().map(runField))
|
|
189
|
+
if (epoch !== validationEpoch) return false
|
|
178
190
|
const form = await runForm()
|
|
179
|
-
return fields.every(Boolean) && form && !Object.values(external).some(Boolean)
|
|
191
|
+
return epoch === validationEpoch && fields.every(Boolean) && form && !Object.values(external).some(Boolean)
|
|
180
192
|
} finally {
|
|
181
193
|
validating.value = false
|
|
182
194
|
}
|
|
@@ -196,10 +208,12 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
|
|
|
196
208
|
}
|
|
197
209
|
|
|
198
210
|
function clearState(): void {
|
|
211
|
+
validationEpoch++
|
|
199
212
|
for (const k of Object.keys(errors)) delete errors[k as keyof T]
|
|
200
213
|
for (const k of Object.keys(external)) delete external[k as keyof T]
|
|
201
214
|
for (const k of Object.keys(touched)) delete touched[k as keyof T]
|
|
202
215
|
runs.clear()
|
|
216
|
+
fromForm.clear()
|
|
203
217
|
submitCount.value = 0
|
|
204
218
|
}
|
|
205
219
|
|
|
@@ -231,18 +245,29 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
|
|
|
231
245
|
{ deep: true },
|
|
232
246
|
)
|
|
233
247
|
|
|
234
|
-
// An edit re-checks a field already touched,
|
|
248
|
+
// An edit re-checks a field already touched, drops an external error on it,
|
|
249
|
+
// and runs the form-level check again once it has run: a cross-field error
|
|
250
|
+
// ("passwords differ") must clear when either field is fixed.
|
|
251
|
+
async function recheck(names: (keyof T)[]): Promise<void> {
|
|
252
|
+
const epoch = validationEpoch
|
|
253
|
+
await Promise.all(names.map((name) => validate(name)))
|
|
254
|
+
if (epoch !== validationEpoch) return
|
|
255
|
+
if (fromForm.size || submitCount.value) await runForm()
|
|
256
|
+
}
|
|
257
|
+
|
|
235
258
|
let snapshot = clone(plain())
|
|
236
259
|
watch(
|
|
237
260
|
plain,
|
|
238
261
|
() => {
|
|
239
262
|
const now = clone(plain())
|
|
263
|
+
const changed: (keyof T)[] = []
|
|
240
264
|
for (const name of Object.keys(now) as (keyof T)[]) {
|
|
241
265
|
if (deepEqual(now[name], snapshot[name])) continue
|
|
242
266
|
if (external[name]) delete external[name]
|
|
243
|
-
if (touched[name] || submitCount.value)
|
|
267
|
+
if (touched[name] || submitCount.value) changed.push(name)
|
|
244
268
|
}
|
|
245
269
|
snapshot = now
|
|
270
|
+
if (changed.length) void recheck(changed)
|
|
246
271
|
},
|
|
247
272
|
{ deep: true },
|
|
248
273
|
)
|
|
@@ -278,8 +303,13 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
|
|
|
278
303
|
try {
|
|
279
304
|
submitCount.value++
|
|
280
305
|
for (const name of keys()) touched[name] = true
|
|
281
|
-
|
|
282
|
-
|
|
306
|
+
// The values that passed are the values sent: an edit made while an
|
|
307
|
+
// async rule was running is checked again before anything goes out.
|
|
308
|
+
let submitted: T
|
|
309
|
+
do {
|
|
310
|
+
submitted = clone(plain())
|
|
311
|
+
if (!(await validate())) return
|
|
312
|
+
} while (!deepEqual(plain(), submitted))
|
|
283
313
|
await fn(submitted)
|
|
284
314
|
if (deepEqual(plain(), submitted)) {
|
|
285
315
|
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
|
+
}
|