@leavepulse/ui 0.43.3 → 0.44.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 (340) hide show
  1. package/README.md +75 -7
  2. package/dist/assets/{LpCommandPalette-DH7sPcKP.css → LpCommandPalette-DCP5g2ik.css} +21 -21
  3. package/dist/assets/{LpDropzone-BBoWvi-J.css → LpDropzone-i4BdMi1H.css} +12 -12
  4. package/dist/assets/{LpImageEditor-Ct7Y3aNu.css → LpImageEditor-CbMA82g8.css} +6 -6
  5. package/dist/assets/{LpShortcutsDialog-C6YoQ8_M.css → LpShortcutsDialog-ChUyU7oV.css} +7 -7
  6. package/dist/assets/{LpSkeleton-D7xU8IPV.css → LpSkeleton-H3Z5dEIk.css} +4 -1
  7. package/dist/assets/{LpTable-BgPLJOXa.css → LpTable-DDQNdf86.css} +9 -9
  8. package/dist/chunks/ContextMenuItems-CrfeNPWT.js +123 -0
  9. package/dist/chunks/{LayoutCanvas-Dd20boUG.js → LayoutCanvas-D-jPOAvg.js} +26 -9
  10. package/dist/chunks/{LayoutNode-DGpZA9LX.js → LayoutNode-D8Xy9N-o.js} +20 -8
  11. package/dist/chunks/{LpActionBar-BlvtGB2P.js → LpActionBar-BbwCF6l5.js} +24 -10
  12. package/dist/chunks/{LpAddressInput-B-Zvxsn7.js → LpAddressInput-BrOqkZqg.js} +53 -48
  13. package/dist/chunks/{LpAlert-BAYHDSS1.js → LpAlert-DkOIjsbq.js} +15 -8
  14. package/dist/chunks/{LpAppShell-BGQVIY-3.js → LpAppShell-BUKPcHco.js} +51 -24
  15. package/dist/chunks/{LpAutocomplete-DExlIZe_.js → LpAutocomplete-DxV3Y9h4.js} +50 -23
  16. package/dist/chunks/{LpAvatar-B3-xAOH3.js → LpAvatar-ClBbEYZN.js} +17 -8
  17. package/dist/chunks/LpBreadcrumbs-B8IJgFIc.js +95 -0
  18. package/dist/chunks/{LpButton-5VXXCjwF.js → LpButton-kqJBHIIb.js} +44 -14
  19. package/dist/chunks/{LpCard-BhI1N_vL.js → LpCard-CpRByR0q.js} +1 -1
  20. package/dist/chunks/{LpCheckbox-CVccKYaH.js → LpCheckbox-DpqchzH4.js} +7 -3
  21. package/dist/chunks/{LpCodeBlock-Bo0B4TFR.js → LpCodeBlock-DeHJPq7p.js} +48 -16
  22. package/dist/chunks/{LpColorPicker-BlXea7LM.js → LpColorPicker-Bhf1qxhB.js} +32 -9
  23. package/dist/chunks/{LpConfirmDialog-Q-bxff8z.js → LpConfirmDialog-CIUOSnWo.js} +58 -16
  24. package/dist/chunks/LpConfirmHost-CDm0Wtpv.js +49 -0
  25. package/dist/chunks/LpContextMenu-DQw5dGfG.js +68 -0
  26. package/dist/chunks/LpDatePicker-Drirx1AK.js +191 -0
  27. package/dist/chunks/{LpDrawer-BZ6k3hvo.js → LpDrawer-bScx-KMO.js} +63 -26
  28. package/dist/chunks/{LpFileTree-_5vFBgaK.js → LpFileTree-CSI_5SK2.js} +23 -8
  29. package/dist/chunks/{LpFileTreeNode-BdcIYKIE.js → LpFileTreeNode-B4MiSQX2.js} +2 -2
  30. package/dist/chunks/LpFormField-CIk5qosh.js +87 -0
  31. package/dist/chunks/{LpInput-9EXWUM9B.js → LpInput-C0e8y8_d.js} +26 -11
  32. package/dist/chunks/{LpLightbox-2sYVMY-S.js → LpLightbox-Mr0W_6HK.js} +147 -60
  33. package/dist/chunks/{LpLink-DCND-zyu.js → LpLink-Krkjhe_y.js} +15 -6
  34. package/dist/chunks/{LpLogViewer-CaRy5MeS.js → LpLogViewer-YBOaFnc_.js} +68 -34
  35. package/dist/chunks/{LpMapPicker-ul4oYno6.js → LpMapPicker-NUzS9e37.js} +38 -13
  36. package/dist/chunks/{LpMediaGallery-FF-NquIo.js → LpMediaGallery-DfJmMW0I.js} +6 -3
  37. package/dist/chunks/{LpModal-Bt0pQCI_.js → LpModal-DPwGKBPv.js} +49 -26
  38. package/dist/chunks/{LpModalNav-YaifHrXC.js → LpModalNav-DPg5H9f3.js} +9 -6
  39. package/dist/chunks/{LpNotificationBell-DyGGsMAM.js → LpNotificationBell-t5yn-siX.js} +58 -24
  40. package/dist/chunks/{LpNumberField-CWuX08iX.js → LpNumberField-DWnQKktS.js} +20 -7
  41. package/dist/chunks/{LpNumberFlow-amWYaeuA.js → LpNumberFlow-z4WPZcZu.js} +22 -12
  42. package/dist/chunks/{LpOtpInput-y2v-pmiN.js → LpOtpInput-CziPF3Oq.js} +37 -8
  43. package/dist/chunks/{LpPagination-5FxjmmB5.js → LpPagination-DFngX7Xo.js} +30 -19
  44. package/dist/chunks/{LpPasswordInput--sjYCkLS.js → LpPasswordInput-CWVDuBJ5.js} +10 -7
  45. package/dist/chunks/{LpPhoneInput-BDSKVl_z.js → LpPhoneInput-CD-ZTeSd.js} +57 -33
  46. package/dist/chunks/{LpPie-D6OnzwlJ.js → LpPie-BLco31Nx.js} +78 -21
  47. package/dist/chunks/{LpPopover-8268UmlC.js → LpPopover-p_e_shN3.js} +4 -2
  48. package/dist/chunks/{LpProgress-Btq5W4Ut.js → LpProgress-CBU1iI69.js} +5 -4
  49. package/dist/chunks/{LpRadioGroup-xPIuVDO_.js → LpRadioGroup-DmEW0l78.js} +9 -2
  50. package/dist/chunks/{LpSegmented-fV0Sk92p.js → LpSegmented-Dejd7cIx.js} +7 -3
  51. package/dist/chunks/{LpSelect-ANIVtrpM.js → LpSelect-DrRD9I7L.js} +40 -18
  52. package/dist/chunks/{LpSidebar-ClBUwoEz.js → LpSidebar-BgwUNWpJ.js} +30 -22
  53. package/dist/chunks/{LpSidebarNav-llOcs_Fn.js → LpSidebarNav-Bqxz8aWT.js} +14 -9
  54. package/dist/chunks/{LpSkeleton-CTI-vBgW.js → LpSkeleton-BB6XIV-w.js} +1 -1
  55. package/dist/chunks/{LpSlider-BwNIBkAK.js → LpSlider-Cuzmrd7p.js} +7 -2
  56. package/dist/chunks/{LpSparkline-C6xQ2dwv.js → LpSparkline-CB80N8SJ.js} +11 -4
  57. package/dist/chunks/{LpStat-CQaW8R--.js → LpStat-dXtyrdX9.js} +1 -1
  58. package/dist/chunks/{LpSwitch-Blizvqyt.js → LpSwitch-B1aLKulj.js} +40 -26
  59. package/dist/chunks/{LpTableOfContents-CjvxPMpU.js → LpTableOfContents-BUcAfGPQ.js} +36 -41
  60. package/dist/chunks/{LpTabs-Cefe_lys.js → LpTabs-_qMVaZJ6.js} +19 -5
  61. package/dist/chunks/LpTextarea-yP8hGedR.js +77 -0
  62. package/dist/chunks/LpThemeProvider-Wjm7Tuit.js +35 -0
  63. package/dist/chunks/LpThemeSwitcher-CKLc10KZ.js +169 -0
  64. package/dist/chunks/{LpTilt-DuyKbpkA.js → LpTilt-B7KHX1zH.js} +2 -2
  65. package/dist/chunks/{LpTimeField-K6nAGboX.js → LpTimeField-C5HRgS5H.js} +13 -7
  66. package/dist/chunks/{LpToaster-gDB1EaU3.js → LpToaster--oT6lfhL.js} +57 -28
  67. package/dist/chunks/{LpTooltip-hCLPAqMG.js → LpTooltip-CBdzeKNf.js} +13 -6
  68. package/dist/chunks/LpTooltipProvider-CftjV7Xc.js +25 -0
  69. package/dist/chunks/{LpTopologyCanvas-Cm1L39uL.js → LpTopologyCanvas-DocsLal3.js} +6 -1
  70. package/dist/chunks/{LpUptimeBar-DvAMno0S.js → LpUptimeBar-DLtxOMRy.js} +43 -14
  71. package/dist/chunks/{LpThemeSwitcher-Cue-myST.js → useTheme-C8JHUlPM.js} +173 -215
  72. package/dist/component-names.d.ts +1 -1
  73. package/dist/components/ContextMenuItems.vue.d.ts +8 -0
  74. package/dist/components/ContextMenuItems.vue.js +4 -0
  75. package/dist/components/LayoutCanvas.vue.js +1 -1
  76. package/dist/components/LayoutNode.vue.d.ts +4 -0
  77. package/dist/components/LayoutNode.vue.js +1 -1
  78. package/dist/components/LpActionBar.vue.d.ts +3 -0
  79. package/dist/components/LpActionBar.vue.js +1 -1
  80. package/dist/components/LpAddressInput.vue.d.ts +1 -1
  81. package/dist/components/LpAddressInput.vue.js +1 -1
  82. package/dist/components/LpAlert.vue.d.ts +9 -0
  83. package/dist/components/LpAlert.vue.js +1 -1
  84. package/dist/components/LpAppShell.vue.d.ts +10 -0
  85. package/dist/components/LpAppShell.vue.js +1 -1
  86. package/dist/components/LpAutocomplete.vue.d.ts +31 -6
  87. package/dist/components/LpAutocomplete.vue.js +1 -1
  88. package/dist/components/LpAvatar.vue.d.ts +3 -3
  89. package/dist/components/LpAvatar.vue.js +1 -1
  90. package/dist/components/LpBadge.vue.d.ts +2 -2
  91. package/dist/components/LpBreadcrumbs.vue.d.ts +13 -9
  92. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  93. package/dist/components/LpButton.vue.d.ts +35 -98
  94. package/dist/components/LpButton.vue.js +1 -1
  95. package/dist/components/LpCard.vue.js +1 -1
  96. package/dist/components/LpChart.vue.d.ts +31 -2
  97. package/dist/components/LpChart.vue.js +317 -95
  98. package/dist/components/LpCheckbox.vue.js +1 -1
  99. package/dist/components/LpCodeBlock.vue.d.ts +2 -0
  100. package/dist/components/LpCodeBlock.vue.js +1 -1
  101. package/dist/components/LpColorPicker.vue.js +1 -1
  102. package/dist/components/LpCommandPalette.vue.d.ts +8 -10
  103. package/dist/components/LpCommandPalette.vue.js +88 -78
  104. package/dist/components/LpConfirmDialog.vue.d.ts +15 -3
  105. package/dist/components/LpConfirmDialog.vue.js +1 -1
  106. package/dist/components/LpConfirmHost.vue.d.ts +9 -0
  107. package/dist/components/LpConfirmHost.vue.js +4 -0
  108. package/dist/components/LpContextMenu.vue.d.ts +3 -5
  109. package/dist/components/LpContextMenu.vue.js +1 -1
  110. package/dist/components/LpDatePicker.vue.d.ts +24 -6
  111. package/dist/components/LpDatePicker.vue.js +1 -1
  112. package/dist/components/LpDrawer.vue.d.ts +10 -0
  113. package/dist/components/LpDrawer.vue.js +1 -1
  114. package/dist/components/LpDropzone.vue.js +22 -6
  115. package/dist/components/LpFileTree.vue.d.ts +11 -2
  116. package/dist/components/LpFileTree.vue.js +1 -1
  117. package/dist/components/LpFileTreeNode.vue.js +1 -1
  118. package/dist/components/LpFormField.vue.d.ts +9 -0
  119. package/dist/components/LpFormField.vue.js +1 -1
  120. package/dist/components/LpImageEditor.vue.js +92 -57
  121. package/dist/components/LpInput.vue.d.ts +3 -3
  122. package/dist/components/LpInput.vue.js +1 -1
  123. package/dist/components/LpLightbox.vue.d.ts +17 -0
  124. package/dist/components/LpLightbox.vue.js +1 -1
  125. package/dist/components/LpLink.vue.d.ts +11 -11
  126. package/dist/components/LpLink.vue.js +1 -1
  127. package/dist/components/LpLogViewer.vue.d.ts +20 -2
  128. package/dist/components/LpLogViewer.vue.js +1 -1
  129. package/dist/components/LpMapPicker.vue.d.ts +4 -4
  130. package/dist/components/LpMapPicker.vue.js +1 -1
  131. package/dist/components/LpMediaGallery.vue.js +1 -1
  132. package/dist/components/LpModal.vue.d.ts +16 -6
  133. package/dist/components/LpModal.vue.js +1 -1
  134. package/dist/components/LpModalNav.vue.d.ts +0 -1
  135. package/dist/components/LpModalNav.vue.js +1 -1
  136. package/dist/components/LpNotificationBell.vue.d.ts +11 -2
  137. package/dist/components/LpNotificationBell.vue.js +1 -1
  138. package/dist/components/LpNumberField.vue.d.ts +2 -0
  139. package/dist/components/LpNumberField.vue.js +1 -1
  140. package/dist/components/LpNumberFlow.vue.js +1 -1
  141. package/dist/components/LpOtpInput.vue.js +1 -1
  142. package/dist/components/LpPagination.vue.d.ts +2 -0
  143. package/dist/components/LpPagination.vue.js +1 -1
  144. package/dist/components/LpPasswordInput.vue.js +1 -1
  145. package/dist/components/LpPhoneInput.vue.js +1 -1
  146. package/dist/components/LpPie.vue.d.ts +21 -2
  147. package/dist/components/LpPie.vue.js +1 -1
  148. package/dist/components/LpPopover.vue.d.ts +2 -0
  149. package/dist/components/LpPopover.vue.js +1 -1
  150. package/dist/components/LpProgress.vue.js +1 -1
  151. package/dist/components/LpRadioGroup.vue.js +1 -1
  152. package/dist/components/LpSection.vue.d.ts +1 -1
  153. package/dist/components/LpSegmented.vue.d.ts +5 -0
  154. package/dist/components/LpSegmented.vue.js +1 -1
  155. package/dist/components/LpSelect.vue.d.ts +1 -0
  156. package/dist/components/LpSelect.vue.js +1 -1
  157. package/dist/components/LpShortcutsDialog.vue.d.ts +1 -1
  158. package/dist/components/LpShortcutsDialog.vue.js +11 -8
  159. package/dist/components/LpSidebar.vue.d.ts +6 -2
  160. package/dist/components/LpSidebar.vue.js +1 -1
  161. package/dist/components/LpSidebarNav.vue.js +1 -1
  162. package/dist/components/LpSkeleton.vue.js +1 -1
  163. package/dist/components/LpSlider.vue.js +1 -1
  164. package/dist/components/LpSparkline.vue.d.ts +1 -0
  165. package/dist/components/LpSparkline.vue.js +1 -1
  166. package/dist/components/LpStat.vue.js +1 -1
  167. package/dist/components/LpSwitch.vue.js +1 -1
  168. package/dist/components/LpTable.vue.d.ts +23 -1
  169. package/dist/components/LpTable.vue.js +74 -36
  170. package/dist/components/LpTableOfContents.vue.d.ts +4 -2
  171. package/dist/components/LpTableOfContents.vue.js +1 -1
  172. package/dist/components/LpTabs.vue.d.ts +2 -0
  173. package/dist/components/LpTabs.vue.js +1 -1
  174. package/dist/components/LpTextarea.vue.d.ts +19 -6
  175. package/dist/components/LpTextarea.vue.js +1 -1
  176. package/dist/components/LpThemeProvider.vue.d.ts +22 -0
  177. package/dist/components/LpThemeProvider.vue.js +4 -0
  178. package/dist/components/LpThemeSwitcher.vue.d.ts +13 -0
  179. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  180. package/dist/components/LpTilt.vue.js +1 -1
  181. package/dist/components/LpTimeField.vue.js +1 -1
  182. package/dist/components/LpToaster.vue.d.ts +6 -1
  183. package/dist/components/LpToaster.vue.js +1 -1
  184. package/dist/components/LpTooltip.vue.d.ts +1 -2
  185. package/dist/components/LpTooltip.vue.js +1 -1
  186. package/dist/components/LpTooltipProvider.vue.d.ts +22 -0
  187. package/dist/components/LpTooltipProvider.vue.js +4 -0
  188. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  189. package/dist/components/LpUptimeBar.vue.d.ts +4 -0
  190. package/dist/components/LpUptimeBar.vue.js +1 -1
  191. package/dist/components/chart.d.ts +32 -1
  192. package/dist/components/chart.js +39 -4
  193. package/dist/components/codeHighlight.d.ts +6 -0
  194. package/dist/components/codeHighlight.js +92 -20
  195. package/dist/components/color.d.ts +1 -1
  196. package/dist/components/color.js +20 -9
  197. package/dist/components/commandSearch.d.ts +0 -1
  198. package/dist/components/commandSearch.js +1 -17
  199. package/dist/components/confirm.d.ts +25 -0
  200. package/dist/components/confirm.js +26 -0
  201. package/dist/components/countries.d.ts +6 -0
  202. package/dist/components/countries.js +7 -0
  203. package/dist/components/fileTree.js +16 -7
  204. package/dist/components/imageCrop.d.ts +42 -0
  205. package/dist/components/imageCrop.js +47 -0
  206. package/dist/components/link.d.ts +12 -0
  207. package/dist/components/link.js +19 -0
  208. package/dist/components/modalDepth.d.ts +18 -2
  209. package/dist/components/modalDepth.js +17 -5
  210. package/dist/components/otp.d.ts +5 -0
  211. package/dist/components/otp.js +10 -0
  212. package/dist/components/sidebar.d.ts +7 -1
  213. package/dist/components/tableSort.d.ts +8 -0
  214. package/dist/components/tableSort.js +31 -0
  215. package/dist/composables/useField.d.ts +34 -0
  216. package/dist/composables/useField.js +45 -0
  217. package/dist/composables/useFormDraft.d.ts +10 -3
  218. package/dist/composables/useFormDraft.js +9 -14
  219. package/dist/composables/useHotkeys.d.ts +17 -1
  220. package/dist/composables/useHotkeys.js +55 -18
  221. package/dist/composables/useInputFilter.d.ts +17 -0
  222. package/dist/composables/useInputFilter.js +47 -10
  223. package/dist/composables/useLpConfig.d.ts +24 -0
  224. package/dist/composables/useLpConfig.js +44 -0
  225. package/dist/composables/useRovingFocus.js +7 -0
  226. package/dist/composables/useTilt.d.ts +2 -2
  227. package/dist/composables/useTilt.js +5 -6
  228. package/dist/composables/useToast.d.ts +20 -0
  229. package/dist/composables/useToast.js +28 -4
  230. package/dist/composables/useZoomPan.d.ts +3 -3
  231. package/dist/composables/useZoomPan.js +4 -4
  232. package/dist/index.d.ts +33 -3
  233. package/dist/index.js +185 -139
  234. package/dist/layout/tree.d.ts +2 -0
  235. package/dist/layout/tree.js +16 -1
  236. package/dist/layout/useLayout.d.ts +15 -0
  237. package/dist/layout/useLayout.js +41 -14
  238. package/dist/theme/useTheme.d.ts +32 -9
  239. package/package.json +8 -7
  240. package/src/component-names.ts +5 -7
  241. package/src/components/ContextMenuItems.vue +88 -0
  242. package/src/components/LayoutCanvas.vue +26 -6
  243. package/src/components/LayoutNode.vue +19 -2
  244. package/src/components/LpActionBar.vue +30 -7
  245. package/src/components/LpAddressInput.vue +57 -41
  246. package/src/components/LpAlert.vue +16 -3
  247. package/src/components/LpAppShell.vue +45 -12
  248. package/src/components/LpAutocomplete.vue +46 -10
  249. package/src/components/LpAvatar.vue +19 -5
  250. package/src/components/LpBreadcrumbs.vue +65 -41
  251. package/src/components/LpButton.vue +59 -13
  252. package/src/components/LpChart.vue +272 -28
  253. package/src/components/LpCheckbox.vue +7 -3
  254. package/src/components/LpCodeBlock.vue +50 -14
  255. package/src/components/LpColorPicker.vue +39 -9
  256. package/src/components/LpCommandPalette.vue +36 -25
  257. package/src/components/LpConfirmDialog.vue +68 -17
  258. package/src/components/LpConfirmHost.vue +59 -0
  259. package/src/components/LpContextMenu.vue +14 -115
  260. package/src/components/LpDatePicker.vue +99 -68
  261. package/src/components/LpDrawer.vue +56 -19
  262. package/src/components/LpDropzone.vue +21 -4
  263. package/src/components/LpFileTree.vue +33 -7
  264. package/src/components/LpFormField.vue +30 -7
  265. package/src/components/LpImageEditor.vue +96 -57
  266. package/src/components/LpInput.vue +18 -7
  267. package/src/components/LpLightbox.vue +140 -29
  268. package/src/components/LpLink.vue +24 -13
  269. package/src/components/LpLogViewer.vue +86 -17
  270. package/src/components/LpMapPicker.vue +36 -13
  271. package/src/components/LpMediaGallery.vue +8 -2
  272. package/src/components/LpModal.vue +53 -27
  273. package/src/components/LpModalNav.vue +5 -2
  274. package/src/components/LpNotificationBell.vue +63 -17
  275. package/src/components/LpNumberField.vue +21 -6
  276. package/src/components/LpNumberFlow.vue +26 -10
  277. package/src/components/LpOtpInput.vue +43 -8
  278. package/src/components/LpPagination.vue +23 -7
  279. package/src/components/LpPasswordInput.vue +4 -1
  280. package/src/components/LpPhoneInput.vue +53 -27
  281. package/src/components/LpPie.vue +94 -15
  282. package/src/components/LpPopover.vue +3 -0
  283. package/src/components/LpProgress.vue +5 -3
  284. package/src/components/LpRadioGroup.vue +9 -2
  285. package/src/components/LpSegmented.vue +12 -2
  286. package/src/components/LpSelect.vue +0 -0
  287. package/src/components/LpShortcutsDialog.vue +8 -3
  288. package/src/components/LpSidebar.vue +14 -9
  289. package/src/components/LpSidebarNav.vue +17 -5
  290. package/src/components/LpSkeleton.vue +4 -1
  291. package/src/components/LpSlider.vue +7 -1
  292. package/src/components/LpSparkline.vue +11 -3
  293. package/src/components/LpSwitch.vue +30 -7
  294. package/src/components/LpTable.vue +91 -32
  295. package/src/components/LpTableOfContents.vue +50 -32
  296. package/src/components/LpTabs.vue +23 -6
  297. package/src/components/LpTextarea.vue +44 -6
  298. package/src/components/LpThemeProvider.vue +41 -0
  299. package/src/components/LpThemeSwitcher.vue +64 -17
  300. package/src/components/LpTilt.vue +2 -2
  301. package/src/components/LpTimeField.vue +14 -3
  302. package/src/components/LpToaster.vue +66 -26
  303. package/src/components/LpTooltip.vue +22 -5
  304. package/src/components/LpTooltipProvider.vue +24 -0
  305. package/src/components/LpTopologyCanvas.vue +12 -1
  306. package/src/components/LpUptimeBar.vue +46 -8
  307. package/src/components/chart.ts +77 -5
  308. package/src/components/codeHighlight.ts +111 -15
  309. package/src/components/color.ts +30 -9
  310. package/src/components/commandSearch.ts +0 -17
  311. package/src/components/confirm.ts +58 -0
  312. package/src/components/countries.ts +12 -0
  313. package/src/components/fileTree.ts +19 -7
  314. package/src/components/imageCrop.ts +107 -0
  315. package/src/components/link.ts +34 -0
  316. package/src/components/modalDepth.ts +48 -6
  317. package/src/components/otp.ts +16 -0
  318. package/src/components/sidebar.ts +7 -1
  319. package/src/components/tableSort.ts +37 -0
  320. package/src/composables/useField.ts +79 -0
  321. package/src/composables/useFormDraft.ts +25 -20
  322. package/src/composables/useHotkeys.ts +89 -18
  323. package/src/composables/useInputFilter.ts +77 -14
  324. package/src/composables/useLpConfig.ts +87 -0
  325. package/src/composables/useRovingFocus.ts +11 -0
  326. package/src/composables/useTilt.ts +5 -6
  327. package/src/composables/useToast.ts +56 -4
  328. package/src/composables/useZoomPan.ts +6 -6
  329. package/src/index.ts +38 -3
  330. package/src/layout/tree.ts +30 -0
  331. package/src/layout/useLayout.ts +68 -18
  332. package/src/nuxt.ts +59 -17
  333. package/src/runtime/config.plugin.ts +14 -0
  334. package/src/runtime/theme.plugin.ts +42 -22
  335. package/src/theme/useTheme.ts +206 -100
  336. package/dist/chunks/LpBreadcrumbs-p4jHe_V-.js +0 -62
  337. package/dist/chunks/LpContextMenu-CwW1QXe_.js +0 -217
  338. package/dist/chunks/LpDatePicker-CwOToxGq.js +0 -164
  339. package/dist/chunks/LpFormField-CazBc2-a.js +0 -62
  340. package/dist/chunks/LpTextarea-t7F6gv8E.js +0 -47
@@ -12,37 +12,47 @@ import {
12
12
  ToastViewport,
13
13
  } from "reka-ui"
14
14
  import { onBeforeUnmount, ref, watch } from "vue"
15
+ import { useLabels } from "../composables/useLpConfig"
15
16
  import { useToast, type ToastItem } from "../composables/useToast"
16
17
  import { CLOSE_ICON } from "./dropdown"
17
18
  import LpIcon from "./LpIcon.vue"
18
19
 
20
+ const props = defineProps<{ labels?: Partial<{ dismiss: string }> }>()
21
+ const text = useLabels("toast", { dismiss: "Dismiss" }, () => props.labels)
22
+
19
23
  const { queue, dismiss } = useToast()
20
24
 
25
+ // A toast replaced through its `key` keeps its id and gets a new revision, so
26
+ // everything tied to one rendered toast goes by both.
27
+ const tag = (t: ToastItem) => `${t.id}:${t.rev}`
28
+
21
29
  // Toasts that are playing their exit animation. We don't splice them out of the
22
30
  // queue immediately on close — that would unmount before `toast-out` can run.
23
31
  // Instead we flip `open` to false (reka sets data-state=closed → the keyframe
24
32
  // plays), then drop the toast for real once `animationend` fires. A safety
25
33
  // timer removes it even if the animationend event is missed (e.g. tab hidden).
26
- const closing = ref<Set<number>>(new Set())
34
+ const closing = ref<Set<string>>(new Set())
27
35
 
28
- function beginClose(id: number) {
29
- if (closing.value.has(id)) return
30
- closing.value = new Set(closing.value).add(id)
36
+ function beginClose(t: ToastItem) {
37
+ const at = tag(t)
38
+ if (closing.value.has(at)) return
39
+ closing.value = new Set(closing.value).add(at)
31
40
  // Fallback slightly longer than the 140ms toast-out duration.
32
- window.setTimeout(() => finishClose(id), 220)
41
+ window.setTimeout(() => finishClose(t.id, at), 220)
33
42
  }
34
43
 
35
- function finishClose(id: number) {
36
- if (!closing.value.has(id)) return
44
+ function finishClose(id: number, at: string) {
45
+ if (!closing.value.has(at)) return
37
46
  const next = new Set(closing.value)
38
- next.delete(id)
47
+ next.delete(at)
39
48
  closing.value = next
40
- dismiss(id)
49
+ // Replaced while it was leaving: the new revision stays.
50
+ if (queue.some((t) => tag(t) === at)) dismiss(id)
41
51
  }
42
52
 
43
- function onAnimEnd(e: AnimationEvent, id: number) {
53
+ function onAnimEnd(e: AnimationEvent, t: ToastItem) {
44
54
  // Only the exit keyframe should trigger removal, not toast-in.
45
- if (e.animationName === "toast-out") finishClose(id)
55
+ if (e.animationName === "toast-out") finishClose(t.id, tag(t))
46
56
  }
47
57
 
48
58
  const variantIcon: Record<ToastItem["variant"], string> = {
@@ -97,7 +107,7 @@ watch(
97
107
  // Same for the closing set — a toast evicted by the queue cap or clear()
98
108
  // leaves without an animationend, so prune dead ids here.
99
109
  if (closing.value.size) {
100
- const live = new Set([...closing.value].filter((id) => queue.some((t) => t.id === id)))
110
+ const live = new Set([...closing.value].filter((at) => queue.some((t) => tag(t) === at)))
101
111
  if (live.size !== closing.value.size) closing.value = live
102
112
  }
103
113
  if (queue.some((t) => t.dismissAt !== null)) startTimer()
@@ -126,16 +136,41 @@ function progress(t: ToastItem): number {
126
136
  return Math.max(0, Math.min(100, (remaining / t.duration) * 100))
127
137
  }
128
138
 
129
- async function runClick(t: ToastItem) {
130
- if (t.onClick) {
131
- await t.onClick()
132
- beginClose(t.id)
139
+ /*
140
+ * reka cancels the click that ends a swipe by adding a listener after ours, so
141
+ * by the time it runs our handler has already acted on it: a swiped-away
142
+ * "View details" toast navigated. The swipe marks the toast instead, and the
143
+ * mark lasts only until that trailing click has had its chance.
144
+ */
145
+ let swiped: string | null = null
146
+ function onSwipeDone(t: ToastItem) {
147
+ swiped = tag(t)
148
+ window.setTimeout(() => (swiped = null), 0)
149
+ }
150
+
151
+ const busy = ref<Set<string>>(new Set())
152
+
153
+ async function settle(t: ToastItem, run: () => void | Promise<void>) {
154
+ const at = tag(t)
155
+ if (busy.value.has(at)) return
156
+ busy.value = new Set(busy.value).add(at)
157
+ try {
158
+ await run()
159
+ beginClose(t)
160
+ } finally {
161
+ const next = new Set(busy.value)
162
+ next.delete(at)
163
+ busy.value = next
133
164
  }
134
165
  }
135
166
 
136
- async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>[number]) {
137
- await action.onClick()
138
- beginClose(t.id)
167
+ function runClick(t: ToastItem) {
168
+ if (swiped === tag(t)) return
169
+ if (t.onClick) return settle(t, t.onClick)
170
+ }
171
+
172
+ function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>[number]) {
173
+ return settle(t, action.onClick)
139
174
  }
140
175
  </script>
141
176
 
@@ -143,13 +178,16 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
143
178
  <ToastProvider>
144
179
  <ToastRoot
145
180
  v-for="t in queue"
146
- :key="t.id"
147
- :open="!closing.has(t.id)"
181
+ :key="tag(t)"
182
+ :open="!closing.has(tag(t))"
148
183
  :duration="t.duration"
184
+ :type="t.variant === 'error' || t.variant === 'warning' ? 'foreground' : 'background'"
149
185
  class="pointer-events-auto relative overflow-hidden rounded-card border border-line bg-surface-raised shadow-panel data-[state=open]:animate-[toast-in_200ms_var(--ease-emphasized)] data-[state=closed]:animate-[toast-out_140ms_ease]"
150
186
  :class="{ 'cursor-pointer': t.onClick }"
151
- @update:open="(open) => !open && beginClose(t.id)"
152
- @animationend="(e: AnimationEvent) => onAnimEnd(e, t.id)"
187
+ @update:open="(open) => !open && beginClose(t)"
188
+ @animationend="(e: AnimationEvent) => onAnimEnd(e, t)"
189
+ @swipe-end="onSwipeDone(t)"
190
+ @swipe-cancel="onSwipeDone(t)"
153
191
  @pause="onPause(t)"
154
192
  @resume="onResume(t)"
155
193
  @click="runClick(t)"
@@ -178,7 +216,9 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
178
216
  v-for="(action, i) in t.actions"
179
217
  :key="i"
180
218
  type="button"
181
- class="inline-flex items-center gap-1.5 rounded-control border border-line bg-surface-soft px-2.5 py-1 text-xs font-medium text-ink outline-none transition-[border-color,background-color,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] hover:border-line-strong hover:bg-surface-soft/70 active:scale-95 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring"
219
+ :disabled="busy.has(tag(t))"
220
+ :aria-busy="busy.has(tag(t)) || undefined"
221
+ class="inline-flex items-center gap-1.5 rounded-control disabled:opacity-60 border border-line bg-surface-soft px-2.5 py-1 text-xs font-medium text-ink outline-none transition-[border-color,background-color,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] hover:border-line-strong hover:bg-surface-soft/70 active:scale-95 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring"
182
222
  @click="runAction(t, action)"
183
223
  >
184
224
  <LpIcon v-if="action.icon" :name="action.icon" :size="14" />
@@ -190,8 +230,8 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
190
230
  <button
191
231
  type="button"
192
232
  class="group -mr-1 -mt-1 rounded-md p-1 text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink"
193
- aria-label="Dismiss"
194
- @click.stop="beginClose(t.id)"
233
+ :aria-label="text.dismiss"
234
+ @click.stop="beginClose(t)"
195
235
  >
196
236
  <LpIcon
197
237
  name="lucide:x"
@@ -28,13 +28,14 @@
28
28
  * them and the control stops opening. Put `aria-label` on the trigger instead.
29
29
  */
30
30
  import {
31
+ injectTooltipProviderContext,
31
32
  TooltipContent,
32
33
  TooltipPortal,
33
34
  TooltipProvider,
34
35
  TooltipRoot,
35
36
  TooltipTrigger,
36
37
  } from "reka-ui"
37
- import { onBeforeUnmount, ref, watch } from "vue"
38
+ import { Fragment, onBeforeUnmount, ref, watch } from "vue"
38
39
  import {
39
40
  springSettled,
40
41
  springStep,
@@ -54,7 +55,7 @@ const props = withDefaults(
54
55
  * adding a description.
55
56
  */
56
57
  label?: boolean
57
- /** Milliseconds before the tip appears on hover. */
58
+ /** Milliseconds before the tip appears on hover. Unset follows the provider, else 200. */
58
59
  delay?: number
59
60
  /**
60
61
  * Show on any focus, not just keyboard focus. Off by default: a tip that
@@ -83,7 +84,7 @@ const props = withDefaults(
83
84
  */
84
85
  disabled?: boolean
85
86
  }>(),
86
- { delay: 200, side: "top" },
87
+ { side: "top" },
87
88
  )
88
89
 
89
90
  /*
@@ -101,6 +102,16 @@ const props = withDefaults(
101
102
  * the pointer exactly reads as jitter, while a slight lag reads as following.
102
103
  * Same reasoning, and the same shared "float" feel, as LpChart's readout.
103
104
  */
105
+ /*
106
+ * A provider of its own only when none is above. Each tip in its own provider
107
+ * meant moving along a toolbar waited the full delay at every button; under a
108
+ * shared one (LpTooltipProvider at the root) the next tip opens at once.
109
+ */
110
+ const shared = injectTooltipProviderContext(null)
111
+
112
+ const reducedMotion = () =>
113
+ typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
114
+
104
115
  const target = { x: 0, y: 0 }
105
116
  const offset = ref({ x: 0, y: 0 })
106
117
 
@@ -155,6 +166,11 @@ const onPointerMove = (e: PointerEvent) => {
155
166
  const half = props.side === "top" || props.side === "bottom" ? rect.height / 2 : 0
156
167
  const dir = props.side === "bottom" ? -1 : 1
157
168
  target.y = e.clientY - (rect.top + rect.height / 2) + half * dir
169
+ // Without motion the tip goes straight to the reading instead of trailing.
170
+ if (reducedMotion()) {
171
+ offset.value = { x: target.x, y: target.y }
172
+ return
173
+ }
158
174
  if (!frame) frame = requestAnimationFrame(settle)
159
175
  }
160
176
 
@@ -205,7 +221,7 @@ onBeforeUnmount(() => {
205
221
  </script>
206
222
 
207
223
  <template>
208
- <TooltipProvider :delay-duration="delay">
224
+ <component :is="shared ? Fragment : TooltipProvider" v-bind="shared ? {} : { delayDuration: delay ?? 200 }">
209
225
  <!--
210
226
  `disable-closing-trigger` in follow mode: reka dismisses a tip when its
211
227
  trigger is clicked, which is right for a button (the tip explained the
@@ -214,6 +230,7 @@ onBeforeUnmount(() => {
214
230
  -->
215
231
  <TooltipRoot
216
232
  :open="open"
233
+ :delay-duration="delay"
217
234
  :ignore-non-keyboard-focus="!showOnPointerFocus"
218
235
  :disable-hoverable-content="followCursor"
219
236
  :disable-closing-trigger="followCursor"
@@ -245,7 +262,7 @@ onBeforeUnmount(() => {
245
262
  </TooltipContent>
246
263
  </TooltipPortal>
247
264
  </TooltipRoot>
248
- </TooltipProvider>
265
+ </component>
249
266
  </template>
250
267
 
251
268
  <style>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ /*
3
+ * Shared timing for every LpTooltip below it: once one tip is showing, moving
4
+ * to the next opens it without waiting the delay again. Mount once near the
5
+ * app root; tooltips outside any provider keep working on their own.
6
+ */
7
+ import { TooltipProvider } from "reka-ui"
8
+
9
+ withDefaults(
10
+ defineProps<{
11
+ /** Milliseconds before the first tip appears on hover. */
12
+ delay?: number
13
+ /** How long after a tip closes the next one still opens at once. */
14
+ skipDelay?: number
15
+ }>(),
16
+ { delay: 200, skipDelay: 300 },
17
+ )
18
+ </script>
19
+
20
+ <template>
21
+ <TooltipProvider :delay-duration="delay" :skip-delay-duration="skipDelay">
22
+ <slot />
23
+ </TooltipProvider>
24
+ </template>
@@ -22,7 +22,7 @@ import {
22
22
  type NodeTypesObject,
23
23
  } from "@vue-flow/core"
24
24
  import { MiniMap } from "@vue-flow/minimap"
25
- import { computed, markRaw, watch, type Component } from "vue"
25
+ import { computed, defineComponent, h, markRaw, watch, type Component } from "vue"
26
26
  import LpInfraNode, { type InfraNodeData } from "./LpInfraNode.vue"
27
27
  import LpLaneNode from "./LpLaneNode.vue"
28
28
  import LpServiceNode, { type ServiceNodeData } from "./LpServiceNode.vue"
@@ -193,7 +193,18 @@ const emit = defineEmits<{
193
193
  // their own props (data/selected) which Vue Flow supplies at runtime as part of
194
194
  // NodeProps. Widen to `Component` (not the concrete DefineComponent) so the
195
195
  // registry types cleanly without a cast.
196
+ // Vue Flow ships no "group" renderer, so a host frame without one warns and
197
+ // draws nothing inside its styled box. The frame only needs its label.
198
+ const GroupNode = defineComponent({
199
+ props: { label: { type: [String, Object], default: undefined } },
200
+ setup: (props) => () =>
201
+ typeof props.label === "string"
202
+ ? h("div", { class: "px-3 pt-2 text-xs font-medium text-muted" }, props.label)
203
+ : null,
204
+ })
205
+
196
206
  const nodeTypes: NodeTypesObject = {
207
+ group: markRaw(GroupNode) as Component,
197
208
  infra: markRaw(LpInfraNode) as Component,
198
209
  service: markRaw(LpServiceNode) as Component,
199
210
  lane: markRaw(LpLaneNode) as Component,
@@ -11,6 +11,7 @@ import LpNumberFlow from "./LpNumberFlow.vue"
11
11
  import LpRollingText from "./LpRollingText.vue"
12
12
  import LpTooltip from "./LpTooltip.vue"
13
13
  import { useMergedAttrs } from "../composables/useMergedClass"
14
+ import { useLabels } from "../composables/useLpConfig"
14
15
 
15
16
  export type UptimeStatus = "operational" | "degraded" | "down" | "maintenance" | "empty"
16
17
 
@@ -39,6 +40,8 @@ const props = withDefaults(
39
40
  * `{ degraded: '#ffb84d' }` to recolour just one state.
40
41
  */
41
42
  colors?: Partial<Record<UptimeStatus, string>>
43
+ /** Status names, for the tooltip and screen readers. */
44
+ labels?: Partial<Record<UptimeStatus, string>>
42
45
  }>(),
43
46
  { showUptime: true },
44
47
  )
@@ -64,13 +67,17 @@ const opacityFor = (status: UptimeStatus): number =>
64
67
  const colorFor = (status: UptimeStatus): string =>
65
68
  props.colors?.[status] ?? defaultColor[status]
66
69
 
67
- const statusLabel: Record<UptimeStatus, string> = {
68
- operational: "Operational",
69
- degraded: "Degraded",
70
- down: "Down",
71
- maintenance: "Maintenance",
72
- empty: "No data",
73
- }
70
+ const statusLabel = useLabels(
71
+ "uptimeBar",
72
+ {
73
+ operational: "Operational",
74
+ degraded: "Degraded",
75
+ down: "Down",
76
+ maintenance: "Maintenance",
77
+ empty: "No data",
78
+ },
79
+ () => props.labels,
80
+ )
74
81
 
75
82
  // Uptime counts only slices with data; operational + maintenance are "up".
76
83
  const uptimePct = computed(() => {
@@ -114,9 +121,35 @@ const tip = computed(() => {
114
121
  if (i == null) return null
115
122
  const seg = props.segments[i]
116
123
  if (!seg) return null
117
- return { status: seg.status, text: seg.label ?? statusLabel[seg.status] }
124
+ return { status: seg.status, text: seg.label ?? statusLabel.value[seg.status] }
118
125
  })
119
126
 
127
+ /*
128
+ * One Tab stop for the whole bar, arrows to walk it. A quarter's worth of
129
+ * slices as separate stops made the bar ninety Tab presses to get past.
130
+ */
131
+ const focusIndex = ref(-1)
132
+ const slots = ref<HTMLElement[]>([])
133
+ const tabStop = computed(() =>
134
+ focusIndex.value >= 0 && focusIndex.value < props.segments.length
135
+ ? focusIndex.value
136
+ : props.segments.length - 1,
137
+ )
138
+
139
+ function onSlotKeydown(e: KeyboardEvent, i: number) {
140
+ const last = props.segments.length - 1
141
+ const next =
142
+ e.key === "ArrowRight" ? Math.min(last, i + 1)
143
+ : e.key === "ArrowLeft" ? Math.max(0, i - 1)
144
+ : e.key === "Home" ? 0
145
+ : e.key === "End" ? last
146
+ : -1
147
+ if (next < 0) return
148
+ e.preventDefault()
149
+ focusIndex.value = next
150
+ slots.value[next]?.focus()
151
+ }
152
+
120
153
  const uptimeDecimals = computed(() => {
121
154
  const v = uptimePct.value
122
155
  if (v == null) return 0
@@ -168,10 +201,15 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
168
201
  <button
169
202
  v-for="(seg, i) in segments"
170
203
  :key="i"
204
+ ref="slots"
171
205
  type="button"
172
206
  class="lp-uptime__slot min-w-0 flex-1 outline-none focus-visible:ring-2 focus-visible:ring-ring"
207
+ :tabindex="i === tabStop ? 0 : -1"
173
208
  :aria-label="seg.label ?? statusLabel[seg.status]"
174
209
  @pointerenter="hovered = i"
210
+ @focus="focusIndex = i; hovered = i"
211
+ @blur="hovered === i && (hovered = null)"
212
+ @keydown="onSlotKeydown($event, i)"
175
213
  >
176
214
  <span
177
215
  class="lp-uptime__mark block h-full rounded-[1px] transition-[opacity,scale] duration-[var(--duration-fast)] motion-reduce:transition-none"
@@ -30,6 +30,62 @@ export interface ChartSeries {
30
30
 
31
31
  export type ChartAxis = "left" | "right"
32
32
 
33
+ /**
34
+ * A reference level drawn across the plot: a line at `value`, or a band from
35
+ * `value` to `to`. SLOs, budgets, alert thresholds.
36
+ */
37
+ export interface ChartThreshold {
38
+ value: number
39
+ /** Upper (or lower) edge of a band. Omit for a single line. */
40
+ to?: number
41
+ label?: string
42
+ /** Any CSS colour. Defaults to the strong line token. */
43
+ color?: string
44
+ axis?: ChartAxis
45
+ /** Solid instead of the default dashed line. */
46
+ solid?: boolean
47
+ }
48
+
49
+ /**
50
+ * A vertical marker at one x position: a deploy, an incident, a release.
51
+ * `at` is a point index or one of the x labels.
52
+ */
53
+ export interface ChartMarker {
54
+ at: number | string
55
+ label?: string
56
+ color?: string
57
+ }
58
+
59
+ /** The x index a marker points at, or -1 when it names nothing on the axis. */
60
+ export const markerIndex = (at: number | string, labels: string[], count: number): number => {
61
+ if (typeof at === "number") return Number.isInteger(at) && at >= 0 && at < count ? at : -1
62
+ return labels.indexOf(at)
63
+ }
64
+
65
+ /**
66
+ * A one-sentence reading of the chart for assistive technology: what is
67
+ * plotted, over how many points, and each series' range and latest value.
68
+ */
69
+ export const chartSummary = (
70
+ series: NormalSeries[],
71
+ labels: string[],
72
+ fmt: (s: NormalSeries, v: number) => string,
73
+ title?: string,
74
+ ): string => {
75
+ const parts: string[] = []
76
+ const span = labels.length ? `${labels.length} points, ${labels[0]} to ${labels[labels.length - 1]}` : ""
77
+ parts.push([title, span].filter(Boolean).join(", "))
78
+ for (const s of series) {
79
+ const values = s.values.filter((v): v is number => v != null)
80
+ if (!values.length) continue
81
+ const last = values[values.length - 1]
82
+ parts.push(
83
+ `${s.name}: from ${fmt(s, Math.min(...values))} to ${fmt(s, Math.max(...values))}, latest ${fmt(s, last)}`,
84
+ )
85
+ }
86
+ return parts.filter(Boolean).join(". ")
87
+ }
88
+
33
89
  export interface NormalSeries {
34
90
  name: string
35
91
  color: string
@@ -592,6 +648,7 @@ export const calloutLabels = (
592
648
  return side === "right" ? a : -a
593
649
  }
594
650
  const list = sides[side].sort((p, q) => rank(p.mid) - rank(q.mid))
651
+ if (!list.length) continue
595
652
 
596
653
  /*
597
654
  * Resolve collisions by NUDGING, not by stacking.
@@ -676,11 +733,26 @@ export const calloutLabels = (
676
733
  return out
677
734
  }
678
735
 
679
- /** Compact default tick formatting: 1.2k, 3.4M — never 1200.0000001. */
736
+ /** Compact default tick formatting: 1.2k, 3.4M — never 1200.0000001 or 1000k. */
680
737
  export const formatTick = (v: number): string => {
738
+ if (!Number.isFinite(v)) return String(v)
739
+ const units: [number, string][] = [
740
+ [1e3, "k"],
741
+ [1e6, "M"],
742
+ [1e9, "B"],
743
+ [1e12, "T"],
744
+ ]
681
745
  const abs = Math.abs(v)
682
- if (abs >= 1e9) return `${Number((v / 1e9).toFixed(1))}B`
683
- if (abs >= 1e6) return `${Number((v / 1e6).toFixed(1))}M`
684
- if (abs >= 1e3) return `${Number((v / 1e3).toFixed(1))}k`
685
- return String(Number(v.toFixed(2)))
746
+ let i = units.length - 1
747
+ while (i >= 0 && abs < units[i][0]) i--
748
+ if (i < 0) {
749
+ const plain = Number(v.toFixed(2))
750
+ return Math.abs(plain) >= 1000 ? formatTick(Math.sign(v) * 1000) : String(plain)
751
+ }
752
+ const scaled = Number((v / units[i][0]).toFixed(1))
753
+ // 999 950 rounds to "1000k"; it reads as the next unit up.
754
+ if (Math.abs(scaled) >= 1000 && i < units.length - 1) {
755
+ return `${Number((v / units[i + 1][0]).toFixed(1))}${units[i + 1][1]}`
756
+ }
757
+ return `${scaled}${units[i][1]}`
686
758
  }
@@ -79,19 +79,67 @@ const BOOLEANS = new Set([
79
79
  "Some", "Ok", "Err", // rust common variants read as constants
80
80
  ])
81
81
 
82
- // One master regex, alternation ordered so longer/greedier rules win first.
83
- // Order: comments → strings → numbers → identifiers → punctuation.
84
- const MASTER = new RegExp(
85
- [
86
- "(\\/\\/[^\\n]*|#[^\\n]*|\\/\\*[\\s\\S]*?\\*\\/)", // 1 comment
87
- "(\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|`(?:\\\\.|[^`\\\\])*`)", // 2 string
88
- "(@[A-Za-z_$][\\w$]*)", // 3 annotation (@Override, @ConfigSerializable — JVM)
89
- "(\\b\\d[\\d_]*(?:\\.\\d+)?(?:e[+-]?\\d+)?[fFlLdD]?\\b)", // 4 number (JVM suffixes)
90
- "([A-Za-z_$][\\w$]*)", // 5 identifier (keyword/bool/func/prop/plain)
91
- "([{}()\\[\\];:,.<>=+\\-*/%!&|?]+)", // 6 punctuation
92
- ].join("|"),
93
- "g",
94
- )
82
+ /*
83
+ * Comment syntax is the one thing the shared tokenizer cannot guess: `#` opens
84
+ * a comment in shell, Python, YAML and TOML but is a private field in
85
+ * TypeScript, and `//` is a comment in C-family code but part of every URL in a
86
+ * YAML file. So each language names its own comment forms.
87
+ */
88
+ type CommentStyle = "c" | "hash" | "html" | "none"
89
+
90
+ const COMMENTS: Record<CodeLang, CommentStyle> = {
91
+ ts: "c",
92
+ js: "c",
93
+ json: "none",
94
+ bash: "hash",
95
+ html: "html",
96
+ python: "hash",
97
+ rust: "c",
98
+ yaml: "hash",
99
+ toml: "hash",
100
+ kotlin: "c",
101
+ java: "c",
102
+ groovy: "c",
103
+ plain: "none",
104
+ }
105
+
106
+ const COMMENT_PATTERN: Record<CommentStyle, string> = {
107
+ c: "\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\/",
108
+ // A `#` only starts a comment at the start of a word, so `a#b` in a URL or
109
+ // `${#arr[@]}` in shell stay code.
110
+ hash: "(?<![\\w$])#[^\\n]*",
111
+ html: "<!--[\\s\\S]*?-->",
112
+ none: "(?!)",
113
+ }
114
+
115
+ /** Where an unterminated block comment opens on a line, and what closes it. */
116
+ const BLOCK: Partial<Record<CommentStyle, { open: string; close: string }>> = {
117
+ c: { open: "/*", close: "*/" },
118
+ html: { open: "<!--", close: "-->" },
119
+ }
120
+
121
+ const masters = new Map<CommentStyle, RegExp>()
122
+
123
+ // One master regex per comment style, alternation ordered so longer/greedier
124
+ // rules win first. Order: comments → strings → numbers → identifiers → punctuation.
125
+ function masterFor(style: CommentStyle): RegExp {
126
+ let re = masters.get(style)
127
+ if (!re) {
128
+ re = new RegExp(
129
+ [
130
+ `(${COMMENT_PATTERN[style]})`, // 1 comment
131
+ "(\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|`(?:\\\\.|[^`\\\\])*`)", // 2 string
132
+ "(@[A-Za-z_$][\\w$]*)", // 3 annotation (@Override, @ConfigSerializable — JVM)
133
+ "(\\b\\d[\\d_]*(?:\\.\\d+)?(?:e[+-]?\\d+)?[fFlLdD]?\\b)", // 4 number (JVM suffixes)
134
+ "(#?[A-Za-z_$][\\w$]*)", // 5 identifier (keyword/bool/func/prop/plain), incl. #private
135
+ "([{}()\\[\\];:,.<>=+\\-*/%!&|?]+)", // 6 punctuation
136
+ ].join("|"),
137
+ "g",
138
+ )
139
+ masters.set(style, re)
140
+ }
141
+ return re
142
+ }
95
143
 
96
144
  function classifyIdent(name: string, before: string, after: string): string {
97
145
  if (KEYWORDS.has(name)) return C.keyword
@@ -103,11 +151,13 @@ function classifyIdent(name: string, before: string, after: string): string {
103
151
 
104
152
  /** Tokenize one line into coloured spans. JSON keys get the property colour. */
105
153
  export function tokenizeLine(line: string, lang: CodeLang): Token[] {
154
+ if (lang === "plain") return line ? [{ text: line, cls: "" }] : []
106
155
  const out: Token[] = []
107
156
  let last = 0
108
157
  let m: RegExpExecArray | null
109
- MASTER.lastIndex = 0
110
- while ((m = MASTER.exec(line)) !== null) {
158
+ const master = masterFor(COMMENTS[lang])
159
+ master.lastIndex = 0
160
+ while ((m = master.exec(line)) !== null) {
111
161
  if (m.index > last) out.push({ text: line.slice(last, m.index), cls: "" })
112
162
  const [full, comment, str, annotation, num, ident, punct] = m
113
163
  if (comment != null) out.push({ text: full, cls: C.comment })
@@ -135,3 +185,49 @@ export function tokenizeLine(line: string, lang: CodeLang): Token[] {
135
185
  if (last < line.length) out.push({ text: line.slice(last), cls: "" })
136
186
  return out
137
187
  }
188
+
189
+ /**
190
+ * Tokenize a whole snippet, carrying a block comment (`/* … *\/`, `<!-- … -->`)
191
+ * across the lines it spans. `tokenizeLine` alone sees each line in isolation
192
+ * and coloured the middle of a doc comment as code.
193
+ */
194
+ export function tokenizeLines(lines: string[], lang: CodeLang): Token[][] {
195
+ const block = BLOCK[COMMENTS[lang]]
196
+ if (!block) return lines.map((line) => tokenizeLine(line, lang))
197
+ const out: Token[][] = []
198
+ let open = false
199
+ for (const line of lines) {
200
+ let rest = line
201
+ const tokens: Token[] = []
202
+ if (open) {
203
+ const end = rest.indexOf(block.close)
204
+ if (end === -1) {
205
+ out.push(rest ? [{ text: rest, cls: C.comment }] : [])
206
+ continue
207
+ }
208
+ tokens.push({ text: rest.slice(0, end + block.close.length), cls: C.comment })
209
+ rest = rest.slice(end + block.close.length)
210
+ open = false
211
+ }
212
+ const lineTokens = tokenizeLine(rest, lang)
213
+ // A closed comment is already a comment token, so an opener left in plain
214
+ // code is one the line never closes: from there on, the line is comment.
215
+ const at = lineTokens.findIndex(
216
+ (t) => t.cls !== C.comment && t.cls !== C.string && t.text.includes(block.open),
217
+ )
218
+ if (at >= 0) {
219
+ const cut = lineTokens[at].text.indexOf(block.open)
220
+ const tail = lineTokens
221
+ .slice(at)
222
+ .map((t) => t.text)
223
+ .join("")
224
+ .slice(cut)
225
+ tokens.push(...lineTokens.slice(0, at))
226
+ if (cut > 0) tokens.push({ text: lineTokens[at].text.slice(0, cut), cls: lineTokens[at].cls })
227
+ tokens.push({ text: tail, cls: C.comment })
228
+ open = true
229
+ } else tokens.push(...lineTokens)
230
+ out.push(tokens)
231
+ }
232
+ return out
233
+ }