@leavepulse/ui 0.44.0 → 0.45.1

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 (271) hide show
  1. package/README.md +49 -3
  2. package/dist/assets/{LpCommandPalette-DCP5g2ik.css → LpCommandPalette-HzAA4gri.css} +21 -21
  3. package/dist/assets/{LpRollingText-CR1mb0Vt.css → LpRollingText-BGnE6q9j.css} +3 -0
  4. package/dist/assets/{LpShortcutsDialog-ChUyU7oV.css → LpShortcutsDialog-CqYX3yy6.css} +28 -7
  5. package/dist/assets/{LpTable-DDQNdf86.css → LpTable-CPwWxTJS.css} +49 -9
  6. package/dist/chunks/{ContextMenuItems-CrfeNPWT.js → ContextMenuItems-BtKU7074.js} +2 -1
  7. package/dist/chunks/{LayoutCanvas-D-jPOAvg.js → LayoutCanvas-BkaGXTjx.js} +1 -1
  8. package/dist/chunks/{LayoutNode-D8Xy9N-o.js → LayoutNode-B3_9E1Xa.js} +9 -5
  9. package/dist/chunks/{LpActionBar-BbwCF6l5.js → LpActionBar-CnfFVIwC.js} +3 -3
  10. package/dist/chunks/{LpAddressInput-BrOqkZqg.js → LpAddressInput-CJOYGZlI.js} +7 -7
  11. package/dist/chunks/{LpAlert-DkOIjsbq.js → LpAlert-B6ilrM7T.js} +1 -1
  12. package/dist/chunks/{LpAppShell-BUKPcHco.js → LpAppShell-B07Lf48e.js} +4 -4
  13. package/dist/chunks/{LpAutocomplete-DxV3Y9h4.js → LpAutocomplete-C8IgSMsi.js} +2 -1
  14. package/dist/chunks/{LpAvatar-ClBbEYZN.js → LpAvatar-C1XStdOo.js} +3 -3
  15. package/dist/chunks/{LpBreadcrumbs-B8IJgFIc.js → LpBreadcrumbs-CQJd73Gm.js} +1 -1
  16. package/dist/chunks/{LpButton-kqJBHIIb.js → LpButton-CKSwC6Mu.js} +1 -1
  17. package/dist/chunks/{LpCalendar-B7Ko5RdR.js → LpCalendar-DrA2McTU.js} +1 -1
  18. package/dist/chunks/{LpCard-CpRByR0q.js → LpCard-C2IvucmX.js} +1 -1
  19. package/dist/chunks/{LpCheckbox-DpqchzH4.js → LpCheckbox-Bkj13CbW.js} +1 -1
  20. package/dist/chunks/{LpCodeBlock-DeHJPq7p.js → LpCodeBlock-ca7ANFyW.js} +1 -1
  21. package/dist/chunks/{LpColorPicker-Bhf1qxhB.js → LpColorPicker-zz4VGkG4.js} +46 -20
  22. package/dist/chunks/{LpConfirmDialog-CIUOSnWo.js → LpConfirmDialog-BTVAR4tF.js} +3 -3
  23. package/dist/chunks/{LpConfirmHost-CDm0Wtpv.js → LpConfirmHost-Ci_uCHgQ.js} +1 -1
  24. package/dist/chunks/{LpContextMenu-DQw5dGfG.js → LpContextMenu-BqkAYa9X.js} +2 -1
  25. package/dist/chunks/{LpDatePicker-Drirx1AK.js → LpDatePicker-BrdFtSTy.js} +16 -10
  26. package/dist/chunks/LpDateRangePicker-Drd7EbIz.js +210 -0
  27. package/dist/chunks/{LpDisclosure-ChQYmjlR.js → LpDisclosure-Cc23-JOT.js} +1 -1
  28. package/dist/chunks/{LpDrawer-bScx-KMO.js → LpDrawer-CfRhHwmE.js} +3 -3
  29. package/dist/chunks/{LpDropdownMenu-DJpoqRCb.js → LpDropdownMenu-DdJGQzPn.js} +2 -1
  30. package/dist/chunks/{LpEmptyState-Dsdqz1Wj.js → LpEmptyState-BJbZXC8D.js} +1 -1
  31. package/dist/chunks/LpFieldNotice-wtieLUTj.js +64 -0
  32. package/dist/chunks/{LpFileTree-CSI_5SK2.js → LpFileTree-CNurOvkV.js} +3 -3
  33. package/dist/chunks/{LpFileTreeNode-B4MiSQX2.js → LpFileTreeNode-D_qRRdsH.js} +3 -3
  34. package/dist/chunks/{LpFormField-CIk5qosh.js → LpFormField-CtVYj6tO.js} +29 -15
  35. package/dist/chunks/{LpIcon-CCnX5_2j.js → LpIcon-DYtaid0K.js} +20 -6
  36. package/dist/chunks/{LpInput-C0e8y8_d.js → LpInput-D-YGQbEv.js} +39 -16
  37. package/dist/chunks/{LpLightbox-Mr0W_6HK.js → LpLightbox-DSDahF23.js} +1 -1
  38. package/dist/chunks/{LpLogViewer-YBOaFnc_.js → LpLogViewer-KypbgOyo.js} +252 -58
  39. package/dist/chunks/{LpMapPicker-NUzS9e37.js → LpMapPicker-Cc84LaM3.js} +1 -1
  40. package/dist/chunks/{LpMediaGallery-DfJmMW0I.js → LpMediaGallery-YKr-cQPN.js} +2 -2
  41. package/dist/chunks/{LpModal-DPwGKBPv.js → LpModal-BzTVeNqe.js} +3 -3
  42. package/dist/chunks/{LpModalNav-DPg5H9f3.js → LpModalNav-CjABSGCu.js} +14 -8
  43. package/dist/chunks/{LpNotificationBell-t5yn-siX.js → LpNotificationBell-CCwyJP5C.js} +26 -14
  44. package/dist/chunks/{LpNumberField-DWnQKktS.js → LpNumberField-B77O-z28.js} +1 -1
  45. package/dist/chunks/{LpPagination-DFngX7Xo.js → LpPagination-BcDlo3r_.js} +1 -1
  46. package/dist/chunks/{LpPasswordInput-CWVDuBJ5.js → LpPasswordInput-eKzQhbQP.js} +2 -2
  47. package/dist/chunks/{LpPhoneInput-CD-ZTeSd.js → LpPhoneInput-BCbrrhgE.js} +126 -23
  48. package/dist/chunks/{LpPie-BLco31Nx.js → LpPie-BEJ2SfI6.js} +9 -10
  49. package/dist/chunks/{LpPopover-p_e_shN3.js → LpPopover-DCKeqiVn.js} +1 -0
  50. package/dist/chunks/LpRangeCalendar-DZuaRV0W.js +331 -0
  51. package/dist/chunks/{LpRollingText-CM5gkSeF.js → LpRollingText-DB7upf-5.js} +1 -1
  52. package/dist/chunks/{LpScrollArea-D8xyKXcF.js → LpScrollArea-Bry1fdOM.js} +14 -2
  53. package/dist/chunks/{LpSection-aM6J_u51.js → LpSection-tKEH0jYf.js} +1 -1
  54. package/dist/chunks/{LpSegmented-Dejd7cIx.js → LpSegmented-C09EEam2.js} +1 -1
  55. package/dist/chunks/{LpSelect-DrRD9I7L.js → LpSelect-DNIL6MB9.js} +20 -23
  56. package/dist/chunks/{LpSidebar-BgwUNWpJ.js → LpSidebar-Bwws_X29.js} +3 -3
  57. package/dist/chunks/{LpSidebarNav-Bqxz8aWT.js → LpSidebarNav-kx-LdqNU.js} +3 -3
  58. package/dist/chunks/{LpStat-dXtyrdX9.js → LpStat-B-7udBRP.js} +1 -1
  59. package/dist/chunks/{LpStepper-Bk-XaQYU.js → LpStepper-C7XiVKZj.js} +34 -19
  60. package/dist/chunks/{LpSwitch-B1aLKulj.js → LpSwitch-OrcOJX9m.js} +1 -1
  61. package/dist/chunks/LpTableFilter-BV8PpLM9.js +158 -0
  62. package/dist/chunks/{LpTabs-_qMVaZJ6.js → LpTabs-29iW1XOx.js} +1 -1
  63. package/dist/chunks/LpTagsInput-D1SuqbhB.js +300 -0
  64. package/dist/chunks/{LpTextarea-yP8hGedR.js → LpTextarea-uuScWo6-.js} +1 -1
  65. package/dist/chunks/{LpThemeProvider-Wjm7Tuit.js → LpThemeProvider-Be4r2TQX.js} +1 -1
  66. package/dist/chunks/{LpThemeSwitcher-CKLc10KZ.js → LpThemeSwitcher-B4aVPbNC.js} +19 -22
  67. package/dist/chunks/{LpTimeField-C5HRgS5H.js → LpTimeField-Cqe7fXnh.js} +1 -1
  68. package/dist/chunks/{LpToaster--oT6lfhL.js → LpToaster-DTtWuKvq.js} +76 -9
  69. package/dist/chunks/{LpTooltip-CBdzeKNf.js → LpTooltip-CXVKcOXT.js} +3 -2
  70. package/dist/chunks/{LpTopologyCanvas-DocsLal3.js → LpTopologyCanvas-6ozNrQKh.js} +2 -1
  71. package/dist/chunks/{LpUptimeBar-DLtxOMRy.js → LpUptimeBar-ugcv25xj.js} +2 -2
  72. package/dist/chunks/icons-CkA0njzm.js +43 -0
  73. package/dist/chunks/{useTheme-C8JHUlPM.js → useTheme-DJm1JSsY.js} +47 -50
  74. package/dist/component-names.d.ts +1 -1
  75. package/dist/components/ContextMenuItems.vue.js +1 -1
  76. package/dist/components/LayoutCanvas.vue.js +1 -1
  77. package/dist/components/LayoutNode.vue.js +1 -1
  78. package/dist/components/LpActionBar.vue.js +1 -1
  79. package/dist/components/LpAddressInput.vue.js +1 -1
  80. package/dist/components/LpAlert.vue.js +1 -1
  81. package/dist/components/LpAppShell.vue.js +1 -1
  82. package/dist/components/LpAutocomplete.vue.js +1 -1
  83. package/dist/components/LpAvatar.vue.js +1 -1
  84. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  85. package/dist/components/LpButton.vue.d.ts +2 -2
  86. package/dist/components/LpButton.vue.js +1 -1
  87. package/dist/components/LpCalendar.vue.js +1 -1
  88. package/dist/components/LpCard.vue.js +1 -1
  89. package/dist/components/LpChart.vue.d.ts +16 -1
  90. package/dist/components/LpChart.vue.js +63 -5
  91. package/dist/components/LpCheckbox.vue.js +1 -1
  92. package/dist/components/LpCodeBlock.vue.js +1 -1
  93. package/dist/components/LpColorPicker.vue.d.ts +2 -2
  94. package/dist/components/LpColorPicker.vue.js +1 -1
  95. package/dist/components/LpCommandPalette.vue.d.ts +6 -0
  96. package/dist/components/LpCommandPalette.vue.js +62 -11
  97. package/dist/components/LpConfirmDialog.vue.js +1 -1
  98. package/dist/components/LpConfirmHost.vue.js +1 -1
  99. package/dist/components/LpContextMenu.vue.js +1 -1
  100. package/dist/components/LpDatePicker.vue.js +1 -1
  101. package/dist/components/LpDateRangePicker.vue.d.ts +60 -0
  102. package/dist/components/LpDateRangePicker.vue.js +4 -0
  103. package/dist/components/LpDisclosure.vue.js +1 -1
  104. package/dist/components/LpDrawer.vue.js +1 -1
  105. package/dist/components/LpDropdownMenu.vue.js +1 -1
  106. package/dist/components/LpDropzone.vue.js +1 -1
  107. package/dist/components/LpEmptyState.vue.js +1 -1
  108. package/dist/components/LpFieldNotice.vue.d.ts +8 -0
  109. package/dist/components/LpFieldNotice.vue.js +4 -0
  110. package/dist/components/LpFileTree.vue.js +1 -1
  111. package/dist/components/LpFileTreeNode.vue.js +1 -1
  112. package/dist/components/LpFormField.vue.d.ts +4 -1
  113. package/dist/components/LpFormField.vue.js +1 -1
  114. package/dist/components/LpIcon.vue.d.ts +0 -1
  115. package/dist/components/LpIcon.vue.js +1 -1
  116. package/dist/components/LpImageEditor.vue.js +2 -2
  117. package/dist/components/LpInput.vue.d.ts +19 -0
  118. package/dist/components/LpInput.vue.js +1 -1
  119. package/dist/components/LpLightbox.vue.js +1 -1
  120. package/dist/components/LpLogViewer.vue.d.ts +52 -1
  121. package/dist/components/LpLogViewer.vue.js +1 -1
  122. package/dist/components/LpMapPicker.vue.js +1 -1
  123. package/dist/components/LpMediaGallery.vue.js +1 -1
  124. package/dist/components/LpModal.vue.js +1 -1
  125. package/dist/components/LpModalNav.vue.js +1 -1
  126. package/dist/components/LpNotificationBell.vue.js +1 -1
  127. package/dist/components/LpNumberField.vue.js +1 -1
  128. package/dist/components/LpPagination.vue.js +1 -1
  129. package/dist/components/LpPasswordInput.vue.js +1 -1
  130. package/dist/components/LpPhoneInput.vue.d.ts +13 -0
  131. package/dist/components/LpPhoneInput.vue.js +1 -1
  132. package/dist/components/LpPie.vue.js +1 -1
  133. package/dist/components/LpPopover.vue.js +1 -1
  134. package/dist/components/LpProgress.vue.d.ts +1 -1
  135. package/dist/components/LpRangeCalendar.vue.d.ts +51 -0
  136. package/dist/components/LpRangeCalendar.vue.js +4 -0
  137. package/dist/components/LpRollingText.vue.d.ts +1 -1
  138. package/dist/components/LpRollingText.vue.js +1 -1
  139. package/dist/components/LpScrollArea.vue.d.ts +2 -2
  140. package/dist/components/LpScrollArea.vue.js +1 -1
  141. package/dist/components/LpSection.vue.js +1 -1
  142. package/dist/components/LpSegmented.vue.js +1 -1
  143. package/dist/components/LpSelect.vue.js +1 -1
  144. package/dist/components/LpShortcutsDialog.vue.d.ts +6 -0
  145. package/dist/components/LpShortcutsDialog.vue.js +56 -14
  146. package/dist/components/LpSidebar.vue.js +1 -1
  147. package/dist/components/LpSidebarNav.vue.js +1 -1
  148. package/dist/components/LpStat.vue.js +1 -1
  149. package/dist/components/LpStepper.vue.js +1 -1
  150. package/dist/components/LpSwitch.vue.js +1 -1
  151. package/dist/components/LpTable.vue.d.ts +84 -2
  152. package/dist/components/LpTable.vue.js +481 -107
  153. package/dist/components/LpTableFilter.vue.d.ts +31 -0
  154. package/dist/components/LpTableFilter.vue.js +4 -0
  155. package/dist/components/LpTabs.vue.js +1 -1
  156. package/dist/components/LpTagsInput.vue.d.ts +77 -0
  157. package/dist/components/LpTagsInput.vue.js +4 -0
  158. package/dist/components/LpTextarea.vue.js +1 -1
  159. package/dist/components/LpThemeProvider.vue.js +1 -1
  160. package/dist/components/LpThemeSwitcher.vue.d.ts +2 -2
  161. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  162. package/dist/components/LpTimeField.vue.js +1 -1
  163. package/dist/components/LpToaster.vue.d.ts +2 -0
  164. package/dist/components/LpToaster.vue.js +1 -1
  165. package/dist/components/LpTooltip.vue.js +1 -1
  166. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  167. package/dist/components/LpUptimeBar.vue.js +1 -1
  168. package/dist/components/chart.js +3 -2
  169. package/dist/components/commandSearch.js +3 -0
  170. package/dist/components/dateRange.d.ts +43 -0
  171. package/dist/components/dateRange.js +76 -0
  172. package/dist/components/exportData.d.ts +58 -0
  173. package/dist/components/exportData.js +165 -0
  174. package/dist/components/logSearch.d.ts +18 -0
  175. package/dist/components/logSearch.js +36 -0
  176. package/dist/components/phone.d.ts +41 -0
  177. package/dist/components/phone.js +80 -0
  178. package/dist/components/tableFilter.d.ts +36 -0
  179. package/dist/components/tableFilter.js +84 -0
  180. package/dist/components/tags.d.ts +20 -0
  181. package/dist/components/tags.js +37 -0
  182. package/dist/composables/useField.d.ts +4 -0
  183. package/dist/composables/useFieldNotice.d.ts +17 -0
  184. package/dist/composables/useFieldNotice.js +28 -0
  185. package/dist/composables/useForm.d.ts +53 -0
  186. package/dist/composables/useForm.js +207 -0
  187. package/dist/composables/useHotkeys.d.ts +65 -5
  188. package/dist/composables/useHotkeys.js +170 -9
  189. package/dist/composables/useInputMask.d.ts +59 -0
  190. package/dist/composables/useInputMask.js +170 -0
  191. package/dist/composables/useLocale.d.ts +2 -0
  192. package/dist/composables/useLocale.js +9 -0
  193. package/dist/composables/usePaintSelect.d.ts +22 -0
  194. package/dist/composables/usePaintSelect.js +169 -0
  195. package/dist/composables/useShift.js +7 -2
  196. package/dist/icons-all.d.ts +1 -0
  197. package/dist/icons-all.js +3 -0
  198. package/dist/icons.d.ts +14 -1
  199. package/dist/index.d.ts +29 -3
  200. package/dist/index.js +195 -138
  201. package/dist/kit-icons.d.ts +2 -0
  202. package/dist/theme/backdrop.d.ts +12 -0
  203. package/dist/theme/presets.d.ts +0 -2
  204. package/package.json +13 -4
  205. package/src/component-names.ts +4 -0
  206. package/src/components/ContextMenuItems.vue +1 -1
  207. package/src/components/LayoutNode.vue +11 -2
  208. package/src/components/LpActionBar.vue +1 -1
  209. package/src/components/LpAutocomplete.vue +1 -0
  210. package/src/components/LpAvatar.vue +2 -2
  211. package/src/components/LpButton.vue +1 -1
  212. package/src/components/LpChart.vue +80 -1
  213. package/src/components/LpColorPicker.vue +34 -1
  214. package/src/components/LpCommandPalette.vue +82 -8
  215. package/src/components/LpContextMenu.vue +1 -1
  216. package/src/components/LpDatePicker.vue +11 -4
  217. package/src/components/LpDateRangePicker.vue +226 -0
  218. package/src/components/LpDrawer.vue +1 -1
  219. package/src/components/LpDropdownMenu.vue +1 -0
  220. package/src/components/LpFieldNotice.vue +65 -0
  221. package/src/components/LpFormField.vue +33 -4
  222. package/src/components/LpIcon.vue +22 -4
  223. package/src/components/LpInput.vue +46 -6
  224. package/src/components/LpLogViewer.vue +225 -22
  225. package/src/components/LpModal.vue +2 -2
  226. package/src/components/LpModalNav.vue +13 -2
  227. package/src/components/LpNotificationBell.vue +15 -7
  228. package/src/components/LpPhoneInput.vue +179 -20
  229. package/src/components/LpPie.vue +8 -5
  230. package/src/components/LpPopover.vue +1 -0
  231. package/src/components/LpRangeCalendar.vue +365 -0
  232. package/src/components/LpRollingText.vue +3 -0
  233. package/src/components/LpScrollArea.vue +22 -2
  234. package/src/components/LpSelect.vue +0 -0
  235. package/src/components/LpShortcutsDialog.vue +77 -8
  236. package/src/components/LpStepper.vue +23 -10
  237. package/src/components/LpSwitch.vue +1 -1
  238. package/src/components/LpTable.vue +584 -65
  239. package/src/components/LpTableFilter.vue +175 -0
  240. package/src/components/LpTagsInput.vue +364 -0
  241. package/src/components/LpThemeSwitcher.vue +19 -23
  242. package/src/components/LpToaster.vue +91 -5
  243. package/src/components/LpTooltip.vue +9 -2
  244. package/src/components/LpTopologyCanvas.vue +7 -1
  245. package/src/components/chart.ts +5 -3
  246. package/src/components/commandSearch.ts +6 -0
  247. package/src/components/dateRange.ts +134 -0
  248. package/src/components/exportData.ts +228 -0
  249. package/src/components/logSearch.ts +46 -0
  250. package/src/components/phone.ts +140 -0
  251. package/src/components/tableFilter.ts +122 -0
  252. package/src/components/tags.ts +62 -0
  253. package/src/composables/useField.ts +4 -0
  254. package/src/composables/useFieldNotice.ts +59 -0
  255. package/src/composables/useForm.ts +314 -0
  256. package/src/composables/useHotkeys.ts +269 -13
  257. package/src/composables/useInputMask.ts +254 -0
  258. package/src/composables/useLocale.ts +14 -0
  259. package/src/composables/usePaintSelect.ts +243 -0
  260. package/src/composables/useShift.ts +13 -2
  261. package/src/iconSubset.d.mts +10 -0
  262. package/src/iconSubset.mjs +51 -0
  263. package/src/icons-all.ts +10 -0
  264. package/src/icons.ts +59 -8
  265. package/src/index.ts +37 -3
  266. package/src/kit-icons.ts +5 -0
  267. package/src/nuxt.ts +30 -2
  268. package/src/runtime/theme.plugin.ts +2 -2
  269. package/src/theme/backdrop.ts +69 -0
  270. package/src/theme/presets.ts +6 -64
  271. package/src/tokens/tokens.css +53 -4
@@ -0,0 +1,254 @@
1
+ import { computed, toValue, type MaybeRefOrGetter } from "vue"
2
+
3
+ /*
4
+ * Format-as-you-type for a native <input> without losing the caret.
5
+ *
6
+ * A mask is a string of tokens and literals: "+0 (000) 000-00-00",
7
+ * "0000 0000 0000 0000", "AA-0000". Each token accepts one character; anything
8
+ * else is a literal the mask inserts itself. A backslash makes the next mask
9
+ * character literal ("\\0" is a literal zero). A list of masks picks the first
10
+ * one the typed characters fit, and a function derives the mask from the raw
11
+ * value typed so far (card networks, phone plans).
12
+ *
13
+ * The caret is tracked by how many accepted characters sit before it, not by
14
+ * its offset, so inserting a literal ahead of it never shoves it backwards.
15
+ * Literals are only written once a character follows them, so Backspace walks
16
+ * back naturally instead of hitting an auto-inserted separator.
17
+ */
18
+
19
+ export type MaskToken = RegExp | { pattern: RegExp; transform?: (ch: string) => string }
20
+ export type MaskTokens = Record<string, MaskToken>
21
+ export type MaskSpec = string | readonly string[] | ((raw: string) => string)
22
+
23
+ export const DEFAULT_MASK_TOKENS: MaskTokens = {
24
+ "0": /\d/,
25
+ a: /\p{L}/u,
26
+ A: { pattern: /\p{L}/u, transform: (ch) => ch.toLocaleUpperCase() },
27
+ "*": /[\p{L}\d]/u,
28
+ }
29
+
30
+ type Slot = { literal: string } | { test: (ch: string) => boolean; transform: (ch: string) => string }
31
+
32
+ export interface MaskResult {
33
+ masked: string
34
+ raw: string
35
+ /** Caret offset in `masked`. */
36
+ caret: number
37
+ /** Every token of the mask is filled. */
38
+ complete: boolean
39
+ }
40
+
41
+ export interface MaskOptions {
42
+ mask: MaskSpec
43
+ tokens?: MaskTokens
44
+ /**
45
+ * Caret position: an offset into `input`, or with `fromRaw` the number of raw
46
+ * characters before it. Defaults to the end.
47
+ */
48
+ caret?: number
49
+ /** `input` holds raw characters only; literals in it are never consumed as mask literals. */
50
+ fromRaw?: boolean
51
+ }
52
+
53
+ function tester(token: MaskToken): { test: (ch: string) => boolean; transform: (ch: string) => string } {
54
+ const pattern = token instanceof RegExp ? token : token.pattern
55
+ const re = new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""))
56
+ const transform = token instanceof RegExp ? undefined : token.transform
57
+ return { test: (ch) => re.test(ch), transform: transform ?? ((ch) => ch) }
58
+ }
59
+
60
+ export function parseMask(mask: string, tokens: MaskTokens = DEFAULT_MASK_TOKENS): Slot[] {
61
+ const slots: Slot[] = []
62
+ const chars = Array.from(mask)
63
+ for (let i = 0; i < chars.length; i++) {
64
+ const ch = chars[i]!
65
+ if (ch === "\\" && i + 1 < chars.length) {
66
+ slots.push({ literal: chars[++i]! })
67
+ continue
68
+ }
69
+ const token = tokens[ch]
70
+ slots.push(token ? tester(token) : { literal: ch })
71
+ }
72
+ return slots
73
+ }
74
+
75
+ interface Run extends MaskResult {
76
+ overflow: boolean
77
+ /** Offsets in the input of the characters consumed as tokens. */
78
+ tokenOffsets: number[]
79
+ }
80
+
81
+ function run(slots: Slot[], input: string, caret: number, fromRaw: boolean): Run {
82
+ const tokenStarts: number[] = []
83
+ const tokenEnds: number[] = []
84
+ const tokenOffsets: number[] = []
85
+ let out = ""
86
+ let raw = ""
87
+ let pending = ""
88
+ let si = 0
89
+ let offset = 0
90
+ let before = 0
91
+ let overflow = false
92
+ for (const ch of input) {
93
+ const at = offset
94
+ offset += ch.length
95
+ if (si >= slots.length) {
96
+ if (slots.some((s) => "test" in s && s.test(ch))) overflow = true
97
+ continue
98
+ }
99
+ while (si < slots.length) {
100
+ const slot = slots[si]!
101
+ if ("literal" in slot) {
102
+ pending += slot.literal
103
+ si++
104
+ if (!fromRaw && slot.literal === ch) break
105
+ continue
106
+ }
107
+ if (slot.test(ch)) {
108
+ const value = slot.transform(ch)
109
+ out += pending
110
+ pending = ""
111
+ tokenStarts.push(out.length)
112
+ out += value
113
+ tokenEnds.push(out.length)
114
+ raw += value
115
+ tokenOffsets.push(at)
116
+ si++
117
+ if (fromRaw ? raw.length <= caret : at < caret) before++
118
+ }
119
+ break
120
+ }
121
+ }
122
+ const rest = slots.slice(si)
123
+ const complete = !rest.some((s) => "test" in s)
124
+ // A fully typed mask also shows its closing literals ("(000)" ends with ")").
125
+ if (complete && out) out += pending + rest.map((s) => ("literal" in s ? s.literal : "")).join("")
126
+ const at = before === 0 ? (tokenStarts[0] ?? 0) : tokenEnds[before - 1]!
127
+ return {
128
+ masked: out,
129
+ raw,
130
+ caret: complete && before === tokenEnds.length ? out.length : at,
131
+ complete,
132
+ overflow,
133
+ tokenOffsets,
134
+ }
135
+ }
136
+
137
+ function resolve(options: MaskOptions, input: string, caret: number): Run {
138
+ const tokens = options.tokens ?? DEFAULT_MASK_TOKENS
139
+ const fromRaw = !!options.fromRaw
140
+ const { mask } = options
141
+ if (typeof mask === "string") return run(parseMask(mask, tokens), input, caret, fromRaw)
142
+ if (typeof mask === "function") {
143
+ let current = mask("")
144
+ let result = run(parseMask(current, tokens), input, caret, fromRaw)
145
+ for (let i = 0; i < 4; i++) {
146
+ const next = mask(result.raw)
147
+ if (next === current) break
148
+ current = next
149
+ result = run(parseMask(current, tokens), input, caret, fromRaw)
150
+ }
151
+ return result
152
+ }
153
+ let last: Run | undefined
154
+ for (const m of mask) {
155
+ last = run(parseMask(m, tokens), input, caret, fromRaw)
156
+ if (!last.overflow) return last
157
+ }
158
+ return last ?? run([], input, caret, fromRaw)
159
+ }
160
+
161
+ /** Format `input` through a mask and map the caret into the result. */
162
+ export function applyMask(input: string, options: MaskOptions): MaskResult {
163
+ const caret = options.caret ?? (options.fromRaw ? Number.POSITIVE_INFINITY : input.length)
164
+ const { masked, raw, caret: at, complete } = resolve(options, input, caret)
165
+ return { masked, raw, caret: at, complete }
166
+ }
167
+
168
+ /**
169
+ * The value after deleting one accepted character next to a collapsed caret,
170
+ * skipping over literals, or null when there is nothing to delete that way.
171
+ */
172
+ export function maskDelete(
173
+ value: string,
174
+ caret: number,
175
+ direction: "backward" | "forward",
176
+ options: Omit<MaskOptions, "caret" | "fromRaw">,
177
+ ): MaskResult | null {
178
+ const { tokenOffsets, raw } = resolve(options, value, caret)
179
+ const index =
180
+ direction === "backward"
181
+ ? tokenOffsets.filter((o) => o < caret).length - 1
182
+ : tokenOffsets.findIndex((o) => o >= caret)
183
+ if (index < 0) return null
184
+ const chars = Array.from(raw)
185
+ chars.splice(index, 1)
186
+ return applyMask(chars.join(""), { ...options, caret: index, fromRaw: true })
187
+ }
188
+
189
+ export interface UseInputMaskOptions {
190
+ mask: MaybeRefOrGetter<MaskSpec | undefined>
191
+ tokens?: MaybeRefOrGetter<MaskTokens | undefined>
192
+ /** Called with every value the user produces. */
193
+ onChange?: (masked: string, raw: string, complete: boolean) => void
194
+ }
195
+
196
+ /**
197
+ * Bind `onBeforeInput`, `onInput` and `onCompositionEnd` to the input, and show
198
+ * `format(model).masked` as its value.
199
+ */
200
+ export function useInputMask(options: UseInputMaskOptions) {
201
+ const spec = computed(() => {
202
+ const mask = toValue(options.mask)
203
+ return mask === undefined || mask === "" ? undefined : { mask, tokens: toValue(options.tokens) }
204
+ })
205
+
206
+ function format(value: string | null | undefined): MaskResult {
207
+ const s = spec.value
208
+ const text = value ?? ""
209
+ return s ? applyMask(text, s) : { masked: text, raw: text, caret: text.length, complete: true }
210
+ }
211
+
212
+ function commit(el: HTMLInputElement, result: MaskResult): void {
213
+ if (el.value !== result.masked) el.value = result.masked
214
+ if (el.ownerDocument.activeElement === el) {
215
+ try {
216
+ el.setSelectionRange(result.caret, result.caret)
217
+ } catch {
218
+ // Input types without a selection cannot place a caret.
219
+ }
220
+ }
221
+ options.onChange?.(result.masked, result.raw, result.complete)
222
+ }
223
+
224
+ function onBeforeInput(event: InputEvent): void {
225
+ const s = spec.value
226
+ const el = event.target
227
+ if (!s || event.isComposing || !(el instanceof HTMLInputElement)) return
228
+ const backward = event.inputType === "deleteContentBackward"
229
+ if (!backward && event.inputType !== "deleteContentForward") return
230
+ const start = el.selectionStart
231
+ if (start === null || start !== el.selectionEnd) return
232
+ event.preventDefault()
233
+ const result = maskDelete(el.value, start, backward ? "backward" : "forward", s)
234
+ if (result) commit(el, result)
235
+ }
236
+
237
+ function reformat(el: HTMLInputElement): void {
238
+ const s = spec.value
239
+ if (!s) return
240
+ commit(el, applyMask(el.value, { ...s, caret: el.selectionStart ?? el.value.length }))
241
+ }
242
+
243
+ /** Attach to `input`. Composition is left alone until it ends. */
244
+ function onInput(event: Event): void {
245
+ if ((event as InputEvent).isComposing || !(event.target instanceof HTMLInputElement)) return
246
+ reformat(event.target)
247
+ }
248
+
249
+ function onCompositionEnd(event: CompositionEvent): void {
250
+ if (event.target instanceof HTMLInputElement) reformat(event.target)
251
+ }
252
+
253
+ return { active: computed(() => !!spec.value), format, onBeforeInput, onInput, onCompositionEnd }
254
+ }
@@ -0,0 +1,14 @@
1
+ import { computed, toValue, type ComputedRef, type MaybeRefOrGetter } from "vue"
2
+ import { injectConfigProviderContext } from "reka-ui"
3
+
4
+ /*
5
+ * The locale a date is written in: the component's own `locale`, else the one
6
+ * the app gave reka's ConfigProvider, else undefined, which lets Intl use the
7
+ * browser's. The calendars already followed ConfigProvider while the field
8
+ * that shows the picked date did not, so a Russian app showed a Russian
9
+ * calendar under "Oct 8, 2026".
10
+ */
11
+ export function useLocale(own?: MaybeRefOrGetter<string | undefined>): ComputedRef<string | undefined> {
12
+ const context = injectConfigProviderContext({})
13
+ return computed(() => toValue(own) || context.locale?.value || undefined)
14
+ }
@@ -0,0 +1,243 @@
1
+ import { onBeforeUnmount, onMounted, readonly, ref, toValue, type MaybeRefOrGetter, type Ref } from "vue"
2
+ import { useHotkeys, type HotkeyHelp } from "./useHotkeys"
3
+
4
+ /*
5
+ * Paint selection: press on one item's tick and sweep across the others, and
6
+ * every item the pointer passes takes the state the first one is switching
7
+ * to. Starting on an unticked item ticks the run, starting on a ticked one
8
+ * clears it, and sweeping back un-paints what the stroke covered, restoring
9
+ * the items as they were before it. Near the edge of the scrolling box the list
10
+ * scrolls on its own, so a stroke can run past what is visible.
11
+ *
12
+ * Items are found in the DOM by `data-paint-key`, in document order, so the
13
+ * run follows what the user sees (sorted, filtered, collapsed groups skipped).
14
+ * A mouse or pen paints at once. A finger holds still for HOLD_MS first, so a
15
+ * swipe through the list still scrolls it.
16
+ *
17
+ * The press itself ticks the first item, so the click the browser sends after
18
+ * the stroke is swallowed before the item's own handler sees it; otherwise the
19
+ * first item would toggle back.
20
+ *
21
+ * `onPointerDown(event, key, { hold })` makes any pointer hold first. That is
22
+ * how a list without visible ticks enters selection: a quick click on the row
23
+ * stays the row's click, a long press selects it and the same press goes on
24
+ * painting.
25
+ */
26
+ export interface UsePaintSelectOptions<K> {
27
+ /** The element that holds the items. */
28
+ root: Ref<HTMLElement | null>
29
+ selection: () => readonly K[]
30
+ set: (keys: K[]) => void
31
+ /** Turn painting off without unmounting (e.g. selection mode is off). */
32
+ enabled?: MaybeRefOrGetter<boolean>
33
+ /** Map an item's `data-paint-key` back to its key. Defaults to the string itself. */
34
+ resolve?: (raw: string) => K | undefined
35
+ /** Every key, for Ctrl+A while the focus is inside `root`. Omit to leave Ctrl+A alone. */
36
+ selectAll?: () => K[]
37
+ /** How Ctrl+A is listed in the shortcuts reference. */
38
+ help?: MaybeRefOrGetter<HotkeyHelp | undefined>
39
+ }
40
+
41
+ const HOLD_MS = 320
42
+ const SLOP_PX = 8
43
+ const EDGE_PX = 40
44
+ const SCROLL_STEP = 14
45
+
46
+ interface Stroke<K> {
47
+ keys: K[]
48
+ anchor: number
49
+ paint: boolean
50
+ before: Set<K>
51
+ x: number
52
+ y: number
53
+ scroller: HTMLElement | null
54
+ }
55
+
56
+ function scrollParent(el: HTMLElement | null): HTMLElement | null {
57
+ for (let node = el?.parentElement; node; node = node.parentElement) {
58
+ const { overflowY } = getComputedStyle(node)
59
+ if ((overflowY === "auto" || overflowY === "scroll") && node.scrollHeight > node.clientHeight) return node
60
+ }
61
+ return null
62
+ }
63
+
64
+ export function usePaintSelect<K = string>(options: UsePaintSelectOptions<K>) {
65
+ const resolve = options.resolve ?? ((raw: string) => raw as unknown as K)
66
+ let stroke: Stroke<K> | null = null
67
+ let pending: { key: K; pointerId: number; x: number; y: number; timer: ReturnType<typeof setTimeout> } | null = null
68
+ let swallowClick = false
69
+ let frame = 0
70
+ /** True while a stroke is painting, for UI that reacts to it (a tick column revealing). */
71
+ const painting = ref(false)
72
+
73
+ const enabled = () => toValue(options.enabled) ?? true
74
+
75
+ function keysInView(): K[] {
76
+ const items = options.root.value?.querySelectorAll<HTMLElement>("[data-paint-key]") ?? []
77
+ const out: K[] = []
78
+ for (const item of items) {
79
+ if (item.offsetParent === null) continue
80
+ const key = resolve(item.dataset.paintKey ?? "")
81
+ if (key !== undefined) out.push(key)
82
+ }
83
+ return out
84
+ }
85
+
86
+ function keyAt(x: number, y: number): K | undefined {
87
+ const item = document.elementFromPoint(x, y)?.closest<HTMLElement>("[data-paint-key]")
88
+ if (!item || !options.root.value?.contains(item)) return undefined
89
+ return resolve(item.dataset.paintKey ?? "")
90
+ }
91
+
92
+ function extend() {
93
+ if (!stroke) return
94
+ const key = keyAt(stroke.x, stroke.y)
95
+ const at = key === undefined ? -1 : stroke.keys.indexOf(key)
96
+ if (at < 0) return
97
+ const [lo, hi] = at < stroke.anchor ? [at, stroke.anchor] : [stroke.anchor, at]
98
+ const next = new Set(stroke.before)
99
+ for (let i = lo; i <= hi; i++) {
100
+ const k = stroke.keys[i]!
101
+ if (stroke.paint) next.add(k)
102
+ else next.delete(k)
103
+ }
104
+ options.set([...next])
105
+ }
106
+
107
+ function tick() {
108
+ frame = 0
109
+ if (!stroke) return
110
+ const box = stroke.scroller?.getBoundingClientRect() ?? { top: 0, bottom: window.innerHeight }
111
+ const delta = stroke.y > box.bottom - EDGE_PX ? SCROLL_STEP : stroke.y < box.top + EDGE_PX ? -SCROLL_STEP : 0
112
+ if (delta) {
113
+ if (stroke.scroller) stroke.scroller.scrollTop += delta
114
+ else window.scrollBy(0, delta)
115
+ extend()
116
+ }
117
+ frame = requestAnimationFrame(tick)
118
+ }
119
+
120
+ function begin(key: K, x: number, y: number) {
121
+ const keys = keysInView()
122
+ const anchor = keys.indexOf(key)
123
+ if (anchor < 0) return
124
+ const before = new Set(options.selection())
125
+ stroke = { keys, anchor, paint: !before.has(key), before, x, y, scroller: scrollParent(options.root.value) }
126
+ painting.value = true
127
+ // A sweep over text would otherwise select it, and a hold that started
128
+ // before the stroke may already have.
129
+ window.getSelection()?.removeAllRanges()
130
+ if (options.root.value) options.root.value.style.userSelect = "none"
131
+ extend()
132
+ if (!frame) frame = requestAnimationFrame(tick)
133
+ }
134
+
135
+ function cancelPending() {
136
+ if (pending) clearTimeout(pending.timer)
137
+ pending = null
138
+ }
139
+
140
+ /** Bind to `pointerdown` on the item's tick (or the whole item). */
141
+ function onPointerDown(event: PointerEvent, key: K, behaviour: { hold?: number } = {}) {
142
+ if (!enabled() || event.button !== 0) return
143
+ cancelPending()
144
+ if (behaviour.hold === undefined && event.pointerType !== "touch") {
145
+ // Keeps the press from starting a text selection or moving focus mid-stroke.
146
+ event.preventDefault()
147
+ begin(key, event.clientX, event.clientY)
148
+ return
149
+ }
150
+ const { pointerId, clientX: x, clientY: y } = event
151
+ pending = {
152
+ key,
153
+ pointerId,
154
+ x,
155
+ y,
156
+ timer: setTimeout(() => {
157
+ pending = null
158
+ begin(key, x, y)
159
+ navigator.vibrate?.(8)
160
+ }, behaviour.hold ?? HOLD_MS),
161
+ }
162
+ }
163
+
164
+ function onMove(event: PointerEvent) {
165
+ if (pending && event.pointerId === pending.pointerId) {
166
+ if (Math.hypot(event.clientX - pending.x, event.clientY - pending.y) > SLOP_PX) cancelPending()
167
+ return
168
+ }
169
+ if (!stroke) return
170
+ stroke.x = event.clientX
171
+ stroke.y = event.clientY
172
+ extend()
173
+ }
174
+
175
+ function onUp() {
176
+ cancelPending()
177
+ if (!stroke) return
178
+ stroke = null
179
+ painting.value = false
180
+ if (options.root.value) options.root.value.style.userSelect = ""
181
+ if (frame) cancelAnimationFrame(frame)
182
+ frame = 0
183
+ swallowClick = true
184
+ setTimeout(() => (swallowClick = false), 0)
185
+ }
186
+
187
+ function onClickCapture(event: MouseEvent) {
188
+ if (!swallowClick) return
189
+ swallowClick = false
190
+ event.stopPropagation()
191
+ event.preventDefault()
192
+ }
193
+
194
+ // A long press is also how a finger asks for the context menu; once it has
195
+ // turned into painting, that menu would open over the stroke.
196
+ function onContextMenu(event: Event) {
197
+ if (!stroke && !swallowClick) return
198
+ event.preventDefault()
199
+ event.stopPropagation()
200
+ }
201
+
202
+ // A finger that has started painting must not scroll the page under it.
203
+ function onTouchMove(event: TouchEvent) {
204
+ if (stroke) event.preventDefault()
205
+ }
206
+
207
+ useHotkeys(
208
+ () =>
209
+ options.selectAll && enabled()
210
+ ? [
211
+ {
212
+ key: "a",
213
+ code: "KeyA",
214
+ ctrl: true,
215
+ help: toValue(options.help),
216
+ handler: () => options.set(options.selectAll!()),
217
+ },
218
+ ]
219
+ : [],
220
+ { scope: options.root },
221
+ )
222
+
223
+ onMounted(() => {
224
+ window.addEventListener("pointermove", onMove)
225
+ window.addEventListener("pointerup", onUp)
226
+ window.addEventListener("pointercancel", onUp)
227
+ window.addEventListener("click", onClickCapture, true)
228
+ window.addEventListener("contextmenu", onContextMenu, true)
229
+ options.root.value?.addEventListener("touchmove", onTouchMove, { passive: false })
230
+ })
231
+ onBeforeUnmount(() => {
232
+ onUp()
233
+ swallowClick = false
234
+ window.removeEventListener("pointermove", onMove)
235
+ window.removeEventListener("pointerup", onUp)
236
+ window.removeEventListener("pointercancel", onUp)
237
+ window.removeEventListener("click", onClickCapture, true)
238
+ window.removeEventListener("contextmenu", onContextMenu, true)
239
+ options.root.value?.removeEventListener("touchmove", onTouchMove)
240
+ })
241
+
242
+ return { onPointerDown, painting: readonly(painting) }
243
+ }
@@ -87,6 +87,13 @@ export function useShift(
87
87
  /** Where the box last settled, so a retune has a size to ease FROM. */
88
88
  let lastHeight = 0
89
89
  let lastWidth = 0
90
+ /**
91
+ * Whether the last measurement was of a box actually laid out. An empty box
92
+ * in the flow measures 0 and is a real size to grow from (a bar or message
93
+ * appearing in it); a box under `display: none` also measures 0 but has no
94
+ * layout yet, and growing from that would animate content in from nothing.
95
+ */
96
+ let laidOut = false
90
97
  /** True while a tween owns the size, so observers don't cancel it midway. */
91
98
  let inFlight = false
92
99
  /** When the in-flight tween started, to tell a duplicate from a follow-up. */
@@ -192,6 +199,8 @@ export function useShift(
192
199
  restoreScroll(tops)
193
200
  lastHeight = toHeight
194
201
  lastWidth = toWidth
202
+ const wasLaidOut = laidOut
203
+ laidOut = node.getClientRects().length > 0
195
204
 
196
205
  // Nothing moved on either axis, so there is nothing to ease — and leaving
197
206
  // the size unset keeps the box free to size itself.
@@ -203,8 +212,8 @@ export function useShift(
203
212
  // different thing it was meant to catch: the box was not in the layout yet,
204
213
  // so there is no size to ease out of.
205
214
  const movesHeight =
206
- wantsHeight() && fromHeight !== 0 && toHeight !== fromHeight
207
- const movesWidth = wantsWidth() && fromWidth !== 0 && toWidth !== fromWidth
215
+ wantsHeight() && (fromHeight !== 0 || wasLaidOut) && toHeight !== fromHeight
216
+ const movesWidth = wantsWidth() && (fromWidth !== 0 || wasLaidOut) && toWidth !== fromWidth
208
217
  if (!movesHeight && !movesWidth) return
209
218
 
210
219
  if (movesHeight) node.style.height = `${fromHeight}px`
@@ -250,6 +259,7 @@ export function useShift(
250
259
  resizing.value = false
251
260
  lastHeight = 0
252
261
  lastWidth = 0
262
+ laidOut = false
253
263
  inFlight = false
254
264
  }
255
265
 
@@ -263,6 +273,7 @@ export function useShift(
263
273
 
264
274
  lastHeight = node.offsetHeight
265
275
  lastWidth = node.offsetWidth
276
+ laidOut = node.getClientRects().length > 0
266
277
  scrollHost = node
267
278
  node.addEventListener("scroll", rememberScroll, { capture: true, passive: true })
268
279
  if (wantsHeight()) node.style.height = `${lastHeight}px`
@@ -0,0 +1,10 @@
1
+ export interface IconCollection {
2
+ prefix: string
3
+ icons: Record<string, { body: string; width?: number; height?: number }>
4
+ aliases?: Record<string, { parent: string }>
5
+ width?: number
6
+ height?: number
7
+ }
8
+ export function namesInText(text: string): string[]
9
+ export function namesInDirs(dirs: string[]): Set<string>
10
+ export function subset(collection: IconCollection, names: Iterable<string>): IconCollection
@@ -0,0 +1,51 @@
1
+ // Shared by the kit's own icon generator and the Nuxt module's app scan: find
2
+ // "lucide:<name>" literals in source text and cut an iconify collection down to
3
+ // just those icons (aliases keep their parent).
4
+ import { readdirSync, readFileSync, statSync } from "node:fs"
5
+ import { join } from "node:path"
6
+
7
+ const NAME = /lucide:([a-z0-9]+(?:-[a-z0-9]+)*)/g
8
+ const SOURCE = /\.(vue|ts|tsx|js|jsx|mjs|md|html)$/
9
+ const SKIP = new Set(["node_modules", "dist", ".nuxt", ".output", ".git"])
10
+
11
+ export function namesInText(text) {
12
+ return [...text.matchAll(NAME)].map((m) => m[1])
13
+ }
14
+
15
+ export function namesInDirs(dirs) {
16
+ const names = new Set()
17
+ const walk = (dir) => {
18
+ let entries
19
+ try {
20
+ entries = readdirSync(dir)
21
+ } catch {
22
+ return
23
+ }
24
+ for (const entry of entries) {
25
+ if (SKIP.has(entry)) continue
26
+ const path = join(dir, entry)
27
+ if (statSync(path).isDirectory()) walk(path)
28
+ else if (SOURCE.test(entry)) for (const n of namesInText(readFileSync(path, "utf8"))) names.add(n)
29
+ }
30
+ }
31
+ for (const dir of dirs) walk(dir)
32
+ return names
33
+ }
34
+
35
+ export function subset(collection, names) {
36
+ const icons = {}
37
+ const aliases = {}
38
+ for (const name of [...names].sort()) {
39
+ if (collection.icons[name]) {
40
+ icons[name] = collection.icons[name]
41
+ continue
42
+ }
43
+ const alias = collection.aliases?.[name]
44
+ if (!alias) continue
45
+ aliases[name] = alias
46
+ if (collection.icons[alias.parent]) icons[alias.parent] = collection.icons[alias.parent]
47
+ }
48
+ const out = { prefix: collection.prefix, icons, width: collection.width, height: collection.height }
49
+ if (Object.keys(aliases).length) out.aliases = aliases
50
+ return out
51
+ }
@@ -0,0 +1,10 @@
1
+ import lucide from "@iconify-json/lucide/icons.json"
2
+ import type { IconifyJSON } from "@iconify/vue"
3
+ import { registerIconCollection } from "./icons"
4
+
5
+ /*
6
+ * Side-effect entry: `import "@leavepulse/ui/icons-all"` registers every lucide
7
+ * icon up front, for apps that render arbitrary icon names during SSR or want
8
+ * no late chunk. Costs the whole collection (~540 KB before compression).
9
+ */
10
+ registerIconCollection(lucide as IconifyJSON)