@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.
Files changed (640) hide show
  1. package/README.md +49 -0
  2. package/dist/assets/LoaderMark-DxBV2iDJ.css +43 -0
  3. package/dist/assets/LpActivityLine-B3EwNQEB.css +33 -0
  4. package/dist/assets/{LpCommandPalette-DxyjhYn_.css → LpCommandPalette-u-zOxKWa.css} +21 -31
  5. package/dist/assets/LpDock-Cbh0wQUv.css +17 -0
  6. package/dist/assets/{LpDropzone-Qb8k7cg4.css → LpDropzone-Bb1vBTeU.css} +12 -17
  7. package/dist/assets/{LpImageEditor-CElPDzpF.css → LpImageEditor-XM5bAziW.css} +6 -6
  8. package/dist/assets/LpLogViewer-BDNW6P52.css +38 -0
  9. package/dist/assets/{LpPalettePreview-CJmAKgYW.css → LpPalettePreview-B9VKDrvA.css} +2 -7
  10. package/dist/assets/LpRepeater-C1Cw27hj.css +21 -0
  11. package/dist/assets/{LpRollingText-BGnE6q9j.css → LpRollingText-dyOXedGO.css} +7 -0
  12. package/dist/assets/LpShinyText-D69cBYDR.css +61 -0
  13. package/dist/assets/LpStatus-430OGTZY.css +17 -0
  14. package/dist/assets/LpStepMark-Drvx33Vg.css +10 -0
  15. package/dist/assets/{LpTable-BPERfJIk.css → LpTable-DHxEQ_-3.css} +19 -19
  16. package/dist/assets/LpTabs-BJ-nrJuI.css +18 -0
  17. package/dist/assets/LpTextReveal-DjI1MsDS.css +35 -0
  18. package/dist/assets/LpTypewriter-CXAm9Nca.css +27 -0
  19. package/dist/chunks/AttentionDot-DCBf4aWg.js +25 -0
  20. package/dist/chunks/{ContextMenuItems-B7XFHksm.js → ContextMenuItems-6kxEivBI.js} +11 -5
  21. package/dist/chunks/{DropzoneLinkField-CjkrpDqO.js → DropzoneLinkField-Bw8Flkhk.js} +2 -2
  22. package/dist/chunks/FieldWrapper-BiennROB.js +43 -0
  23. package/dist/chunks/{LayoutCanvas-DF9CovaK.js → LayoutCanvas-BsJIzhdd.js} +1 -1
  24. package/dist/chunks/{LayoutNode-D9bIFzk0.js → LayoutNode-DJVhkSGk.js} +6 -8
  25. package/dist/chunks/LegendSwatch-BhRjrtzv.js +18 -0
  26. package/dist/chunks/LoaderMark-C1dubiKn.js +85 -0
  27. package/dist/chunks/{LpActionBar-MPKMu3pz.js → LpActionBar-CzOFar5t.js} +14 -20
  28. package/dist/chunks/LpActivityLine-CJvzrNpj.js +95 -0
  29. package/dist/chunks/{LpAddressInput-DG0BTqdH.js → LpAddressInput-Chv_Xf8q.js} +7 -7
  30. package/dist/chunks/LpAlert-C5igj802.js +119 -0
  31. package/dist/chunks/{LpAppShell-DPh1756q.js → LpAppShell-BiWByLmt.js} +15 -16
  32. package/dist/chunks/{LpAutocomplete-Dgn6hw3p.js → LpAutocomplete-D92niDHE.js} +15 -32
  33. package/dist/chunks/{LpAvatar-Bs0Fb1lC.js → LpAvatar-CLPl-qOF.js} +18 -6
  34. package/dist/chunks/LpBadge-BBq1Er9T.js +126 -0
  35. package/dist/chunks/LpBreadcrumbs-JKEi_yFF.js +144 -0
  36. package/dist/chunks/{LpButton-DNR-YAq5.js → LpButton-CVxBfGjd.js} +12 -4
  37. package/dist/chunks/{LpCalendar-BLya39Fh.js → LpCalendar-pVL91-Pl.js} +1 -1
  38. package/dist/chunks/{LpCard-Bf71NXZq.js → LpCard-CqKLnEhl.js} +18 -8
  39. package/dist/chunks/{LpCheckbox-Da5oZPHo.js → LpCheckbox-DHc2bHDe.js} +9 -6
  40. package/dist/chunks/{LpCodeBlock-lLbmslfs.js → LpCodeBlock-BGsQhxw8.js} +24 -32
  41. package/dist/chunks/LpCodeDisplay-CbJMs-IK.js +189 -0
  42. package/dist/chunks/LpCollapse-CWS9M_lm.js +115 -0
  43. package/dist/chunks/{LpColorPicker-DtEfBAcK.js → LpColorPicker-B7HSIFmn.js} +11 -6
  44. package/dist/chunks/{LpConfirmDialog-D7s4jakB.js → LpConfirmDialog-CfGCux8m.js} +3 -3
  45. package/dist/chunks/{LpConfirmHost-DrYI5y1K.js → LpConfirmHost-exB7JmcO.js} +1 -1
  46. package/dist/chunks/{LpContextMenu-CRnjoqSt.js → LpContextMenu-Cj96ENDC.js} +16 -7
  47. package/dist/chunks/LpCopyButton-DzHjnFs0.js +133 -0
  48. package/dist/chunks/LpCountdown-BQnmB4gq.js +231 -0
  49. package/dist/chunks/LpDataList-CHNYQw7h.js +121 -0
  50. package/dist/chunks/LpDatePicker-ac1_6o5P.js +169 -0
  51. package/dist/chunks/LpDateRangePicker-B15gYrZJ.js +127 -0
  52. package/dist/chunks/{LpDisclosure-6AplYBub.js → LpDisclosure-DOarzlAG.js} +22 -11
  53. package/dist/chunks/{LpDrawer-Q4SOxM4s.js → LpDrawer-DGNKRsGq.js} +9 -4
  54. package/dist/chunks/{LpDropdownMenu-DWTt0ooM.js → LpDropdownMenu-R4aYVtVV.js} +14 -6
  55. package/dist/chunks/LpEditable-Ws3DNQHn.js +238 -0
  56. package/dist/chunks/{LpEmptyState-om1UnFjV.js → LpEmptyState-KTKipws2.js} +1 -1
  57. package/dist/chunks/{LpFieldNotice-CHuDeLjc.js → LpFieldNotice-CurbNuXI.js} +1 -1
  58. package/dist/chunks/{LpFileTree-CW24YsVX.js → LpFileTree-DDNg34tJ.js} +5 -3
  59. package/dist/chunks/{LpFileTreeNode-BXhTHaKW.js → LpFileTreeNode-DmPuYNZ_.js} +42 -55
  60. package/dist/chunks/{LpFormField-DPb47HEm.js → LpFormField-BWwgm1E4.js} +23 -9
  61. package/dist/chunks/LpFrame-BVQCQLDI.js +155 -0
  62. package/dist/chunks/LpGauge-C14Z9503.js +204 -0
  63. package/dist/chunks/LpGlowBorder-B8V-QzIl.js +40 -0
  64. package/dist/chunks/{LpHintExample-C1eKYdeL.js → LpHintExample-9Py0wc-L.js} +11 -34
  65. package/dist/chunks/{LpHintLayer-7UOdWQG3.js → LpHintLayer-HvwUA5Ec.js} +27 -14
  66. package/dist/chunks/{LpHints-BUtBp9-w.js → LpHints-DxbPKOzQ.js} +3 -3
  67. package/dist/chunks/LpHoverCard-spGxyw7T.js +130 -0
  68. package/dist/chunks/{LpIcon-6tSrZ3Zy.js → LpIcon-HJP5xMz4.js} +1 -1
  69. package/dist/chunks/LpIdenticon-BzdC_qz9.js +63 -0
  70. package/dist/chunks/{LpInfraNode-Ds_8SZgN.js → LpInfraNode-D64hywJI.js} +39 -25
  71. package/dist/chunks/{LpInput-CUyr6PIE.js → LpInput-CDuwODJG.js} +44 -18
  72. package/dist/chunks/{LpLightbox-B9J8rzsj.js → LpLightbox-XJ0LpTcZ.js} +69 -69
  73. package/dist/chunks/LpLoader-DjJEX8hL.js +92 -0
  74. package/dist/chunks/{LpLogViewer-4QFc-sVN.js → LpLogViewer-DKK4ySZl.js} +225 -129
  75. package/dist/chunks/LpMagnetic-CtXKLa2Y.js +62 -0
  76. package/dist/chunks/{LpMapPicker-BTbkFeSG.js → LpMapPicker-BtIl7NOM.js} +34 -31
  77. package/dist/chunks/LpMasonry--k0571qG.js +185 -0
  78. package/dist/chunks/{LpMediaGallery-CuPcscx3.js → LpMediaGallery-rmI1DjKN.js} +3 -3
  79. package/dist/chunks/{LpModal-hk1noDcr.js → LpModal-eBMt2Fqn.js} +8 -3
  80. package/dist/chunks/{LpModalNav-DQd2keZm.js → LpModalNav-DiyFCHiZ.js} +4 -4
  81. package/dist/chunks/{LpNotificationBell-X84PJIxD.js → LpNotificationBell-D-WhE5m2.js} +32 -48
  82. package/dist/chunks/LpNumberField-B9k-h2HE.js +140 -0
  83. package/dist/chunks/{LpNumberFlow-z4WPZcZu.js → LpNumberFlow-Cfyh033Q.js} +8 -28
  84. package/dist/chunks/{LpOtpInput-PbeNJ5In.js → LpOtpInput-CeBPsM7D.js} +2 -1
  85. package/dist/chunks/{LpPagination-Bx-VG4Ag.js → LpPagination-VNCX6pFC.js} +37 -6
  86. package/dist/chunks/{LpPalettePreview-CFav_7co.js → LpPalettePreview-BNB8T_MY.js} +1 -1
  87. package/dist/chunks/{LpPasswordInput-CbnXitAS.js → LpPasswordInput-GL2KOFK8.js} +26 -22
  88. package/dist/chunks/{LpPhoneInput-DP69HONS.js → LpPhoneInput-C60tx1tC.js} +12 -7
  89. package/dist/chunks/{LpPie-yLF9-aDR.js → LpPie-BgXZ3xmm.js} +159 -48
  90. package/dist/chunks/LpPipeline-B31h41Mu.js +329 -0
  91. package/dist/chunks/{LpPopover-DVnkHaOu.js → LpPopover-DmZ3wUtP.js} +13 -7
  92. package/dist/chunks/{LpProgress-CA86wxXu.js → LpProgress-CE-m5OpD.js} +23 -63
  93. package/dist/chunks/LpQuota-DaL8TgNe.js +162 -0
  94. package/dist/chunks/{LpRadioGroup-8S5hf8lE.js → LpRadioGroup-CGBr5nIp.js} +1 -1
  95. package/dist/chunks/{LpRangeCalendar-ByQi-WUz.js → LpRangeCalendar-c5qj7YcC.js} +1 -1
  96. package/dist/chunks/{LpRangeSlider-C1yF9-nP.js → LpRangeSlider-Dv6lNvHY.js} +6 -2
  97. package/dist/chunks/LpRelativeTime-yin9uQP7.js +73 -0
  98. package/dist/chunks/LpRepeater-D0S7jjwr.js +180 -0
  99. package/dist/chunks/LpRevealOnScroll-DcQjexLy.js +76 -0
  100. package/dist/chunks/{LpRollingText-DB7upf-5.js → LpRollingText-DiL717LK.js} +4 -3
  101. package/dist/chunks/LpScramble-TDO5Kdou.js +85 -0
  102. package/dist/chunks/LpSecretField-CfWyHv9m.js +316 -0
  103. package/dist/chunks/{LpSection-BmRXe6ZP.js → LpSection-BW4xJ-Li.js} +1 -1
  104. package/dist/chunks/{LpSegmented-CW3bFHM4.js → LpSegmented-BO99Sr4m.js} +11 -14
  105. package/dist/chunks/{LpSelect-BCyDYoih.js → LpSelect-C_8cJtj6.js} +91 -111
  106. package/dist/chunks/LpShaderBackdrop-Bote0iuy.js +332 -0
  107. package/dist/chunks/LpShinyText-DX2ajtWq.js +48 -0
  108. package/dist/chunks/{LpSidebar-DfIiS-lB.js → LpSidebar-CZRXtvem.js} +5 -3
  109. package/dist/chunks/{LpSidebarNav-C5FDNI0t.js → LpSidebarNav-DBg1U1oW.js} +3 -3
  110. package/dist/chunks/{LpSlider-Cuzmrd7p.js → LpSlider-DuoRpOrH.js} +6 -2
  111. package/dist/chunks/{LpSparkline-DXT4KrSi.js → LpSparkline-CExIRDb3.js} +32 -27
  112. package/dist/chunks/LpSpotlight-4F3NeUMY.js +41 -0
  113. package/dist/chunks/{LpStat-iBMF5QNk.js → LpStat-DFKlPJgy.js} +38 -35
  114. package/dist/chunks/LpStatus-BsGLrpor.js +86 -0
  115. package/dist/chunks/LpStepMark-TrzOioa9.js +101 -0
  116. package/dist/chunks/LpStepper-BbnS5jUr.js +76 -0
  117. package/dist/chunks/{LpSwap-D82jiecV.js → LpSwap-DzE6M3Mg.js} +1 -1
  118. package/dist/chunks/LpSwitch-DrzKrZZV.js +112 -0
  119. package/dist/chunks/{LpTableFilter-CuIbgdW0.js → LpTableFilter-DyvHGbOk.js} +3 -2
  120. package/dist/chunks/{LpTableOfContents-C_4VBktT.js → LpTableOfContents-rYvo8z5T.js} +34 -21
  121. package/dist/chunks/{LpTagsInput-DiXrpYeE.js → LpTagsInput-DZg80W-B.js} +68 -71
  122. package/dist/chunks/LpTextReveal-CtAnOUkp.js +140 -0
  123. package/dist/chunks/{LpTextarea-xoQd5Lnp.js → LpTextarea-DiWjeVoJ.js} +12 -11
  124. package/dist/chunks/{LpThemeProvider-CQ2jC9TA.js → LpThemeProvider-DcbfOxsU.js} +1 -1
  125. package/dist/chunks/LpThemeStudio-DxVZMtzN.js +416 -0
  126. package/dist/chunks/LpThemeStudioSection-BwhqYDLf.js +20 -0
  127. package/dist/chunks/LpThemeSwitcher-CadVBTBn.js +278 -0
  128. package/dist/chunks/{LpTimeField-MFgAsNc5.js → LpTimeField-BFQVf_PW.js} +13 -5
  129. package/dist/chunks/{LpToaster-C4zE9zB7.js → LpToaster-Wv9ksj4q.js} +49 -82
  130. package/dist/chunks/{LpTooltip-suFCM34F.js → LpTooltip-Cz8SQwRt.js} +8 -3
  131. package/dist/chunks/{LpTopologyCanvas-6ozNrQKh.js → LpTopologyCanvas-C17A1XCb.js} +1 -1
  132. package/dist/chunks/{LpTour-BUp3Jt2e.js → LpTour-CY2bFXge.js} +24 -18
  133. package/dist/chunks/LpTruncate-D_5Pfv5B.js +112 -0
  134. package/dist/chunks/LpTypewriter-BCL_a6wE.js +159 -0
  135. package/dist/chunks/{LpUptimeBar-B6kHw4Cj.js → LpUptimeBar-Tew1Ykx4.js} +10 -9
  136. package/dist/chunks/{LpVideoBackdrop-qWRYBOTQ.js → LpVideoBackdrop-DS1wGv-X.js} +7 -8
  137. package/dist/chunks/{MenuTitle-obgek0B7.js → MenuTitle-BjQZX8tQ.js} +1 -1
  138. package/dist/chunks/OptionContent-CmYUUeWj.js +40 -0
  139. package/dist/chunks/PickerField-DoHv0WMH.js +125 -0
  140. package/dist/chunks/RevealToggle-C-KzaQGJ.js +65 -0
  141. package/dist/chunks/StepConnector-37OgEOXA.js +41 -0
  142. package/dist/chunks/TourCard-BJ8i7dk0.js +123 -0
  143. package/dist/chunks/backdrop-Dkf2vM81.js +59 -0
  144. package/dist/chunks/{icons-DVhZ_plB.js → icons-4IywzkHk.js} +1 -1
  145. package/dist/chunks/{useTheme-2qHG8NEU.js → useTheme-BOV_-AiY.js} +9 -65
  146. package/dist/component-names.d.ts +1 -1
  147. package/dist/components/AttentionDot.vue.d.ts +9 -0
  148. package/dist/components/AttentionDot.vue.js +4 -0
  149. package/dist/components/ContextMenuItems.vue.js +1 -1
  150. package/dist/components/DropzoneLinkField.vue.js +1 -1
  151. package/dist/components/FieldWrapper.vue.d.ts +25 -0
  152. package/dist/components/FieldWrapper.vue.js +4 -0
  153. package/dist/components/LayoutCanvas.vue.js +1 -1
  154. package/dist/components/LayoutNode.vue.d.ts +4 -4
  155. package/dist/components/LayoutNode.vue.js +1 -1
  156. package/dist/components/LegendSwatch.vue.d.ts +6 -0
  157. package/dist/components/LegendSwatch.vue.js +4 -0
  158. package/dist/components/LoaderMark.vue.d.ts +12 -0
  159. package/dist/components/LoaderMark.vue.js +4 -0
  160. package/dist/components/LpActionBar.vue.js +1 -1
  161. package/dist/components/LpActivityLine.vue.d.ts +58 -0
  162. package/dist/components/LpActivityLine.vue.js +4 -0
  163. package/dist/components/LpAddressInput.vue.js +1 -1
  164. package/dist/components/LpAlert.vue.d.ts +2 -2
  165. package/dist/components/LpAlert.vue.js +1 -1
  166. package/dist/components/LpAppShell.vue.d.ts +8 -7
  167. package/dist/components/LpAppShell.vue.js +1 -1
  168. package/dist/components/LpAutocomplete.vue.js +1 -1
  169. package/dist/components/LpAvatar.vue.d.ts +16 -0
  170. package/dist/components/LpAvatar.vue.js +1 -1
  171. package/dist/components/LpBadge.vue.d.ts +19 -5
  172. package/dist/components/LpBadge.vue.js +1 -1
  173. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  174. package/dist/components/LpButton.vue.d.ts +2 -2
  175. package/dist/components/LpButton.vue.js +1 -1
  176. package/dist/components/LpCalendar.vue.js +1 -1
  177. package/dist/components/LpCard.vue.d.ts +24 -0
  178. package/dist/components/LpCard.vue.js +1 -1
  179. package/dist/components/LpChart.vue.js +131 -160
  180. package/dist/components/LpCheckbox.vue.d.ts +2 -2
  181. package/dist/components/LpCheckbox.vue.js +1 -1
  182. package/dist/components/LpCodeBlock.vue.js +1 -1
  183. package/dist/components/LpCodeDisplay.vue.d.ts +89 -0
  184. package/dist/components/LpCodeDisplay.vue.js +4 -0
  185. package/dist/components/LpCollapse.vue.d.ts +58 -0
  186. package/dist/components/LpCollapse.vue.js +4 -0
  187. package/dist/components/LpColorPicker.vue.d.ts +2 -2
  188. package/dist/components/LpColorPicker.vue.js +1 -1
  189. package/dist/components/LpCommandPalette.vue.js +34 -23
  190. package/dist/components/LpConfirmDialog.vue.js +1 -1
  191. package/dist/components/LpConfirmHost.vue.js +1 -1
  192. package/dist/components/LpContextMenu.vue.js +1 -1
  193. package/dist/components/LpCopyButton.vue.d.ts +82 -0
  194. package/dist/components/LpCopyButton.vue.js +4 -0
  195. package/dist/components/LpCountdown.vue.d.ts +100 -0
  196. package/dist/components/LpCountdown.vue.js +4 -0
  197. package/dist/components/LpDataList.vue.d.ts +66 -0
  198. package/dist/components/LpDataList.vue.js +4 -0
  199. package/dist/components/LpDatePicker.vue.d.ts +3 -1
  200. package/dist/components/LpDatePicker.vue.js +1 -1
  201. package/dist/components/LpDateRangePicker.vue.d.ts +3 -1
  202. package/dist/components/LpDateRangePicker.vue.js +1 -1
  203. package/dist/components/LpDisclosure.vue.js +1 -1
  204. package/dist/components/LpDock.vue.d.ts +74 -0
  205. package/dist/components/LpDock.vue.js +204 -0
  206. package/dist/components/LpDrawer.vue.js +1 -1
  207. package/dist/components/LpDropdownMenu.vue.js +1 -1
  208. package/dist/components/LpDropzone.vue.d.ts +8 -1
  209. package/dist/components/LpDropzone.vue.js +36 -15
  210. package/dist/components/LpEditable.vue.d.ts +85 -0
  211. package/dist/components/LpEditable.vue.js +4 -0
  212. package/dist/components/LpEmptyState.vue.js +1 -1
  213. package/dist/components/LpFieldNotice.vue.js +1 -1
  214. package/dist/components/LpFileTree.vue.js +1 -1
  215. package/dist/components/LpFileTreeNode.vue.js +1 -1
  216. package/dist/components/LpFormField.vue.js +1 -1
  217. package/dist/components/LpFrame.vue.d.ts +22 -0
  218. package/dist/components/LpFrame.vue.js +4 -0
  219. package/dist/components/LpGauge.vue.d.ts +116 -0
  220. package/dist/components/LpGauge.vue.js +4 -0
  221. package/dist/components/LpGlowBorder.vue.d.ts +47 -0
  222. package/dist/components/LpGlowBorder.vue.js +4 -0
  223. package/dist/components/LpHintExample.vue.d.ts +2 -2
  224. package/dist/components/LpHintExample.vue.js +1 -1
  225. package/dist/components/LpHintLayer.vue.js +1 -1
  226. package/dist/components/LpHints.vue.js +1 -1
  227. package/dist/components/LpHoverCard.vue.d.ts +60 -0
  228. package/dist/components/LpHoverCard.vue.js +4 -0
  229. package/dist/components/LpIcon.vue.js +1 -1
  230. package/dist/components/LpIdenticon.vue.d.ts +37 -0
  231. package/dist/components/LpIdenticon.vue.js +4 -0
  232. package/dist/components/LpImageEditor.vue.js +13 -19
  233. package/dist/components/LpInfraNode.vue.js +1 -1
  234. package/dist/components/LpInput.vue.d.ts +10 -0
  235. package/dist/components/LpInput.vue.js +1 -1
  236. package/dist/components/LpLightbox.vue.js +1 -1
  237. package/dist/components/LpLoader.vue.d.ts +106 -0
  238. package/dist/components/LpLoader.vue.js +4 -0
  239. package/dist/components/LpLogViewer.vue.js +1 -1
  240. package/dist/components/LpMagnetic.vue.d.ts +28 -0
  241. package/dist/components/LpMagnetic.vue.js +4 -0
  242. package/dist/components/LpMapPicker.vue.js +1 -1
  243. package/dist/components/LpMasonry.vue.d.ts +42 -0
  244. package/dist/components/LpMasonry.vue.js +4 -0
  245. package/dist/components/LpMediaGallery.vue.js +1 -1
  246. package/dist/components/LpModal.vue.js +1 -1
  247. package/dist/components/LpModalNav.vue.js +1 -1
  248. package/dist/components/LpNotificationBell.vue.d.ts +15 -6
  249. package/dist/components/LpNotificationBell.vue.js +1 -1
  250. package/dist/components/LpNumberField.vue.d.ts +57 -0
  251. package/dist/components/LpNumberField.vue.js +1 -1
  252. package/dist/components/LpNumberFlow.vue.js +1 -1
  253. package/dist/components/LpOtpInput.vue.d.ts +12 -4
  254. package/dist/components/LpOtpInput.vue.js +1 -1
  255. package/dist/components/LpPagination.vue.js +1 -1
  256. package/dist/components/LpPaletteFacts.vue.js +2 -2
  257. package/dist/components/LpPaletteHeader.vue.js +2 -2
  258. package/dist/components/LpPalettePreview.vue.js +1 -1
  259. package/dist/components/LpPaletteStats.vue.js +2 -2
  260. package/dist/components/LpPasswordInput.vue.d.ts +19 -2
  261. package/dist/components/LpPasswordInput.vue.js +1 -1
  262. package/dist/components/LpPhoneInput.vue.js +1 -1
  263. package/dist/components/LpPie.vue.d.ts +3 -1
  264. package/dist/components/LpPie.vue.js +1 -1
  265. package/dist/components/LpPipeline.vue.d.ts +91 -0
  266. package/dist/components/LpPipeline.vue.js +4 -0
  267. package/dist/components/LpPopover.vue.d.ts +2 -2
  268. package/dist/components/LpPopover.vue.js +1 -1
  269. package/dist/components/LpProgress.vue.d.ts +11 -4
  270. package/dist/components/LpProgress.vue.js +1 -1
  271. package/dist/components/LpQuota.vue.d.ts +68 -0
  272. package/dist/components/LpQuota.vue.js +4 -0
  273. package/dist/components/LpRadioGroup.vue.js +1 -1
  274. package/dist/components/LpRangeCalendar.vue.js +1 -1
  275. package/dist/components/LpRangeSlider.vue.js +1 -1
  276. package/dist/components/LpRelativeTime.vue.d.ts +41 -0
  277. package/dist/components/LpRelativeTime.vue.js +4 -0
  278. package/dist/components/LpRepeater.vue.d.ts +48 -0
  279. package/dist/components/LpRepeater.vue.js +4 -0
  280. package/dist/components/LpRevealOnScroll.vue.d.ts +67 -0
  281. package/dist/components/LpRevealOnScroll.vue.js +4 -0
  282. package/dist/components/LpRollingText.vue.d.ts +3 -1
  283. package/dist/components/LpRollingText.vue.js +1 -1
  284. package/dist/components/LpScramble.vue.d.ts +55 -0
  285. package/dist/components/LpScramble.vue.js +4 -0
  286. package/dist/components/LpScrollArea.vue.d.ts +1 -1
  287. package/dist/components/LpSecretField.vue.d.ts +108 -0
  288. package/dist/components/LpSecretField.vue.js +4 -0
  289. package/dist/components/LpSection.vue.js +1 -1
  290. package/dist/components/LpSegmented.vue.js +1 -1
  291. package/dist/components/LpSelect.vue.d.ts +2 -0
  292. package/dist/components/LpSelect.vue.js +1 -1
  293. package/dist/components/LpShaderBackdrop.vue.d.ts +39 -0
  294. package/dist/components/LpShaderBackdrop.vue.js +4 -0
  295. package/dist/components/LpShinyText.vue.d.ts +39 -0
  296. package/dist/components/LpShinyText.vue.js +4 -0
  297. package/dist/components/LpShortcutsDialog.vue.js +1 -1
  298. package/dist/components/LpSidebar.vue.js +1 -1
  299. package/dist/components/LpSidebarNav.vue.js +1 -1
  300. package/dist/components/LpSlider.vue.js +1 -1
  301. package/dist/components/LpSparkline.vue.d.ts +4 -3
  302. package/dist/components/LpSparkline.vue.js +1 -1
  303. package/dist/components/LpSpotlight.vue.d.ts +37 -0
  304. package/dist/components/LpSpotlight.vue.js +4 -0
  305. package/dist/components/LpStat.vue.js +1 -1
  306. package/dist/components/LpStatus.vue.d.ts +65 -0
  307. package/dist/components/LpStatus.vue.js +4 -0
  308. package/dist/components/LpStepMark.vue.d.ts +31 -0
  309. package/dist/components/LpStepMark.vue.js +4 -0
  310. package/dist/components/LpStepper.vue.js +1 -1
  311. package/dist/components/LpSwap.vue.js +1 -1
  312. package/dist/components/LpSwitch.vue.d.ts +2 -2
  313. package/dist/components/LpSwitch.vue.js +1 -1
  314. package/dist/components/LpTable.vue.js +104 -120
  315. package/dist/components/LpTableFilter.vue.js +1 -1
  316. package/dist/components/LpTableOfContents.vue.d.ts +6 -1
  317. package/dist/components/LpTableOfContents.vue.js +1 -1
  318. package/dist/components/LpTabs.vue.d.ts +5 -5
  319. package/dist/components/LpTabs.vue.js +25 -33
  320. package/dist/components/LpTagsInput.vue.js +1 -1
  321. package/dist/components/LpTextReveal.vue.d.ts +58 -0
  322. package/dist/components/LpTextReveal.vue.js +4 -0
  323. package/dist/components/LpTextarea.vue.js +1 -1
  324. package/dist/components/LpThemeProvider.vue.js +1 -1
  325. package/dist/components/LpThemeStudio.vue.d.ts +54 -0
  326. package/dist/components/LpThemeStudio.vue.js +4 -0
  327. package/dist/components/LpThemeStudioSection.vue.d.ts +16 -0
  328. package/dist/components/LpThemeStudioSection.vue.js +4 -0
  329. package/dist/components/LpThemeSwitcher.vue.d.ts +32 -1
  330. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  331. package/dist/components/LpTimeField.vue.d.ts +2 -0
  332. package/dist/components/LpTimeField.vue.js +1 -1
  333. package/dist/components/LpToaster.vue.js +1 -1
  334. package/dist/components/LpTooltip.vue.d.ts +2 -2
  335. package/dist/components/LpTooltip.vue.js +1 -1
  336. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  337. package/dist/components/LpTour.vue.js +1 -1
  338. package/dist/components/LpTruncate.vue.d.ts +34 -0
  339. package/dist/components/LpTruncate.vue.js +4 -0
  340. package/dist/components/LpTypewriter.vue.d.ts +61 -0
  341. package/dist/components/LpTypewriter.vue.js +4 -0
  342. package/dist/components/LpUptimeBar.vue.js +1 -1
  343. package/dist/components/LpVideoBackdrop.vue.js +1 -1
  344. package/dist/components/MenuTitle.vue.js +1 -1
  345. package/dist/components/OptionContent.vue.d.ts +7 -0
  346. package/dist/components/OptionContent.vue.js +4 -0
  347. package/dist/components/PickerField.vue.d.ts +45 -0
  348. package/dist/components/PickerField.vue.js +4 -0
  349. package/dist/components/RevealToggle.vue.d.ts +21 -0
  350. package/dist/components/RevealToggle.vue.js +4 -0
  351. package/dist/components/StepConnector.vue.d.ts +19 -0
  352. package/dist/components/StepConnector.vue.js +4 -0
  353. package/dist/components/TourCard.vue.js +1 -1
  354. package/dist/components/badge.d.ts +1 -0
  355. package/dist/components/badge.js +4 -0
  356. package/dist/components/chart.d.ts +5 -1
  357. package/dist/components/chart.js +2 -21
  358. package/dist/components/dateRange.d.ts +11 -0
  359. package/dist/components/dateRange.js +12 -0
  360. package/dist/components/dock.d.ts +19 -0
  361. package/dist/components/dock.js +16 -0
  362. package/dist/components/dropdown.d.ts +10 -1
  363. package/dist/components/dropdown.js +7 -2
  364. package/dist/components/fieldText.d.ts +38 -0
  365. package/dist/components/fieldText.js +53 -0
  366. package/dist/components/fileTree.d.ts +7 -3
  367. package/dist/components/fileTree.js +12 -18
  368. package/dist/components/format.d.ts +41 -0
  369. package/dist/components/format.js +67 -0
  370. package/dist/components/identicon.d.ts +38 -0
  371. package/dist/components/identicon.js +85 -0
  372. package/dist/components/masonry.d.ts +32 -0
  373. package/dist/components/masonry.js +40 -0
  374. package/dist/components/menuWhen.js +1 -1
  375. package/dist/components/meter.d.ts +64 -0
  376. package/dist/components/meter.js +53 -0
  377. package/dist/components/otp.d.ts +1 -1
  378. package/dist/components/otp.js +7 -2
  379. package/dist/components/pipeline.d.ts +55 -0
  380. package/dist/components/pipeline.js +51 -0
  381. package/dist/components/quota.d.ts +57 -0
  382. package/dist/components/quota.js +40 -0
  383. package/dist/components/relativeTime.d.ts +24 -0
  384. package/dist/components/relativeTime.js +56 -0
  385. package/dist/components/reveal.d.ts +18 -0
  386. package/dist/components/reveal.js +32 -0
  387. package/dist/components/scramble.d.ts +68 -0
  388. package/dist/components/scramble.js +58 -0
  389. package/dist/components/shaderBackdrop.d.ts +20 -0
  390. package/dist/components/shaderBackdrop.js +156 -0
  391. package/dist/components/slider.d.ts +5 -0
  392. package/dist/components/slider.js +24 -0
  393. package/dist/components/slotText.d.ts +2 -0
  394. package/dist/components/slotText.js +9 -0
  395. package/dist/components/status.d.ts +14 -0
  396. package/dist/components/status.js +22 -0
  397. package/dist/components/tableFacets.d.ts +8 -0
  398. package/dist/components/tableFacets.js +19 -0
  399. package/dist/components/textSplit.d.ts +17 -0
  400. package/dist/components/textSplit.js +24 -0
  401. package/dist/components/timeSpan.d.ts +78 -0
  402. package/dist/components/timeSpan.js +85 -0
  403. package/dist/components/tone.d.ts +43 -0
  404. package/dist/components/tone.js +82 -0
  405. package/dist/components/tour.js +3 -2
  406. package/dist/components/typewriter.d.ts +36 -0
  407. package/dist/components/typewriter.js +53 -0
  408. package/dist/components/videoBackdrop.js +5 -6
  409. package/dist/composables/easing.d.ts +22 -1
  410. package/dist/composables/easing.js +42 -1
  411. package/dist/composables/pseudoLocale.d.ts +15 -0
  412. package/dist/composables/pseudoLocale.js +96 -0
  413. package/dist/composables/scroll.js +5 -3
  414. package/dist/composables/useClipboard.d.ts +4 -1
  415. package/dist/composables/useClipboard.js +12 -2
  416. package/dist/composables/useField.d.ts +13 -0
  417. package/dist/composables/useField.js +26 -1
  418. package/dist/composables/useForm.js +32 -9
  419. package/dist/composables/useInView.d.ts +11 -0
  420. package/dist/composables/useInView.js +39 -0
  421. package/dist/composables/useLoadingGate.d.ts +8 -0
  422. package/dist/composables/useLoadingGate.js +38 -0
  423. package/dist/composables/useLpConfig.d.ts +10 -0
  424. package/dist/composables/useLpConfig.js +11 -1
  425. package/dist/composables/useMediaQuery.js +3 -1
  426. package/dist/composables/useNow.d.ts +16 -0
  427. package/dist/composables/useNow.js +39 -0
  428. package/dist/composables/usePersisted.js +14 -7
  429. package/dist/composables/usePillTransition.d.ts +5 -2
  430. package/dist/composables/usePillTransition.js +4 -3
  431. package/dist/composables/usePointerFrame.d.ts +18 -0
  432. package/dist/composables/usePointerFrame.js +31 -0
  433. package/dist/composables/useReducedMotion.d.ts +2 -0
  434. package/dist/composables/useReducedMotion.js +7 -0
  435. package/dist/composables/useRepeater.d.ts +17 -0
  436. package/dist/composables/useRepeater.js +31 -0
  437. package/dist/composables/useResizeObserver.d.ts +8 -0
  438. package/dist/composables/useResizeObserver.js +33 -0
  439. package/dist/composables/useRevealedValue.d.ts +31 -0
  440. package/dist/composables/useRevealedValue.js +26 -0
  441. package/dist/composables/useScramble.d.ts +42 -0
  442. package/dist/composables/useScramble.js +76 -0
  443. package/dist/composables/useShift.js +2 -10
  444. package/dist/composables/useSpotlight.d.ts +18 -0
  445. package/dist/composables/useSpotlight.js +53 -0
  446. package/dist/composables/useTilt.js +26 -18
  447. package/dist/composables/useTween.d.ts +43 -0
  448. package/dist/composables/useTween.js +69 -0
  449. package/dist/composables/useView.d.ts +18 -0
  450. package/dist/composables/useView.js +64 -0
  451. package/dist/icons-all.js +1 -1
  452. package/dist/index.d.ts +98 -1
  453. package/dist/index.js +263 -105
  454. package/dist/theme/define.d.ts +12 -0
  455. package/dist/theme/studio.d.ts +90 -0
  456. package/package.json +8 -2
  457. package/src/component-names.ts +33 -0
  458. package/src/components/AttentionDot.vue +27 -0
  459. package/src/components/ContextMenuItems.vue +5 -2
  460. package/src/components/FieldWrapper.vue +38 -0
  461. package/src/components/LayoutNode.vue +5 -9
  462. package/src/components/LegendSwatch.vue +15 -0
  463. package/src/components/LoaderMark.vue +90 -0
  464. package/src/components/LpActionBar.vue +12 -20
  465. package/src/components/LpActivityLine.vue +186 -0
  466. package/src/components/LpAlert.vue +54 -24
  467. package/src/components/LpAppShell.vue +7 -11
  468. package/src/components/LpAutocomplete.vue +16 -14
  469. package/src/components/LpAvatar.vue +17 -2
  470. package/src/components/LpBadge.vue +77 -13
  471. package/src/components/LpBreadcrumbs.vue +101 -35
  472. package/src/components/LpButton.vue +15 -3
  473. package/src/components/LpCard.vue +30 -7
  474. package/src/components/LpChart.vue +162 -171
  475. package/src/components/LpCheckbox.vue +10 -4
  476. package/src/components/LpCodeBlock.vue +11 -19
  477. package/src/components/LpCodeDisplay.vue +228 -0
  478. package/src/components/LpCollapse.vue +162 -0
  479. package/src/components/LpColorPicker.vue +10 -1
  480. package/src/components/LpCommandPalette.vue +38 -24
  481. package/src/components/LpContextMenu.vue +13 -3
  482. package/src/components/LpCopyButton.vue +185 -0
  483. package/src/components/LpCountdown.vue +308 -0
  484. package/src/components/LpDataList.vue +154 -0
  485. package/src/components/LpDatePicker.vue +55 -104
  486. package/src/components/LpDateRangePicker.vue +51 -95
  487. package/src/components/LpDisclosure.vue +58 -36
  488. package/src/components/LpDock.vue +274 -0
  489. package/src/components/LpDrawer.vue +10 -1
  490. package/src/components/LpDropdownMenu.vue +11 -2
  491. package/src/components/LpDropzone.vue +34 -13
  492. package/src/components/LpEditable.vue +295 -0
  493. package/src/components/LpFileTree.vue +5 -1
  494. package/src/components/LpFileTreeNode.vue +25 -50
  495. package/src/components/LpFormField.vue +22 -3
  496. package/src/components/LpFrame.vue +200 -0
  497. package/src/components/LpGauge.vue +287 -0
  498. package/src/components/LpGlowBorder.vue +60 -0
  499. package/src/components/LpHintExample.vue +9 -32
  500. package/src/components/LpHintLayer.vue +13 -3
  501. package/src/components/LpHoverCard.vue +175 -0
  502. package/src/components/LpIdenticon.vue +69 -0
  503. package/src/components/LpImageEditor.vue +15 -18
  504. package/src/components/LpInfraNode.vue +27 -16
  505. package/src/components/LpInput.vue +30 -16
  506. package/src/components/LpLightbox.vue +44 -23
  507. package/src/components/LpLoader.vue +145 -0
  508. package/src/components/LpLogViewer.vue +311 -82
  509. package/src/components/LpMagnetic.vue +97 -0
  510. package/src/components/LpMapPicker.vue +26 -28
  511. package/src/components/LpMasonry.vue +247 -0
  512. package/src/components/LpModal.vue +10 -1
  513. package/src/components/LpNotificationBell.vue +33 -49
  514. package/src/components/LpNumberField.vue +79 -14
  515. package/src/components/LpNumberFlow.vue +10 -35
  516. package/src/components/LpOtpInput.vue +7 -2
  517. package/src/components/LpPagination.vue +38 -4
  518. package/src/components/LpPalettePreview.vue +2 -7
  519. package/src/components/LpPasswordInput.vue +25 -18
  520. package/src/components/LpPhoneInput.vue +12 -3
  521. package/src/components/LpPie.vue +197 -36
  522. package/src/components/LpPipeline.vue +366 -0
  523. package/src/components/LpPopover.vue +14 -1
  524. package/src/components/LpProgress.vue +22 -84
  525. package/src/components/LpQuota.vue +197 -0
  526. package/src/components/LpRangeSlider.vue +5 -1
  527. package/src/components/LpRelativeTime.vue +90 -0
  528. package/src/components/LpRepeater.vue +131 -0
  529. package/src/components/LpRevealOnScroll.vue +125 -0
  530. package/src/components/LpRollingText.vue +12 -1
  531. package/src/components/LpScramble.vue +107 -0
  532. package/src/components/LpSecretField.vue +320 -0
  533. package/src/components/LpSegmented.vue +3 -10
  534. package/src/components/LpSelect.vue +74 -65
  535. package/src/components/LpShaderBackdrop.vue +434 -0
  536. package/src/components/LpShinyText.vue +139 -0
  537. package/src/components/LpSidebar.vue +2 -0
  538. package/src/components/LpSlider.vue +5 -1
  539. package/src/components/LpSparkline.vue +55 -37
  540. package/src/components/LpSpotlight.vue +51 -0
  541. package/src/components/LpStat.vue +6 -2
  542. package/src/components/LpStatus.vue +127 -0
  543. package/src/components/LpStepMark.vue +138 -0
  544. package/src/components/LpStepper.vue +37 -43
  545. package/src/components/LpSwap.vue +2 -2
  546. package/src/components/LpSwitch.vue +15 -22
  547. package/src/components/LpTable.vue +47 -63
  548. package/src/components/LpTableFilter.vue +1 -0
  549. package/src/components/LpTableOfContents.vue +53 -29
  550. package/src/components/LpTabs.vue +10 -21
  551. package/src/components/LpTagsInput.vue +53 -37
  552. package/src/components/LpTextReveal.vue +236 -0
  553. package/src/components/LpTextarea.vue +11 -11
  554. package/src/components/LpThemeStudio.vue +216 -0
  555. package/src/components/LpThemeStudioSection.vue +16 -0
  556. package/src/components/LpThemeSwitcher.vue +156 -47
  557. package/src/components/LpTimeField.vue +9 -1
  558. package/src/components/LpToaster.vue +63 -83
  559. package/src/components/LpTooltip.vue +10 -1
  560. package/src/components/LpTour.vue +24 -13
  561. package/src/components/LpTruncate.vue +135 -0
  562. package/src/components/LpTypewriter.vue +236 -0
  563. package/src/components/LpUptimeBar.vue +6 -5
  564. package/src/components/LpVideoBackdrop.vue +15 -13
  565. package/src/components/OptionContent.vue +17 -0
  566. package/src/components/PickerField.vue +59 -0
  567. package/src/components/RevealToggle.vue +51 -0
  568. package/src/components/StepConnector.vue +54 -0
  569. package/src/components/TourCard.vue +29 -16
  570. package/src/components/badge.ts +8 -0
  571. package/src/components/chart.ts +8 -23
  572. package/src/components/dateRange.ts +25 -0
  573. package/src/components/dock.ts +31 -0
  574. package/src/components/dropdown.ts +15 -3
  575. package/src/components/fieldText.ts +95 -0
  576. package/src/components/fileTree.ts +19 -21
  577. package/src/components/format.ts +136 -0
  578. package/src/components/identicon.ts +160 -0
  579. package/src/components/masonry.ts +73 -0
  580. package/src/components/meter.ts +122 -0
  581. package/src/components/otp.ts +14 -3
  582. package/src/components/pipeline.ts +101 -0
  583. package/src/components/quota.ts +111 -0
  584. package/src/components/relativeTime.ts +118 -0
  585. package/src/components/reveal.ts +56 -0
  586. package/src/components/scramble.ts +138 -0
  587. package/src/components/shaderBackdrop.ts +208 -0
  588. package/src/components/slider.ts +30 -0
  589. package/src/components/slotText.ts +13 -0
  590. package/src/components/status.ts +46 -0
  591. package/src/components/tableFacets.ts +25 -0
  592. package/src/components/textSplit.ts +55 -0
  593. package/src/components/timeSpan.ts +183 -0
  594. package/src/components/tone.ts +144 -0
  595. package/src/components/tour.ts +3 -2
  596. package/src/components/typewriter.ts +104 -0
  597. package/src/components/videoBackdrop.ts +7 -6
  598. package/src/composables/easing.ts +66 -1
  599. package/src/composables/pseudoLocale.ts +105 -0
  600. package/src/composables/scroll.ts +6 -3
  601. package/src/composables/useClipboard.ts +25 -3
  602. package/src/composables/useField.ts +38 -1
  603. package/src/composables/useForm.ts +41 -11
  604. package/src/composables/useInView.ts +61 -0
  605. package/src/composables/useLoadingGate.ts +63 -0
  606. package/src/composables/useLpConfig.ts +20 -1
  607. package/src/composables/useMediaQuery.ts +6 -2
  608. package/src/composables/useNow.ts +84 -0
  609. package/src/composables/usePersisted.ts +19 -8
  610. package/src/composables/usePillTransition.ts +21 -13
  611. package/src/composables/usePointerFrame.ts +72 -0
  612. package/src/composables/useReducedMotion.ts +22 -0
  613. package/src/composables/useRepeater.ts +44 -0
  614. package/src/composables/useResizeObserver.ts +59 -0
  615. package/src/composables/useRevealedValue.ts +84 -0
  616. package/src/composables/useScramble.ts +152 -0
  617. package/src/composables/useShift.ts +10 -16
  618. package/src/composables/useSpotlight.ts +82 -0
  619. package/src/composables/useTilt.ts +40 -23
  620. package/src/composables/useTween.ts +149 -0
  621. package/src/composables/useView.ts +96 -0
  622. package/src/index.css +1 -0
  623. package/src/index.ts +143 -1
  624. package/src/kit-icons.ts +1 -1
  625. package/src/theme/define.ts +65 -0
  626. package/src/theme/studio.ts +202 -0
  627. package/src/tokens/effects.css +151 -0
  628. package/src/tokens/tokens.css +131 -39
  629. package/src/tokens/utilities.css +81 -0
  630. package/dist/assets/LpTabs-Bk7v4bZz.css +0 -18
  631. package/dist/chunks/LpAlert-BdvxvcWE.js +0 -85
  632. package/dist/chunks/LpBadge-Fe0D1bYx.js +0 -62
  633. package/dist/chunks/LpBreadcrumbs-rzOKkLiI.js +0 -98
  634. package/dist/chunks/LpDatePicker-BMfM454Y.js +0 -243
  635. package/dist/chunks/LpDateRangePicker-CNb9X3eL.js +0 -195
  636. package/dist/chunks/LpNumberField-DO1ufKvE.js +0 -107
  637. package/dist/chunks/LpStepper-C2slwObu.js +0 -90
  638. package/dist/chunks/LpSwitch-CZy9_IoA.js +0 -132
  639. package/dist/chunks/LpThemeSwitcher-CDoziW0z.js +0 -166
  640. 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 === document.body || node === document.documentElement) return null
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
- const box = scrollParent(el) ?? (document.scrollingElement as HTMLElement | null)
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 === document.scrollingElement ? new DOMRect(0, 0, window.innerWidth, window.innerHeight) : box.getBoundingClientRect()
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: (text: string, options?: CopyOptions) => Promise<boolean>
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(text: string, options: CopyOptions = {}): Promise<boolean> {
148
+ async function copy(data: string | Blob, options: CopyOptions = {}): Promise<boolean> {
129
149
  const { toast = false, errorToast = false } = options
130
150
  try {
131
- if (!(await writeAsync(text)) && !writeLegacy(text)) throw new Error("clipboard unavailable")
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", number>()
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 = (runs.get(name) ?? 0) + 1
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 = (runs.get("form") ?? 0) + 1
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 (!errors[name]) errors[name] = text
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) return await runField(name)
176
- if (errors.form) delete errors.form
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, and drops an external error on it.
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) void validate(name)
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
- if (!(await validate())) return
282
- const submitted = clone(plain())
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(() => ({ ...defaults, ...config.value.labels?.[scope], ...own?.() }) as T)
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 = window.matchMedia?.(query)
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
+ }