@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,136 @@
1
+ /*
2
+ * Numbers as the kit prints them: byte sizes (a file tree's size column, a
3
+ * quota's "4.2 GB of 5 GB") and compact figures (a chart's axis ticks, a
4
+ * counter's "1.2k"). One module so the size column and the quota bar agree on
5
+ * units and rounding, and so the axis and the counter cross a million the same
6
+ * way. Pure, so the edge cases are tested without a DOM.
7
+ */
8
+
9
+ /*
10
+ * Building an Intl formatter costs far more than using one, and a file tree
11
+ * formats a size on every row, so each distinct setup is built once.
12
+ */
13
+ const numberFormats = new Map<string, Intl.NumberFormat>()
14
+
15
+ function numberFormat(locale: string | undefined, options: Intl.NumberFormatOptions): Intl.NumberFormat {
16
+ const key = `${locale ?? ""}|${JSON.stringify(options)}`
17
+ let format = numberFormats.get(key)
18
+ if (!format) {
19
+ format = new Intl.NumberFormat(locale, options)
20
+ numberFormats.set(key, format)
21
+ }
22
+ return format
23
+ }
24
+
25
+ const BYTE_UNITS = ["byte", "kilobyte", "megabyte", "gigabyte", "terabyte", "petabyte"] as const
26
+
27
+ export interface FormatBytesOptions {
28
+ /**
29
+ * Keep the decimal place a value under 10 gets even when it is zero
30
+ * ("1.0 MB" rather than "1 MB"), so a column of sizes keeps one shape.
31
+ */
32
+ fixed?: boolean
33
+ }
34
+
35
+ /**
36
+ * A byte count in the largest unit that keeps it readable: 1 536 → "1.5 kB".
37
+ * Binary steps, the way file managers and storage limits show sizes. Unit
38
+ * names come from Intl, so a Russian app reads "1,5 МБ" without a table of
39
+ * its own. One decimal under 10 (2.4 MB), none above (240 MB). Negative or
40
+ * non-finite input is "—": there is no size to show.
41
+ */
42
+ export function formatBytes(bytes: number, locale?: string, options: FormatBytesOptions = {}): string {
43
+ if (!Number.isFinite(bytes) || bytes < 0) return "—"
44
+ let scaled = bytes
45
+ let step = 0
46
+ // Compared as it will print, so 1 048 575 B is "1.0 MB", not "1024 kB".
47
+ while (Math.round(scaled) >= 1024 && step < BYTE_UNITS.length - 1) {
48
+ scaled /= 1024
49
+ step += 1
50
+ }
51
+ // Decided on the rounded value: 9.96 kB prints as "10 kB", not "10.0 kB".
52
+ const places = step > 0 && Number(scaled.toFixed(1)) < 10 ? 1 : 0
53
+ // The number and the unit symbol are assembled here rather than taken whole:
54
+ // Intl's "short" form spells the smallest unit out ("1023 byte", "1023 Byte")
55
+ // while every other unit is a symbol, and its "narrow" form glues English
56
+ // symbols to the figure ("1023B"). Narrow symbols with one no-break space
57
+ // read the same in every locale: "1023 B", "1.5 kB", "1,5 МБ".
58
+ const parts = numberFormat(locale, {
59
+ style: "unit",
60
+ unit: BYTE_UNITS[step],
61
+ unitDisplay: "narrow",
62
+ // A figure here never passes 1023 below petabytes, and "1,023 kB" reads as
63
+ // a decimal in half the world.
64
+ useGrouping: false,
65
+ minimumFractionDigits: options.fixed ? places : 0,
66
+ maximumFractionDigits: places,
67
+ }).formatToParts(scaled)
68
+ const unit = parts.filter((p) => p.type === "unit").map((p) => p.value).join("")
69
+ const figure = parts.filter((p) => p.type !== "unit" && p.type !== "literal").map((p) => p.value).join("")
70
+ return unit ? `${figure}\u00a0${unit}` : figure
71
+ }
72
+
73
+ const COMPACT_UNITS: readonly (readonly [number, string])[] = [
74
+ [1, ""],
75
+ [1e3, "k"],
76
+ [1e6, "M"],
77
+ [1e9, "B"],
78
+ [1e12, "T"],
79
+ ]
80
+
81
+ export interface CompactNumberOptions {
82
+ /**
83
+ * Writes the decimal separator the locale's way ("1,2k" in Russian). The
84
+ * k/M/B/T suffixes stay: Intl's own compact notation changes the shape of
85
+ * the figure per locale ("1,2 тыс."), which a tick label or a rolling
86
+ * counter cannot absorb. Unset keeps a plain "." whatever the browser says.
87
+ */
88
+ locale?: string
89
+ /** Places for a figure below a thousand (12.25). Default 2. */
90
+ plainDecimals?: number
91
+ /** Places once a unit is in play (1.2k). Default 1. */
92
+ decimals?: number
93
+ /**
94
+ * Keep trailing zeros ("5.0M", "1.0k") so the figure's shape only changes
95
+ * at a rollover. Off, they are dropped ("5M"), the way a label is written.
96
+ */
97
+ fixed?: boolean
98
+ }
99
+
100
+ /**
101
+ * A figure shortened with k/M/B/T: 1 200 → "1.2k", 3 400 000 → "3.4M".
102
+ *
103
+ * The unit is picked by what will PRINT, not by the raw size: 999 950 at one
104
+ * place rounds to "1000k", which reads as the next unit up, so it becomes
105
+ * "1M"; 999.999 with two places would be "1000", so it becomes "1k".
106
+ */
107
+ export function compactNumber(value: number, options: CompactNumberOptions = {}): string {
108
+ if (!Number.isFinite(value)) return String(value)
109
+ const { locale, plainDecimals = 2, decimals = 1, fixed = false } = options
110
+ const abs = Math.abs(value)
111
+ let i = COMPACT_UNITS.length - 1
112
+ while (i > 0 && abs < COMPACT_UNITS[i][0]) i--
113
+
114
+ const placesAt = (at: number) => (at === 0 ? plainDecimals : decimals)
115
+ const roundAt = (at: number) => Number((value / COMPACT_UNITS[at][0]).toFixed(placesAt(at)))
116
+ let scaled = roundAt(i)
117
+ if (Math.abs(scaled) >= 1000 && i < COMPACT_UNITS.length - 1) {
118
+ i++
119
+ scaled = roundAt(i)
120
+ }
121
+
122
+ const places = placesAt(i)
123
+ // -0.001 rounds to -0, which would print as "-0".
124
+ if (scaled === 0) scaled = 0
125
+ const number =
126
+ locale === undefined
127
+ ? fixed
128
+ ? scaled.toFixed(places)
129
+ : String(scaled)
130
+ : numberFormat(locale, {
131
+ minimumFractionDigits: fixed ? places : 0,
132
+ maximumFractionDigits: places,
133
+ useGrouping: false,
134
+ }).format(scaled)
135
+ return `${number}${COMPACT_UNITS[i][1]}`
136
+ }
@@ -0,0 +1,160 @@
1
+ /*
2
+ * Identicons: a picture derived from a string, so something with no avatar
3
+ * of its own (a new account, a server, an API key, a commit) still gets a
4
+ * face that is the same every time and different from its neighbours.
5
+ *
6
+ * The pattern is a small pixel grid mirrored left to right, the way faces
7
+ * and logos are: symmetry is what makes a random grid read as a shape rather
8
+ * than noise, and it halves the bits each picture needs.
9
+ *
10
+ * The colour comes from the same hash, but only the HUE is free. Saturation
11
+ * and lightness are held to a middle band so every identicon reads on a dark
12
+ * surface and on a light one alike; a free lightness would hand some seeds a
13
+ * near-black shape that vanishes in a dark theme and others a pale one that
14
+ * vanishes in a light theme.
15
+ *
16
+ * Pure and deterministic: no randomness beyond the seed, no DOM. The same
17
+ * seed gives the same picture on the server, in the client and across
18
+ * releases, which is what lets a user recognise it.
19
+ */
20
+
21
+ export interface IdenticonOptions {
22
+ /** Cells per side. 5 is the familiar look; more cells, busier shapes. */
23
+ grid?: number
24
+ /** Saturation range, percent. Kept in the middle so the colour holds on any theme. */
25
+ saturation?: readonly [number, number]
26
+ /** Lightness range, percent: dark enough for a light theme, light enough for a dark one. */
27
+ lightness?: readonly [number, number]
28
+ }
29
+
30
+ export interface Identicon {
31
+ /** Rows of cells, `true` filled. Mirrored left to right. */
32
+ cells: boolean[][]
33
+ /** The hue the seed picked, 0–360. */
34
+ hue: number
35
+ /** The shape's colour as a CSS `hsl()`. */
36
+ color: string
37
+ }
38
+
39
+ /*
40
+ * FNV-1a over the string's UTF-16 units. Not a secure hash and not meant to
41
+ * be: it only has to spread similar ids ("user-1", "user-2") far apart, which
42
+ * it does, in a few lines that give the same answer on every engine.
43
+ */
44
+ export function hashString(seed: string): number {
45
+ let h = 0x811c9dc5
46
+ for (let i = 0; i < seed.length; i++) {
47
+ h ^= seed.charCodeAt(i)
48
+ h = Math.imul(h, 0x01000193)
49
+ }
50
+ return h >>> 0
51
+ }
52
+
53
+ /*
54
+ * A small seeded generator (mulberry32). The hash alone has 32 bits, and a
55
+ * 7×7 grid wants more than that; drawing from a generator seeded with it
56
+ * keeps every bit dependent on the whole string.
57
+ */
58
+ function generator(seed: number): () => number {
59
+ let a = seed
60
+ return () => {
61
+ a = (a + 0x6d2b79f5) | 0
62
+ let t = Math.imul(a ^ (a >>> 15), 1 | a)
63
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
64
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296
65
+ }
66
+ }
67
+
68
+ /** The grid and colour for a seed. */
69
+ export function identicon(seed: string, options: IdenticonOptions = {}): Identicon {
70
+ const size = Math.max(3, Math.min(12, Math.round(options.grid ?? 5)))
71
+ const [sMin, sMax] = options.saturation ?? [55, 72]
72
+ const [lMin, lMax] = options.lightness ?? [48, 60]
73
+ const next = generator(hashString(seed))
74
+
75
+ const hue = Math.floor(next() * 360)
76
+ const saturation = Math.round(sMin + next() * (sMax - sMin))
77
+ const lightness = Math.round(lMin + next() * (lMax - lMin))
78
+
79
+ const half = Math.ceil(size / 2)
80
+ const cells: boolean[][] = []
81
+ let filled = 0
82
+ for (let y = 0; y < size; y++) {
83
+ const row: boolean[] = new Array(size).fill(false)
84
+ for (let x = 0; x < half; x++) {
85
+ const on = next() < 0.5
86
+ row[x] = on
87
+ row[size - 1 - x] = on
88
+ if (on) filled++
89
+ }
90
+ cells.push(row)
91
+ }
92
+
93
+ /*
94
+ * A grid that came out nearly empty or nearly full is not a shape, just a
95
+ * dot or a block. Flip cells in a seed-driven order until the half holds
96
+ * between a third and two thirds of its cells.
97
+ */
98
+ const total = half * size
99
+ const low = Math.ceil(total / 3)
100
+ const high = Math.floor((total * 2) / 3)
101
+ while (filled < low || filled > high) {
102
+ const y = Math.floor(next() * size)
103
+ const x = Math.floor(next() * half)
104
+ const on = cells[y][x]
105
+ if ((filled < low && on) || (filled > high && !on)) continue
106
+ cells[y][x] = !on
107
+ cells[y][size - 1 - x] = !on
108
+ filled += on ? -1 : 1
109
+ }
110
+
111
+ return { cells, hue, color: `hsl(${hue} ${saturation}% ${lightness}%)` }
112
+ }
113
+
114
+ /** The filled cells as rectangles, merged along each row: fewer nodes than one per cell. */
115
+ export function identiconRects(cells: boolean[][]): { x: number; y: number; w: number }[] {
116
+ const rects: { x: number; y: number; w: number }[] = []
117
+ cells.forEach((row, y) => {
118
+ let x = 0
119
+ while (x < row.length) {
120
+ if (!row[x]) {
121
+ x++
122
+ continue
123
+ }
124
+ let w = 1
125
+ while (x + w < row.length && row[x + w]) w++
126
+ rects.push({ x, y, w })
127
+ x += w
128
+ }
129
+ })
130
+ return rects
131
+ }
132
+
133
+ export interface IdenticonSvgOptions extends IdenticonOptions {
134
+ /** Pixel size of the square image. */
135
+ size?: number
136
+ /** Fill behind the pattern, any CSS colour; transparent when unset. */
137
+ background?: string
138
+ /** Empty margin around the grid, as a share of the side. */
139
+ padding?: number
140
+ }
141
+
142
+ /**
143
+ * The identicon as standalone SVG markup, for places a component cannot go:
144
+ * a favicon, a canvas, a notification icon (`data:image/svg+xml,…`).
145
+ */
146
+ export function identiconSvg(seed: string, options: IdenticonSvgOptions = {}): string {
147
+ const { cells, color } = identicon(seed, options)
148
+ const n = cells.length
149
+ const pad = n * Math.max(0, Math.min(0.4, options.padding ?? 0.12))
150
+ const view = n + pad * 2
151
+ const px = options.size ?? 64
152
+ const bg = options.background ? `<rect width="${view}" height="${view}" fill="${options.background}"/>` : ""
153
+ const shapes = identiconRects(cells)
154
+ .map((r) => `<rect x="${r.x + pad}" y="${r.y + pad}" width="${r.w}" height="1"/>`)
155
+ .join("")
156
+ return (
157
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${px}" height="${px}" viewBox="0 0 ${view} ${view}" shape-rendering="crispEdges">` +
158
+ `${bg}<g fill="${color}">${shapes}</g></svg>`
159
+ )
160
+ }
@@ -0,0 +1,73 @@
1
+ /*
2
+ * Column maths for LpMasonry, kept free of the DOM so it can be tested.
3
+ */
4
+
5
+ export interface MasonryColumns {
6
+ /**
7
+ * Fixed count, or a table of minimum container widths to counts
8
+ * (`{ 0: 1, 480: 2, 900: 3 }`). Omit to derive the count from `minColumnWidth`.
9
+ */
10
+ columns?: number | Record<number, number>
11
+ /** Narrowest a column may get when the count is derived. */
12
+ minColumnWidth: number
13
+ /** Upper bound for a derived count, so a wide screen does not shred cards into slivers. */
14
+ maxColumns?: number
15
+ gap: number
16
+ }
17
+
18
+ /** How many columns fit a container `width` px wide. Always at least one. */
19
+ export function masonryColumnCount(width: number, options: MasonryColumns): number {
20
+ const { columns, minColumnWidth, maxColumns, gap } = options
21
+ if (typeof columns === "number") return Math.max(1, Math.floor(columns))
22
+ if (columns) {
23
+ let picked = 1
24
+ let best = -Infinity
25
+ for (const [min, count] of Object.entries(columns)) {
26
+ const at = Number(min)
27
+ if (width >= at && at > best) {
28
+ best = at
29
+ picked = count
30
+ }
31
+ }
32
+ return Math.max(1, Math.floor(picked))
33
+ }
34
+ const fit = Math.floor((width + gap) / (Math.max(1, minColumnWidth) + gap))
35
+ return Math.max(1, Math.min(fit, maxColumns ?? Infinity))
36
+ }
37
+
38
+ export interface MasonryPlacement {
39
+ /** Left edge of each item, in px, in input order. */
40
+ x: number[]
41
+ /** Top edge of each item, in px, in input order. */
42
+ y: number[]
43
+ /** Column each item went to. */
44
+ column: number[]
45
+ /** Width every item is laid out at. */
46
+ columnWidth: number
47
+ /** Height of the tallest column: the container's height. */
48
+ height: number
49
+ }
50
+
51
+ /**
52
+ * Places items in order, each into the column that is currently shortest
53
+ * (leftmost on a tie). In order, so the first items stay at the top and an
54
+ * item added at the end never reshuffles the ones before it.
55
+ */
56
+ export function placeMasonry(heights: number[], count: number, width: number, gap: number): MasonryPlacement {
57
+ const columns = Math.max(1, count)
58
+ const columnWidth = Math.max(0, (width - gap * (columns - 1)) / columns)
59
+ const bottoms = new Array<number>(columns).fill(0)
60
+ const x: number[] = []
61
+ const y: number[] = []
62
+ const column: number[] = []
63
+ for (const height of heights) {
64
+ let shortest = 0
65
+ for (let c = 1; c < columns; c += 1) if (bottoms[c]! < bottoms[shortest]! - 0.5) shortest = c
66
+ x.push(shortest * (columnWidth + gap))
67
+ y.push(bottoms[shortest]!)
68
+ column.push(shortest)
69
+ bottoms[shortest] = bottoms[shortest]! + Math.max(0, height) + gap
70
+ }
71
+ const tallest = Math.max(0, ...bottoms)
72
+ return { x, y, column, columnWidth, height: heights.length ? tallest - gap : 0 }
73
+ }
@@ -0,0 +1,122 @@
1
+ /*
2
+ * The arithmetic shared by the kit's meters: LpGauge, LpQuota, LpStatus and
3
+ * LpActivityLine. A reading against a range, the tone it earns, and the colour
4
+ * that tone paints in. Kept in one place so a gauge and a quota showing the same
5
+ * disk agree on when it turns amber, instead of each picking its own breakpoints.
6
+ * The colours themselves are tone.ts's: a meter tone is one of its aliases.
7
+ */
8
+ import { TONE_ALIASES, TONES, type ToneLook } from "./tone"
9
+
10
+ /**
11
+ * The semantic tones a meter speaks in. They name what the reading MEANS, not
12
+ * which colour it is: a theme decides the colour, and `ok` stays the "fine"
13
+ * colour whatever that happens to be.
14
+ */
15
+ export type MeterTone = "ok" | "warning" | "danger" | "brand" | "neutral"
16
+
17
+ /** One step of a scale: from `at` (in the meter's own units) upward, `tone`. */
18
+ export interface MeterThreshold {
19
+ at: number
20
+ tone: MeterTone
21
+ }
22
+
23
+ const METER_TONES: readonly MeterTone[] = ["ok", "warning", "danger", "brand", "neutral"]
24
+ const byMeterTone = (pick: (look: ToneLook) => string) =>
25
+ Object.fromEntries(METER_TONES.map((t) => [t, pick(TONES[TONE_ALIASES[t]])])) as Record<MeterTone, string>
26
+
27
+ /** The solid colour of a tone, as a CSS value a style or an SVG stroke takes. */
28
+ export const TONE_COLOR: Record<MeterTone, string> = byMeterTone((look) => look.color)
29
+
30
+ /** The tinted background of a tone, for pills and highlighted rows. */
31
+ export const TONE_SOFT: Record<MeterTone, string> = byMeterTone((look) => look.soft)
32
+
33
+ /**
34
+ * Where `value` sits between `min` and `max`, as 0–1. An empty or inverted
35
+ * range (no limit known yet) reads as empty rather than as NaN or Infinity,
36
+ * which would otherwise reach an SVG attribute and draw nothing at all.
37
+ */
38
+ export function meterFraction(value: number, min: number, max: number): number {
39
+ const span = max - min
40
+ if (!(span > 0) || !Number.isFinite(value)) return 0
41
+ return Math.max(0, Math.min(1, (value - min) / span))
42
+ }
43
+
44
+ /**
45
+ * The capacity scale every finite resource is read against: comfortable well
46
+ * past half, uncomfortable in the last fifth, urgent in the last tenth. The
47
+ * same breakpoints LpProgress's `auto` tone uses, so the two never disagree
48
+ * about one disk.
49
+ */
50
+ export function capacityThresholds(
51
+ min: number,
52
+ max: number,
53
+ warnAt = 0.8,
54
+ dangerAt = 0.9,
55
+ ): MeterThreshold[] {
56
+ const span = max - min
57
+ return [
58
+ { at: min + span * warnAt, tone: "warning" },
59
+ { at: min + span * dangerAt, tone: "danger" },
60
+ ]
61
+ }
62
+
63
+ /**
64
+ * The tone a reading earns on a scale. Thresholds are steps, not bands: each
65
+ * one holds from its `at` until the next, and below the first the reading is
66
+ * `base`. Sorted here, so a caller can list them in whatever order reads best.
67
+ *
68
+ * Steps go either way, which is what lets one shape describe both "higher is
69
+ * worse" (load: ok → warning → danger) and "higher is better" (a score:
70
+ * danger from 0, warning from 50, ok from 80).
71
+ */
72
+ export function toneAt(value: number, thresholds: readonly MeterThreshold[], base: MeterTone = "ok"): MeterTone {
73
+ let tone = base
74
+ for (const step of [...thresholds].sort((a, b) => a.at - b.at)) {
75
+ if (value >= step.at) tone = step.tone
76
+ }
77
+ return tone
78
+ }
79
+
80
+ /* ── Bar (LpProgress, LpQuota) ──────────────────────────────────────────── */
81
+
82
+ /**
83
+ * The track a bar meter fills, shared so LpProgress's bar and LpQuota's read as
84
+ * one family. Each adds its own clipping: a progress bar clips its fill, a
85
+ * quota lets its limit mark stand out past the track.
86
+ */
87
+ export const METER_TRACK = "h-1.5 w-full rounded-pill bg-surface-soft"
88
+
89
+ /** The row above a bar: what is measured at the start, the reading at the end. Each sets its own size and gap. */
90
+ export const METER_HEADER = "flex items-baseline justify-between"
91
+
92
+ /* ── Arc geometry (LpGauge) ─────────────────────────────────────────────── */
93
+
94
+ /**
95
+ * A point on a circle, with the angle in degrees clockwise from twelve
96
+ * o'clock — the way a dial is read, so -90 is nine o'clock and 90 is three.
97
+ */
98
+ export function polar(cx: number, cy: number, r: number, deg: number): { x: number; y: number } {
99
+ const rad = (deg * Math.PI) / 180
100
+ return { x: cx + r * Math.sin(rad), y: cy - r * Math.cos(rad) }
101
+ }
102
+
103
+ /** An SVG path along a circle from one angle to another, clockwise. */
104
+ export function arcPath(cx: number, cy: number, r: number, from: number, to: number): string {
105
+ const a = polar(cx, cy, r, from)
106
+ const b = polar(cx, cy, r, to)
107
+ const large = to - from > 180 ? 1 : 0
108
+ const f = (n: number) => Number(n.toFixed(3))
109
+ return `M ${f(a.x)} ${f(a.y)} A ${f(r)} ${f(r)} 0 ${large} 1 ${f(b.x)} ${f(b.y)}`
110
+ }
111
+
112
+ /**
113
+ * The sweep of each gauge shape, as [start, end] angles. A semicircle reads
114
+ * left to right across the top; the full dial opens at the bottom, a 270°
115
+ * sweep, because a closed circle has no visible start or end and that is
116
+ * LpProgress's ring, a different thing (progress toward done, not a reading
117
+ * within a range).
118
+ */
119
+ export const GAUGE_SWEEP: Record<"semi" | "full", readonly [number, number]> = {
120
+ semi: [-90, 90],
121
+ full: [-135, 135],
122
+ }
@@ -3,6 +3,8 @@
3
3
  * model. reka empties a cell by deleting its slot (numeric mode) or setting it
4
4
  * to "", so the array can have holes in the middle.
5
5
  */
6
+ import { groupCode } from "./fieldText"
7
+
6
8
  export type OtpCells = (string | number | undefined)[]
7
9
 
8
10
  /** The code typed so far: the filled cells in order. */
@@ -36,10 +38,19 @@ export function upperCells<T extends OtpCells>(cells: T): T {
36
38
  * The cell indexes a group separator follows: after every `group` cells, never
37
39
  * after the last. Eight characters are read and copied as two blocks of four,
38
40
  * which is how such codes are printed, far more easily than as one row.
41
+ *
42
+ * The blocks are LpCodeDisplay's (`groupCode`), so a code shown by one and
43
+ * typed into the other breaks in the same places; exact sizes (`[4, 6, 5]`)
44
+ * work here too. Unlike there, no `group` means no breaks: an input does not
45
+ * guess a grouping the printed code may not have.
39
46
  */
40
- export function groupBreaks(length: number, group: number | undefined): Set<number> {
47
+ export function groupBreaks(length: number, group: number | number[] | undefined): Set<number> {
41
48
  const breaks = new Set<number>()
42
- if (!group || group < 1 || group >= length) return breaks
43
- for (let i = group; i < length; i += group) breaks.add(i - 1)
49
+ if (Array.isArray(group) ? !group.some((n) => n > 0) : !group || group < 1) return breaks
50
+ let at = 0
51
+ for (const block of groupCode("x".repeat(Math.max(0, length)), group)) {
52
+ at += block.length
53
+ if (at < length) breaks.add(at - 1)
54
+ }
44
55
  return breaks
45
56
  }
@@ -0,0 +1,101 @@
1
+ /*
2
+ * The state of a run of jobs, read off the jobs themselves. Pure, so the
3
+ * rules LpPipeline draws by (which connector is filled, which step is
4
+ * "current", what a run as a whole amounts to) are testable without a DOM.
5
+ */
6
+ import { toEpoch } from "./timeSpan"
7
+
8
+ export type StepStatus = "pending" | "running" | "success" | "failed" | "skipped" | "cancelled"
9
+
10
+ export interface PipelineStep {
11
+ label: string
12
+ /** Stable id: what `expanded` and the `step` slot address. Defaults to the index. */
13
+ key?: string
14
+ status: StepStatus
15
+ /** A line under the label: the command, the host, the reason it was skipped. */
16
+ description?: string
17
+ /** Replaces the glyph while the step is pending (a step that is "deploy" more than "3"). */
18
+ icon?: string
19
+ /**
20
+ * Time the step took, in ms. When it is not known yet, `startedAt` (and
21
+ * `finishedAt`) let the pipeline work it out — and keep a running step's
22
+ * clock ticking without the app re-sending it every second.
23
+ */
24
+ duration?: number
25
+ startedAt?: Date | string | number
26
+ finishedAt?: Date | string | number
27
+ /** Jobs inside this one, shown when the step is expanded (vertical layout). */
28
+ steps?: PipelineStep[]
29
+ }
30
+
31
+ /** Done, one way or another: nothing more will happen to the step. */
32
+ export function isSettled(status: StepStatus): boolean {
33
+ return status !== "pending" && status !== "running"
34
+ }
35
+
36
+ /**
37
+ * Whether the line leaving a step is drawn as travelled. A step that passed
38
+ * or was skipped hands over to the next; one that failed or was cancelled is
39
+ * where the run stopped, so the line after it stays empty — the break in the
40
+ * line is the place to look.
41
+ */
42
+ export function connectorFilled(status: StepStatus): boolean {
43
+ return status === "success" || status === "skipped"
44
+ }
45
+
46
+ /**
47
+ * The step a viewer is following: the running one, else the first that has
48
+ * not run yet after something has, else the last failure. -1 when the run has
49
+ * not started or is cleanly over.
50
+ */
51
+ export function currentStepIndex(steps: readonly Pick<PipelineStep, "status">[]): number {
52
+ const running = steps.findIndex((s) => s.status === "running")
53
+ if (running !== -1) return running
54
+ const failed = steps.findIndex((s) => s.status === "failed")
55
+ if (failed !== -1) return failed
56
+ const started = steps.some((s) => isSettled(s.status))
57
+ if (!started) return -1
58
+ return steps.findIndex((s) => s.status === "pending")
59
+ }
60
+
61
+ /**
62
+ * What the run amounts to. A failure anywhere outranks everything (one red
63
+ * job fails the build), then work in progress, then a cancellation; a run of
64
+ * passes and skips is a pass. Nothing started is pending.
65
+ */
66
+ export function overallStatus(steps: readonly Pick<PipelineStep, "status">[]): StepStatus {
67
+ if (steps.length === 0) return "pending"
68
+ const has = (s: StepStatus) => steps.some((step) => step.status === s)
69
+ if (has("failed")) return "failed"
70
+ if (has("running")) return "running"
71
+ if (has("cancelled")) return "cancelled"
72
+ if (steps.every((s) => s.status === "skipped")) return "skipped"
73
+ if (steps.every((s) => s.status === "success" || s.status === "skipped")) return "success"
74
+ if (steps.some((s) => isSettled(s.status))) return "running"
75
+ return "pending"
76
+ }
77
+
78
+ /**
79
+ * Ms a step has taken, as of `now`. An explicit `duration` wins; otherwise
80
+ * from `startedAt` to `finishedAt`, or to `now` while it is still running.
81
+ * null when there is nothing to say (pending, or no times given).
82
+ */
83
+ export function stepElapsed(step: PipelineStep, now: number): number | null {
84
+ if (typeof step.duration === "number" && Number.isFinite(step.duration)) return Math.max(0, step.duration)
85
+ const start = toEpoch(step.startedAt)
86
+ if (Number.isNaN(start)) return null
87
+ const end = toEpoch(step.finishedAt)
88
+ if (!Number.isNaN(end)) return Math.max(0, end - start)
89
+ return step.status === "running" ? Math.max(0, now - start) : null
90
+ }
91
+
92
+ /** "2/5": finished children out of all, for a step whose inside is folded away. */
93
+ export function childProgress(step: PipelineStep): { done: number; total: number } | null {
94
+ if (!step.steps?.length) return null
95
+ return { done: step.steps.filter((s) => isSettled(s.status)).length, total: step.steps.length }
96
+ }
97
+
98
+ /** The key a step is addressed by. */
99
+ export function stepKey(step: Pick<PipelineStep, "key">, index: number): string {
100
+ return step.key ?? String(index)
101
+ }