@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
@@ -16,18 +16,52 @@ export default { inheritAttrs: false }
16
16
  * eased box, so the thing being typed into never moves.
17
17
  */
18
18
  import { Label } from "reka-ui"
19
- import { useId } from "vue"
19
+ import { computed, useId, useSlots } from "vue"
20
+ import LpFieldNotice from "./LpFieldNotice.vue"
20
21
  import LpShift from "./LpShift.vue"
22
+ import { fieldMessageId, provideField } from "../composables/useField"
23
+ import { useFieldNotice } from "../composables/useFieldNotice"
21
24
  import { useMergedAttrs } from "../composables/useMergedClass"
22
25
 
23
- defineProps<{
26
+ const props = defineProps<{
24
27
  label?: string
25
28
  hint?: string
26
29
  error?: string
27
30
  required?: boolean
31
+ /** Id of the control; generated when unset. The label points at it. */
32
+ id?: string
33
+ invalid?: boolean
34
+ disabled?: boolean
28
35
  }>()
29
36
 
30
- const id = useId()
37
+ const generated = useId()
38
+ const id = computed(() => props.id ?? generated)
39
+ const messageId = computed(() => fieldMessageId(id.value))
40
+ const slots = useSlots()
41
+ const hasMessage = computed(() => !!props.error || !!props.hint || !!slots.hint)
42
+ const notice = useFieldNotice()
43
+ const noticeId = computed(() => `${id.value}-notice`)
44
+
45
+ // The control inside reads its id, description and states from here, so the
46
+ // label, the message line and aria-invalid stay tied to it without the
47
+ // consumer threading ids by hand.
48
+ provideField(
49
+ computed(() => ({
50
+ id: id.value,
51
+ labelId: props.label ? `${id.value}-label` : undefined,
52
+ describedBy:
53
+ [hasMessage.value ? messageId.value : "", notice.message.value ? noticeId.value : ""]
54
+ .filter(Boolean)
55
+ .join(" ") || undefined,
56
+ invalid: !!props.invalid || !!props.error,
57
+ required: !!props.required,
58
+ disabled: !!props.disabled,
59
+ notify: notice.show,
60
+ clearNotice: notice.clear,
61
+ })),
62
+ )
63
+
64
+ defineExpose({ notify: notice.show, clearNotice: notice.clear })
31
65
 
32
66
  // The field stacks label and control with a fixed gap; a denser form needs that gap smaller.
33
67
  const { class: rootClass, attrs: rest } = useMergedAttrs(
@@ -44,12 +78,18 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
44
78
  <div
45
79
  :class="rootClass"
46
80
  v-bind="rest">
47
- <Label v-if="label" :for="id" class="text-sm font-medium text-ink">
81
+ <Label v-if="label" :id="`${id}-label`" :for="id" class="text-sm font-medium text-ink">
48
82
  {{ label }}
49
83
  <span v-if="required" class="text-danger">*</span>
50
84
  </Label>
51
85
 
52
- <slot :id="id" />
86
+ <!-- The note from `notify` floats from here, over the message line below,
87
+ so raising one never moves the form; the line fades out under it
88
+ rather than showing through. -->
89
+ <div class="relative">
90
+ <slot :id="id" :describedBy="hasMessage ? messageId : undefined" />
91
+ <LpFieldNotice :notice="notice" :id="noticeId" />
92
+ </div>
53
93
 
54
94
  <!-- Swapping hint→error changes the text but not the height, so the eased
55
95
  box only ever moves when a message appears or clears.
@@ -60,12 +100,24 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
60
100
  leave every message-less field a row taller than before — and would be
61
101
  a step the easing could not cover, since it exists even at height 0. -->
62
102
  <LpShift axis="height">
63
- <p v-if="error" class="pt-1.5 text-xs text-danger">{{ error }}</p>
103
+ <p
104
+ v-if="error"
105
+ :id="messageId"
106
+ class="pt-1.5 text-xs text-danger transition-opacity duration-fast"
107
+ :class="notice.message.value ? 'opacity-0' : ''"
108
+ >
109
+ {{ error }}
110
+ </p>
64
111
  <!-- The hint is a slot as well as a prop: most are a sentence, but some
65
112
  carry a link or a unit in <code>, and a prop can only be text. Callers
66
113
  had been reaching for `#hint` on the assumption it existed — it
67
114
  rendered nothing, so the explanation silently went missing. -->
68
- <p v-else-if="hint || $slots.hint" class="pt-1.5 text-xs text-muted">
115
+ <p
116
+ v-else-if="hint || $slots.hint"
117
+ :id="messageId"
118
+ class="pt-1.5 text-xs text-muted transition-opacity duration-fast"
119
+ :class="notice.message.value ? 'opacity-0' : ''"
120
+ >
69
121
  <slot name="hint">{{ hint }}</slot>
70
122
  </p>
71
123
  </LpShift>
@@ -8,11 +8,13 @@
8
8
  * inline <svg> synchronously from getIcon() so the server and client produce
9
9
  * identical markup. The <Icon> component only resolves icons after mount, which
10
10
  * left empty SSR placeholders and caused hydration mismatches. We fall back to
11
- * <Icon> only for icons that aren't registered yet (e.g. fetched on demand).
11
+ * <Icon> only for non-lucide icons that aren't registered (fetched on demand).
12
+ * A lucide icon outside the kit's own set loads from the bundled collection
13
+ * (see icons.ts); until then an empty box of the same size holds its place.
12
14
  */
13
- import { computed } from "vue"
15
+ import { computed, watchEffect } from "vue"
14
16
  import { Icon, getIcon } from "@iconify/vue"
15
- import "../icons" // registers the lucide collection offline
17
+ import { iconsVersion, requestIcon } from "../icons"
16
18
 
17
19
  const props = defineProps<{
18
20
  /** iconify name, e.g. "lucide:check". Bare names assume the lucide set. */
@@ -25,7 +27,15 @@ function resolve(name: string): string {
25
27
  }
26
28
 
27
29
  const fullName = computed(() => resolve(props.name))
28
- const data = computed(() => getIcon(fullName.value))
30
+ const data = computed(() => {
31
+ void iconsVersion.value
32
+ return getIcon(fullName.value)
33
+ })
34
+ const lucide = computed(() => fullName.value.startsWith("lucide:"))
35
+
36
+ watchEffect(() => {
37
+ if (!data.value && lucide.value) requestIcon(fullName.value)
38
+ })
29
39
  const dim = computed(() => props.size ?? "1em")
30
40
  </script>
31
41
 
@@ -39,6 +49,14 @@ const dim = computed(() => props.size ?? "1em")
39
49
  aria-hidden="true"
40
50
  v-html="data.body"
41
51
  />
52
+ <svg
53
+ v-else-if="lucide"
54
+ xmlns="http://www.w3.org/2000/svg"
55
+ :width="dim"
56
+ :height="dim"
57
+ viewBox="0 0 24 24"
58
+ aria-hidden="true"
59
+ />
42
60
  <Icon
43
61
  v-else
44
62
  :icon="fullName"
@@ -24,6 +24,8 @@ export default { inheritAttrs: false }
24
24
  * already know from every avatar cropper.
25
25
  */
26
26
  import { computed, onBeforeUnmount, ref, shallowRef, watch } from "vue"
27
+ import { useLabels } from "../composables/useLpConfig"
28
+ import { coverScale, cropGeometry, isSideways } from "./imageCrop"
27
29
  import { useHotkeys } from "../composables/useHotkeys"
28
30
  import { useReveal } from "../composables/useReveal"
29
31
  import { useZoomPan } from "../composables/useZoomPan"
@@ -81,15 +83,18 @@ const props = withDefaults(
81
83
  // mount. An editor inside a dialog that opens a minute later still draws.
82
84
  const { el: revealAnchor, revealed } = useReveal({ immediate: () => !props.animate })
83
85
 
84
- const text = computed(() => ({
85
- rotateLeft: "Rotate left",
86
- rotateRight: "Rotate right",
87
- zoomIn: "Zoom in",
88
- zoomOut: "Zoom out",
89
- reset: "Reset",
90
- canvas: "Crop area — arrows pan, +/− zoom, R rotates, Alt+wheel turns freely",
91
- ...props.labels,
92
- }))
86
+ const text = useLabels(
87
+ "imageEditor",
88
+ {
89
+ rotateLeft: "Rotate left",
90
+ rotateRight: "Rotate right",
91
+ zoomIn: "Zoom in",
92
+ zoomOut: "Zoom out",
93
+ reset: "Reset",
94
+ canvas: "Crop area — arrows pan, +/− zoom, R rotates, Alt+wheel turns freely",
95
+ },
96
+ () => props.labels,
97
+ )
93
98
 
94
99
  const ASPECTS: Record<string, number | null> = {
95
100
  free: null,
@@ -105,7 +110,24 @@ const rotation = ref(0)
105
110
  const viewport = ref<HTMLElement | null>(null)
106
111
  const zoom = useZoomPan({ min: 1, max: 6 })
107
112
 
108
- watch(viewport, (el) => zoom.setViewport(el))
113
+ const frameSize = ref<{ width: number; height: number } | null>(null)
114
+ let frameObserver: ResizeObserver | null = null
115
+
116
+ watch(viewport, (el) => {
117
+ zoom.setViewport(el)
118
+ frameObserver?.disconnect()
119
+ frameObserver = null
120
+ if (!el) return
121
+ // Fractional, not clientWidth: a rounded-down frame left a hairline of
122
+ // background showing along the edges of a turned picture.
123
+ frameSize.value = { width: el.clientWidth, height: el.clientHeight }
124
+ if (typeof ResizeObserver !== "undefined") {
125
+ frameObserver = new ResizeObserver(([entry]) => {
126
+ frameSize.value = { width: entry.contentRect.width, height: entry.contentRect.height }
127
+ })
128
+ frameObserver.observe(el)
129
+ }
130
+ })
109
131
 
110
132
  /** Which way up it ends, 0-270, whatever the accumulated angle. */
111
133
  const upright = computed(() => ((rotation.value % 360) + 360) % 360)
@@ -114,14 +136,24 @@ const upright = computed(() => ((rotation.value % 360) + 360) % 360)
114
136
  * back to where it started is not an edit, however far the dial travelled. */
115
137
  const edited = computed(() => upright.value !== 0 || zoom.scale.value !== 1)
116
138
 
139
+ // Each file gets a generation: a slow decode of an earlier file must neither
140
+ // replace the current picture nor leak the bitmap it produced.
141
+ let generation = 0
142
+
117
143
  watch(
118
144
  () => props.file,
119
145
  async (file) => {
120
146
  revoke()
121
147
  rotation.value = 0
122
148
  zoom.reset()
149
+ const mine = ++generation
123
150
  objectUrl.value = URL.createObjectURL(file)
124
- source.value = await decode(file, objectUrl.value)
151
+ const decoded = await decode(file, objectUrl.value)
152
+ if (mine !== generation) {
153
+ if (decoded instanceof ImageBitmap) decoded.close()
154
+ return
155
+ }
156
+ source.value = decoded
125
157
  },
126
158
  { immediate: true },
127
159
  )
@@ -172,7 +204,11 @@ function revoke() {
172
204
  source.value = null
173
205
  }
174
206
 
175
- onBeforeUnmount(revoke)
207
+ onBeforeUnmount(() => {
208
+ generation++
209
+ revoke()
210
+ frameObserver?.disconnect()
211
+ })
176
212
 
177
213
  function rotate(by: number) {
178
214
  // Accumulated, never wrapped to 0-359. Normalising made the fourth quarter
@@ -218,7 +254,9 @@ const boxRatio = computed(() => {
218
254
  if (ratio) return ratio
219
255
  const size = sourceSize.value
220
256
  if (!size) return 1
221
- const turned = upright.value % 180 !== 0
257
+ // Snapped to the nearest quarter: a few degrees of levelling must not flip a
258
+ // landscape frame to portrait.
259
+ const turned = isSideways(rotation.value)
222
260
  const w = turned ? size.height : size.width
223
261
  const h = turned ? size.width : size.height
224
262
  return w / h
@@ -226,7 +264,31 @@ const boxRatio = computed(() => {
226
264
 
227
265
  const frameStyle = computed(() => ({ aspectRatio: String(boxRatio.value) }))
228
266
 
267
+ /*
268
+ * The picture is sized to cover the frame AFTER its turn, and centred. Filling
269
+ * the frame with object-cover and then rotating the element left empty bands
270
+ * on any non-square frame turned sideways, and at a free angle bare corners.
271
+ */
272
+ const sized = computed(() => {
273
+ const size = sourceSize.value
274
+ const frame = frameSize.value
275
+ if (!size || !frame || !frame.width || !frame.height) return null
276
+ const cover = coverScale(size.width, size.height, frame.width, frame.height, rotation.value)
277
+ const width = size.width * cover
278
+ const height = size.height * cover
279
+ return {
280
+ width: `${width}px`,
281
+ height: `${height}px`,
282
+ left: "50%",
283
+ top: "50%",
284
+ marginLeft: `${-width / 2}px`,
285
+ marginTop: `${-height / 2}px`,
286
+ maxWidth: "none",
287
+ }
288
+ })
289
+
229
290
  const imageStyle = computed(() => ({
291
+ ...sized.value,
230
292
  ...zoom.style.value,
231
293
  // Rotation sits innermost, after the composable's scale and translate, so a
232
294
  // drag still moves the picture the way the pointer went once it is turned.
@@ -254,51 +316,31 @@ async function exportImage(): Promise<Blob | null> {
254
316
  if (!picture || !size || !frame) return null
255
317
  if (!edited.value && props.aspect === "free") return null
256
318
 
257
- // Work in "upright space": the picture as the person sees it, already
258
- // turned. The rotation is re-applied once, when drawing, which keeps the
259
- // maths free of quarter-turn special cases.
260
- const turned = upright.value % 180 !== 0
261
- const uprightW = turned ? size.height : size.width
262
- const uprightH = turned ? size.width : size.height
263
-
264
- // object-cover: the picture fills the frame and the overflow is what panning
265
- // reaches. One frame pixel is this many upright pixels.
266
- const cover = Math.max(frame.clientWidth / uprightW, frame.clientHeight / uprightH)
267
- const shown = cover * zoom.scale.value
268
- const cropW = Math.min(uprightW, frame.clientWidth / shown)
269
- const cropH = Math.min(uprightH, frame.clientHeight / shown)
270
-
271
- // `offset` is in content pixels — the same space the cover scale maps from —
272
- // so it converts with `cover`, not with the zoomed scale.
273
- const centreX = clamp(
274
- uprightW / 2 - zoom.offset.value.x / cover,
275
- cropW / 2,
276
- uprightW - cropW / 2,
277
- )
278
- const centreY = clamp(
279
- uprightH / 2 - zoom.offset.value.y / cover,
280
- cropH / 2,
281
- uprightH - cropH / 2,
282
- )
283
-
284
- const scale = Math.min(1, props.maxEdge / Math.max(cropW, cropH))
319
+ // The crop in upright space, from the same geometry the preview is drawn
320
+ // with, then the whole source drawn through one transform: no per-quarter
321
+ // special cases, and a free angle comes out as it looked on screen.
322
+ const crop = cropGeometry({
323
+ sourceWidth: size.width,
324
+ sourceHeight: size.height,
325
+ frameWidth: frameSize.value?.width ?? frame.clientWidth,
326
+ frameHeight: frameSize.value?.height ?? frame.clientHeight,
327
+ rotation: rotation.value,
328
+ scale: zoom.scale.value,
329
+ offset: zoom.offset.value,
330
+ })
331
+
332
+ const scale = Math.min(1, props.maxEdge / Math.max(crop.width, crop.height))
285
333
  const canvas = document.createElement("canvas")
286
- canvas.width = Math.max(1, Math.round(cropW * scale))
287
- canvas.height = Math.max(1, Math.round(cropH * scale))
334
+ canvas.width = Math.max(1, Math.round(crop.width * scale))
335
+ canvas.height = Math.max(1, Math.round(crop.height * scale))
288
336
  const ctx = canvas.getContext("2d")
289
337
  if (!ctx) return null
290
338
 
291
339
  ctx.translate(canvas.width / 2, canvas.height / 2)
292
- ctx.rotate((upright.value * Math.PI) / 180)
293
340
  ctx.scale(scale, scale)
294
-
295
- // Back to source coordinates for the read: a quarter turn swaps the axes.
296
- const readW = turned ? cropH : cropW
297
- const readH = turned ? cropW : cropH
298
- const readX = (turned ? centreY : centreX) - readW / 2
299
- const readY = (turned ? centreX : centreY) - readH / 2
300
-
301
- ctx.drawImage(picture, readX, readY, readW, readH, -readW / 2, -readH / 2, readW, readH)
341
+ ctx.translate(-crop.centre.x, -crop.centre.y)
342
+ ctx.rotate((rotation.value * Math.PI) / 180)
343
+ ctx.drawImage(picture, -size.width / 2, -size.height / 2)
302
344
 
303
345
  return await new Promise<Blob | null>((resolve) =>
304
346
  canvas.toBlob(resolve, outputType.value, props.quality),
@@ -312,10 +354,6 @@ const outputType = computed(() => {
312
354
  return ENCODABLE.has(props.file.type) ? props.file.type : "image/png"
313
355
  })
314
356
 
315
- function clamp(value: number, low: number, high: number) {
316
- return Math.min(Math.max(value, low), Math.max(low, high))
317
- }
318
-
319
357
  /*
320
358
  * The wheel.
321
359
  *
@@ -425,8 +463,9 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
425
463
  :src="objectUrl"
426
464
  alt=""
427
465
  draggable="false"
428
- class="lp-editor__image absolute inset-0 size-full select-none object-cover motion-reduce:transition-none"
466
+ class="lp-editor__image absolute select-none motion-reduce:transition-none"
429
467
  :class="[
468
+ sized ? '' : 'inset-0 size-full object-cover',
430
469
  zoom.panning.value ? 'cursor-grabbing' : 'cursor-grab',
431
470
  animate && !revealed ? 'lp-editor__image--waiting' : '',
432
471
  ]"
@@ -5,7 +5,9 @@ export default { inheritAttrs: false }
5
5
  <script setup lang="ts">
6
6
  import { tv, type VariantProps } from "tailwind-variants"
7
7
  import { computed, ref, useAttrs, useSlots } from "vue"
8
+ import { useFieldControl } from "../composables/useField"
8
9
  import { useInputFilter } from "../composables/useInputFilter"
10
+ import { DEFAULT_MASK_TOKENS, useInputMask, type MaskSpec, type MaskTokens } from "../composables/useInputMask"
9
11
  import { mergeClass } from "../composables/useMergedClass"
10
12
  import LpFormField from "./LpFormField.vue"
11
13
  import LpIcon from "./LpIcon.vue"
@@ -72,17 +74,56 @@ const props = withDefaults(
72
74
  * disallowed input/paste at the source — cursor- and IME-safe.
73
75
  */
74
76
  restrict?: RegExp
77
+ /**
78
+ * Format-as-you-type mask ("+0 (000) 000-00-00", a list, or a function of
79
+ * the raw value). The model holds the masked text; `update:raw` carries
80
+ * the accepted characters alone.
81
+ */
82
+ mask?: MaskSpec
83
+ /** Extra or replacement mask tokens, one-character RegExps by key. */
84
+ maskTokens?: MaskTokens
75
85
  }>(),
76
86
  { type: "text", size: "md" },
77
87
  )
78
88
 
79
- defineEmits<{ (e: "update:modelValue", value: string): void }>()
89
+ const emit = defineEmits<{
90
+ (e: "update:modelValue", value: string): void
91
+ (e: "update:raw", value: string): void
92
+ }>()
80
93
 
81
- const { isInvalid, onBeforeInput, onPaste } = useInputFilter({
94
+ const { isInvalid, onBeforeInput, onInput: filterInput, onCompositionEnd, onPaste } = useInputFilter({
82
95
  pattern: () => props.pattern,
83
96
  restrict: () => props.restrict,
84
97
  })
85
98
 
99
+ const masking = useInputMask({
100
+ mask: () => props.mask,
101
+ tokens: () => (props.maskTokens ? { ...DEFAULT_MASK_TOKENS, ...props.maskTokens } : undefined),
102
+ onChange: (_masked, raw) => emit("update:raw", raw),
103
+ })
104
+ const shown = computed(() => (masking.active.value ? masking.format(props.modelValue).masked : props.modelValue))
105
+
106
+ function onBefore(event: InputEvent): void {
107
+ onBeforeInput(event)
108
+ if (!event.defaultPrevented) masking.onBeforeInput(event)
109
+ if (event.defaultPrevented && masking.active.value && event.target instanceof HTMLInputElement) {
110
+ emit("update:modelValue", event.target.value)
111
+ }
112
+ }
113
+
114
+ function onInputEvent(event: Event): void {
115
+ if (masking.active.value) masking.onInput(event)
116
+ else filterInput(event)
117
+ emit("update:modelValue", (event.target as HTMLInputElement).value)
118
+ }
119
+
120
+ function onComposed(event: CompositionEvent): void {
121
+ if (masking.active.value) {
122
+ masking.onCompositionEnd(event)
123
+ emit("update:modelValue", (event.target as HTMLInputElement).value)
124
+ } else onCompositionEnd(event)
125
+ }
126
+
86
127
  // Pattern failure folds into the same invalid state as the explicit prop/error.
87
128
  const patternInvalid = computed(() => isInvalid(props.modelValue))
88
129
 
@@ -93,9 +134,15 @@ const hasTrailing = computed(() => !!slots.trailing)
93
134
  const hasField = computed(
94
135
  () => !!props.label || !!props.hint || !!props.error,
95
136
  )
96
- const isInvalidState = computed(
97
- () => props.invalid || !!props.error || patternInvalid.value,
98
- )
137
+ const field = useFieldControl(() => ({
138
+ wrapped: hasField.value,
139
+ hasMessage: !!props.error || !!props.hint,
140
+ id: typeof attrs.id === "string" ? attrs.id : undefined,
141
+ invalid: props.invalid || !!props.error || patternInvalid.value,
142
+ required: props.required,
143
+ disabled: props.disabled,
144
+ }))
145
+ const isInvalidState = computed(() => field.value.invalid)
99
146
  const shellClass = computed(() =>
100
147
  shell({ size: props.size, invalid: isInvalidState.value }),
101
148
  )
@@ -126,7 +173,8 @@ defineExpose({
126
173
  :hint="hasField ? hint : undefined"
127
174
  :error="hasField ? error : undefined"
128
175
  :required="hasField ? required : undefined"
129
- :class="hasField ? rootClass : undefined"
176
+ :id="hasField ? field.id : undefined"
177
+ :class="hasField ? rootClass : 'contents'"
130
178
  :style="hasField ? rootStyle : undefined"
131
179
  >
132
180
  <div
@@ -140,20 +188,23 @@ defineExpose({
140
188
  <input
141
189
  ref="inputEl"
142
190
  v-bind="inputAttrs"
191
+ :id="field.id"
143
192
  :type="type"
144
- :value="modelValue"
193
+ :value="shown"
145
194
  :placeholder="placeholder"
146
- :disabled="disabled"
147
- :required="required || undefined"
195
+ :disabled="field.disabled"
196
+ :required="field.required || undefined"
148
197
  :aria-invalid="isInvalidState || undefined"
198
+ :aria-describedby="field.describedBy"
149
199
  :class="[
150
200
  'lp-autofill min-w-0 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted',
151
201
  hasLeading ? 'pl-2' : padX[size ?? 'md'],
152
202
  hasTrailing ? '' : padX[size ?? 'md'],
153
203
  ]"
154
- @beforeinput="onBeforeInput"
204
+ @beforeinput="onBefore"
155
205
  @paste="onPaste"
156
- @input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
206
+ @compositionend="onComposed"
207
+ @input="onInputEvent"
157
208
  />
158
209
  <slot name="trailing" />
159
210
  </div>