@leavepulse/ui 0.68.0 → 0.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (640) hide show
  1. package/README.md +37 -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/LpShinyText-D69cBYDR.css +61 -0
  11. package/dist/assets/LpStatus-430OGTZY.css +17 -0
  12. package/dist/assets/LpStepMark-Drvx33Vg.css +10 -0
  13. package/dist/assets/{LpTable-BPERfJIk.css → LpTable-DHxEQ_-3.css} +19 -19
  14. package/dist/assets/LpTabs-BJ-nrJuI.css +18 -0
  15. package/dist/assets/LpTextReveal-DjI1MsDS.css +35 -0
  16. package/dist/assets/LpTypewriter-CXAm9Nca.css +27 -0
  17. package/dist/chunks/AttentionDot-DCBf4aWg.js +25 -0
  18. package/dist/chunks/{ContextMenuItems-B7XFHksm.js → ContextMenuItems-B5uE8Znw.js} +1 -1
  19. package/dist/chunks/{DropzoneLinkField-CjkrpDqO.js → DropzoneLinkField-Bw8Flkhk.js} +2 -2
  20. package/dist/chunks/FieldWrapper-BiennROB.js +43 -0
  21. package/dist/chunks/{LayoutCanvas-DF9CovaK.js → LayoutCanvas-BsJIzhdd.js} +1 -1
  22. package/dist/chunks/{LayoutNode-D9bIFzk0.js → LayoutNode-DJVhkSGk.js} +6 -8
  23. package/dist/chunks/LegendSwatch-BhRjrtzv.js +18 -0
  24. package/dist/chunks/LoaderMark-C1dubiKn.js +85 -0
  25. package/dist/chunks/{LpActionBar-MPKMu3pz.js → LpActionBar-CzOFar5t.js} +14 -20
  26. package/dist/chunks/LpActivityLine-CJvzrNpj.js +95 -0
  27. package/dist/chunks/{LpAddressInput-DG0BTqdH.js → LpAddressInput-CNnLTDZg.js} +7 -7
  28. package/dist/chunks/LpAlert-C5igj802.js +119 -0
  29. package/dist/chunks/{LpAppShell-DPh1756q.js → LpAppShell-CEUAB8kK.js} +15 -16
  30. package/dist/chunks/{LpAutocomplete-Dgn6hw3p.js → LpAutocomplete-D92niDHE.js} +15 -32
  31. package/dist/chunks/{LpAvatar-Bs0Fb1lC.js → LpAvatar-CLPl-qOF.js} +18 -6
  32. package/dist/chunks/LpBadge-HOEPBYmi.js +126 -0
  33. package/dist/chunks/LpBreadcrumbs-DYoPc3P4.js +144 -0
  34. package/dist/chunks/{LpButton-DNR-YAq5.js → LpButton-CVxBfGjd.js} +12 -4
  35. package/dist/chunks/{LpCalendar-BLya39Fh.js → LpCalendar-pVL91-Pl.js} +1 -1
  36. package/dist/chunks/{LpCard-Bf71NXZq.js → LpCard-CqKLnEhl.js} +18 -8
  37. package/dist/chunks/{LpCheckbox-Da5oZPHo.js → LpCheckbox-DHc2bHDe.js} +9 -6
  38. package/dist/chunks/{LpCodeBlock-lLbmslfs.js → LpCodeBlock-BGsQhxw8.js} +24 -32
  39. package/dist/chunks/LpCodeDisplay-CIKKT2iW.js +189 -0
  40. package/dist/chunks/LpCollapse-CWS9M_lm.js +115 -0
  41. package/dist/chunks/{LpColorPicker-DtEfBAcK.js → LpColorPicker-DFugXNUi.js} +10 -5
  42. package/dist/chunks/{LpConfirmDialog-D7s4jakB.js → LpConfirmDialog-CfGCux8m.js} +3 -3
  43. package/dist/chunks/{LpConfirmHost-DrYI5y1K.js → LpConfirmHost-exB7JmcO.js} +1 -1
  44. package/dist/chunks/{LpContextMenu-CRnjoqSt.js → LpContextMenu-COwOwRgI.js} +13 -5
  45. package/dist/chunks/LpCopyButton-DzHjnFs0.js +133 -0
  46. package/dist/chunks/LpCountdown-Dtq1ihlJ.js +231 -0
  47. package/dist/chunks/LpDataList-B-5nHfNw.js +120 -0
  48. package/dist/chunks/LpDatePicker-BUe1VUC0.js +168 -0
  49. package/dist/chunks/LpDateRangePicker-C_k65vGR.js +126 -0
  50. package/dist/chunks/{LpDisclosure-6AplYBub.js → LpDisclosure-DOarzlAG.js} +22 -11
  51. package/dist/chunks/{LpDrawer-Q4SOxM4s.js → LpDrawer-DGNKRsGq.js} +9 -4
  52. package/dist/chunks/{LpDropdownMenu-DWTt0ooM.js → LpDropdownMenu-CRgrhdAH.js} +10 -5
  53. package/dist/chunks/LpEditable-DTqLt2lT.js +236 -0
  54. package/dist/chunks/{LpEmptyState-om1UnFjV.js → LpEmptyState-KTKipws2.js} +1 -1
  55. package/dist/chunks/{LpFieldNotice-CHuDeLjc.js → LpFieldNotice-CurbNuXI.js} +1 -1
  56. package/dist/chunks/{LpFileTree-CW24YsVX.js → LpFileTree-DMISXVHC.js} +5 -3
  57. package/dist/chunks/{LpFileTreeNode-BXhTHaKW.js → LpFileTreeNode-CPXB_Yy3.js} +42 -55
  58. package/dist/chunks/{LpFormField-DPb47HEm.js → LpFormField-BWwgm1E4.js} +23 -9
  59. package/dist/chunks/LpFrame-BVQCQLDI.js +155 -0
  60. package/dist/chunks/LpGauge-Bk8TwJj0.js +204 -0
  61. package/dist/chunks/LpGlowBorder-B8V-QzIl.js +40 -0
  62. package/dist/chunks/{LpHintExample-C1eKYdeL.js → LpHintExample-9Py0wc-L.js} +11 -34
  63. package/dist/chunks/{LpHintLayer-7UOdWQG3.js → LpHintLayer-p4hqzdyA.js} +27 -14
  64. package/dist/chunks/{LpHints-BUtBp9-w.js → LpHints-CCrhRfy9.js} +3 -3
  65. package/dist/chunks/LpHoverCard-spGxyw7T.js +130 -0
  66. package/dist/chunks/{LpIcon-6tSrZ3Zy.js → LpIcon-HJP5xMz4.js} +1 -1
  67. package/dist/chunks/LpIdenticon-BzdC_qz9.js +63 -0
  68. package/dist/chunks/{LpInfraNode-Ds_8SZgN.js → LpInfraNode-CUC3t4na.js} +39 -25
  69. package/dist/chunks/{LpInput-CUyr6PIE.js → LpInput-CDuwODJG.js} +44 -18
  70. package/dist/chunks/{LpLightbox-B9J8rzsj.js → LpLightbox-XJ0LpTcZ.js} +69 -69
  71. package/dist/chunks/LpLoader-DjJEX8hL.js +92 -0
  72. package/dist/chunks/{LpLogViewer-4QFc-sVN.js → LpLogViewer-elZHbj85.js} +225 -129
  73. package/dist/chunks/LpMagnetic-CtXKLa2Y.js +62 -0
  74. package/dist/chunks/{LpMapPicker-BTbkFeSG.js → LpMapPicker-BtIl7NOM.js} +34 -31
  75. package/dist/chunks/LpMasonry--k0571qG.js +185 -0
  76. package/dist/chunks/{LpMediaGallery-CuPcscx3.js → LpMediaGallery-D5swlmZ_.js} +3 -3
  77. package/dist/chunks/{LpModal-hk1noDcr.js → LpModal-eBMt2Fqn.js} +8 -3
  78. package/dist/chunks/{LpModalNav-DQd2keZm.js → LpModalNav-D4Z0wVHR.js} +4 -4
  79. package/dist/chunks/{LpNotificationBell-X84PJIxD.js → LpNotificationBell-DPZmP5dM.js} +32 -48
  80. package/dist/chunks/LpNumberField-B9k-h2HE.js +140 -0
  81. package/dist/chunks/{LpNumberFlow-z4WPZcZu.js → LpNumberFlow-Cfyh033Q.js} +8 -28
  82. package/dist/chunks/{LpOtpInput-PbeNJ5In.js → LpOtpInput-CeBPsM7D.js} +2 -1
  83. package/dist/chunks/{LpPagination-Bx-VG4Ag.js → LpPagination-VNCX6pFC.js} +37 -6
  84. package/dist/chunks/{LpPalettePreview-CFav_7co.js → LpPalettePreview-BNB8T_MY.js} +1 -1
  85. package/dist/chunks/{LpPasswordInput-CbnXitAS.js → LpPasswordInput-DTV18FIh.js} +20 -20
  86. package/dist/chunks/{LpPhoneInput-DP69HONS.js → LpPhoneInput-C60tx1tC.js} +12 -7
  87. package/dist/chunks/{LpPie-yLF9-aDR.js → LpPie-CFuV7pZv.js} +158 -47
  88. package/dist/chunks/LpPipeline-D81yh_NX.js +329 -0
  89. package/dist/chunks/{LpPopover-DVnkHaOu.js → LpPopover-BEIcqb9p.js} +8 -3
  90. package/dist/chunks/{LpProgress-CA86wxXu.js → LpProgress-CE-m5OpD.js} +23 -63
  91. package/dist/chunks/LpQuota-2Sw_gqcJ.js +162 -0
  92. package/dist/chunks/{LpRadioGroup-8S5hf8lE.js → LpRadioGroup-CGBr5nIp.js} +1 -1
  93. package/dist/chunks/{LpRangeCalendar-ByQi-WUz.js → LpRangeCalendar-c5qj7YcC.js} +1 -1
  94. package/dist/chunks/{LpRangeSlider-C1yF9-nP.js → LpRangeSlider-Dv6lNvHY.js} +6 -2
  95. package/dist/chunks/LpRelativeTime-CKzVqAo9.js +73 -0
  96. package/dist/chunks/LpRepeater-D0VjmD6y.js +140 -0
  97. package/dist/chunks/LpRevealOnScroll-DcQjexLy.js +76 -0
  98. package/dist/chunks/LpScramble-TDO5Kdou.js +85 -0
  99. package/dist/chunks/LpSecretField-DMfdJ1cQ.js +307 -0
  100. package/dist/chunks/{LpSection-BmRXe6ZP.js → LpSection-BW4xJ-Li.js} +1 -1
  101. package/dist/chunks/{LpSegmented-CW3bFHM4.js → LpSegmented-BO99Sr4m.js} +11 -14
  102. package/dist/chunks/{LpSelect-BCyDYoih.js → LpSelect-B4f_yRi5.js} +93 -108
  103. package/dist/chunks/LpShaderBackdrop-Bote0iuy.js +332 -0
  104. package/dist/chunks/LpShinyText-DX2ajtWq.js +48 -0
  105. package/dist/chunks/{LpSidebar-DfIiS-lB.js → LpSidebar-D-M0Y_7U.js} +5 -3
  106. package/dist/chunks/{LpSidebarNav-C5FDNI0t.js → LpSidebarNav-DF-TYxOV.js} +3 -3
  107. package/dist/chunks/{LpSlider-Cuzmrd7p.js → LpSlider-DuoRpOrH.js} +6 -2
  108. package/dist/chunks/{LpSparkline-DXT4KrSi.js → LpSparkline-CExIRDb3.js} +32 -27
  109. package/dist/chunks/LpSpotlight-4F3NeUMY.js +41 -0
  110. package/dist/chunks/{LpStat-iBMF5QNk.js → LpStat-B2ix1Q7t.js} +38 -35
  111. package/dist/chunks/LpStatus-C7kx5PKE.js +86 -0
  112. package/dist/chunks/LpStepMark-DK-5BKAt.js +101 -0
  113. package/dist/chunks/LpStepper-DRFfdJaq.js +76 -0
  114. package/dist/chunks/{LpSwap-D82jiecV.js → LpSwap-DzE6M3Mg.js} +1 -1
  115. package/dist/chunks/LpSwitch-DrzKrZZV.js +112 -0
  116. package/dist/chunks/{LpTableFilter-CuIbgdW0.js → LpTableFilter-DyvHGbOk.js} +3 -2
  117. package/dist/chunks/{LpTableOfContents-C_4VBktT.js → LpTableOfContents-rYvo8z5T.js} +34 -21
  118. package/dist/chunks/{LpTagsInput-DiXrpYeE.js → LpTagsInput-DgpBFRV2.js} +68 -71
  119. package/dist/chunks/LpTextReveal-CtAnOUkp.js +140 -0
  120. package/dist/chunks/{LpTextarea-xoQd5Lnp.js → LpTextarea-DiWjeVoJ.js} +12 -11
  121. package/dist/chunks/{LpThemeProvider-CQ2jC9TA.js → LpThemeProvider-DcbfOxsU.js} +1 -1
  122. package/dist/chunks/LpThemeStudio-DguwHdGt.js +416 -0
  123. package/dist/chunks/LpThemeStudioSection-BwhqYDLf.js +20 -0
  124. package/dist/chunks/LpThemeSwitcher-D5uz0MhK.js +278 -0
  125. package/dist/chunks/{LpTimeField-MFgAsNc5.js → LpTimeField-BFQVf_PW.js} +13 -5
  126. package/dist/chunks/{LpToaster-C4zE9zB7.js → LpToaster-Wv9ksj4q.js} +49 -82
  127. package/dist/chunks/{LpTooltip-suFCM34F.js → LpTooltip-Cz8SQwRt.js} +8 -3
  128. package/dist/chunks/{LpTopologyCanvas-6ozNrQKh.js → LpTopologyCanvas-ZhKthfgz.js} +1 -1
  129. package/dist/chunks/{LpTour-BUp3Jt2e.js → LpTour-NfRJNEIP.js} +24 -18
  130. package/dist/chunks/LpTruncate-D_5Pfv5B.js +112 -0
  131. package/dist/chunks/LpTypewriter-BCL_a6wE.js +159 -0
  132. package/dist/chunks/{LpUptimeBar-B6kHw4Cj.js → LpUptimeBar-DWegV2p6.js} +9 -8
  133. package/dist/chunks/{LpVideoBackdrop-qWRYBOTQ.js → LpVideoBackdrop-DS1wGv-X.js} +7 -8
  134. package/dist/chunks/{MenuTitle-obgek0B7.js → MenuTitle-BjQZX8tQ.js} +1 -1
  135. package/dist/chunks/OptionContent-CmYUUeWj.js +40 -0
  136. package/dist/chunks/PickerField-CHRS1h7K.js +116 -0
  137. package/dist/chunks/RevealToggle-C-KzaQGJ.js +65 -0
  138. package/dist/chunks/StepConnector-37OgEOXA.js +41 -0
  139. package/dist/chunks/TourCard-BXuVHsfn.js +123 -0
  140. package/dist/chunks/backdrop-Dkf2vM81.js +59 -0
  141. package/dist/chunks/{icons-DVhZ_plB.js → icons-4IywzkHk.js} +1 -1
  142. package/dist/chunks/{useTheme-2qHG8NEU.js → useTheme-BOV_-AiY.js} +9 -65
  143. package/dist/component-names.d.ts +1 -1
  144. package/dist/components/AttentionDot.vue.d.ts +9 -0
  145. package/dist/components/AttentionDot.vue.js +4 -0
  146. package/dist/components/ContextMenuItems.vue.js +1 -1
  147. package/dist/components/DropzoneLinkField.vue.js +1 -1
  148. package/dist/components/FieldWrapper.vue.d.ts +25 -0
  149. package/dist/components/FieldWrapper.vue.js +4 -0
  150. package/dist/components/LayoutCanvas.vue.js +1 -1
  151. package/dist/components/LayoutNode.vue.d.ts +4 -4
  152. package/dist/components/LayoutNode.vue.js +1 -1
  153. package/dist/components/LegendSwatch.vue.d.ts +6 -0
  154. package/dist/components/LegendSwatch.vue.js +4 -0
  155. package/dist/components/LoaderMark.vue.d.ts +12 -0
  156. package/dist/components/LoaderMark.vue.js +4 -0
  157. package/dist/components/LpActionBar.vue.js +1 -1
  158. package/dist/components/LpActivityLine.vue.d.ts +58 -0
  159. package/dist/components/LpActivityLine.vue.js +4 -0
  160. package/dist/components/LpAddressInput.vue.js +1 -1
  161. package/dist/components/LpAlert.vue.d.ts +2 -2
  162. package/dist/components/LpAlert.vue.js +1 -1
  163. package/dist/components/LpAppShell.vue.d.ts +8 -7
  164. package/dist/components/LpAppShell.vue.js +1 -1
  165. package/dist/components/LpAutocomplete.vue.js +1 -1
  166. package/dist/components/LpAvatar.vue.d.ts +16 -0
  167. package/dist/components/LpAvatar.vue.js +1 -1
  168. package/dist/components/LpBadge.vue.d.ts +19 -5
  169. package/dist/components/LpBadge.vue.js +1 -1
  170. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  171. package/dist/components/LpButton.vue.d.ts +2 -2
  172. package/dist/components/LpButton.vue.js +1 -1
  173. package/dist/components/LpCalendar.vue.js +1 -1
  174. package/dist/components/LpCard.vue.d.ts +24 -0
  175. package/dist/components/LpCard.vue.js +1 -1
  176. package/dist/components/LpChart.vue.d.ts +10 -0
  177. package/dist/components/LpChart.vue.js +122 -142
  178. package/dist/components/LpCheckbox.vue.d.ts +2 -2
  179. package/dist/components/LpCheckbox.vue.js +1 -1
  180. package/dist/components/LpCodeBlock.vue.js +1 -1
  181. package/dist/components/LpCodeDisplay.vue.d.ts +89 -0
  182. package/dist/components/LpCodeDisplay.vue.js +4 -0
  183. package/dist/components/LpCollapse.vue.d.ts +58 -0
  184. package/dist/components/LpCollapse.vue.js +4 -0
  185. package/dist/components/LpColorPicker.vue.d.ts +2 -2
  186. package/dist/components/LpColorPicker.vue.js +1 -1
  187. package/dist/components/LpCommandPalette.vue.js +34 -23
  188. package/dist/components/LpConfirmDialog.vue.js +1 -1
  189. package/dist/components/LpConfirmHost.vue.js +1 -1
  190. package/dist/components/LpContextMenu.vue.js +1 -1
  191. package/dist/components/LpCopyButton.vue.d.ts +82 -0
  192. package/dist/components/LpCopyButton.vue.js +4 -0
  193. package/dist/components/LpCountdown.vue.d.ts +100 -0
  194. package/dist/components/LpCountdown.vue.js +4 -0
  195. package/dist/components/LpDataList.vue.d.ts +66 -0
  196. package/dist/components/LpDataList.vue.js +4 -0
  197. package/dist/components/LpDatePicker.vue.d.ts +1 -1
  198. package/dist/components/LpDatePicker.vue.js +1 -1
  199. package/dist/components/LpDateRangePicker.vue.d.ts +1 -1
  200. package/dist/components/LpDateRangePicker.vue.js +1 -1
  201. package/dist/components/LpDisclosure.vue.js +1 -1
  202. package/dist/components/LpDock.vue.d.ts +74 -0
  203. package/dist/components/LpDock.vue.js +204 -0
  204. package/dist/components/LpDrawer.vue.js +1 -1
  205. package/dist/components/LpDropdownMenu.vue.js +1 -1
  206. package/dist/components/LpDropzone.vue.d.ts +8 -1
  207. package/dist/components/LpDropzone.vue.js +36 -15
  208. package/dist/components/LpEditable.vue.d.ts +85 -0
  209. package/dist/components/LpEditable.vue.js +4 -0
  210. package/dist/components/LpEmptyState.vue.js +1 -1
  211. package/dist/components/LpFieldNotice.vue.js +1 -1
  212. package/dist/components/LpFileTree.vue.js +1 -1
  213. package/dist/components/LpFileTreeNode.vue.js +1 -1
  214. package/dist/components/LpFormField.vue.js +1 -1
  215. package/dist/components/LpFrame.vue.d.ts +22 -0
  216. package/dist/components/LpFrame.vue.js +4 -0
  217. package/dist/components/LpGauge.vue.d.ts +116 -0
  218. package/dist/components/LpGauge.vue.js +4 -0
  219. package/dist/components/LpGlowBorder.vue.d.ts +47 -0
  220. package/dist/components/LpGlowBorder.vue.js +4 -0
  221. package/dist/components/LpHintExample.vue.d.ts +2 -2
  222. package/dist/components/LpHintExample.vue.js +1 -1
  223. package/dist/components/LpHintLayer.vue.js +1 -1
  224. package/dist/components/LpHints.vue.js +1 -1
  225. package/dist/components/LpHoverCard.vue.d.ts +60 -0
  226. package/dist/components/LpHoverCard.vue.js +4 -0
  227. package/dist/components/LpIcon.vue.js +1 -1
  228. package/dist/components/LpIdenticon.vue.d.ts +37 -0
  229. package/dist/components/LpIdenticon.vue.js +4 -0
  230. package/dist/components/LpImageEditor.vue.js +13 -19
  231. package/dist/components/LpInfraNode.vue.js +1 -1
  232. package/dist/components/LpInput.vue.d.ts +10 -0
  233. package/dist/components/LpInput.vue.js +1 -1
  234. package/dist/components/LpLightbox.vue.js +1 -1
  235. package/dist/components/LpLoader.vue.d.ts +106 -0
  236. package/dist/components/LpLoader.vue.js +4 -0
  237. package/dist/components/LpLogViewer.vue.js +1 -1
  238. package/dist/components/LpMagnetic.vue.d.ts +28 -0
  239. package/dist/components/LpMagnetic.vue.js +4 -0
  240. package/dist/components/LpMapPicker.vue.js +1 -1
  241. package/dist/components/LpMasonry.vue.d.ts +42 -0
  242. package/dist/components/LpMasonry.vue.js +4 -0
  243. package/dist/components/LpMediaGallery.vue.js +1 -1
  244. package/dist/components/LpModal.vue.js +1 -1
  245. package/dist/components/LpModalNav.vue.js +1 -1
  246. package/dist/components/LpNotificationBell.vue.d.ts +15 -6
  247. package/dist/components/LpNotificationBell.vue.js +1 -1
  248. package/dist/components/LpNumberField.vue.d.ts +57 -0
  249. package/dist/components/LpNumberField.vue.js +1 -1
  250. package/dist/components/LpNumberFlow.vue.js +1 -1
  251. package/dist/components/LpOtpInput.vue.d.ts +12 -4
  252. package/dist/components/LpOtpInput.vue.js +1 -1
  253. package/dist/components/LpPagination.vue.js +1 -1
  254. package/dist/components/LpPaletteFacts.vue.js +1 -1
  255. package/dist/components/LpPaletteHeader.vue.js +2 -2
  256. package/dist/components/LpPalettePreview.vue.js +1 -1
  257. package/dist/components/LpPaletteStats.vue.js +1 -1
  258. package/dist/components/LpPasswordInput.vue.d.ts +12 -1
  259. package/dist/components/LpPasswordInput.vue.js +1 -1
  260. package/dist/components/LpPhoneInput.vue.js +1 -1
  261. package/dist/components/LpPie.vue.d.ts +3 -1
  262. package/dist/components/LpPie.vue.js +1 -1
  263. package/dist/components/LpPipeline.vue.d.ts +91 -0
  264. package/dist/components/LpPipeline.vue.js +4 -0
  265. package/dist/components/LpPopover.vue.d.ts +2 -2
  266. package/dist/components/LpPopover.vue.js +1 -1
  267. package/dist/components/LpProgress.vue.d.ts +11 -4
  268. package/dist/components/LpProgress.vue.js +1 -1
  269. package/dist/components/LpQuota.vue.d.ts +68 -0
  270. package/dist/components/LpQuota.vue.js +4 -0
  271. package/dist/components/LpRadioGroup.vue.js +1 -1
  272. package/dist/components/LpRangeCalendar.vue.js +1 -1
  273. package/dist/components/LpRangeSlider.vue.js +1 -1
  274. package/dist/components/LpRelativeTime.vue.d.ts +41 -0
  275. package/dist/components/LpRelativeTime.vue.js +4 -0
  276. package/dist/components/LpRepeater.vue.d.ts +48 -0
  277. package/dist/components/LpRepeater.vue.js +4 -0
  278. package/dist/components/LpRevealOnScroll.vue.d.ts +67 -0
  279. package/dist/components/LpRevealOnScroll.vue.js +4 -0
  280. package/dist/components/LpRollingText.vue.d.ts +1 -1
  281. package/dist/components/LpScramble.vue.d.ts +55 -0
  282. package/dist/components/LpScramble.vue.js +4 -0
  283. package/dist/components/LpScrollArea.vue.d.ts +1 -1
  284. package/dist/components/LpSecretField.vue.d.ts +108 -0
  285. package/dist/components/LpSecretField.vue.js +4 -0
  286. package/dist/components/LpSection.vue.js +1 -1
  287. package/dist/components/LpSegmented.vue.js +1 -1
  288. package/dist/components/LpSelect.vue.d.ts +2 -0
  289. package/dist/components/LpSelect.vue.js +1 -1
  290. package/dist/components/LpShaderBackdrop.vue.d.ts +39 -0
  291. package/dist/components/LpShaderBackdrop.vue.js +4 -0
  292. package/dist/components/LpShinyText.vue.d.ts +39 -0
  293. package/dist/components/LpShinyText.vue.js +4 -0
  294. package/dist/components/LpShortcutsDialog.vue.js +1 -1
  295. package/dist/components/LpSidebar.vue.js +1 -1
  296. package/dist/components/LpSidebarNav.vue.js +1 -1
  297. package/dist/components/LpSlider.vue.js +1 -1
  298. package/dist/components/LpSparkline.vue.d.ts +4 -3
  299. package/dist/components/LpSparkline.vue.js +1 -1
  300. package/dist/components/LpSpotlight.vue.d.ts +37 -0
  301. package/dist/components/LpSpotlight.vue.js +4 -0
  302. package/dist/components/LpStat.vue.js +1 -1
  303. package/dist/components/LpStatus.vue.d.ts +65 -0
  304. package/dist/components/LpStatus.vue.js +4 -0
  305. package/dist/components/LpStepMark.vue.d.ts +31 -0
  306. package/dist/components/LpStepMark.vue.js +4 -0
  307. package/dist/components/LpStepper.vue.js +1 -1
  308. package/dist/components/LpSwap.vue.js +1 -1
  309. package/dist/components/LpSwitch.vue.d.ts +2 -2
  310. package/dist/components/LpSwitch.vue.js +1 -1
  311. package/dist/components/LpTable.vue.js +104 -120
  312. package/dist/components/LpTableFilter.vue.js +1 -1
  313. package/dist/components/LpTableOfContents.vue.d.ts +6 -1
  314. package/dist/components/LpTableOfContents.vue.js +1 -1
  315. package/dist/components/LpTabs.vue.d.ts +5 -5
  316. package/dist/components/LpTabs.vue.js +25 -33
  317. package/dist/components/LpTagsInput.vue.js +1 -1
  318. package/dist/components/LpTextReveal.vue.d.ts +58 -0
  319. package/dist/components/LpTextReveal.vue.js +4 -0
  320. package/dist/components/LpTextarea.vue.js +1 -1
  321. package/dist/components/LpThemeProvider.vue.js +1 -1
  322. package/dist/components/LpThemeStudio.vue.d.ts +54 -0
  323. package/dist/components/LpThemeStudio.vue.js +4 -0
  324. package/dist/components/LpThemeStudioSection.vue.d.ts +16 -0
  325. package/dist/components/LpThemeStudioSection.vue.js +4 -0
  326. package/dist/components/LpThemeSwitcher.vue.d.ts +32 -1
  327. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  328. package/dist/components/LpTimeField.vue.d.ts +2 -0
  329. package/dist/components/LpTimeField.vue.js +1 -1
  330. package/dist/components/LpToaster.vue.js +1 -1
  331. package/dist/components/LpTooltip.vue.d.ts +2 -2
  332. package/dist/components/LpTooltip.vue.js +1 -1
  333. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  334. package/dist/components/LpTour.vue.js +1 -1
  335. package/dist/components/LpTruncate.vue.d.ts +34 -0
  336. package/dist/components/LpTruncate.vue.js +4 -0
  337. package/dist/components/LpTypewriter.vue.d.ts +61 -0
  338. package/dist/components/LpTypewriter.vue.js +4 -0
  339. package/dist/components/LpUptimeBar.vue.js +1 -1
  340. package/dist/components/LpVideoBackdrop.vue.js +1 -1
  341. package/dist/components/MenuTitle.vue.js +1 -1
  342. package/dist/components/OptionContent.vue.d.ts +7 -0
  343. package/dist/components/OptionContent.vue.js +4 -0
  344. package/dist/components/PickerField.vue.d.ts +43 -0
  345. package/dist/components/PickerField.vue.js +4 -0
  346. package/dist/components/RevealToggle.vue.d.ts +21 -0
  347. package/dist/components/RevealToggle.vue.js +4 -0
  348. package/dist/components/StepConnector.vue.d.ts +19 -0
  349. package/dist/components/StepConnector.vue.js +4 -0
  350. package/dist/components/TourCard.vue.js +1 -1
  351. package/dist/components/badge.d.ts +1 -0
  352. package/dist/components/badge.js +4 -0
  353. package/dist/components/chart.d.ts +5 -1
  354. package/dist/components/chart.js +2 -21
  355. package/dist/components/dateRange.d.ts +11 -0
  356. package/dist/components/dateRange.js +12 -0
  357. package/dist/components/dock.d.ts +19 -0
  358. package/dist/components/dock.js +16 -0
  359. package/dist/components/dropdown.d.ts +10 -1
  360. package/dist/components/dropdown.js +7 -2
  361. package/dist/components/fieldText.d.ts +38 -0
  362. package/dist/components/fieldText.js +53 -0
  363. package/dist/components/fileTree.d.ts +7 -3
  364. package/dist/components/fileTree.js +12 -18
  365. package/dist/components/format.d.ts +41 -0
  366. package/dist/components/format.js +67 -0
  367. package/dist/components/identicon.d.ts +38 -0
  368. package/dist/components/identicon.js +85 -0
  369. package/dist/components/masonry.d.ts +32 -0
  370. package/dist/components/masonry.js +40 -0
  371. package/dist/components/menuWhen.js +1 -1
  372. package/dist/components/meter.d.ts +64 -0
  373. package/dist/components/meter.js +53 -0
  374. package/dist/components/otp.d.ts +1 -1
  375. package/dist/components/otp.js +7 -2
  376. package/dist/components/pipeline.d.ts +55 -0
  377. package/dist/components/pipeline.js +51 -0
  378. package/dist/components/quota.d.ts +57 -0
  379. package/dist/components/quota.js +40 -0
  380. package/dist/components/relativeTime.d.ts +24 -0
  381. package/dist/components/relativeTime.js +56 -0
  382. package/dist/components/reveal.d.ts +18 -0
  383. package/dist/components/reveal.js +32 -0
  384. package/dist/components/scramble.d.ts +68 -0
  385. package/dist/components/scramble.js +58 -0
  386. package/dist/components/shaderBackdrop.d.ts +20 -0
  387. package/dist/components/shaderBackdrop.js +156 -0
  388. package/dist/components/slider.d.ts +5 -0
  389. package/dist/components/slider.js +24 -0
  390. package/dist/components/slotText.d.ts +2 -0
  391. package/dist/components/slotText.js +9 -0
  392. package/dist/components/status.d.ts +14 -0
  393. package/dist/components/status.js +22 -0
  394. package/dist/components/tableFacets.d.ts +8 -0
  395. package/dist/components/tableFacets.js +19 -0
  396. package/dist/components/textSplit.d.ts +17 -0
  397. package/dist/components/textSplit.js +24 -0
  398. package/dist/components/timeSpan.d.ts +78 -0
  399. package/dist/components/timeSpan.js +85 -0
  400. package/dist/components/tone.d.ts +43 -0
  401. package/dist/components/tone.js +82 -0
  402. package/dist/components/tour.js +3 -2
  403. package/dist/components/typewriter.d.ts +36 -0
  404. package/dist/components/typewriter.js +53 -0
  405. package/dist/components/videoBackdrop.js +5 -6
  406. package/dist/composables/easing.d.ts +22 -1
  407. package/dist/composables/easing.js +42 -1
  408. package/dist/composables/pseudoLocale.d.ts +15 -0
  409. package/dist/composables/pseudoLocale.js +96 -0
  410. package/dist/composables/scroll.js +5 -3
  411. package/dist/composables/useClipboard.d.ts +4 -1
  412. package/dist/composables/useClipboard.js +12 -2
  413. package/dist/composables/useField.d.ts +8 -0
  414. package/dist/composables/useField.js +5 -0
  415. package/dist/composables/useForm.js +19 -5
  416. package/dist/composables/useHintSystem.d.ts +4 -0
  417. package/dist/composables/useHintSystem.js +33 -1
  418. package/dist/composables/useHints.js +9 -3
  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 +61 -0
  451. package/dist/icons-all.js +1 -1
  452. package/dist/index.d.ts +98 -1
  453. package/dist/index.js +262 -104
  454. package/dist/theme/define.d.ts +12 -0
  455. package/dist/theme/studio.d.ts +90 -0
  456. package/package.json +5 -2
  457. package/src/component-names.ts +33 -0
  458. package/src/components/AttentionDot.vue +27 -0
  459. package/src/components/FieldWrapper.vue +38 -0
  460. package/src/components/LayoutNode.vue +5 -9
  461. package/src/components/LegendSwatch.vue +15 -0
  462. package/src/components/LoaderMark.vue +90 -0
  463. package/src/components/LpActionBar.vue +12 -20
  464. package/src/components/LpActivityLine.vue +186 -0
  465. package/src/components/LpAlert.vue +54 -24
  466. package/src/components/LpAppShell.vue +7 -11
  467. package/src/components/LpAutocomplete.vue +16 -14
  468. package/src/components/LpAvatar.vue +17 -2
  469. package/src/components/LpBadge.vue +77 -13
  470. package/src/components/LpBreadcrumbs.vue +101 -35
  471. package/src/components/LpButton.vue +15 -3
  472. package/src/components/LpCard.vue +30 -7
  473. package/src/components/LpChart.vue +152 -156
  474. package/src/components/LpCheckbox.vue +10 -4
  475. package/src/components/LpCodeBlock.vue +11 -19
  476. package/src/components/LpCodeDisplay.vue +228 -0
  477. package/src/components/LpCollapse.vue +162 -0
  478. package/src/components/LpColorPicker.vue +10 -1
  479. package/src/components/LpCommandPalette.vue +38 -24
  480. package/src/components/LpContextMenu.vue +10 -1
  481. package/src/components/LpCopyButton.vue +185 -0
  482. package/src/components/LpCountdown.vue +308 -0
  483. package/src/components/LpDataList.vue +154 -0
  484. package/src/components/LpDatePicker.vue +53 -104
  485. package/src/components/LpDateRangePicker.vue +49 -95
  486. package/src/components/LpDisclosure.vue +58 -36
  487. package/src/components/LpDock.vue +274 -0
  488. package/src/components/LpDrawer.vue +10 -1
  489. package/src/components/LpDropdownMenu.vue +10 -1
  490. package/src/components/LpDropzone.vue +34 -13
  491. package/src/components/LpEditable.vue +293 -0
  492. package/src/components/LpFileTree.vue +5 -1
  493. package/src/components/LpFileTreeNode.vue +25 -50
  494. package/src/components/LpFormField.vue +22 -3
  495. package/src/components/LpFrame.vue +200 -0
  496. package/src/components/LpGauge.vue +287 -0
  497. package/src/components/LpGlowBorder.vue +60 -0
  498. package/src/components/LpHintExample.vue +14 -38
  499. package/src/components/LpHintLayer.vue +13 -3
  500. package/src/components/LpHoverCard.vue +175 -0
  501. package/src/components/LpIdenticon.vue +69 -0
  502. package/src/components/LpImageEditor.vue +15 -18
  503. package/src/components/LpInfraNode.vue +27 -16
  504. package/src/components/LpInput.vue +30 -16
  505. package/src/components/LpLightbox.vue +44 -23
  506. package/src/components/LpLoader.vue +145 -0
  507. package/src/components/LpLogViewer.vue +311 -82
  508. package/src/components/LpMagnetic.vue +97 -0
  509. package/src/components/LpMapPicker.vue +26 -28
  510. package/src/components/LpMasonry.vue +247 -0
  511. package/src/components/LpModal.vue +10 -1
  512. package/src/components/LpNotificationBell.vue +33 -49
  513. package/src/components/LpNumberField.vue +79 -14
  514. package/src/components/LpNumberFlow.vue +10 -35
  515. package/src/components/LpOtpInput.vue +7 -2
  516. package/src/components/LpPagination.vue +38 -4
  517. package/src/components/LpPalettePreview.vue +2 -7
  518. package/src/components/LpPasswordInput.vue +16 -14
  519. package/src/components/LpPhoneInput.vue +12 -3
  520. package/src/components/LpPie.vue +197 -36
  521. package/src/components/LpPipeline.vue +366 -0
  522. package/src/components/LpPopover.vue +10 -1
  523. package/src/components/LpProgress.vue +22 -84
  524. package/src/components/LpQuota.vue +197 -0
  525. package/src/components/LpRangeSlider.vue +5 -1
  526. package/src/components/LpRelativeTime.vue +90 -0
  527. package/src/components/LpRepeater.vue +80 -0
  528. package/src/components/LpRevealOnScroll.vue +125 -0
  529. package/src/components/LpScramble.vue +107 -0
  530. package/src/components/LpSecretField.vue +313 -0
  531. package/src/components/LpSegmented.vue +3 -10
  532. package/src/components/LpSelect.vue +78 -64
  533. package/src/components/LpShaderBackdrop.vue +434 -0
  534. package/src/components/LpShinyText.vue +139 -0
  535. package/src/components/LpSidebar.vue +2 -0
  536. package/src/components/LpSlider.vue +5 -1
  537. package/src/components/LpSparkline.vue +55 -37
  538. package/src/components/LpSpotlight.vue +51 -0
  539. package/src/components/LpStat.vue +6 -2
  540. package/src/components/LpStatus.vue +127 -0
  541. package/src/components/LpStepMark.vue +138 -0
  542. package/src/components/LpStepper.vue +37 -43
  543. package/src/components/LpSwap.vue +2 -2
  544. package/src/components/LpSwitch.vue +15 -22
  545. package/src/components/LpTable.vue +47 -63
  546. package/src/components/LpTableFilter.vue +1 -0
  547. package/src/components/LpTableOfContents.vue +53 -29
  548. package/src/components/LpTabs.vue +10 -21
  549. package/src/components/LpTagsInput.vue +53 -37
  550. package/src/components/LpTextReveal.vue +236 -0
  551. package/src/components/LpTextarea.vue +11 -11
  552. package/src/components/LpThemeStudio.vue +216 -0
  553. package/src/components/LpThemeStudioSection.vue +16 -0
  554. package/src/components/LpThemeSwitcher.vue +156 -47
  555. package/src/components/LpTimeField.vue +9 -1
  556. package/src/components/LpToaster.vue +63 -83
  557. package/src/components/LpTooltip.vue +10 -1
  558. package/src/components/LpTour.vue +24 -13
  559. package/src/components/LpTruncate.vue +135 -0
  560. package/src/components/LpTypewriter.vue +236 -0
  561. package/src/components/LpUptimeBar.vue +6 -5
  562. package/src/components/LpVideoBackdrop.vue +15 -13
  563. package/src/components/OptionContent.vue +17 -0
  564. package/src/components/PickerField.vue +58 -0
  565. package/src/components/RevealToggle.vue +51 -0
  566. package/src/components/StepConnector.vue +54 -0
  567. package/src/components/TourCard.vue +29 -16
  568. package/src/components/badge.ts +8 -0
  569. package/src/components/chart.ts +8 -23
  570. package/src/components/dateRange.ts +25 -0
  571. package/src/components/dock.ts +31 -0
  572. package/src/components/dropdown.ts +15 -3
  573. package/src/components/fieldText.ts +95 -0
  574. package/src/components/fileTree.ts +19 -21
  575. package/src/components/format.ts +136 -0
  576. package/src/components/identicon.ts +160 -0
  577. package/src/components/masonry.ts +73 -0
  578. package/src/components/meter.ts +122 -0
  579. package/src/components/otp.ts +14 -3
  580. package/src/components/pipeline.ts +101 -0
  581. package/src/components/quota.ts +111 -0
  582. package/src/components/relativeTime.ts +118 -0
  583. package/src/components/reveal.ts +56 -0
  584. package/src/components/scramble.ts +138 -0
  585. package/src/components/shaderBackdrop.ts +208 -0
  586. package/src/components/slider.ts +30 -0
  587. package/src/components/slotText.ts +13 -0
  588. package/src/components/status.ts +46 -0
  589. package/src/components/tableFacets.ts +25 -0
  590. package/src/components/textSplit.ts +55 -0
  591. package/src/components/timeSpan.ts +183 -0
  592. package/src/components/tone.ts +144 -0
  593. package/src/components/tour.ts +3 -2
  594. package/src/components/typewriter.ts +104 -0
  595. package/src/components/videoBackdrop.ts +7 -6
  596. package/src/composables/easing.ts +66 -1
  597. package/src/composables/pseudoLocale.ts +105 -0
  598. package/src/composables/scroll.ts +6 -3
  599. package/src/composables/useClipboard.ts +25 -3
  600. package/src/composables/useField.ts +12 -0
  601. package/src/composables/useForm.ts +27 -6
  602. package/src/composables/useHintSystem.ts +40 -0
  603. package/src/composables/useHints.ts +14 -3
  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 +93 -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)
@@ -81,3 +81,15 @@ export function useFieldControl(
81
81
  }
82
82
  })
83
83
  }
84
+
85
+ /**
86
+ * Whether a focusout leaves the whole control, not just moves between its own
87
+ * parts (a trigger and a clear button, the segments of a time field, an input
88
+ * and its stepper). Composite controls emit their `blur` only then: focus
89
+ * events do not bubble, so a consumer's `@blur` on the wrapper never fired, and
90
+ * `useForm().field()` could not mark such a field touched.
91
+ */
92
+ export function focusLeft(event: FocusEvent): boolean {
93
+ const next = event.relatedTarget
94
+ return !(next instanceof Node && (event.currentTarget as Node | null)?.contains(next))
95
+ }
@@ -120,6 +120,9 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
120
120
  const submitting = ref(false)
121
121
  const submitCount = ref(0)
122
122
  const runs = new Map<keyof T | "form", number>()
123
+ // What the form-level check set last, so its next run can take back what no
124
+ // longer holds without touching an error a field rule set on the same key.
125
+ const fromForm = new Map<keyof T | "form", string>()
123
126
  const baseVersion = ref(0)
124
127
 
125
128
  const keys = (): (keyof T)[] => Object.keys(plain()) as (keyof T)[]
@@ -160,11 +163,15 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
160
163
  runs.set("form", run)
161
164
  const found = (await options.validate(plain())) ?? {}
162
165
  if (runs.get("form") !== run) return true
166
+ for (const [name, text] of fromForm) if (errors[name] === text) delete errors[name]
167
+ fromForm.clear()
163
168
  let ok = true
164
169
  for (const [name, text] of Object.entries(found) as [keyof T | "form", string | undefined][]) {
165
170
  if (!text) continue
166
171
  ok = false
167
- if (!errors[name]) errors[name] = text
172
+ if (errors[name]) continue
173
+ errors[name] = text
174
+ fromForm.set(name, text)
168
175
  }
169
176
  return ok
170
177
  }
@@ -173,7 +180,6 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
173
180
  validating.value = true
174
181
  try {
175
182
  if (name !== undefined) return await runField(name)
176
- if (errors.form) delete errors.form
177
183
  const fields = await Promise.all(keys().map(runField))
178
184
  const form = await runForm()
179
185
  return fields.every(Boolean) && form && !Object.values(external).some(Boolean)
@@ -200,6 +206,7 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
200
206
  for (const k of Object.keys(external)) delete external[k as keyof T]
201
207
  for (const k of Object.keys(touched)) delete touched[k as keyof T]
202
208
  runs.clear()
209
+ fromForm.clear()
203
210
  submitCount.value = 0
204
211
  }
205
212
 
@@ -231,18 +238,27 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
231
238
  { deep: true },
232
239
  )
233
240
 
234
- // An edit re-checks a field already touched, and drops an external error on it.
241
+ // An edit re-checks a field already touched, drops an external error on it,
242
+ // and runs the form-level check again once it has run: a cross-field error
243
+ // ("passwords differ") must clear when either field is fixed.
244
+ async function recheck(names: (keyof T)[]): Promise<void> {
245
+ await Promise.all(names.map((name) => validate(name)))
246
+ if (fromForm.size || submitCount.value) await runForm()
247
+ }
248
+
235
249
  let snapshot = clone(plain())
236
250
  watch(
237
251
  plain,
238
252
  () => {
239
253
  const now = clone(plain())
254
+ const changed: (keyof T)[] = []
240
255
  for (const name of Object.keys(now) as (keyof T)[]) {
241
256
  if (deepEqual(now[name], snapshot[name])) continue
242
257
  if (external[name]) delete external[name]
243
- if (touched[name] || submitCount.value) void validate(name)
258
+ if (touched[name] || submitCount.value) changed.push(name)
244
259
  }
245
260
  snapshot = now
261
+ if (changed.length) void recheck(changed)
246
262
  },
247
263
  { deep: true },
248
264
  )
@@ -278,8 +294,13 @@ export function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T
278
294
  try {
279
295
  submitCount.value++
280
296
  for (const name of keys()) touched[name] = true
281
- if (!(await validate())) return
282
- const submitted = clone(plain())
297
+ // The values that passed are the values sent: an edit made while an
298
+ // async rule was running is checked again before anything goes out.
299
+ let submitted: T
300
+ do {
301
+ submitted = clone(plain())
302
+ if (!(await validate())) return
303
+ } while (!deepEqual(plain(), submitted))
283
304
  await fn(submitted)
284
305
  if (deepEqual(plain(), submitted)) {
285
306
  baseline = submitted
@@ -249,6 +249,8 @@ export interface HintTiming {
249
249
  freshLimit: number
250
250
  nudgeAgain: number
251
251
  flush: number
252
+ /** How long a news item opened from a feed waits for its page and feature, ms. */
253
+ newsWait: number
252
254
  }
253
255
 
254
256
  export interface HintSystemOptions {
@@ -278,6 +280,7 @@ const DEFAULT_TIMING: HintTiming = {
278
280
  freshLimit: 3,
279
281
  nudgeAgain: 7 * DAY,
280
282
  flush: 2000,
283
+ newsWait: 8000,
281
284
  }
282
285
 
283
286
  /** Records in localStorage: for anonymous sessions and apps without a server. */
@@ -509,6 +512,41 @@ export function createHintSystem(options: HintSystemOptions) {
509
512
  for (const n of unread(page)) record(newsKey(n.id), { state: "explained" })
510
513
  }
511
514
 
515
+ // A news item opened from a feed elsewhere (a notification bell): the app
516
+ // navigates, and the change is shown as soon as its page is up and the thing
517
+ // (or its sample) is there to point at. Waiting is bounded: an item whose
518
+ // feature never appears is only marked read.
519
+ let pendingNews: (() => void) | null = null
520
+
521
+ function showNews(id: string): void {
522
+ const item = news.find((n) => n.id === id)
523
+ if (!item) return
524
+ record(newsKey(item.id), { state: "explained" })
525
+ const feature = item.feature
526
+ if (!feature) return
527
+ pendingNews?.()
528
+ let done = false
529
+ let stopWatch: WatchStopHandle | undefined
530
+ const finish = () => {
531
+ done = true
532
+ clearTimeout(timer)
533
+ stopWatch?.()
534
+ if (pendingNews === finish) pendingNews = null
535
+ }
536
+ const timer = setTimeout(finish, timing.newsWait)
537
+ pendingNews = finish
538
+ stopWatch = watch(
539
+ () => state.page === item.page && showable(feature) && !state.tour.open,
540
+ (ready) => {
541
+ if (!ready || done) return
542
+ finish()
543
+ startTour(item.page, [feature])
544
+ },
545
+ { immediate: true },
546
+ )
547
+ if (done) stopWatch()
548
+ }
549
+
512
550
  function considerOffer(): void {
513
551
  const page = state.page
514
552
  const ready = state.loaded && !state.tour.open && !state.offer && !anyModalOpen() && !typing()
@@ -651,6 +689,8 @@ export function createHintSystem(options: HintSystemOptions) {
651
689
  },
652
690
  unreadNews: (page?: string) => unread(page),
653
691
  readNews,
692
+ /** Open one news item from a feed: mark it read and show its feature once the app is on its page. */
693
+ showNews,
654
694
  pageTitle: (page: string) => pages[page]?.title ?? page,
655
695
  readFresh(id: string): void {
656
696
  record(featureKey(id), { state: "explained" })
@@ -111,15 +111,26 @@ function detach(el: HTMLElement) {
111
111
  idOf.delete(el)
112
112
  }
113
113
 
114
+ // Inside an LpHintExample the mark names a part of the sample instead: the
115
+ // app drops its real component with made-up data into the sample, and the
116
+ // component's own marks tell the tour where each step points, without
117
+ // counting the sample as the real thing.
118
+ function mark(el: HTMLElement, id: string | null | undefined) {
119
+ if (!id) return
120
+ if (el.closest("[data-lp-hint-example]")) el.dataset.lpHintPart = id
121
+ else attach(el, id)
122
+ }
123
+
114
124
  /** `v-hint="'page.feature'"` on the element that represents a feature. */
115
125
  export const vHint: Directive<HTMLElement, string | null | undefined> = {
116
126
  mounted(el, binding) {
117
- if (binding.value) attach(el, binding.value)
127
+ mark(el, binding.value)
118
128
  },
119
129
  updated(el, binding) {
120
- if (binding.value === idOf.get(el)) return
130
+ if (binding.value === (idOf.get(el) ?? el.dataset.lpHintPart)) return
121
131
  detach(el)
122
- if (binding.value) attach(el, binding.value)
132
+ delete el.dataset.lpHintPart
133
+ mark(el, binding.value)
123
134
  },
124
135
  beforeUnmount(el) {
125
136
  detach(el)
@@ -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
+ }