@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,41 @@
1
+ import type { CountryCode } from "libphonenumber-js/min";
2
+ export type PhoneLib = typeof import("libphonenumber-js/min");
3
+ type Examples = typeof import("libphonenumber-js/examples.mobile.json")["default"];
4
+ export declare function loadPhoneLib(): Promise<PhoneLib>;
5
+ export declare function loadPhoneExamples(): Promise<Examples>;
6
+ /** E.164 allows at most 15 digits, country code included. */
7
+ export declare const E164_MAX_DIGITS = 15;
8
+ export declare function digitsOf(text: string): string;
9
+ /** How many digits sit before `caret` in `text`. */
10
+ export declare function digitsBefore(text: string, caret: number): number;
11
+ /**
12
+ * The caret position in `formatted` that has `count` digits before it, placed
13
+ * right after the last of them so typing continues from there. Zero digits
14
+ * puts it after a leading "+".
15
+ */
16
+ export declare function caretAfterDigits(formatted: string, count: number): number;
17
+ export type PhoneProblem = "too-short" | "too-long" | "invalid" | "invalid-country";
18
+ export interface FormattedPhone {
19
+ /** What the field shows: "+380 50 123 4567". */
20
+ display: string;
21
+ /** Normalised "+380501234567", or "" when there are no digits. */
22
+ e164: string;
23
+ /** Digits that were typed past the longest number the country allows. */
24
+ dropped: number;
25
+ /** The calling code the number starts with ("380"), once recognised. */
26
+ callingCode?: string;
27
+ /** Country libphonenumber recognised from the number, when it did. */
28
+ country?: CountryCode;
29
+ possible: boolean;
30
+ valid: boolean;
31
+ type?: string;
32
+ problem?: PhoneProblem;
33
+ }
34
+ /**
35
+ * Format what was typed, dropping digits beyond what the number may hold:
36
+ * the country's own maximum once libphonenumber knows the country, the E.164
37
+ * cap otherwise. The value always starts with "+", since the field holds a
38
+ * full international number.
39
+ */
40
+ export declare function formatPhone(input: string, phone: PhoneLib | undefined): FormattedPhone;
41
+ export {};
@@ -0,0 +1,80 @@
1
+ let lib;
2
+ let examples;
3
+ function loadPhoneLib() {
4
+ lib ??= import("libphonenumber-js/min");
5
+ return lib;
6
+ }
7
+ function loadPhoneExamples() {
8
+ examples ??= import("libphonenumber-js/examples.mobile.json").then((m) => m.default);
9
+ return examples;
10
+ }
11
+ const E164_MAX_DIGITS = 15;
12
+ function digitsOf(text) {
13
+ return text.replace(/\D/g, "");
14
+ }
15
+ function digitsBefore(text, caret) {
16
+ return digitsOf(text.slice(0, caret)).length;
17
+ }
18
+ function caretAfterDigits(formatted, count) {
19
+ if (count <= 0) return formatted.startsWith("+") ? 1 : 0;
20
+ let seen = 0;
21
+ for (let i = 0; i < formatted.length; i++) {
22
+ if (/\d/.test(formatted[i])) {
23
+ seen++;
24
+ if (seen === count) return i + 1;
25
+ }
26
+ }
27
+ return formatted.length;
28
+ }
29
+ function formatPhone(input, phone) {
30
+ let digits = digitsOf(input);
31
+ let dropped = 0;
32
+ if (digits.length > E164_MAX_DIGITS) {
33
+ dropped += digits.length - E164_MAX_DIGITS;
34
+ digits = digits.slice(0, E164_MAX_DIGITS);
35
+ }
36
+ if (!digits) return { display: input.trim().startsWith("+") ? "+" : "", e164: "", dropped, possible: false, valid: false };
37
+ if (!phone) return { display: `+${digits}`, e164: `+${digits}`, dropped, possible: false, valid: false };
38
+ while (digits.length > 1 && phone.validatePhoneNumberLength(`+${digits}`) === "TOO_LONG") {
39
+ digits = digits.slice(0, -1);
40
+ dropped++;
41
+ }
42
+ const isValid = (d) => phone.parsePhoneNumberFromString(`+${d}`)?.isValid() ?? false;
43
+ while (digits.length > 1 && !isValid(digits) && isValid(digits.slice(0, -1))) {
44
+ digits = digits.slice(0, -1);
45
+ dropped++;
46
+ }
47
+ const typer = new phone.AsYouType();
48
+ let display = typer.input(`+${digits}`);
49
+ const code = typer.getCallingCode();
50
+ if (code && !/\s/.test(display.slice(code.length + 2))) {
51
+ const national = digits.slice(code.length);
52
+ display = `+${code}${national ? " " + (national.match(/.{1,3}/g) ?? []).join(" ") : ""}`;
53
+ }
54
+ const parsed = phone.parsePhoneNumberFromString(`+${digits}`);
55
+ const country = typer.getCountry() ?? parsed?.country;
56
+ const possible = parsed?.isPossible() ?? false;
57
+ const valid = parsed?.isValid() ?? false;
58
+ const length = phone.validatePhoneNumberLength(`+${digits}`);
59
+ const problem = valid ? void 0 : length === "TOO_SHORT" || length === "NOT_A_NUMBER" ? "too-short" : length === "INVALID_COUNTRY" ? "invalid-country" : length === "TOO_LONG" ? "too-long" : "invalid";
60
+ return {
61
+ display,
62
+ e164: `+${digits}`,
63
+ dropped,
64
+ callingCode: code,
65
+ country,
66
+ possible,
67
+ valid,
68
+ type: valid ? parsed?.getType() : void 0,
69
+ problem
70
+ };
71
+ }
72
+ export {
73
+ E164_MAX_DIGITS,
74
+ caretAfterDigits,
75
+ digitsBefore,
76
+ digitsOf,
77
+ formatPhone,
78
+ loadPhoneExamples,
79
+ loadPhoneLib
80
+ };
@@ -0,0 +1,36 @@
1
+ /** Hide everything except `keep`: "show only this" in one step. */
2
+ export declare function onlyValues(values: readonly unknown[], keep: readonly unknown[]): Set<unknown>;
3
+ /** Hide every value: the "None" step before ticking the few that matter. */
4
+ export declare function hideAllValues(values: readonly unknown[]): Set<unknown>;
5
+ /** Swap shown and hidden among the values the column holds. */
6
+ export declare function invertHidden(values: readonly unknown[], hidden: ReadonlySet<unknown>): Set<unknown>;
7
+ /** Show these values again, leaving the rest of the filter as it was. */
8
+ export declare function unhideValues(hidden: ReadonlySet<unknown>, values: readonly unknown[]): Set<unknown>;
9
+ /** Lower-cased and stripped of accents, so "cafe" finds "Café". */
10
+ export declare function foldText(text: string): string;
11
+ /** Every word of the query appears somewhere in the label, in any order. */
12
+ export declare function matchesQuery(label: string, query: string): boolean;
13
+ /** How many rows hold each value of a column. */
14
+ export declare function countValues<Row extends Record<string, unknown>>(rows: readonly Row[], key: string): Map<unknown, number>;
15
+ /**
16
+ * A chip's text for one column's filter: the values still shown, the first
17
+ * `listed` by name and the rest as "+N", which says more than "3 of 12".
18
+ */
19
+ export declare function summarizeShown(shownLabels: readonly string[], listed?: number): string;
20
+ export interface PersistedTableState {
21
+ sort?: {
22
+ key: string;
23
+ dir: "asc" | "desc";
24
+ } | null;
25
+ hidden?: Record<string, unknown[]>;
26
+ columns?: string[];
27
+ }
28
+ /**
29
+ * The state worth keeping across visits, as JSON. Only plain values survive:
30
+ * a Date or an object hidden in a filter cannot come back as the same value
31
+ * (the filter compares by identity), so it is left out rather than restored
32
+ * as something that matches nothing.
33
+ */
34
+ export declare function serializeTableState(state: PersistedTableState): string;
35
+ /** Read it back, dropping anything about columns the table no longer has. */
36
+ export declare function parseTableState(raw: string | null, columnKeys: readonly string[]): PersistedTableState | null;
@@ -0,0 +1,84 @@
1
+ function onlyValues(values, keep) {
2
+ const kept = new Set(keep);
3
+ return new Set(values.filter((v) => !kept.has(v)));
4
+ }
5
+ function hideAllValues(values) {
6
+ return new Set(values);
7
+ }
8
+ function invertHidden(values, hidden) {
9
+ return new Set(values.filter((v) => !hidden.has(v)));
10
+ }
11
+ function unhideValues(hidden, values) {
12
+ const next = new Set(hidden);
13
+ for (const v of values) next.delete(v);
14
+ return next;
15
+ }
16
+ function foldText(text) {
17
+ return text.normalize("NFKD").replace(new RegExp("\\p{M}", "gu"), "").toLocaleLowerCase();
18
+ }
19
+ function matchesQuery(label, query) {
20
+ const words = foldText(query).split(/\s+/).filter(Boolean);
21
+ if (!words.length) return true;
22
+ const hay = foldText(label);
23
+ return words.every((w) => hay.includes(w));
24
+ }
25
+ function countValues(rows, key) {
26
+ const counts = /* @__PURE__ */ new Map();
27
+ for (const row of rows) counts.set(row[key], (counts.get(row[key]) ?? 0) + 1);
28
+ return counts;
29
+ }
30
+ function summarizeShown(shownLabels, listed = 2) {
31
+ if (shownLabels.length <= listed) return shownLabels.join(", ");
32
+ return `${shownLabels.slice(0, listed).join(", ")} +${shownLabels.length - listed}`;
33
+ }
34
+ function isPlain(value) {
35
+ return value === null || ["string", "number", "boolean"].includes(typeof value);
36
+ }
37
+ function serializeTableState(state) {
38
+ const hidden = {};
39
+ for (const [key, values] of Object.entries(state.hidden ?? {})) {
40
+ const plain = values.filter(isPlain);
41
+ if (plain.length) hidden[key] = plain;
42
+ }
43
+ return JSON.stringify({ sort: state.sort ?? null, hidden, columns: state.columns ?? [] });
44
+ }
45
+ function parseTableState(raw, columnKeys) {
46
+ if (!raw) return null;
47
+ let data;
48
+ try {
49
+ data = JSON.parse(raw);
50
+ } catch {
51
+ return null;
52
+ }
53
+ if (!data || typeof data !== "object") return null;
54
+ const known = new Set(columnKeys);
55
+ const record = data;
56
+ const out = {};
57
+ const sort = record.sort;
58
+ if (sort === null) out.sort = null;
59
+ else if (sort && typeof sort.key === "string" && known.has(sort.key) && (sort.dir === "asc" || sort.dir === "desc")) {
60
+ out.sort = { key: sort.key, dir: sort.dir };
61
+ }
62
+ if (record.hidden && typeof record.hidden === "object") {
63
+ out.hidden = {};
64
+ for (const [key, values] of Object.entries(record.hidden)) {
65
+ if (known.has(key) && Array.isArray(values)) out.hidden[key] = values.filter(isPlain);
66
+ }
67
+ }
68
+ if (Array.isArray(record.columns)) {
69
+ out.columns = record.columns.filter((c) => typeof c === "string" && known.has(c));
70
+ }
71
+ return out;
72
+ }
73
+ export {
74
+ countValues,
75
+ foldText,
76
+ hideAllValues,
77
+ invertHidden,
78
+ matchesQuery,
79
+ onlyValues,
80
+ parseTableState,
81
+ serializeTableState,
82
+ summarizeShown,
83
+ unhideValues
84
+ };
@@ -0,0 +1,20 @@
1
+ export type TagDuplicates = "reject" | "ignore" | "allow";
2
+ /** An error message for an invalid tag; anything else accepts it. */
3
+ export type TagValidator = (tag: string, tags: readonly string[]) => string | null | undefined | void;
4
+ export type TagRejection = {
5
+ value: string;
6
+ reason: "max" | "duplicate" | "invalid";
7
+ message?: string;
8
+ };
9
+ export interface AddTagsOptions {
10
+ max?: number;
11
+ duplicates?: TagDuplicates;
12
+ validate?: TagValidator;
13
+ normalize?: (tag: string) => string;
14
+ }
15
+ export declare function splitTags(text: string, delimiter: string | RegExp | readonly string[]): string[];
16
+ export declare function addTags(current: readonly string[], candidates: readonly string[], options?: AddTagsOptions): {
17
+ tags: string[];
18
+ rejected: TagRejection[];
19
+ ignored: string[];
20
+ };
@@ -0,0 +1,37 @@
1
+ function splitTags(text, delimiter) {
2
+ const parts = delimiter instanceof RegExp ? text.split(new RegExp(delimiter.source, delimiter.flags.includes("g") ? delimiter.flags : `${delimiter.flags}g`)) : (typeof delimiter === "string" ? [delimiter] : delimiter).reduce(
3
+ (acc, d) => d ? acc.flatMap((p) => p.split(d)) : acc,
4
+ [text]
5
+ );
6
+ return parts.map((p) => p.trim()).filter(Boolean);
7
+ }
8
+ function addTags(current, candidates, options = {}) {
9
+ const tags = [...current];
10
+ const rejected = [];
11
+ const ignored = [];
12
+ const duplicates = options.duplicates ?? "reject";
13
+ for (const candidate of candidates) {
14
+ const value = options.normalize ? options.normalize(candidate) : candidate;
15
+ if (!value) continue;
16
+ if (tags.includes(value) && duplicates !== "allow") {
17
+ if (duplicates === "reject") rejected.push({ value: candidate, reason: "duplicate" });
18
+ else ignored.push(value);
19
+ continue;
20
+ }
21
+ if (options.max && tags.length >= options.max) {
22
+ rejected.push({ value: candidate, reason: "max" });
23
+ continue;
24
+ }
25
+ const message = options.validate?.(value, tags);
26
+ if (typeof message === "string" && message) {
27
+ rejected.push({ value: candidate, reason: "invalid", message });
28
+ continue;
29
+ }
30
+ tags.push(value);
31
+ }
32
+ return { tags, rejected, ignored };
33
+ }
34
+ export {
35
+ addTags,
36
+ splitTags
37
+ };
@@ -1,4 +1,5 @@
1
1
  import { type ComputedRef, type InjectionKey } from "vue";
2
+ import type { FieldNoticeOptions } from "./useFieldNotice";
2
3
  export interface FieldContext {
3
4
  id: string;
4
5
  /** Id of the visible label, for controls a `<label for>` cannot name (groups). */
@@ -7,6 +8,9 @@ export interface FieldContext {
7
8
  invalid: boolean;
8
9
  required: boolean;
9
10
  disabled: boolean;
11
+ /** Float a short note under the control without moving the layout (see useFieldNotice). */
12
+ notify?: (text: string, options?: FieldNoticeOptions) => void;
13
+ clearNotice?: () => void;
10
14
  }
11
15
  export declare const LP_FIELD: InjectionKey<ComputedRef<FieldContext>>;
12
16
  export declare function provideField(context: ComputedRef<FieldContext>): void;
@@ -0,0 +1,17 @@
1
+ import { type Ref } from "vue";
2
+ export type FieldNoticeTone = "info" | "danger";
3
+ export interface FieldNoticeOptions {
4
+ tone?: FieldNoticeTone;
5
+ /** How long it stays, in ms. 0 keeps it until `clear()`. */
6
+ duration?: number;
7
+ }
8
+ export interface FieldNotice {
9
+ message: Ref<string>;
10
+ tone: Ref<FieldNoticeTone>;
11
+ /** Bumped on every `show`, so a repeat of the same note can nudge rather than sit still. */
12
+ seq: Ref<number>;
13
+ show: (text: string, options?: FieldNoticeOptions) => void;
14
+ clear: () => void;
15
+ }
16
+ export declare const FIELD_NOTICE_MS = 2600;
17
+ export declare function useFieldNotice(defaultDuration?: number): FieldNotice;
@@ -0,0 +1,28 @@
1
+ import { ref, getCurrentScope, onScopeDispose } from "vue";
2
+ const FIELD_NOTICE_MS = 2600;
3
+ function useFieldNotice(defaultDuration = FIELD_NOTICE_MS) {
4
+ const message = ref("");
5
+ const tone = ref("info");
6
+ const seq = ref(0);
7
+ let timer;
8
+ function clear() {
9
+ if (timer) clearTimeout(timer);
10
+ timer = void 0;
11
+ message.value = "";
12
+ }
13
+ function show(text, options = {}) {
14
+ if (timer) clearTimeout(timer);
15
+ timer = void 0;
16
+ message.value = text;
17
+ tone.value = options.tone ?? "info";
18
+ seq.value++;
19
+ const duration = options.duration ?? defaultDuration;
20
+ if (duration > 0 && text) timer = setTimeout(clear, duration);
21
+ }
22
+ if (getCurrentScope()) onScopeDispose(clear);
23
+ return { message, tone, seq, show, clear };
24
+ }
25
+ export {
26
+ FIELD_NOTICE_MS,
27
+ useFieldNotice
28
+ };
@@ -0,0 +1,53 @@
1
+ import { type ComputedRef, type MaybeRefOrGetter, type Ref, type UnwrapRef } from "vue";
2
+ export type FormErrorMessage = string | null | undefined | void | false;
3
+ export type FieldRule<V, T> = (value: V, values: T) => FormErrorMessage | Promise<FormErrorMessage>;
4
+ export type FormRules<T> = {
5
+ [K in keyof T]?: FieldRule<T[K], T> | FieldRule<T[K], T>[];
6
+ };
7
+ export type FormErrors<T> = Partial<Record<keyof T | "form", string>>;
8
+ export interface UseFormOptions<T extends object> {
9
+ initial: MaybeRefOrGetter<T>;
10
+ rules?: FormRules<T>;
11
+ /** Cross-field checks; return messages by field, or under `form` for the whole form. */
12
+ validate?: (values: T) => FormErrors<T> | void | Promise<FormErrors<T> | void>;
13
+ }
14
+ export interface FieldBinding<V> {
15
+ modelValue: V;
16
+ "onUpdate:modelValue": (value: V) => void;
17
+ onBlur: () => void;
18
+ error: string | undefined;
19
+ invalid: boolean;
20
+ }
21
+ export interface UseForm<T extends object> {
22
+ values: UnwrapRef<T>;
23
+ /** Errors as currently computed, shown or not. */
24
+ errors: ComputedRef<FormErrors<T>>;
25
+ /** Errors the user should see now. */
26
+ visibleErrors: ComputedRef<FormErrors<T>>;
27
+ touched: Partial<Record<keyof T, boolean>>;
28
+ dirty: ComputedRef<boolean>;
29
+ valid: ComputedRef<boolean>;
30
+ validating: Ref<boolean>;
31
+ submitting: Ref<boolean>;
32
+ submitCount: Ref<number>;
33
+ isDirty: (name: keyof T) => boolean;
34
+ /** Props for a kit control: `<LpInput v-bind="form.field('email')" />`. */
35
+ field: <K extends keyof T>(name: K) => FieldBinding<T[K]>;
36
+ set: <K extends keyof T>(name: K, value: T[K]) => void;
37
+ setValues: (values: Partial<T>) => void;
38
+ touch: (name: keyof T) => void;
39
+ /** Run the checks for one field, or all of them; resolves to whether it passed. */
40
+ validate: (name?: keyof T) => Promise<boolean>;
41
+ /** Errors from elsewhere, a server reply most often. Cleared field by field on edit. */
42
+ setErrors: (errors: FormErrors<T>) => void;
43
+ /** Back to `values` (or the current initial), which also become the new baseline. */
44
+ reset: (values?: T) => void;
45
+ /**
46
+ * Wrap the submit handler: touches everything, validates, and calls `fn`
47
+ * only when valid and not already submitting. A successful `fn` makes the
48
+ * submitted values the new baseline.
49
+ */
50
+ handleSubmit: (fn: (values: T) => unknown) => (event?: Event) => Promise<void>;
51
+ }
52
+ export declare function deepEqual(a: unknown, b: unknown): boolean;
53
+ export declare function useForm<T extends object>(options: UseFormOptions<T>): UseForm<T>;
@@ -0,0 +1,207 @@
1
+ import { toValue, reactive, ref, computed, watch, toRaw } from "vue";
2
+ function clone(value) {
3
+ try {
4
+ return structuredClone(toRaw(value));
5
+ } catch {
6
+ return JSON.parse(JSON.stringify(value));
7
+ }
8
+ }
9
+ function deepEqual(a, b) {
10
+ if (Object.is(a, b)) return true;
11
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
12
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
13
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
14
+ const ka = Object.keys(a);
15
+ const kb = Object.keys(b);
16
+ if (ka.length !== kb.length) return false;
17
+ return ka.every((k) => deepEqual(a[k], b[k]));
18
+ }
19
+ function message(result) {
20
+ return typeof result === "string" && result ? result : void 0;
21
+ }
22
+ function useForm(options) {
23
+ let baseline = clone(toValue(options.initial));
24
+ const values = reactive(clone(baseline));
25
+ const plain = () => values;
26
+ const errors = reactive({});
27
+ const external = reactive({});
28
+ const touched = reactive({});
29
+ const validating = ref(false);
30
+ const submitting = ref(false);
31
+ const submitCount = ref(0);
32
+ const runs = /* @__PURE__ */ new Map();
33
+ const baseVersion = ref(0);
34
+ const keys = () => Object.keys(plain());
35
+ const dirty = computed(() => {
36
+ void baseVersion.value;
37
+ return !deepEqual(plain(), baseline);
38
+ });
39
+ function isDirty(name) {
40
+ void baseVersion.value;
41
+ return !deepEqual(plain()[name], baseline[name]);
42
+ }
43
+ async function checkField(name) {
44
+ const rule = options.rules?.[name];
45
+ const list = Array.isArray(rule) ? rule : rule ? [rule] : [];
46
+ for (const r of list) {
47
+ const found = message(await r(plain()[name], plain()));
48
+ if (found) return found;
49
+ }
50
+ return void 0;
51
+ }
52
+ async function runField(name) {
53
+ const run = (runs.get(name) ?? 0) + 1;
54
+ runs.set(name, run);
55
+ const found = await checkField(name);
56
+ if (runs.get(name) !== run) return !errors[name];
57
+ if (found) errors[name] = found;
58
+ else delete errors[name];
59
+ return !found && !external[name];
60
+ }
61
+ async function runForm() {
62
+ if (!options.validate) return true;
63
+ const run = (runs.get("form") ?? 0) + 1;
64
+ runs.set("form", run);
65
+ const found = await options.validate(plain()) ?? {};
66
+ if (runs.get("form") !== run) return true;
67
+ let ok = true;
68
+ for (const [name, text] of Object.entries(found)) {
69
+ if (!text) continue;
70
+ ok = false;
71
+ if (!errors[name]) errors[name] = text;
72
+ }
73
+ return ok;
74
+ }
75
+ async function validate(name) {
76
+ validating.value = true;
77
+ try {
78
+ if (name !== void 0) return await runField(name);
79
+ if (errors.form) delete errors.form;
80
+ const fields = await Promise.all(keys().map(runField));
81
+ const form = await runForm();
82
+ return fields.every(Boolean) && form && !Object.values(external).some(Boolean);
83
+ } finally {
84
+ validating.value = false;
85
+ }
86
+ }
87
+ function touch(name) {
88
+ touched[name] = true;
89
+ void validate(name);
90
+ }
91
+ function set(name, value) {
92
+ values[name] = value;
93
+ }
94
+ function setValues(next) {
95
+ for (const [k, v] of Object.entries(next)) set(k, v);
96
+ }
97
+ function clearState() {
98
+ for (const k of Object.keys(errors)) delete errors[k];
99
+ for (const k of Object.keys(external)) delete external[k];
100
+ for (const k of Object.keys(touched)) delete touched[k];
101
+ runs.clear();
102
+ submitCount.value = 0;
103
+ }
104
+ function adopt(next) {
105
+ baseline = clone(next);
106
+ const target = values;
107
+ for (const k of Object.keys(target)) if (!(k in baseline)) delete target[k];
108
+ Object.assign(target, clone(baseline));
109
+ baseVersion.value++;
110
+ }
111
+ function reset(next) {
112
+ adopt(next ?? toValue(options.initial));
113
+ clearState();
114
+ }
115
+ function setErrors(next) {
116
+ for (const [k, v] of Object.entries(next)) {
117
+ if (v) external[k] = v;
118
+ else delete external[k];
119
+ }
120
+ }
121
+ watch(
122
+ () => toValue(options.initial),
123
+ (next) => {
124
+ if (!dirty.value && !deepEqual(next, baseline)) adopt(next);
125
+ },
126
+ { deep: true }
127
+ );
128
+ let snapshot = clone(plain());
129
+ watch(
130
+ plain,
131
+ () => {
132
+ const now = clone(plain());
133
+ for (const name of Object.keys(now)) {
134
+ if (deepEqual(now[name], snapshot[name])) continue;
135
+ if (external[name]) delete external[name];
136
+ if (touched[name] || submitCount.value) void validate(name);
137
+ }
138
+ snapshot = now;
139
+ },
140
+ { deep: true }
141
+ );
142
+ const merged = computed(() => ({ ...errors, ...external }));
143
+ const visibleErrors = computed(() => {
144
+ const all = merged.value;
145
+ if (submitCount.value) return all;
146
+ const shown = {};
147
+ for (const [k, v] of Object.entries(all)) {
148
+ if (touched[k] || external[k]) shown[k] = v;
149
+ }
150
+ return shown;
151
+ });
152
+ const valid = computed(() => !Object.values(merged.value).some(Boolean));
153
+ function field(name) {
154
+ const error = visibleErrors.value[name];
155
+ return {
156
+ modelValue: values[name],
157
+ "onUpdate:modelValue": (value) => set(name, value),
158
+ onBlur: () => touch(name),
159
+ error,
160
+ invalid: !!error
161
+ };
162
+ }
163
+ function handleSubmit(fn) {
164
+ return async (event) => {
165
+ event?.preventDefault();
166
+ if (submitting.value) return;
167
+ submitting.value = true;
168
+ try {
169
+ submitCount.value++;
170
+ for (const name of keys()) touched[name] = true;
171
+ if (!await validate()) return;
172
+ const submitted = clone(plain());
173
+ await fn(submitted);
174
+ if (deepEqual(plain(), submitted)) {
175
+ baseline = submitted;
176
+ baseVersion.value++;
177
+ }
178
+ } finally {
179
+ submitting.value = false;
180
+ }
181
+ };
182
+ }
183
+ return {
184
+ values,
185
+ errors: merged,
186
+ visibleErrors,
187
+ touched,
188
+ dirty,
189
+ valid,
190
+ validating,
191
+ submitting,
192
+ submitCount,
193
+ isDirty,
194
+ field,
195
+ set,
196
+ setValues,
197
+ touch,
198
+ validate,
199
+ setErrors,
200
+ reset,
201
+ handleSubmit
202
+ };
203
+ }
204
+ export {
205
+ deepEqual,
206
+ useForm
207
+ };