@leavepulse/ui 0.43.4 → 0.45.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 (437) hide show
  1. package/README.md +121 -7
  2. package/dist/assets/{LpCommandPalette-B-0Est4Z.css → LpCommandPalette-nGNnzXqQ.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/{LpRollingText-CR1mb0Vt.css → LpRollingText-BGnE6q9j.css} +3 -0
  6. package/dist/assets/{LpShortcutsDialog-C6YoQ8_M.css → LpShortcutsDialog-CqYX3yy6.css} +28 -7
  7. package/dist/assets/{LpSkeleton-D7xU8IPV.css → LpSkeleton-H3Z5dEIk.css} +4 -1
  8. package/dist/assets/{LpTable-BgPLJOXa.css → LpTable-CPwWxTJS.css} +49 -9
  9. package/dist/chunks/ContextMenuItems-BtKU7074.js +124 -0
  10. package/dist/chunks/{LayoutCanvas-Dd20boUG.js → LayoutCanvas-BkaGXTjx.js} +26 -9
  11. package/dist/chunks/{LayoutNode-DGpZA9LX.js → LayoutNode-B3_9E1Xa.js} +28 -12
  12. package/dist/chunks/{LpActionBar-BlvtGB2P.js → LpActionBar-CnfFVIwC.js} +26 -12
  13. package/dist/chunks/{LpAddressInput-B-Zvxsn7.js → LpAddressInput-BwX8uo4_.js} +54 -49
  14. package/dist/chunks/{LpAlert-BAYHDSS1.js → LpAlert-B6ilrM7T.js} +16 -9
  15. package/dist/chunks/{LpAppShell-BGQVIY-3.js → LpAppShell-C53_RaGF.js} +53 -26
  16. package/dist/chunks/{LpAutocomplete-DExlIZe_.js → LpAutocomplete-C8IgSMsi.js} +52 -24
  17. package/dist/chunks/{LpAvatar-B3-xAOH3.js → LpAvatar-C1XStdOo.js} +19 -10
  18. package/dist/chunks/LpBreadcrumbs-CQJd73Gm.js +95 -0
  19. package/dist/chunks/{LpButton-5VXXCjwF.js → LpButton-CKSwC6Mu.js} +45 -15
  20. package/dist/chunks/{LpCalendar-B7Ko5RdR.js → LpCalendar-DrA2McTU.js} +1 -1
  21. package/dist/chunks/{LpCard-BhI1N_vL.js → LpCard-C2IvucmX.js} +1 -1
  22. package/dist/chunks/{LpCheckbox-CVccKYaH.js → LpCheckbox-Bkj13CbW.js} +8 -4
  23. package/dist/chunks/{LpCodeBlock-Bo0B4TFR.js → LpCodeBlock-ca7ANFyW.js} +49 -17
  24. package/dist/chunks/{LpColorPicker-BlXea7LM.js → LpColorPicker-zz4VGkG4.js} +75 -26
  25. package/dist/chunks/{LpConfirmDialog-Q-bxff8z.js → LpConfirmDialog-DCw5jR1t.js} +59 -17
  26. package/dist/chunks/LpConfirmHost-CbywMpMP.js +49 -0
  27. package/dist/chunks/LpContextMenu-BqkAYa9X.js +69 -0
  28. package/dist/chunks/LpDatePicker-DuusgT69.js +195 -0
  29. package/dist/chunks/LpDateRangePicker-DHeuznfC.js +208 -0
  30. package/dist/chunks/{LpDisclosure-ChQYmjlR.js → LpDisclosure-Cc23-JOT.js} +1 -1
  31. package/dist/chunks/{LpDrawer-BZ6k3hvo.js → LpDrawer-Dt4aB4Fr.js} +65 -28
  32. package/dist/chunks/{LpDropdownMenu-DJpoqRCb.js → LpDropdownMenu-DdJGQzPn.js} +2 -1
  33. package/dist/chunks/{LpEmptyState-Dsdqz1Wj.js → LpEmptyState-BJbZXC8D.js} +1 -1
  34. package/dist/chunks/LpFieldNotice-wtieLUTj.js +64 -0
  35. package/dist/chunks/{LpFileTree-_5vFBgaK.js → LpFileTree-CNurOvkV.js} +25 -10
  36. package/dist/chunks/{LpFileTreeNode-BdcIYKIE.js → LpFileTreeNode-D_qRRdsH.js} +3 -3
  37. package/dist/chunks/LpFormField-CtVYj6tO.js +101 -0
  38. package/dist/chunks/{LpIcon-CCnX5_2j.js → LpIcon-DYtaid0K.js} +20 -6
  39. package/dist/chunks/{LpInput-9EXWUM9B.js → LpInput-D-YGQbEv.js} +58 -20
  40. package/dist/chunks/{LpLightbox-2sYVMY-S.js → LpLightbox-DSDahF23.js} +148 -61
  41. package/dist/chunks/{LpLink-DCND-zyu.js → LpLink-Krkjhe_y.js} +15 -6
  42. package/dist/chunks/{LpLogViewer-CaRy5MeS.js → LpLogViewer-KypbgOyo.js} +300 -72
  43. package/dist/chunks/{LpMapPicker-ul4oYno6.js → LpMapPicker-Cc84LaM3.js} +39 -14
  44. package/dist/chunks/{LpMediaGallery-FF-NquIo.js → LpMediaGallery-YKr-cQPN.js} +7 -4
  45. package/dist/chunks/{LpModal-Bt0pQCI_.js → LpModal-DMMDQHAu.js} +52 -29
  46. package/dist/chunks/{LpModalNav-YaifHrXC.js → LpModalNav-BVPDyIup.js} +10 -7
  47. package/dist/chunks/{LpNotificationBell-DyGGsMAM.js → LpNotificationBell-CCwyJP5C.js} +79 -33
  48. package/dist/chunks/{LpNumberField-CWuX08iX.js → LpNumberField-B77O-z28.js} +21 -8
  49. package/dist/chunks/{LpNumberFlow-amWYaeuA.js → LpNumberFlow-z4WPZcZu.js} +22 -12
  50. package/dist/chunks/{LpOtpInput-y2v-pmiN.js → LpOtpInput-CziPF3Oq.js} +37 -8
  51. package/dist/chunks/{LpPagination-5FxjmmB5.js → LpPagination-BcDlo3r_.js} +31 -20
  52. package/dist/chunks/{LpPasswordInput--sjYCkLS.js → LpPasswordInput-eKzQhbQP.js} +11 -8
  53. package/dist/chunks/LpPhoneInput-BCbrrhgE.js +336 -0
  54. package/dist/chunks/{LpPie-D6OnzwlJ.js → LpPie-BEJ2SfI6.js} +84 -28
  55. package/dist/chunks/{LpPopover-8268UmlC.js → LpPopover-DCKeqiVn.js} +5 -2
  56. package/dist/chunks/{LpProgress-Btq5W4Ut.js → LpProgress-CBU1iI69.js} +5 -4
  57. package/dist/chunks/{LpRadioGroup-xPIuVDO_.js → LpRadioGroup-DmEW0l78.js} +9 -2
  58. package/dist/chunks/LpRangeCalendar-DZuaRV0W.js +331 -0
  59. package/dist/chunks/{LpRollingText-CM5gkSeF.js → LpRollingText-DB7upf-5.js} +1 -1
  60. package/dist/chunks/{LpScrollArea-D8xyKXcF.js → LpScrollArea-Bry1fdOM.js} +14 -2
  61. package/dist/chunks/{LpSection-aM6J_u51.js → LpSection-tKEH0jYf.js} +1 -1
  62. package/dist/chunks/{LpSegmented-fV0Sk92p.js → LpSegmented-C09EEam2.js} +8 -4
  63. package/dist/chunks/{LpSelect-ANIVtrpM.js → LpSelect-DNIL6MB9.js} +50 -31
  64. package/dist/chunks/{LpSidebar-ClBUwoEz.js → LpSidebar-CQNB0-yU.js} +30 -22
  65. package/dist/chunks/{LpSidebarNav-llOcs_Fn.js → LpSidebarNav-kx-LdqNU.js} +16 -11
  66. package/dist/chunks/{LpSkeleton-CTI-vBgW.js → LpSkeleton-BB6XIV-w.js} +1 -1
  67. package/dist/chunks/{LpSlider-BwNIBkAK.js → LpSlider-Cuzmrd7p.js} +7 -2
  68. package/dist/chunks/{LpSparkline-C6xQ2dwv.js → LpSparkline-CB80N8SJ.js} +11 -4
  69. package/dist/chunks/{LpStat-CQaW8R--.js → LpStat-B-7udBRP.js} +2 -2
  70. package/dist/chunks/{LpStepper-Bk-XaQYU.js → LpStepper-C7XiVKZj.js} +34 -19
  71. package/dist/chunks/{LpSwitch-Blizvqyt.js → LpSwitch-OrcOJX9m.js} +41 -27
  72. package/dist/chunks/LpTableFilter-BV8PpLM9.js +158 -0
  73. package/dist/chunks/{LpTableOfContents-CjvxPMpU.js → LpTableOfContents-BUcAfGPQ.js} +36 -41
  74. package/dist/chunks/{LpTabs-Cefe_lys.js → LpTabs-29iW1XOx.js} +20 -6
  75. package/dist/chunks/LpTagsInput-D1SuqbhB.js +300 -0
  76. package/dist/chunks/LpTextarea-uuScWo6-.js +77 -0
  77. package/dist/chunks/LpThemeProvider-Be4r2TQX.js +35 -0
  78. package/dist/chunks/LpThemeSwitcher-B4aVPbNC.js +166 -0
  79. package/dist/chunks/{LpTilt-DuyKbpkA.js → LpTilt-B7KHX1zH.js} +2 -2
  80. package/dist/chunks/{LpTimeField-K6nAGboX.js → LpTimeField-Cqe7fXnh.js} +14 -8
  81. package/dist/chunks/{LpToaster-gDB1EaU3.js → LpToaster-DpJvoCOh.js} +94 -31
  82. package/dist/chunks/{LpTooltip-hCLPAqMG.js → LpTooltip-CXVKcOXT.js} +14 -6
  83. package/dist/chunks/LpTooltipProvider-CftjV7Xc.js +25 -0
  84. package/dist/chunks/{LpTopologyCanvas-Cm1L39uL.js → LpTopologyCanvas-6ozNrQKh.js} +8 -2
  85. package/dist/chunks/{LpUptimeBar-DvAMno0S.js → LpUptimeBar-ugcv25xj.js} +44 -15
  86. package/dist/chunks/icons-CkA0njzm.js +43 -0
  87. package/dist/chunks/{LpThemeSwitcher-Cue-myST.js → useTheme-DJm1JSsY.js} +215 -260
  88. package/dist/component-names.d.ts +1 -1
  89. package/dist/components/ContextMenuItems.vue.d.ts +8 -0
  90. package/dist/components/ContextMenuItems.vue.js +4 -0
  91. package/dist/components/LayoutCanvas.vue.js +1 -1
  92. package/dist/components/LayoutNode.vue.d.ts +4 -0
  93. package/dist/components/LayoutNode.vue.js +1 -1
  94. package/dist/components/LpActionBar.vue.d.ts +3 -0
  95. package/dist/components/LpActionBar.vue.js +1 -1
  96. package/dist/components/LpAddressInput.vue.d.ts +1 -1
  97. package/dist/components/LpAddressInput.vue.js +1 -1
  98. package/dist/components/LpAlert.vue.d.ts +9 -0
  99. package/dist/components/LpAlert.vue.js +1 -1
  100. package/dist/components/LpAppShell.vue.d.ts +10 -0
  101. package/dist/components/LpAppShell.vue.js +1 -1
  102. package/dist/components/LpAutocomplete.vue.d.ts +31 -6
  103. package/dist/components/LpAutocomplete.vue.js +1 -1
  104. package/dist/components/LpAvatar.vue.d.ts +3 -3
  105. package/dist/components/LpAvatar.vue.js +1 -1
  106. package/dist/components/LpBadge.vue.d.ts +2 -2
  107. package/dist/components/LpBreadcrumbs.vue.d.ts +13 -9
  108. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  109. package/dist/components/LpButton.vue.d.ts +35 -98
  110. package/dist/components/LpButton.vue.js +1 -1
  111. package/dist/components/LpCalendar.vue.js +1 -1
  112. package/dist/components/LpCard.vue.js +1 -1
  113. package/dist/components/LpChart.vue.d.ts +47 -3
  114. package/dist/components/LpChart.vue.js +377 -97
  115. package/dist/components/LpCheckbox.vue.js +1 -1
  116. package/dist/components/LpCodeBlock.vue.d.ts +2 -0
  117. package/dist/components/LpCodeBlock.vue.js +1 -1
  118. package/dist/components/LpColorPicker.vue.d.ts +2 -2
  119. package/dist/components/LpColorPicker.vue.js +1 -1
  120. package/dist/components/LpCommandPalette.vue.d.ts +14 -3
  121. package/dist/components/LpCommandPalette.vue.js +142 -77
  122. package/dist/components/LpConfirmDialog.vue.d.ts +15 -3
  123. package/dist/components/LpConfirmDialog.vue.js +1 -1
  124. package/dist/components/LpConfirmHost.vue.d.ts +9 -0
  125. package/dist/components/LpConfirmHost.vue.js +4 -0
  126. package/dist/components/LpContextMenu.vue.d.ts +3 -5
  127. package/dist/components/LpContextMenu.vue.js +1 -1
  128. package/dist/components/LpDatePicker.vue.d.ts +24 -6
  129. package/dist/components/LpDatePicker.vue.js +1 -1
  130. package/dist/components/LpDateRangePicker.vue.d.ts +60 -0
  131. package/dist/components/LpDateRangePicker.vue.js +4 -0
  132. package/dist/components/LpDisclosure.vue.js +1 -1
  133. package/dist/components/LpDrawer.vue.d.ts +10 -0
  134. package/dist/components/LpDrawer.vue.js +1 -1
  135. package/dist/components/LpDropdownMenu.vue.js +1 -1
  136. package/dist/components/LpDropzone.vue.js +23 -7
  137. package/dist/components/LpEmptyState.vue.js +1 -1
  138. package/dist/components/LpFieldNotice.vue.d.ts +8 -0
  139. package/dist/components/LpFieldNotice.vue.js +4 -0
  140. package/dist/components/LpFileTree.vue.d.ts +11 -2
  141. package/dist/components/LpFileTree.vue.js +1 -1
  142. package/dist/components/LpFileTreeNode.vue.js +1 -1
  143. package/dist/components/LpFormField.vue.d.ts +13 -1
  144. package/dist/components/LpFormField.vue.js +1 -1
  145. package/dist/components/LpIcon.vue.d.ts +0 -1
  146. package/dist/components/LpIcon.vue.js +1 -1
  147. package/dist/components/LpImageEditor.vue.js +93 -58
  148. package/dist/components/LpInput.vue.d.ts +22 -3
  149. package/dist/components/LpInput.vue.js +1 -1
  150. package/dist/components/LpLightbox.vue.d.ts +17 -0
  151. package/dist/components/LpLightbox.vue.js +1 -1
  152. package/dist/components/LpLink.vue.d.ts +11 -11
  153. package/dist/components/LpLink.vue.js +1 -1
  154. package/dist/components/LpLogViewer.vue.d.ts +72 -3
  155. package/dist/components/LpLogViewer.vue.js +1 -1
  156. package/dist/components/LpMapPicker.vue.d.ts +4 -4
  157. package/dist/components/LpMapPicker.vue.js +1 -1
  158. package/dist/components/LpMediaGallery.vue.js +1 -1
  159. package/dist/components/LpModal.vue.d.ts +16 -6
  160. package/dist/components/LpModal.vue.js +1 -1
  161. package/dist/components/LpModalNav.vue.d.ts +0 -1
  162. package/dist/components/LpModalNav.vue.js +1 -1
  163. package/dist/components/LpNotificationBell.vue.d.ts +11 -2
  164. package/dist/components/LpNotificationBell.vue.js +1 -1
  165. package/dist/components/LpNumberField.vue.d.ts +2 -0
  166. package/dist/components/LpNumberField.vue.js +1 -1
  167. package/dist/components/LpNumberFlow.vue.js +1 -1
  168. package/dist/components/LpOtpInput.vue.js +1 -1
  169. package/dist/components/LpPagination.vue.d.ts +2 -0
  170. package/dist/components/LpPagination.vue.js +1 -1
  171. package/dist/components/LpPasswordInput.vue.js +1 -1
  172. package/dist/components/LpPhoneInput.vue.d.ts +13 -0
  173. package/dist/components/LpPhoneInput.vue.js +1 -1
  174. package/dist/components/LpPie.vue.d.ts +21 -2
  175. package/dist/components/LpPie.vue.js +1 -1
  176. package/dist/components/LpPopover.vue.d.ts +2 -0
  177. package/dist/components/LpPopover.vue.js +1 -1
  178. package/dist/components/LpProgress.vue.d.ts +1 -1
  179. package/dist/components/LpProgress.vue.js +1 -1
  180. package/dist/components/LpRadioGroup.vue.js +1 -1
  181. package/dist/components/LpRangeCalendar.vue.d.ts +51 -0
  182. package/dist/components/LpRangeCalendar.vue.js +4 -0
  183. package/dist/components/LpRollingText.vue.d.ts +1 -1
  184. package/dist/components/LpRollingText.vue.js +1 -1
  185. package/dist/components/LpScrollArea.vue.d.ts +2 -2
  186. package/dist/components/LpScrollArea.vue.js +1 -1
  187. package/dist/components/LpSection.vue.d.ts +1 -1
  188. package/dist/components/LpSection.vue.js +1 -1
  189. package/dist/components/LpSegmented.vue.d.ts +5 -0
  190. package/dist/components/LpSegmented.vue.js +1 -1
  191. package/dist/components/LpSelect.vue.d.ts +1 -0
  192. package/dist/components/LpSelect.vue.js +1 -1
  193. package/dist/components/LpShortcutsDialog.vue.d.ts +7 -1
  194. package/dist/components/LpShortcutsDialog.vue.js +60 -15
  195. package/dist/components/LpSidebar.vue.d.ts +6 -2
  196. package/dist/components/LpSidebar.vue.js +1 -1
  197. package/dist/components/LpSidebarNav.vue.js +1 -1
  198. package/dist/components/LpSkeleton.vue.js +1 -1
  199. package/dist/components/LpSlider.vue.js +1 -1
  200. package/dist/components/LpSparkline.vue.d.ts +1 -0
  201. package/dist/components/LpSparkline.vue.js +1 -1
  202. package/dist/components/LpStat.vue.js +1 -1
  203. package/dist/components/LpStepper.vue.js +1 -1
  204. package/dist/components/LpSwitch.vue.js +1 -1
  205. package/dist/components/LpTable.vue.d.ts +107 -3
  206. package/dist/components/LpTable.vue.js +540 -128
  207. package/dist/components/LpTableFilter.vue.d.ts +31 -0
  208. package/dist/components/LpTableFilter.vue.js +4 -0
  209. package/dist/components/LpTableOfContents.vue.d.ts +4 -2
  210. package/dist/components/LpTableOfContents.vue.js +1 -1
  211. package/dist/components/LpTabs.vue.d.ts +2 -0
  212. package/dist/components/LpTabs.vue.js +1 -1
  213. package/dist/components/LpTagsInput.vue.d.ts +77 -0
  214. package/dist/components/LpTagsInput.vue.js +4 -0
  215. package/dist/components/LpTextarea.vue.d.ts +19 -6
  216. package/dist/components/LpTextarea.vue.js +1 -1
  217. package/dist/components/LpThemeProvider.vue.d.ts +22 -0
  218. package/dist/components/LpThemeProvider.vue.js +4 -0
  219. package/dist/components/LpThemeSwitcher.vue.d.ts +15 -2
  220. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  221. package/dist/components/LpTilt.vue.js +1 -1
  222. package/dist/components/LpTimeField.vue.js +1 -1
  223. package/dist/components/LpToaster.vue.d.ts +6 -1
  224. package/dist/components/LpToaster.vue.js +1 -1
  225. package/dist/components/LpTooltip.vue.d.ts +1 -2
  226. package/dist/components/LpTooltip.vue.js +1 -1
  227. package/dist/components/LpTooltipProvider.vue.d.ts +22 -0
  228. package/dist/components/LpTooltipProvider.vue.js +4 -0
  229. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  230. package/dist/components/LpUptimeBar.vue.d.ts +4 -0
  231. package/dist/components/LpUptimeBar.vue.js +1 -1
  232. package/dist/components/chart.d.ts +32 -1
  233. package/dist/components/chart.js +42 -6
  234. package/dist/components/codeHighlight.d.ts +6 -0
  235. package/dist/components/codeHighlight.js +92 -20
  236. package/dist/components/color.d.ts +1 -1
  237. package/dist/components/color.js +20 -9
  238. package/dist/components/commandSearch.js +3 -0
  239. package/dist/components/confirm.d.ts +25 -0
  240. package/dist/components/confirm.js +26 -0
  241. package/dist/components/countries.d.ts +6 -0
  242. package/dist/components/countries.js +7 -0
  243. package/dist/components/dateRange.d.ts +43 -0
  244. package/dist/components/dateRange.js +76 -0
  245. package/dist/components/exportData.d.ts +58 -0
  246. package/dist/components/exportData.js +165 -0
  247. package/dist/components/fileTree.js +16 -7
  248. package/dist/components/imageCrop.d.ts +42 -0
  249. package/dist/components/imageCrop.js +47 -0
  250. package/dist/components/link.d.ts +12 -0
  251. package/dist/components/link.js +19 -0
  252. package/dist/components/logSearch.d.ts +18 -0
  253. package/dist/components/logSearch.js +36 -0
  254. package/dist/components/modalDepth.d.ts +18 -2
  255. package/dist/components/modalDepth.js +17 -5
  256. package/dist/components/otp.d.ts +5 -0
  257. package/dist/components/otp.js +10 -0
  258. package/dist/components/phone.d.ts +41 -0
  259. package/dist/components/phone.js +80 -0
  260. package/dist/components/sidebar.d.ts +7 -1
  261. package/dist/components/tableFilter.d.ts +36 -0
  262. package/dist/components/tableFilter.js +84 -0
  263. package/dist/components/tableSort.d.ts +8 -0
  264. package/dist/components/tableSort.js +31 -0
  265. package/dist/components/tags.d.ts +20 -0
  266. package/dist/components/tags.js +37 -0
  267. package/dist/composables/useField.d.ts +38 -0
  268. package/dist/composables/useField.js +45 -0
  269. package/dist/composables/useFieldNotice.d.ts +17 -0
  270. package/dist/composables/useFieldNotice.js +28 -0
  271. package/dist/composables/useForm.d.ts +53 -0
  272. package/dist/composables/useForm.js +207 -0
  273. package/dist/composables/useFormDraft.d.ts +10 -3
  274. package/dist/composables/useFormDraft.js +9 -14
  275. package/dist/composables/useHotkeys.d.ts +81 -5
  276. package/dist/composables/useHotkeys.js +223 -25
  277. package/dist/composables/useInputFilter.d.ts +17 -0
  278. package/dist/composables/useInputFilter.js +47 -10
  279. package/dist/composables/useInputMask.d.ts +59 -0
  280. package/dist/composables/useInputMask.js +170 -0
  281. package/dist/composables/useLpConfig.d.ts +24 -0
  282. package/dist/composables/useLpConfig.js +44 -0
  283. package/dist/composables/usePaintSelect.d.ts +22 -0
  284. package/dist/composables/usePaintSelect.js +169 -0
  285. package/dist/composables/useRovingFocus.js +7 -0
  286. package/dist/composables/useShift.js +7 -2
  287. package/dist/composables/useTilt.d.ts +2 -2
  288. package/dist/composables/useTilt.js +5 -6
  289. package/dist/composables/useToast.d.ts +20 -0
  290. package/dist/composables/useToast.js +28 -4
  291. package/dist/composables/useZoomPan.d.ts +3 -3
  292. package/dist/composables/useZoomPan.js +4 -4
  293. package/dist/icons-all.d.ts +1 -0
  294. package/dist/icons-all.js +3 -0
  295. package/dist/icons.d.ts +14 -1
  296. package/dist/index.d.ts +60 -4
  297. package/dist/index.js +245 -141
  298. package/dist/kit-icons.d.ts +2 -0
  299. package/dist/layout/tree.d.ts +2 -0
  300. package/dist/layout/tree.js +16 -1
  301. package/dist/layout/useLayout.d.ts +15 -0
  302. package/dist/layout/useLayout.js +41 -14
  303. package/dist/theme/backdrop.d.ts +12 -0
  304. package/dist/theme/presets.d.ts +0 -2
  305. package/dist/theme/useTheme.d.ts +32 -9
  306. package/package.json +18 -8
  307. package/src/component-names.ts +9 -7
  308. package/src/components/ContextMenuItems.vue +88 -0
  309. package/src/components/LayoutCanvas.vue +26 -6
  310. package/src/components/LayoutNode.vue +30 -4
  311. package/src/components/LpActionBar.vue +31 -8
  312. package/src/components/LpAddressInput.vue +57 -41
  313. package/src/components/LpAlert.vue +16 -3
  314. package/src/components/LpAppShell.vue +45 -12
  315. package/src/components/LpAutocomplete.vue +47 -10
  316. package/src/components/LpAvatar.vue +20 -6
  317. package/src/components/LpBreadcrumbs.vue +65 -41
  318. package/src/components/LpButton.vue +60 -14
  319. package/src/components/LpChart.vue +351 -28
  320. package/src/components/LpCheckbox.vue +7 -3
  321. package/src/components/LpCodeBlock.vue +50 -14
  322. package/src/components/LpColorPicker.vue +73 -10
  323. package/src/components/LpCommandPalette.vue +115 -20
  324. package/src/components/LpConfirmDialog.vue +68 -17
  325. package/src/components/LpConfirmHost.vue +59 -0
  326. package/src/components/LpContextMenu.vue +15 -116
  327. package/src/components/LpDatePicker.vue +104 -68
  328. package/src/components/LpDateRangePicker.vue +224 -0
  329. package/src/components/LpDrawer.vue +56 -19
  330. package/src/components/LpDropdownMenu.vue +1 -0
  331. package/src/components/LpDropzone.vue +21 -4
  332. package/src/components/LpFieldNotice.vue +65 -0
  333. package/src/components/LpFileTree.vue +33 -7
  334. package/src/components/LpFormField.vue +59 -7
  335. package/src/components/LpIcon.vue +22 -4
  336. package/src/components/LpImageEditor.vue +96 -57
  337. package/src/components/LpInput.vue +62 -11
  338. package/src/components/LpLightbox.vue +140 -29
  339. package/src/components/LpLink.vue +24 -13
  340. package/src/components/LpLogViewer.vue +311 -39
  341. package/src/components/LpMapPicker.vue +36 -13
  342. package/src/components/LpMediaGallery.vue +8 -2
  343. package/src/components/LpModal.vue +54 -28
  344. package/src/components/LpModalNav.vue +5 -2
  345. package/src/components/LpNotificationBell.vue +77 -23
  346. package/src/components/LpNumberField.vue +21 -6
  347. package/src/components/LpNumberFlow.vue +26 -10
  348. package/src/components/LpOtpInput.vue +43 -8
  349. package/src/components/LpPagination.vue +23 -7
  350. package/src/components/LpPasswordInput.vue +4 -1
  351. package/src/components/LpPhoneInput.vue +224 -39
  352. package/src/components/LpPie.vue +102 -20
  353. package/src/components/LpPopover.vue +4 -0
  354. package/src/components/LpProgress.vue +5 -3
  355. package/src/components/LpRadioGroup.vue +9 -2
  356. package/src/components/LpRangeCalendar.vue +365 -0
  357. package/src/components/LpRollingText.vue +3 -0
  358. package/src/components/LpScrollArea.vue +22 -2
  359. package/src/components/LpSegmented.vue +12 -2
  360. package/src/components/LpSelect.vue +0 -0
  361. package/src/components/LpShortcutsDialog.vue +83 -9
  362. package/src/components/LpSidebar.vue +14 -9
  363. package/src/components/LpSidebarNav.vue +17 -5
  364. package/src/components/LpSkeleton.vue +4 -1
  365. package/src/components/LpSlider.vue +7 -1
  366. package/src/components/LpSparkline.vue +11 -3
  367. package/src/components/LpStepper.vue +23 -10
  368. package/src/components/LpSwitch.vue +31 -8
  369. package/src/components/LpTable.vue +668 -90
  370. package/src/components/LpTableFilter.vue +175 -0
  371. package/src/components/LpTableOfContents.vue +50 -32
  372. package/src/components/LpTabs.vue +23 -6
  373. package/src/components/LpTagsInput.vue +364 -0
  374. package/src/components/LpTextarea.vue +44 -6
  375. package/src/components/LpThemeProvider.vue +41 -0
  376. package/src/components/LpThemeSwitcher.vue +76 -33
  377. package/src/components/LpTilt.vue +2 -2
  378. package/src/components/LpTimeField.vue +14 -3
  379. package/src/components/LpToaster.vue +115 -27
  380. package/src/components/LpTooltip.vue +29 -5
  381. package/src/components/LpTooltipProvider.vue +24 -0
  382. package/src/components/LpTopologyCanvas.vue +19 -2
  383. package/src/components/LpUptimeBar.vue +46 -8
  384. package/src/components/chart.ts +82 -8
  385. package/src/components/codeHighlight.ts +111 -15
  386. package/src/components/color.ts +30 -9
  387. package/src/components/commandSearch.ts +6 -0
  388. package/src/components/confirm.ts +58 -0
  389. package/src/components/countries.ts +12 -0
  390. package/src/components/dateRange.ts +134 -0
  391. package/src/components/exportData.ts +228 -0
  392. package/src/components/fileTree.ts +19 -7
  393. package/src/components/imageCrop.ts +107 -0
  394. package/src/components/link.ts +34 -0
  395. package/src/components/logSearch.ts +46 -0
  396. package/src/components/modalDepth.ts +48 -6
  397. package/src/components/otp.ts +16 -0
  398. package/src/components/phone.ts +140 -0
  399. package/src/components/sidebar.ts +7 -1
  400. package/src/components/tableFilter.ts +122 -0
  401. package/src/components/tableSort.ts +37 -0
  402. package/src/components/tags.ts +62 -0
  403. package/src/composables/useField.ts +83 -0
  404. package/src/composables/useFieldNotice.ts +59 -0
  405. package/src/composables/useForm.ts +314 -0
  406. package/src/composables/useFormDraft.ts +25 -20
  407. package/src/composables/useHotkeys.ts +355 -28
  408. package/src/composables/useInputFilter.ts +77 -14
  409. package/src/composables/useInputMask.ts +254 -0
  410. package/src/composables/useLpConfig.ts +87 -0
  411. package/src/composables/usePaintSelect.ts +243 -0
  412. package/src/composables/useRovingFocus.ts +11 -0
  413. package/src/composables/useShift.ts +13 -2
  414. package/src/composables/useTilt.ts +5 -6
  415. package/src/composables/useToast.ts +56 -4
  416. package/src/composables/useZoomPan.ts +6 -6
  417. package/src/iconSubset.d.mts +10 -0
  418. package/src/iconSubset.mjs +51 -0
  419. package/src/icons-all.ts +10 -0
  420. package/src/icons.ts +59 -8
  421. package/src/index.ts +73 -4
  422. package/src/kit-icons.ts +5 -0
  423. package/src/layout/tree.ts +30 -0
  424. package/src/layout/useLayout.ts +68 -18
  425. package/src/nuxt.ts +87 -17
  426. package/src/runtime/config.plugin.ts +14 -0
  427. package/src/runtime/theme.plugin.ts +43 -23
  428. package/src/theme/backdrop.ts +69 -0
  429. package/src/theme/presets.ts +6 -64
  430. package/src/theme/useTheme.ts +206 -100
  431. package/src/tokens/tokens.css +46 -0
  432. package/dist/chunks/LpBreadcrumbs-p4jHe_V-.js +0 -62
  433. package/dist/chunks/LpContextMenu-CwW1QXe_.js +0 -217
  434. package/dist/chunks/LpDatePicker-CwOToxGq.js +0 -164
  435. package/dist/chunks/LpFormField-CazBc2-a.js +0 -62
  436. package/dist/chunks/LpPhoneInput-BDSKVl_z.js +0 -209
  437. package/dist/chunks/LpTextarea-t7F6gv8E.js +0 -47
@@ -31,12 +31,12 @@ const props = withDefaults(
31
31
  { max: 10, lift: 8, scale: 1.03, glare: false, perspective: 900 },
32
32
  )
33
33
 
34
- const tilt = useTilt({
34
+ const tilt = useTilt(() => ({
35
35
  max: props.max,
36
36
  lift: props.lift,
37
37
  scale: props.scale,
38
38
  glare: props.glare,
39
- })
39
+ }))
40
40
 
41
41
  const stageStyle = computed(() => ({ perspective: `${props.perspective}px` }))
42
42
  </script>
@@ -13,6 +13,7 @@
13
13
  import { Time } from "@internationalized/date"
14
14
  import { TimeFieldInput, TimeFieldRoot } from "reka-ui"
15
15
  import { computed } from "vue"
16
+ import { useFieldControl } from "../composables/useField"
16
17
  import LpIcon from "./LpIcon.vue"
17
18
 
18
19
  const props = withDefaults(
@@ -50,6 +51,12 @@ function onUpdate(next: unknown) {
50
51
  emit("update:modelValue", `${pad(next.hour)}:${pad(next.minute)}`)
51
52
  }
52
53
 
54
+ const field = useFieldControl(() => ({
55
+ wrapped: false,
56
+ invalid: props.invalid,
57
+ disabled: props.disabled,
58
+ }))
59
+
53
60
  const shellSize = {
54
61
  sm: "h-(--size-control-sm) text-xs",
55
62
  md: "h-(--size-control-md) text-sm",
@@ -64,16 +71,20 @@ const shellSize = {
64
71
  :hour-cycle="hourCycle"
65
72
  :locale="locale"
66
73
  granularity="minute"
67
- :disabled="disabled"
74
+ :disabled="field.disabled"
75
+ :aria-invalid="field.invalid || undefined"
76
+ :aria-describedby="field.describedBy"
68
77
  class="flex w-full items-center gap-0.5 rounded-control border bg-surface-soft px-3 text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-55"
69
78
  :class="[
70
79
  shellSize[size],
71
- invalid ? 'border-danger focus-within:ring-danger-soft' : 'border-line focus-within:border-brand',
80
+ field.invalid ? 'border-danger focus-within:ring-danger-soft' : 'border-line focus-within:border-brand',
72
81
  ]"
73
82
  @update:model-value="onUpdate"
74
83
  >
75
84
  <LpIcon v-if="icon" name="lucide:clock" :size="15" class="mr-1.5 shrink-0 text-muted" />
76
- <template v-for="item in segments" :key="item.part">
85
+ <!-- Indexed keys: in 12-hour mode the segments hold two "literal" parts
86
+ (the ":" and the space before AM/PM), which a key by part repeats. -->
87
+ <template v-for="(item, i) in segments" :key="i">
77
88
  <TimeFieldInput v-if="item.part === 'literal'" :part="item.part" class="text-muted">
78
89
  {{ item.value }}
79
90
  </TimeFieldInput>
@@ -11,38 +11,95 @@ import {
11
11
  ToastTitle,
12
12
  ToastViewport,
13
13
  } from "reka-ui"
14
- import { onBeforeUnmount, ref, watch } from "vue"
14
+ import { nextTick, onBeforeUnmount, onMounted, ref, useId, watch } from "vue"
15
+ import { prefersReducedMotion } from "../composables/easing"
16
+ import { useLabels } from "../composables/useLpConfig"
15
17
  import { useToast, type ToastItem } from "../composables/useToast"
16
18
  import { CLOSE_ICON } from "./dropdown"
17
19
  import LpIcon from "./LpIcon.vue"
18
20
 
21
+ const props = defineProps<{ labels?: Partial<{ dismiss: string }> }>()
22
+ const text = useLabels("toast", { dismiss: "Dismiss" }, () => props.labels)
23
+
19
24
  const { queue, dismiss } = useToast()
20
25
 
26
+ /*
27
+ * The stack slides rather than jumps. A new toast lands at the bottom and the
28
+ * ones above used to leap up by its height in a single frame; one leaving let
29
+ * the rest drop the same way. Each toast's layout position (offsetTop, which
30
+ * ignores transforms) is remembered, and when the list changes every toast
31
+ * that moved is played from where it was to where it is (FLIP). The observer
32
+ * fires after the DOM changes and before the frame is painted, so the jump is
33
+ * never seen. Toasts render through reka's portal into the viewport, which is
34
+ * why this watches the DOM rather than a TransitionGroup.
35
+ */
36
+ let observer: MutationObserver | undefined
37
+ const tops = new WeakMap<Element, number>()
38
+
39
+ function toastsIn(root: Element): HTMLElement[] {
40
+ return [...root.querySelectorAll<HTMLElement>("li[data-state]")]
41
+ }
42
+
43
+ function slide(root: Element) {
44
+ const reduce = prefersReducedMotion()
45
+ const height = (root as HTMLElement).offsetHeight
46
+ for (const el of toastsIn(root)) {
47
+ const before = tops.get(el)
48
+ const now = el.offsetTop - height
49
+ tops.set(el, now)
50
+ if (before === undefined || before === now || reduce || el.hasAttribute("data-swipe")) continue
51
+ el.animate([{ translate: `0 ${before - now}px` }, { translate: "0 0" }], {
52
+ duration: 220,
53
+ easing: "cubic-bezier(0.2, 0, 0, 1)",
54
+ })
55
+ }
56
+ }
57
+
58
+ // The viewport's component root is a wrapper fragment, not the list, so the
59
+ // list is found by a mark of its own once it is teleported into place.
60
+ const viewportId = useId()
61
+ onMounted(() =>
62
+ nextTick(() => {
63
+ const root = document.querySelector(`[data-lp-toasts="${viewportId}"]`)
64
+ if (!root || typeof MutationObserver === "undefined") return
65
+ for (const el of toastsIn(root)) tops.set(el, el.offsetTop - (root as HTMLElement).offsetHeight)
66
+ observer = new MutationObserver(() => slide(root))
67
+ observer.observe(root, { childList: true, subtree: true })
68
+ }),
69
+ )
70
+ onBeforeUnmount(() => observer?.disconnect())
71
+
72
+ // A toast replaced through its `key` keeps its id and gets a new revision, so
73
+ // everything tied to one rendered toast goes by both.
74
+ const tag = (t: ToastItem) => `${t.id}:${t.rev}`
75
+
21
76
  // Toasts that are playing their exit animation. We don't splice them out of the
22
77
  // queue immediately on close — that would unmount before `toast-out` can run.
23
78
  // Instead we flip `open` to false (reka sets data-state=closed → the keyframe
24
79
  // plays), then drop the toast for real once `animationend` fires. A safety
25
80
  // timer removes it even if the animationend event is missed (e.g. tab hidden).
26
- const closing = ref<Set<number>>(new Set())
81
+ const closing = ref<Set<string>>(new Set())
27
82
 
28
- function beginClose(id: number) {
29
- if (closing.value.has(id)) return
30
- closing.value = new Set(closing.value).add(id)
83
+ function beginClose(t: ToastItem) {
84
+ const at = tag(t)
85
+ if (closing.value.has(at)) return
86
+ closing.value = new Set(closing.value).add(at)
31
87
  // Fallback slightly longer than the 140ms toast-out duration.
32
- window.setTimeout(() => finishClose(id), 220)
88
+ window.setTimeout(() => finishClose(t.id, at), 220)
33
89
  }
34
90
 
35
- function finishClose(id: number) {
36
- if (!closing.value.has(id)) return
91
+ function finishClose(id: number, at: string) {
92
+ if (!closing.value.has(at)) return
37
93
  const next = new Set(closing.value)
38
- next.delete(id)
94
+ next.delete(at)
39
95
  closing.value = next
40
- dismiss(id)
96
+ // Replaced while it was leaving: the new revision stays.
97
+ if (queue.some((t) => tag(t) === at)) dismiss(id)
41
98
  }
42
99
 
43
- function onAnimEnd(e: AnimationEvent, id: number) {
100
+ function onAnimEnd(e: AnimationEvent, t: ToastItem) {
44
101
  // Only the exit keyframe should trigger removal, not toast-in.
45
- if (e.animationName === "toast-out") finishClose(id)
102
+ if (e.animationName === "toast-out") finishClose(t.id, tag(t))
46
103
  }
47
104
 
48
105
  const variantIcon: Record<ToastItem["variant"], string> = {
@@ -97,7 +154,7 @@ watch(
97
154
  // Same for the closing set — a toast evicted by the queue cap or clear()
98
155
  // leaves without an animationend, so prune dead ids here.
99
156
  if (closing.value.size) {
100
- const live = new Set([...closing.value].filter((id) => queue.some((t) => t.id === id)))
157
+ const live = new Set([...closing.value].filter((at) => queue.some((t) => tag(t) === at)))
101
158
  if (live.size !== closing.value.size) closing.value = live
102
159
  }
103
160
  if (queue.some((t) => t.dismissAt !== null)) startTimer()
@@ -126,16 +183,41 @@ function progress(t: ToastItem): number {
126
183
  return Math.max(0, Math.min(100, (remaining / t.duration) * 100))
127
184
  }
128
185
 
129
- async function runClick(t: ToastItem) {
130
- if (t.onClick) {
131
- await t.onClick()
132
- beginClose(t.id)
186
+ /*
187
+ * reka cancels the click that ends a swipe by adding a listener after ours, so
188
+ * by the time it runs our handler has already acted on it: a swiped-away
189
+ * "View details" toast navigated. The swipe marks the toast instead, and the
190
+ * mark lasts only until that trailing click has had its chance.
191
+ */
192
+ let swiped: string | null = null
193
+ function onSwipeDone(t: ToastItem) {
194
+ swiped = tag(t)
195
+ window.setTimeout(() => (swiped = null), 0)
196
+ }
197
+
198
+ const busy = ref<Set<string>>(new Set())
199
+
200
+ async function settle(t: ToastItem, run: () => void | Promise<void>) {
201
+ const at = tag(t)
202
+ if (busy.value.has(at)) return
203
+ busy.value = new Set(busy.value).add(at)
204
+ try {
205
+ await run()
206
+ beginClose(t)
207
+ } finally {
208
+ const next = new Set(busy.value)
209
+ next.delete(at)
210
+ busy.value = next
133
211
  }
134
212
  }
135
213
 
136
- async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>[number]) {
137
- await action.onClick()
138
- beginClose(t.id)
214
+ function runClick(t: ToastItem) {
215
+ if (swiped === tag(t)) return
216
+ if (t.onClick) return settle(t, t.onClick)
217
+ }
218
+
219
+ function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>[number]) {
220
+ return settle(t, action.onClick)
139
221
  }
140
222
  </script>
141
223
 
@@ -143,13 +225,16 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
143
225
  <ToastProvider>
144
226
  <ToastRoot
145
227
  v-for="t in queue"
146
- :key="t.id"
147
- :open="!closing.has(t.id)"
228
+ :key="tag(t)"
229
+ :open="!closing.has(tag(t))"
148
230
  :duration="t.duration"
231
+ :type="t.variant === 'error' || t.variant === 'warning' ? 'foreground' : 'background'"
149
232
  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
233
  :class="{ 'cursor-pointer': t.onClick }"
151
- @update:open="(open) => !open && beginClose(t.id)"
152
- @animationend="(e: AnimationEvent) => onAnimEnd(e, t.id)"
234
+ @update:open="(open) => !open && beginClose(t)"
235
+ @animationend="(e: AnimationEvent) => onAnimEnd(e, t)"
236
+ @swipe-end="onSwipeDone(t)"
237
+ @swipe-cancel="onSwipeDone(t)"
153
238
  @pause="onPause(t)"
154
239
  @resume="onResume(t)"
155
240
  @click="runClick(t)"
@@ -178,7 +263,9 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
178
263
  v-for="(action, i) in t.actions"
179
264
  :key="i"
180
265
  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"
266
+ :disabled="busy.has(tag(t))"
267
+ :aria-busy="busy.has(tag(t)) || undefined"
268
+ 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
269
  @click="runAction(t, action)"
183
270
  >
184
271
  <LpIcon v-if="action.icon" :name="action.icon" :size="14" />
@@ -190,8 +277,8 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
190
277
  <button
191
278
  type="button"
192
279
  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)"
280
+ :aria-label="text.dismiss"
281
+ @click.stop="beginClose(t)"
195
282
  >
196
283
  <LpIcon
197
284
  name="lucide:x"
@@ -216,6 +303,7 @@ async function runAction(t: ToastItem, action: NonNullable<ToastItem["actions"]>
216
303
  --z-toast (200) > --z-modal (110) wins as intended. -->
217
304
  <Teleport to="body">
218
305
  <ToastViewport
306
+ :data-lp-toasts="viewportId"
219
307
  class="fixed bottom-0 right-0 z-(--z-toast) flex w-[min(92vw,22rem)] flex-col gap-2 p-4 outline-none"
220
308
  />
221
309
  </Teleport>
@@ -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 { onBeforeUnmount, ref, watch, type FunctionalComponent } 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,23 @@ 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
+ * Under a shared provider the tip renders straight into it. `Fragment` cannot
113
+ * stand in for the wrapper: given through `<component :is>` it takes slots,
114
+ * not children, and rendered nothing at all, so every tip inside an
115
+ * LpTooltipProvider vanished together with its trigger.
116
+ */
117
+ const PassThrough: FunctionalComponent = (_, { slots }) => slots.default?.()
118
+
119
+ const reducedMotion = () =>
120
+ typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
121
+
104
122
  const target = { x: 0, y: 0 }
105
123
  const offset = ref({ x: 0, y: 0 })
106
124
 
@@ -155,6 +173,11 @@ const onPointerMove = (e: PointerEvent) => {
155
173
  const half = props.side === "top" || props.side === "bottom" ? rect.height / 2 : 0
156
174
  const dir = props.side === "bottom" ? -1 : 1
157
175
  target.y = e.clientY - (rect.top + rect.height / 2) + half * dir
176
+ // Without motion the tip goes straight to the reading instead of trailing.
177
+ if (reducedMotion()) {
178
+ offset.value = { x: target.x, y: target.y }
179
+ return
180
+ }
158
181
  if (!frame) frame = requestAnimationFrame(settle)
159
182
  }
160
183
 
@@ -205,7 +228,7 @@ onBeforeUnmount(() => {
205
228
  </script>
206
229
 
207
230
  <template>
208
- <TooltipProvider :delay-duration="delay">
231
+ <component :is="shared ? PassThrough : TooltipProvider" v-bind="shared ? {} : { delayDuration: delay ?? 200 }">
209
232
  <!--
210
233
  `disable-closing-trigger` in follow mode: reka dismisses a tip when its
211
234
  trigger is clicked, which is right for a button (the tip explained the
@@ -214,6 +237,7 @@ onBeforeUnmount(() => {
214
237
  -->
215
238
  <TooltipRoot
216
239
  :open="open"
240
+ :delay-duration="delay"
217
241
  :ignore-non-keyboard-focus="!showOnPointerFocus"
218
242
  :disable-hoverable-content="followCursor"
219
243
  :disable-closing-trigger="followCursor"
@@ -245,7 +269,7 @@ onBeforeUnmount(() => {
245
269
  </TooltipContent>
246
270
  </TooltipPortal>
247
271
  </TooltipRoot>
248
- </TooltipProvider>
272
+ </component>
249
273
  </template>
250
274
 
251
275
  <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,
@@ -406,8 +417,12 @@ defineExpose({ fitView, setViewport: applyViewport })
406
417
  class="lp-topology"
407
418
  >
408
419
  <Background :gap="22" :size="1.4" pattern-color="var(--color-line-strong)" />
420
+ <!-- On a phone the minimap, the zoom controls and the legend would all
421
+ share the bottom of a canvas a few hundred pixels wide and cover each
422
+ other and the graph; pinch-zoom and panning do the minimap's job there. -->
409
423
  <MiniMap
410
424
  v-if="minimap"
425
+ class="max-sm:!hidden"
411
426
  pannable
412
427
  zoomable
413
428
  node-color="var(--color-surface-soft)"
@@ -416,7 +431,9 @@ defineExpose({ fitView, setViewport: applyViewport })
416
431
  <Controls v-if="controls" />
417
432
 
418
433
  <!-- Edge-type legend: makes the colour/dash encoding self-explanatory. -->
419
- <Panel v-if="legend" :position="legendPosition" :class="legendOffset">
434
+ <!-- On a phone the legend would sit on the graph itself; the edge styles
435
+ stay readable without it there. -->
436
+ <Panel v-if="legend" :position="legendPosition" :class="legendOffset" class="max-sm:hidden">
420
437
  <div
421
438
  class="flex flex-col gap-1.5 rounded-card border border-line bg-surface-overlay/90 px-3 py-2 text-[11px] backdrop-blur"
422
439
  >
@@ -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
@@ -101,10 +157,12 @@ export const niceScale = (
101
157
  ): { min: number; max: number; step: number } => {
102
158
  if (!Number.isFinite(min) || !Number.isFinite(max)) return { min: 0, max: 1, step: 1 }
103
159
  if (min === max) {
104
- // A flat series still needs a band to sit in.
160
+ // A flat series still needs a band to sit in, but the band must not cross
161
+ // zero: an all-zero count would otherwise read as dipping to -0.5.
105
162
  const pad = Math.abs(min) || 1
106
- min -= pad / 2
107
- max += pad / 2
163
+ min = min >= 0 ? Math.max(0, min - pad / 2) : min - pad / 2
164
+ max = max <= 0 ? Math.min(0, max + pad / 2) : max + pad / 2
165
+ if (min === max) max = min + pad
108
166
  }
109
167
  const raw = (max - min) / Math.max(1, ticks)
110
168
  const mag = 10 ** Math.floor(Math.log10(raw))
@@ -592,6 +650,7 @@ export const calloutLabels = (
592
650
  return side === "right" ? a : -a
593
651
  }
594
652
  const list = sides[side].sort((p, q) => rank(p.mid) - rank(q.mid))
653
+ if (!list.length) continue
595
654
 
596
655
  /*
597
656
  * Resolve collisions by NUDGING, not by stacking.
@@ -676,11 +735,26 @@ export const calloutLabels = (
676
735
  return out
677
736
  }
678
737
 
679
- /** Compact default tick formatting: 1.2k, 3.4M — never 1200.0000001. */
738
+ /** Compact default tick formatting: 1.2k, 3.4M — never 1200.0000001 or 1000k. */
680
739
  export const formatTick = (v: number): string => {
740
+ if (!Number.isFinite(v)) return String(v)
741
+ const units: [number, string][] = [
742
+ [1e3, "k"],
743
+ [1e6, "M"],
744
+ [1e9, "B"],
745
+ [1e12, "T"],
746
+ ]
681
747
  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)))
748
+ let i = units.length - 1
749
+ while (i >= 0 && abs < units[i][0]) i--
750
+ if (i < 0) {
751
+ const plain = Number(v.toFixed(2))
752
+ return Math.abs(plain) >= 1000 ? formatTick(Math.sign(v) * 1000) : String(plain)
753
+ }
754
+ const scaled = Number((v / units[i][0]).toFixed(1))
755
+ // 999 950 rounds to "1000k"; it reads as the next unit up.
756
+ if (Math.abs(scaled) >= 1000 && i < units.length - 1) {
757
+ return `${Number((v / units[i + 1][0]).toFixed(1))}${units[i + 1][1]}`
758
+ }
759
+ return `${scaled}${units[i][1]}`
686
760
  }