@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
@@ -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
+ }
@@ -50,7 +50,15 @@ export function clampHsv(c: Hsv): Hsv {
50
50
  }
51
51
  }
52
52
 
53
- export function hsvToRgb({ h, s, v, a }: Hsv): Rgb {
53
+ // NaN and Infinity read as 0: one bad channel must not throw out of a public
54
+ // conversion or poison the others.
55
+ const finite = (n: number) => (Number.isFinite(n) ? n : 0)
56
+
57
+ export function hsvToRgb(color: Hsv): Rgb {
58
+ const h = finite(color.h)
59
+ const s = clamp(finite(color.s), 0, 1)
60
+ const v = clamp(finite(color.v), 0, 1)
61
+ const a = Number.isFinite(color.a) ? color.a : 1
54
62
  const hue = normalizeHue(h) / 60
55
63
  const c = v * s
56
64
  const x = c * (1 - Math.abs((hue % 2) - 1))
@@ -99,10 +107,20 @@ export function rgbToHsv({ r, g, b, a }: Rgb): Hsv {
99
107
 
100
108
  const hex2 = (n: number) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0")
101
109
 
110
+ /**
111
+ * Alpha quantised to the one precision every format can carry, a byte, so hex,
112
+ * rgb and hsl agree on whether a colour is opaque: 0.996 used to print as an
113
+ * opaque rgb() and as "#…fe" in hex.
114
+ */
115
+ function alphaByte(a: number): number {
116
+ return Math.round(clamp(a, 0, 1) * 255)
117
+ }
118
+
102
119
  /** "#rrggbb", or "#rrggbbaa" when the colour is not fully opaque. */
103
120
  export function rgbToHex({ r, g, b, a }: Rgb): string {
104
121
  const base = `#${hex2(r)}${hex2(g)}${hex2(b)}`
105
- return a >= 1 ? base : base + hex2(a * 255)
122
+ const byte = alphaByte(a)
123
+ return byte >= 255 ? base : base + hex2(byte)
106
124
  }
107
125
 
108
126
  /**
@@ -199,20 +217,23 @@ export function hsvToHsl({ h, s, v }: Hsv): { h: number; s: number; l: number }
199
217
  /** The colour as a CSS string in the requested format. */
200
218
  export function formatColor(c: Hsv, format: ColorFormat): string {
201
219
  const rgb = hsvToRgb(c)
202
- const a = Math.round(clamp(c.a, 0, 1) * 100) / 100
220
+ const byte = alphaByte(rgb.a)
221
+ const a = Math.round((byte / 255) * 1000) / 1000
203
222
 
204
223
  if (format === "hex") return rgbToHex(rgb)
205
224
  if (format === "rgb") {
206
- return a >= 1
225
+ return byte >= 255
207
226
  ? `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`
208
227
  : `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a})`
209
228
  }
210
229
 
211
- const { h, s, l } = hsvToHsl(c)
212
- const hh = Math.round(h)
213
- const ss = Math.round(s * 100)
214
- const ll = Math.round(l * 100)
215
- return a >= 1 ? `hsl(${hh}, ${ss}%, ${ll}%)` : `hsla(${hh}, ${ss}%, ${ll}%, ${a})`
230
+ // One decimal: whole percentages drift up to 4 per channel on the way back.
231
+ const { h, s, l } = hsvToHsl({ ...c, h: finite(c.h), s: clamp(finite(c.s), 0, 1), v: clamp(finite(c.v), 0, 1) })
232
+ const dec = (n: number) => Math.round(n * 10) / 10
233
+ const hh = dec(h) % 360
234
+ const ss = dec(s * 100)
235
+ const ll = dec(l * 100)
236
+ return byte >= 255 ? `hsl(${hh}, ${ss}%, ${ll}%)` : `hsla(${hh}, ${ss}%, ${ll}%, ${a})`
216
237
  }
217
238
 
218
239
  /** Always opaque — for the swatch behind an alpha checkerboard, and for rails. */
@@ -63,6 +63,12 @@ function score(command: SearchableCommand, terms: CommandSearchTerms): number {
63
63
  if (!hit) return 0
64
64
  total += hit
65
65
  }
66
+ // A label that IS what was typed, or starts with it, is what the person is
67
+ // naming: "theme" must put the Theme command above ThemeSwitcher, which only
68
+ // contains the word but also matches its keywords and description.
69
+ const typed = terms.include.join(" ")
70
+ if (typed && label === typed) total += 6
71
+ else if (typed && label.startsWith(typed)) total += 2
66
72
  return total
67
73
  }
68
74
 
@@ -0,0 +1,58 @@
1
+ import { reactive } from "vue"
2
+
3
+ /*
4
+ * Imperative confirmation: `if (await confirm({ title: "Delete?" })) ...`.
5
+ * Requests queue here and the one mounted LpConfirmHost shows them one at a
6
+ * time. Pinned to a global symbol like the toast queue, so a barrel and a deep
7
+ * import share one host.
8
+ */
9
+
10
+ export interface ConfirmOptions {
11
+ title: string
12
+ description?: string
13
+ danger?: boolean
14
+ icon?: string
15
+ confirmLabel?: string
16
+ cancelLabel?: string
17
+ loadingLabel?: string
18
+ /**
19
+ * Work to run on confirm. The dialog stays open with a loading state while it
20
+ * runs and shows the error's message if it rejects; the promise from
21
+ * `confirm()` resolves true only once it succeeded.
22
+ */
23
+ action?: () => unknown
24
+ }
25
+
26
+ export interface ConfirmRequest extends ConfirmOptions {
27
+ id: number
28
+ resolve: (confirmed: boolean) => void
29
+ }
30
+
31
+ const STATE_KEY = Symbol.for("@leavepulse/ui:confirm-queue")
32
+ type GlobalWithConfirm = typeof globalThis & {
33
+ [STATE_KEY]?: { queue: ConfirmRequest[]; nextId: number }
34
+ }
35
+ const g = globalThis as GlobalWithConfirm
36
+ const state = (g[STATE_KEY] ??= { queue: reactive<ConfirmRequest[]>([]), nextId: 0 })
37
+
38
+ export const confirmQueue = state.queue
39
+
40
+ /** Ask for confirmation. Resolves false when the user cancels or dismisses. */
41
+ export function confirm(options: ConfirmOptions): Promise<boolean> {
42
+ if (typeof window === "undefined") return Promise.resolve(false)
43
+ return new Promise((resolve) => {
44
+ state.queue.push({ ...options, id: state.nextId++, resolve })
45
+ })
46
+ }
47
+
48
+ /** Settle a request and drop it from the queue. Used by the host. */
49
+ export function settleConfirm(id: number, confirmed: boolean): void {
50
+ const i = state.queue.findIndex((r) => r.id === id)
51
+ if (i === -1) return
52
+ const [request] = state.queue.splice(i, 1)
53
+ request?.resolve(confirmed)
54
+ }
55
+
56
+ export function useConfirm(): typeof confirm {
57
+ return confirm
58
+ }
@@ -65,3 +65,15 @@ export function matchCountryByValue(
65
65
  }
66
66
  return best
67
67
  }
68
+
69
+ /**
70
+ * Countries matching a search query: by name, or by dial code when the query
71
+ * carries digits ("+44", "44"). A query without digits must not match on the
72
+ * dial code at all, or every country would match the empty string.
73
+ */
74
+ export function filterCountries(list: Country[], query: string): Country[] {
75
+ const q = query.trim().toLowerCase()
76
+ if (!q) return list
77
+ const digits = q.replace(/\D/g, "")
78
+ return list.filter((c) => c.name.toLowerCase().includes(q) || (digits !== "" && c.dial.includes(digits)))
79
+ }
@@ -0,0 +1,134 @@
1
+ import {
2
+ type CalendarDate,
3
+ endOfMonth,
4
+ getLocalTimeZone,
5
+ parseDate,
6
+ startOfMonth,
7
+ today,
8
+ } from "@internationalized/date"
9
+
10
+ /*
11
+ * Date ranges as the kit models them: two ISO "YYYY-MM-DD" strings, inclusive
12
+ * on both ends, the same shape LpDatePicker uses for a single day. Presets are
13
+ * plain functions so a consumer can add "This quarter" or "Year to date"
14
+ * without the kit knowing about it.
15
+ */
16
+ export interface DateRange {
17
+ start: string
18
+ end: string
19
+ }
20
+
21
+ export interface DateRangePreset {
22
+ label: string
23
+ range: () => DateRange
24
+ }
25
+
26
+ export interface DateRangePresetLabels {
27
+ today: string
28
+ last7: string
29
+ last30: string
30
+ thisMonth: string
31
+ lastMonth: string
32
+ }
33
+
34
+ function span(start: CalendarDate, end: CalendarDate): DateRange {
35
+ return { start: start.toString(), end: end.toString() }
36
+ }
37
+
38
+ /** The default preset list. `now` is injectable so the set can be tested. */
39
+ export function defaultRangePresets(
40
+ labels: DateRangePresetLabels,
41
+ now: () => CalendarDate = () => today(getLocalTimeZone()),
42
+ ): DateRangePreset[] {
43
+ return [
44
+ { label: labels.today, range: () => span(now(), now()) },
45
+ { label: labels.last7, range: () => span(now().subtract({ days: 6 }), now()) },
46
+ { label: labels.last30, range: () => span(now().subtract({ days: 29 }), now()) },
47
+ { label: labels.thisMonth, range: () => span(startOfMonth(now()), endOfMonth(now())) },
48
+ {
49
+ label: labels.lastMonth,
50
+ range: () => {
51
+ const previous = now().subtract({ months: 1 })
52
+ return span(startOfMonth(previous), endOfMonth(previous))
53
+ },
54
+ },
55
+ ]
56
+ }
57
+
58
+ export function parseIsoDay(iso: string | undefined): CalendarDate | undefined {
59
+ if (!iso) return undefined
60
+ try {
61
+ return parseDate(iso.slice(0, 10))
62
+ } catch {
63
+ return undefined
64
+ }
65
+ }
66
+
67
+ /** A range with both ends parsed and in order, or undefined. */
68
+ export function normalizeRange(range: Partial<DateRange> | undefined): DateRange | undefined {
69
+ const start = parseIsoDay(range?.start)
70
+ const end = parseIsoDay(range?.end)
71
+ if (!start || !end) return undefined
72
+ return start.compare(end) <= 0 ? span(start, end) : span(end, start)
73
+ }
74
+
75
+ export function sameRange(a: Partial<DateRange> | undefined, b: Partial<DateRange> | undefined): boolean {
76
+ const x = normalizeRange(a)
77
+ const y = normalizeRange(b)
78
+ return !!x && !!y && x.start === y.start && x.end === y.end
79
+ }
80
+
81
+ /** Clip a preset to the allowed bounds; undefined when nothing of it is left. */
82
+ export function clampRange(range: DateRange, min?: string, max?: string): DateRange | undefined {
83
+ const r = normalizeRange(range)
84
+ if (!r) return undefined
85
+ const start = min && r.start < min ? min : r.start
86
+ const end = max && r.end > max ? max : r.end
87
+ return start <= end ? { start, end } : undefined
88
+ }
89
+
90
+ /**
91
+ * "Jun 3 – 18, 2026" style text: Intl's own range formatting collapses the
92
+ * shared month and year, which is how a range reads naturally in every locale.
93
+ */
94
+ export function formatRange(
95
+ range: DateRange,
96
+ locale?: string,
97
+ format: Intl.DateTimeFormatOptions = { year: "numeric", month: "short", day: "numeric" },
98
+ ): string {
99
+ const start = parseIsoDay(range.start)
100
+ const end = parseIsoDay(range.end)
101
+ if (!start || !end) return ""
102
+ const zone = "UTC"
103
+ const fmt = new Intl.DateTimeFormat(locale, { ...format, timeZone: zone })
104
+ return fmt.formatRange(start.toDate(zone), end.toDate(zone))
105
+ }
106
+
107
+ export interface ReshapeRules {
108
+ /** Whether the moving end may land on this day (min, max, disabled). */
109
+ allowed?: (iso: string) => boolean
110
+ /** Days a range may not include. */
111
+ unavailable?: (iso: string) => boolean
112
+ /** Longest range, in days, counting both ends. */
113
+ maximumDays?: number
114
+ }
115
+
116
+ /**
117
+ * The range one end makes when it is dragged to `moving` while the other stays
118
+ * at `anchor`; passing the anchor flips which end is which. Null when the result
119
+ * would break a rule, so a drag simply does not follow onto that day.
120
+ */
121
+ export function reshapeRange(anchor: string, moving: string, rules: ReshapeRules = {}): DateRange | null {
122
+ const a = parseIsoDay(anchor)
123
+ const m = parseIsoDay(moving)
124
+ if (!a || !m) return null
125
+ if (rules.allowed && !rules.allowed(m.toString())) return null
126
+ const [start, end] = a.compare(m) <= 0 ? [a, m] : [m, a]
127
+ if (rules.maximumDays && end.compare(start) + 1 > rules.maximumDays) return null
128
+ if (rules.unavailable) {
129
+ for (let d = start; d.compare(end) <= 0; d = d.add({ days: 1 })) {
130
+ if (rules.unavailable(d.toString())) return null
131
+ }
132
+ }
133
+ return { start: start.toString(), end: end.toString() }
134
+ }
@@ -0,0 +1,228 @@
1
+ /*
2
+ * Data export shared by the table and the chart: CSV text, a file download,
3
+ * and an SVG rasterised with its theme resolved.
4
+ */
5
+
6
+ export interface CsvColumn<Row> {
7
+ key: string
8
+ /** Header cell. Defaults to the key. */
9
+ label?: string
10
+ /** The value written for a row; the row's `key` field when omitted. */
11
+ value?: (row: Row) => unknown
12
+ }
13
+
14
+ export interface CsvOptions {
15
+ /** Field separator. Default ",". Some locales' spreadsheets expect ";". */
16
+ separator?: string
17
+ /** Prefix a UTF-8 byte order mark so Excel reads non-ASCII text correctly. */
18
+ bom?: boolean
19
+ /**
20
+ * Neutralise cells a spreadsheet would run as a formula (leading = + - @,
21
+ * tab or CR) by prefixing a quote. On by default: exported data is often
22
+ * user-supplied, and opening it is enough to trigger a formula.
23
+ */
24
+ guardFormulas?: boolean
25
+ /** Line ending. Default "\r\n", as RFC 4180 specifies. */
26
+ newline?: string
27
+ }
28
+
29
+ const FORMULA_START = /^[=+\-@\t\r]/
30
+
31
+ function cellText(value: unknown): string {
32
+ if (value == null) return ""
33
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? "" : value.toISOString()
34
+ if (typeof value === "object") return JSON.stringify(value)
35
+ return String(value)
36
+ }
37
+
38
+ /** One CSV field, quoted when it holds a separator, a quote or a line break. */
39
+ export function csvField(value: unknown, options: CsvOptions = {}): string {
40
+ const separator = options.separator ?? ","
41
+ let text = cellText(value)
42
+ if ((options.guardFormulas ?? true) && typeof value === "string" && FORMULA_START.test(text)) {
43
+ text = `'${text}`
44
+ }
45
+ const needsQuotes = text.includes(separator) || /["\r\n]/.test(text) || /^\s|\s$/.test(text)
46
+ return needsQuotes ? `"${text.replace(/"/g, '""')}"` : text
47
+ }
48
+
49
+ /** Rows as CSV text, a header line first. */
50
+ export function toCsv<Row>(rows: readonly Row[], columns: readonly CsvColumn<Row>[], options: CsvOptions = {}): string {
51
+ const separator = options.separator ?? ","
52
+ const newline = options.newline ?? "\r\n"
53
+ const line = (cells: unknown[]) => cells.map((c) => csvField(c, options)).join(separator)
54
+ const out = [line(columns.map((c) => c.label ?? c.key))]
55
+ for (const row of rows) {
56
+ out.push(
57
+ line(columns.map((c) => (c.value ? c.value(row) : (row as Record<string, unknown>)[c.key]))),
58
+ )
59
+ }
60
+ return (options.bom ? "" : "") + out.join(newline) + newline
61
+ }
62
+
63
+ /**
64
+ * Hand the viewer a file to save. Does nothing outside a browser, so a call
65
+ * that runs during SSR is harmless.
66
+ */
67
+ export function downloadFile(name: string, data: Blob | string, type = "text/plain;charset=utf-8"): void {
68
+ if (typeof document === "undefined" || typeof URL === "undefined") return
69
+ const blob = typeof data === "string" ? new Blob([data], { type }) : data
70
+ const url = URL.createObjectURL(blob)
71
+ const a = document.createElement("a")
72
+ a.href = url
73
+ a.download = name
74
+ a.rel = "noopener"
75
+ a.style.display = "none"
76
+ document.body.append(a)
77
+ a.click()
78
+ a.remove()
79
+ setTimeout(() => URL.revokeObjectURL(url), 0)
80
+ }
81
+
82
+ const PAINT_PROPS = [
83
+ "fill",
84
+ "fill-opacity",
85
+ "fill-rule",
86
+ "stroke",
87
+ "stroke-opacity",
88
+ "stroke-width",
89
+ "stroke-dasharray",
90
+ "stroke-dashoffset",
91
+ "stroke-linecap",
92
+ "stroke-linejoin",
93
+ "opacity",
94
+ "stop-color",
95
+ "stop-opacity",
96
+ "color",
97
+ "font-family",
98
+ "font-size",
99
+ "font-weight",
100
+ "letter-spacing",
101
+ "text-anchor",
102
+ "dominant-baseline",
103
+ "visibility",
104
+ "display",
105
+ ] as const
106
+
107
+ /**
108
+ * A standalone copy of a live SVG: every element carries its computed paint as
109
+ * an inline style, so CSS custom properties and classes from the page's
110
+ * stylesheet survive being rendered as an image, and running SMIL animations
111
+ * are frozen at their end state.
112
+ */
113
+ export function inlineSvg(svg: SVGSVGElement): SVGSVGElement {
114
+ const clone = svg.cloneNode(true) as SVGSVGElement
115
+ const source = [svg, ...svg.querySelectorAll("*")]
116
+ const target = [clone, ...clone.querySelectorAll("*")]
117
+ source.forEach((el, i) => {
118
+ const copy = target[i]
119
+ if (!(copy instanceof Element)) return
120
+ const computed = getComputedStyle(el)
121
+ const style = PAINT_PROPS.map((p) => `${p}:${computed.getPropertyValue(p)}`)
122
+ .filter((d) => !d.endsWith(":"))
123
+ .join(";")
124
+ copy.setAttribute("style", style)
125
+ copy.removeAttribute("class")
126
+ })
127
+ // Comment nodes (Vue leaves them in development builds) may hold "--", which
128
+ // is invalid inside an XML comment and makes the whole image fail to load.
129
+ const comments = document.createTreeWalker(clone, NodeFilter.SHOW_COMMENT)
130
+ const dead: Node[] = []
131
+ while (comments.nextNode()) dead.push(comments.currentNode)
132
+ for (const node of dead) node.parentNode?.removeChild(node)
133
+ for (const anim of clone.querySelectorAll("animate")) {
134
+ const attr = anim.getAttribute("attributeName")
135
+ const to = anim.getAttribute("to")
136
+ if (attr && to != null) anim.parentElement?.setAttribute(attr, to)
137
+ anim.remove()
138
+ }
139
+ clone.setAttribute("xmlns", "http://www.w3.org/2000/svg")
140
+ const { width, height } = svg.getBoundingClientRect()
141
+ clone.setAttribute("width", String(width))
142
+ clone.setAttribute("height", String(height))
143
+ clone.style.overflow = "visible"
144
+ return clone
145
+ }
146
+
147
+ export interface RasterOptions {
148
+ /** Device pixels per CSS pixel. Default: the screen's ratio, at least 2. */
149
+ scale?: number
150
+ /** Fill behind the drawing; transparent when omitted. */
151
+ background?: string
152
+ /** Extra space around the drawing, in CSS pixels. */
153
+ padding?: number
154
+ /** Drawn above the SVG, in the page's own font. */
155
+ header?: (ctx: CanvasRenderingContext2D, width: number) => number
156
+ }
157
+
158
+ /** Rasterise an SVG element to a PNG blob with its theme resolved. */
159
+ export async function svgToPng(svg: SVGSVGElement, options: RasterOptions = {}): Promise<Blob> {
160
+ const copy = inlineSvg(svg)
161
+ const width = Number(copy.getAttribute("width"))
162
+ const height = Number(copy.getAttribute("height"))
163
+ const markup = new XMLSerializer().serializeToString(copy)
164
+ const url = URL.createObjectURL(new Blob([markup], { type: "image/svg+xml;charset=utf-8" }))
165
+ try {
166
+ const img = new Image()
167
+ img.decoding = "async"
168
+ const loaded = new Promise<void>((resolve, reject) => {
169
+ img.onload = () => resolve()
170
+ img.onerror = () => reject(new Error("SVG could not be rasterised"))
171
+ })
172
+ img.src = url
173
+ await loaded
174
+
175
+ const scale = options.scale ?? Math.max(2, globalThis.devicePixelRatio || 1)
176
+ const pad = options.padding ?? 0
177
+ const measure = document.createElement("canvas").getContext("2d")
178
+ const headerHeight = measure && options.header ? options.header(measure, width) : 0
179
+ const canvas = document.createElement("canvas")
180
+ canvas.width = Math.ceil((width + pad * 2) * scale)
181
+ canvas.height = Math.ceil((height + headerHeight + pad * 2) * scale)
182
+ const ctx = canvas.getContext("2d")
183
+ if (!ctx) throw new Error("Canvas 2D is unavailable")
184
+ ctx.scale(scale, scale)
185
+ if (options.background) {
186
+ ctx.fillStyle = options.background
187
+ ctx.fillRect(0, 0, width + pad * 2, height + headerHeight + pad * 2)
188
+ }
189
+ ctx.translate(pad, pad)
190
+ if (options.header) options.header(ctx, width)
191
+ ctx.drawImage(img, 0, headerHeight, width, height)
192
+ return await new Promise<Blob>((resolve, reject) =>
193
+ canvas.toBlob((b) => (b ? resolve(b) : reject(new Error("PNG encoding failed"))), "image/png"),
194
+ )
195
+ } finally {
196
+ URL.revokeObjectURL(url)
197
+ }
198
+ }
199
+
200
+ /** The first non-transparent background at or above an element. */
201
+ export function backgroundOf(el: Element | null): string | undefined {
202
+ for (let node = el; node; node = node.parentElement) {
203
+ const bg = getComputedStyle(node).backgroundColor
204
+ if (bg && bg !== "transparent" && !/rgba\([^)]*,\s*0\)$/.test(bg)) return bg
205
+ }
206
+ return undefined
207
+ }
208
+
209
+ /**
210
+ * A colour as the browser resolves it where `within` sits, for APIs that cannot
211
+ * read custom properties (canvas, an SVG drawn as an image).
212
+ */
213
+ export function resolveCssColor(value: string, within: Element): string {
214
+ if (!value.includes("var(") && !value.toLowerCase().includes("currentcolor")) return value
215
+ const probe = document.createElement("span")
216
+ probe.style.color = value
217
+ probe.style.display = "none"
218
+ within.append(probe)
219
+ const resolved = getComputedStyle(probe).color
220
+ probe.remove()
221
+ return resolved || value
222
+ }
223
+
224
+ /** A filesystem-safe name from a title. */
225
+ export function fileStem(title: string | undefined, fallback: string): string {
226
+ const stem = (title ?? "").trim().replace(/[\\/:*?"<>|\u0000-\u001f]+/g, "").replace(/\s+/g, "-")
227
+ return stem || fallback
228
+ }