@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
@@ -22,6 +22,7 @@ import { computed, useId } from "vue"
22
22
  import { usePillTransition } from "../composables/usePillTransition"
23
23
  import LpIcon from "./LpIcon.vue"
24
24
  import { useMergedAttrs } from "../composables/useMergedClass"
25
+ import { useLabels } from "../composables/useLpConfig"
25
26
 
26
27
  const props = withDefaults(
27
28
  defineProps<{
@@ -35,22 +36,37 @@ const props = withDefaults(
35
36
  /** Pages shown on each side of the current one in the inner run. */
36
37
  siblings?: number
37
38
  disabled?: boolean
39
+ labels?: Partial<Record<"nav" | "previous" | "next", string>>
38
40
  }>(),
39
41
  { page: 1, pageSize: 10, siblings: 1 },
40
42
  )
41
43
 
44
+ const text = useLabels(
45
+ "pagination",
46
+ { nav: "Pagination", previous: "Previous page", next: "Next page" },
47
+ () => props.labels,
48
+ )
49
+
42
50
  const emit = defineEmits<{
43
51
  (e: "update:page", value: number): void
44
52
  (e: "change", value: number): void
45
53
  }>()
46
54
 
47
55
  const count = computed(() => {
48
- if (props.pageCount != null) return Math.max(1, props.pageCount)
49
- if (props.total != null) return Math.max(1, Math.ceil(props.total / props.pageSize))
50
- return 1
56
+ // Guarded so a zero page size or a total still loading never yields an
57
+ // "Infinity" or "NaN" page.
58
+ const n =
59
+ props.pageCount != null
60
+ ? props.pageCount
61
+ : props.total != null && props.pageSize > 0
62
+ ? Math.ceil(props.total / props.pageSize)
63
+ : 1
64
+ return Number.isFinite(n) ? Math.max(1, Math.floor(n)) : 1
51
65
  })
52
66
 
53
- const current = computed(() => Math.min(Math.max(1, props.page), count.value))
67
+ const current = computed(() =>
68
+ Number.isFinite(props.page) ? Math.min(Math.max(1, Math.floor(props.page)), count.value) : 1,
69
+ )
54
70
 
55
71
  // A fixed run of page slots. Each slot is either a real page or a gap ("…").
56
72
  // The number of slots is constant for a given `count`, so the row width is
@@ -122,12 +138,12 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
122
138
  <template>
123
139
  <nav
124
140
  :class="rootClass"
125
- v-bind="rest" aria-label="Pagination">
141
+ v-bind="rest" :aria-label="text.nav">
126
142
  <button
127
143
  type="button"
128
144
  :class="[navBase, navArrow]"
129
145
  :disabled="disabled || current === 1"
130
- aria-label="Previous page"
146
+ :aria-label="text.previous"
131
147
  @click="go(current - 1)"
132
148
  >
133
149
  <LpIcon name="lucide:chevron-left" :size="16" />
@@ -171,7 +187,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
171
187
  type="button"
172
188
  :class="[navBase, navArrow]"
173
189
  :disabled="disabled || current === count"
174
- aria-label="Next page"
190
+ :aria-label="text.next"
175
191
  @click="go(current + 1)"
176
192
  >
177
193
  <LpIcon name="lucide:chevron-right" :size="16" />
@@ -6,6 +6,7 @@ export default { inheritAttrs: false }
6
6
  // Password field = LpInput + a show/hide toggle in its trailing slot.
7
7
  // No duplicated field styling — the shell lives in LpInput.
8
8
  import { ref } from "vue"
9
+ import { useLabels } from "../composables/useLpConfig"
9
10
  import LpIcon from "./LpIcon.vue"
10
11
  import LpInput from "./LpInput.vue"
11
12
 
@@ -23,6 +24,7 @@ defineProps<{
23
24
  }>()
24
25
  defineEmits<{ (e: "update:modelValue", value: string): void }>()
25
26
 
27
+ const text = useLabels("passwordInput", { show: "Show password", hide: "Hide password" })
26
28
  const visible = ref(false)
27
29
  const inner = ref<InstanceType<typeof LpInput> | null>(null)
28
30
  defineExpose({
@@ -35,6 +37,7 @@ defineExpose({
35
37
  <template>
36
38
  <LpInput
37
39
  ref="inner"
40
+ v-bind="$attrs"
38
41
  :model-value="modelValue"
39
42
  :type="visible ? 'text' : 'password'"
40
43
  :placeholder="placeholder"
@@ -51,7 +54,7 @@ defineExpose({
51
54
  <button
52
55
  type="button"
53
56
  class="group rounded-md p-1 text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:text-ink"
54
- :aria-label="visible ? 'Hide password' : 'Show password'"
57
+ :aria-label="visible ? text.hide : text.show"
55
58
  @click="visible = !visible"
56
59
  >
57
60
  <LpIcon
@@ -13,8 +13,14 @@ export default { inheritAttrs: false }
13
13
  * match wins). The model is the full string ("+380501234567"); a `change` event
14
14
  * also carries the parsed { country, dialCode, number } for forms that want it.
15
15
  *
16
- * Deliberately NOT a full phone validator (no libphonenumber) — it stays free
17
- * text so any number is allowed; `restrict` only blocks obviously non-phone keys.
16
+ * Numbers are formatted as they are typed ("+380 50 123 4567") with
17
+ * libphonenumber-js, loaded on first use (see phone.ts). Digits past the
18
+ * longest number the country allows are dropped with a passing note, and a
19
+ * number that cannot be right is marked once the field is left, with the reason
20
+ * floating under it. v-model stays the formatted text the field shows; `change`
21
+ * also carries `e164` ("+380501234567"), `possible` and `valid`, which
22
+ * is what a form should store. Until the library arrives the field takes digits
23
+ * as before, unformatted.
18
24
  */
19
25
  import {
20
26
  ComboboxAnchor,
@@ -27,11 +33,24 @@ import {
27
33
  ComboboxTrigger,
28
34
  ComboboxViewport,
29
35
  } from "reka-ui"
30
- import { computed, onMounted, ref } from "vue"
36
+ import { computed, onMounted, ref, shallowRef, useAttrs } from "vue"
37
+ import { useField, useFieldControl } from "../composables/useField"
38
+ import { useFieldNotice, type FieldNoticeOptions } from "../composables/useFieldNotice"
31
39
  import { useInputFilter } from "../composables/useInputFilter"
32
- import { type Country, flagEmoji, loadCountries, matchCountryByValue } from "./countries"
40
+ import { useLabels } from "../composables/useLpConfig"
41
+ import { type Country, filterCountries, flagEmoji, loadCountries, matchCountryByValue } from "./countries"
33
42
  import { OPTION_ITEM, POPOVER_PANEL } from "./dropdown"
43
+ import LpFieldNotice from "./LpFieldNotice.vue"
34
44
  import LpIcon from "./LpIcon.vue"
45
+ import {
46
+ caretAfterDigits,
47
+ digitsBefore,
48
+ digitsOf,
49
+ formatPhone,
50
+ loadPhoneExamples,
51
+ loadPhoneLib,
52
+ type PhoneLib,
53
+ } from "./phone"
35
54
  import { useMergedAttrs } from "../composables/useMergedClass"
36
55
 
37
56
  export interface PhoneDetail {
@@ -41,6 +60,17 @@ export interface PhoneDetail {
41
60
  dialCode: string
42
61
  /** National part (digits after the dial code). */
43
62
  number: string
63
+ /** Normalised "+380501234567"; empty when there are no digits. */
64
+ e164: string
65
+ /** The length fits the country. False until the formatter has loaded. */
66
+ possible: boolean
67
+ /** A real number for the country. False until the formatter has loaded. */
68
+ valid: boolean
69
+ /**
70
+ * "MOBILE", "FIXED_LINE"… when known. The `min` metadata the field loads does
71
+ * not carry number types, so this stays undefined unless that changes.
72
+ */
73
+ type?: string
44
74
  }
45
75
 
46
76
  const props = withDefaults(
@@ -51,6 +81,7 @@ const props = withDefaults(
51
81
  only?: string[]
52
82
  /** Default country (ISO2) used when the field is empty. */
53
83
  defaultCountry?: string
84
+ /** Unset shows an example number for the active country. */
54
85
  placeholder?: string
55
86
  disabled?: boolean
56
87
  invalid?: boolean
@@ -59,7 +90,7 @@ const props = withDefaults(
59
90
  searchPlaceholder?: string
60
91
  }>(),
61
92
  {
62
- placeholder: "Phone number",
93
+ placeholder: undefined,
63
94
  size: "md",
64
95
  emptyText: "No country",
65
96
  searchPlaceholder: "Search country…",
@@ -74,7 +105,9 @@ const emit = defineEmits<{
74
105
  // Phone keys only: digits, "+", spaces and the usual separators. Free typing of
75
106
  // "+code" stays possible; letters and junk are blocked at the source.
76
107
  const PHONE_CHARS = /[\d+()\-\s]/
77
- const { onBeforeInput, onPaste } = useInputFilter({ restrict: () => PHONE_CHARS })
108
+ const { onBeforeInput, onInput: filterInput, onCompositionEnd, onPaste } = useInputFilter({
109
+ restrict: () => PHONE_CHARS,
110
+ })
78
111
 
79
112
  // The dial-code table is code-split; it's empty until the lazy chunk resolves.
80
113
  // Everything degrades gracefully meanwhile: digits still parse, the flag shows a
@@ -100,21 +133,19 @@ const activeCountry = computed<Country | undefined>(
100
133
  const open = ref(false)
101
134
  const query = ref("")
102
135
 
103
- const filtered = computed(() => {
104
- const q = query.value.trim().toLowerCase()
105
- if (!q) return countries.value
106
- return countries.value.filter(
107
- (c) => c.name.toLowerCase().includes(q) || c.dial.includes(q.replace(/\D/g, "")),
108
- )
109
- })
136
+ const filtered = computed(() => filterCountries(countries.value, query.value))
137
+
138
+ const phoneLib = shallowRef<PhoneLib>()
110
139
 
111
140
  function parse(value: string): PhoneDetail {
112
- const country = matchCountryByValue(value, countries.value)
113
- const digits = value.replace(/[^\d]/g, "")
114
- if (country) {
115
- return { country, dialCode: country.dial, number: digits.slice(country.dial.length) }
116
- }
117
- return { country: undefined, dialCode: "", number: digits }
141
+ const formatted = formatPhone(value, phoneLib.value)
142
+ const country =
143
+ (formatted.country && countries.value.find((c) => c.iso2 === formatted.country)) ??
144
+ matchCountryByValue(value, countries.value)
145
+ const digits = digitsOf(formatted.e164)
146
+ const base = { e164: formatted.e164, possible: formatted.possible, valid: formatted.valid, type: formatted.type }
147
+ if (country) return { ...base, country, dialCode: country.dial, number: digits.slice(country.dial.length) }
148
+ return { ...base, country: undefined, dialCode: "", number: digits }
118
149
  }
119
150
 
120
151
  function setValue(value: string) {
@@ -122,8 +153,85 @@ function setValue(value: string) {
122
153
  emit("change", parse(value))
123
154
  }
124
155
 
156
+ /*
157
+ * Notes float under the field: the LpFormField around it shows them when there
158
+ * is one, otherwise the field shows its own.
159
+ */
160
+ const notice = useFieldNotice()
161
+ const outerField = useField()
162
+ const ownNotice = computed(() => !outerField?.value.notify)
163
+ function say(message: string, options?: FieldNoticeOptions) {
164
+ if (outerField?.value.notify) outerField.value.notify(message, options)
165
+ else notice.show(message, options)
166
+ }
167
+ function unsay() {
168
+ if (outerField?.value.clearNotice) outerField.value.clearNotice()
169
+ else notice.clear()
170
+ }
171
+
172
+ /** Shown invalid after the field was left with a number that cannot be right. */
173
+ const flagged = ref(false)
174
+ const countryName = (iso?: string) =>
175
+ (iso && countries.value.find((c) => c.iso2 === iso)?.name) || ""
176
+
177
+ /*
178
+ * Reformat after every edit and put the caret back after the same number of
179
+ * digits it followed, so spaces the formatter adds or moves never push it
180
+ * around.
181
+ */
125
182
  function onInput(e: Event) {
126
- setValue((e.target as HTMLInputElement).value)
183
+ filterInput(e)
184
+ const el = e.target as HTMLInputElement
185
+ const before = digitsBefore(el.value, el.selectionStart ?? el.value.length)
186
+ const result = formatPhone(el.value, phoneLib.value)
187
+ if (el.value !== result.display) {
188
+ el.value = result.display
189
+ const caret = caretAfterDigits(result.display, Math.min(before, digitsOf(result.display).length))
190
+ el.setSelectionRange(caret, caret)
191
+ }
192
+ if (flagged.value) {
193
+ flagged.value = false
194
+ unsay()
195
+ }
196
+ if (result.dropped) {
197
+ const name = countryName(result.country)
198
+ say(
199
+ (name ? text.value.tooLongFor : text.value.tooLong)
200
+ .replace("{country}", name)
201
+ .replace("{max}", String(digitsOf(result.e164).length - (result.callingCode?.length ?? 0))),
202
+ )
203
+ }
204
+ setValue(result.display)
205
+ }
206
+
207
+ /*
208
+ * Backspace or Delete next to a space the formatter put there would only remove
209
+ * the space, and formatting would put it straight back: the key would seem
210
+ * dead. The digit beside it goes instead.
211
+ */
212
+ function onKeyEdit(e: InputEvent) {
213
+ const el = e.target as HTMLInputElement
214
+ const start = el.selectionStart ?? 0
215
+ const end = el.selectionEnd ?? 0
216
+ const backward = e.inputType === "deleteContentBackward"
217
+ const forward = e.inputType === "deleteContentForward"
218
+ if ((!backward && !forward) || start !== end) return
219
+ const value = el.value
220
+ const at = backward ? start - 1 : start
221
+ if (at < 0 || at >= value.length || /\d/.test(value[at]!)) return
222
+ let i = at
223
+ while (i >= 0 && i < value.length && !/\d/.test(value[i]!)) i += backward ? -1 : 1
224
+ if (i < 0 || i >= value.length) return
225
+ e.preventDefault()
226
+ el.value = value.slice(0, i) + value.slice(i + 1)
227
+ const caret = backward ? i : start
228
+ el.setSelectionRange(caret, caret)
229
+ el.dispatchEvent(new Event("input", { bubbles: true }))
230
+ }
231
+
232
+ function onBeforeEdit(e: InputEvent) {
233
+ onKeyEdit(e)
234
+ if (!e.defaultPrevented) onBeforeInput(e)
127
235
  }
128
236
 
129
237
  /**
@@ -139,16 +247,60 @@ function chooseCountry(c: Country) {
139
247
  query.value = ""
140
248
  }
141
249
 
142
- // Load the dial-code table on mount (lazy chunk), then seed the default
143
- // country's "+code" into an empty field so the user starts with the prefix in
144
- // place — but never clobber a value the consumer already provided.
145
250
  onMounted(async () => {
146
- allCountries.value = await loadCountries()
147
- if (!props.modelValue && fallback.value) {
148
- emit("update:modelValue", `+${fallback.value.dial} `)
251
+ const [list, phone] = await Promise.all([loadCountries(), loadPhoneLib().catch(() => undefined)])
252
+ allCountries.value = list
253
+ phoneLib.value = phone
254
+ // A value set before the formatter arrived is shown formatted from now on.
255
+ if (phone && props.modelValue) {
256
+ const display = formatPhone(props.modelValue, phone).display
257
+ if (display !== props.modelValue) setValue(display)
258
+ }
259
+ if (props.placeholder === undefined) examplesFor.value = await loadPhoneExamples().catch(() => undefined)
260
+ })
261
+
262
+ /* An example number for the active country, when the consumer gave no text. */
263
+ const examplesFor = shallowRef<Awaited<ReturnType<typeof loadPhoneExamples>>>()
264
+ const shownPlaceholder = computed(() => {
265
+ if (props.placeholder !== undefined) return props.placeholder
266
+ const phone = phoneLib.value
267
+ const iso = activeCountry.value?.iso2
268
+ if (phone && examplesFor.value && iso) {
269
+ try {
270
+ return phone.getExampleNumber(iso as never, examplesFor.value)?.formatInternational() ?? text.value.placeholder
271
+ } catch {
272
+ return text.value.placeholder
273
+ }
149
274
  }
275
+ return text.value.placeholder
150
276
  })
151
277
 
278
+ // The default country's "+code" is put in place when the user enters an empty
279
+ // field, and taken back out if they leave without typing a number. Seeding it
280
+ // on mount instead made every untouched optional field submit "+380 ", pass
281
+ // `required`, and read as edited.
282
+ const seeded = computed(() => (fallback.value ? `+${fallback.value.dial} ` : ""))
283
+ function onFocus() {
284
+ if (!props.modelValue && seeded.value) setValue(seeded.value)
285
+ }
286
+ function onBlur() {
287
+ if (seeded.value && props.modelValue?.trim() === seeded.value.trim()) {
288
+ setValue("")
289
+ return
290
+ }
291
+ const result = phoneLib.value && props.modelValue ? formatPhone(props.modelValue, phoneLib.value) : undefined
292
+ if (!result || result.valid || digitsOf(result.e164).length <= (activeCountry.value?.dial.length ?? 0)) return
293
+ flagged.value = true
294
+ const name = countryName(result.country) || activeCountry.value?.name || ""
295
+ const reason =
296
+ result.problem === "too-short"
297
+ ? (name ? text.value.tooShortFor : text.value.tooShort)
298
+ : result.problem === "invalid-country"
299
+ ? text.value.unknownCountry
300
+ : text.value.invalidNumber
301
+ say(reason.replace("{country}", name), { tone: "danger", duration: 0 })
302
+ }
303
+
152
304
  const sizeClass = {
153
305
  sm: "h-(--size-control-sm) text-xs",
154
306
  md: "h-(--size-control-md) text-sm",
@@ -157,23 +309,47 @@ const sizeClass = {
157
309
 
158
310
  // The control is full-width with its own frame — the two things a consumer
159
311
  // placing it in a narrow column has to change, so `class="w-48"` must win.
312
+ // style lays out the frame; every other attr (name, autocomplete, @blur…)
313
+ // belongs to the number <input>.
314
+ const attrs = useAttrs()
315
+ const field = useFieldControl(() => ({
316
+ wrapped: false,
317
+ id: typeof attrs.id === "string" ? attrs.id : undefined,
318
+ invalid: props.invalid || flagged.value,
319
+ disabled: props.disabled,
320
+ }))
321
+ const text = useLabels("phoneInput", {
322
+ selectCountry: "Select country",
323
+ placeholder: "Phone number",
324
+ tooLong: "A phone number has at most {max} digits",
325
+ tooLongFor: "Numbers in {country} have at most {max} digits after the code",
326
+ tooShort: "This number is too short",
327
+ tooShortFor: "Too short for {country}",
328
+ invalidNumber: "Not a valid phone number",
329
+ unknownCountry: "Unknown country code",
330
+ })
331
+ const noticeId = `${field.value.id ?? "lp-phone"}-notice`
160
332
  const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
161
333
  [
162
- "flex w-full items-stretch rounded-control border bg-surface-soft text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring",
334
+ "relative flex w-full items-stretch rounded-control border bg-surface-soft text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring",
163
335
  sizeClass[props.size],
164
- props.invalid
336
+ field.value.invalid
165
337
  ? "border-danger focus-within:border-danger focus-within:ring-danger-soft"
166
338
  : "border-line focus-within:border-brand",
167
- props.disabled ? "cursor-not-allowed opacity-55" : "",
339
+ field.value.disabled ? "cursor-not-allowed opacity-55" : "",
168
340
  ].join(" "),
169
341
  )
342
+ const inputAttrs = computed(() => {
343
+ const { style: _s, ...others } = rest.value
344
+ return others
345
+ })
170
346
  </script>
171
347
 
172
348
  <template>
173
349
  <div
174
350
  data-lp-ring-owner
175
351
  :class="rootClass"
176
- v-bind="rest"
352
+ :style="attrs.style as never"
177
353
  >
178
354
  <!-- reset-search-term-on-{select,blur}: as in LpSelect, the trigger shows
179
355
  the selection and the box only filters, so reka seeding the chosen value
@@ -184,15 +360,15 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
184
360
  :ignore-filter="true"
185
361
  :reset-search-term-on-select="false"
186
362
  :reset-search-term-on-blur="false"
187
- :disabled="disabled"
363
+ :disabled="field.disabled"
188
364
  class="flex"
189
365
  >
190
366
  <ComboboxAnchor as-child>
191
367
  <ComboboxTrigger
192
368
  type="button"
193
- :disabled="disabled"
194
- class="flex shrink-0 items-center gap-1 rounded-l-control border-r border-line px-2.5 outline-none hover:bg-white/[0.04] data-[state=open]:bg-white/[0.04]"
195
- aria-label="Select country"
369
+ :disabled="field.disabled"
370
+ class="flex shrink-0 items-center gap-1 rounded-l-control border-r border-line px-2.5 outline-none hover:bg-ink/5 data-[state=open]:bg-ink/5"
371
+ :aria-label="text.selectCountry"
196
372
  >
197
373
  <span class="text-base leading-none">{{ activeCountry ? flagEmoji(activeCountry.iso2) : "🌐" }}</span>
198
374
  <LpIcon
@@ -205,6 +381,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
205
381
 
206
382
  <ComboboxPortal>
207
383
  <ComboboxContent
384
+ :collision-padding="8"
208
385
  position="popper"
209
386
  align="start"
210
387
  :side-offset="6"
@@ -247,17 +424,25 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
247
424
  </ComboboxRoot>
248
425
 
249
426
  <input
250
- :value="modelValue"
251
- :placeholder="placeholder"
252
- :disabled="disabled"
253
- :aria-invalid="invalid || undefined"
254
427
  type="tel"
255
428
  inputmode="tel"
256
429
  autocomplete="tel"
430
+ v-bind="inputAttrs"
431
+ :id="field.id"
432
+ :value="modelValue"
433
+ :placeholder="shownPlaceholder"
434
+ :disabled="field.disabled"
435
+ :required="field.required || undefined"
436
+ :aria-invalid="field.invalid || undefined"
437
+ :aria-describedby="[field.describedBy, notice.message.value ? noticeId : ''].filter(Boolean).join(' ') || undefined"
257
438
  class="lp-autofill min-w-0 flex-1 self-stretch bg-transparent px-3 outline-none placeholder:text-muted"
258
- @beforeinput="onBeforeInput"
439
+ @beforeinput="onBeforeEdit"
259
440
  @paste="onPaste"
441
+ @compositionend="onCompositionEnd"
260
442
  @input="onInput"
443
+ @focus="onFocus"
444
+ @blur="onBlur"
261
445
  />
446
+ <LpFieldNotice v-if="ownNotice" :notice="notice" :id="noticeId" />
262
447
  </div>
263
448
  </template>