@leavepulse/ui 0.43.4 → 0.44.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 (337) hide show
  1. package/README.md +75 -7
  2. package/dist/assets/{LpCommandPalette-B-0Est4Z.css → LpCommandPalette-DCP5g2ik.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/{LpShortcutsDialog-C6YoQ8_M.css → LpShortcutsDialog-ChUyU7oV.css} +7 -7
  6. package/dist/assets/{LpSkeleton-D7xU8IPV.css → LpSkeleton-H3Z5dEIk.css} +4 -1
  7. package/dist/assets/{LpTable-BgPLJOXa.css → LpTable-DDQNdf86.css} +9 -9
  8. package/dist/chunks/ContextMenuItems-CrfeNPWT.js +123 -0
  9. package/dist/chunks/{LayoutCanvas-Dd20boUG.js → LayoutCanvas-D-jPOAvg.js} +26 -9
  10. package/dist/chunks/{LayoutNode-DGpZA9LX.js → LayoutNode-D8Xy9N-o.js} +20 -8
  11. package/dist/chunks/{LpActionBar-BlvtGB2P.js → LpActionBar-BbwCF6l5.js} +24 -10
  12. package/dist/chunks/{LpAddressInput-B-Zvxsn7.js → LpAddressInput-BrOqkZqg.js} +53 -48
  13. package/dist/chunks/{LpAlert-BAYHDSS1.js → LpAlert-DkOIjsbq.js} +15 -8
  14. package/dist/chunks/{LpAppShell-BGQVIY-3.js → LpAppShell-BUKPcHco.js} +51 -24
  15. package/dist/chunks/{LpAutocomplete-DExlIZe_.js → LpAutocomplete-DxV3Y9h4.js} +50 -23
  16. package/dist/chunks/{LpAvatar-B3-xAOH3.js → LpAvatar-ClBbEYZN.js} +17 -8
  17. package/dist/chunks/LpBreadcrumbs-B8IJgFIc.js +95 -0
  18. package/dist/chunks/{LpButton-5VXXCjwF.js → LpButton-kqJBHIIb.js} +44 -14
  19. package/dist/chunks/{LpCard-BhI1N_vL.js → LpCard-CpRByR0q.js} +1 -1
  20. package/dist/chunks/{LpCheckbox-CVccKYaH.js → LpCheckbox-DpqchzH4.js} +7 -3
  21. package/dist/chunks/{LpCodeBlock-Bo0B4TFR.js → LpCodeBlock-DeHJPq7p.js} +48 -16
  22. package/dist/chunks/{LpColorPicker-BlXea7LM.js → LpColorPicker-Bhf1qxhB.js} +32 -9
  23. package/dist/chunks/{LpConfirmDialog-Q-bxff8z.js → LpConfirmDialog-CIUOSnWo.js} +58 -16
  24. package/dist/chunks/LpConfirmHost-CDm0Wtpv.js +49 -0
  25. package/dist/chunks/LpContextMenu-DQw5dGfG.js +68 -0
  26. package/dist/chunks/LpDatePicker-Drirx1AK.js +191 -0
  27. package/dist/chunks/{LpDrawer-BZ6k3hvo.js → LpDrawer-bScx-KMO.js} +63 -26
  28. package/dist/chunks/{LpFileTree-_5vFBgaK.js → LpFileTree-CSI_5SK2.js} +23 -8
  29. package/dist/chunks/{LpFileTreeNode-BdcIYKIE.js → LpFileTreeNode-B4MiSQX2.js} +2 -2
  30. package/dist/chunks/LpFormField-CIk5qosh.js +87 -0
  31. package/dist/chunks/{LpInput-9EXWUM9B.js → LpInput-C0e8y8_d.js} +26 -11
  32. package/dist/chunks/{LpLightbox-2sYVMY-S.js → LpLightbox-Mr0W_6HK.js} +147 -60
  33. package/dist/chunks/{LpLink-DCND-zyu.js → LpLink-Krkjhe_y.js} +15 -6
  34. package/dist/chunks/{LpLogViewer-CaRy5MeS.js → LpLogViewer-YBOaFnc_.js} +68 -34
  35. package/dist/chunks/{LpMapPicker-ul4oYno6.js → LpMapPicker-NUzS9e37.js} +38 -13
  36. package/dist/chunks/{LpMediaGallery-FF-NquIo.js → LpMediaGallery-DfJmMW0I.js} +6 -3
  37. package/dist/chunks/{LpModal-Bt0pQCI_.js → LpModal-DPwGKBPv.js} +49 -26
  38. package/dist/chunks/{LpModalNav-YaifHrXC.js → LpModalNav-DPg5H9f3.js} +9 -6
  39. package/dist/chunks/{LpNotificationBell-DyGGsMAM.js → LpNotificationBell-t5yn-siX.js} +58 -24
  40. package/dist/chunks/{LpNumberField-CWuX08iX.js → LpNumberField-DWnQKktS.js} +20 -7
  41. package/dist/chunks/{LpNumberFlow-amWYaeuA.js → LpNumberFlow-z4WPZcZu.js} +22 -12
  42. package/dist/chunks/{LpOtpInput-y2v-pmiN.js → LpOtpInput-CziPF3Oq.js} +37 -8
  43. package/dist/chunks/{LpPagination-5FxjmmB5.js → LpPagination-DFngX7Xo.js} +30 -19
  44. package/dist/chunks/{LpPasswordInput--sjYCkLS.js → LpPasswordInput-CWVDuBJ5.js} +10 -7
  45. package/dist/chunks/{LpPhoneInput-BDSKVl_z.js → LpPhoneInput-CD-ZTeSd.js} +57 -33
  46. package/dist/chunks/{LpPie-D6OnzwlJ.js → LpPie-BLco31Nx.js} +78 -21
  47. package/dist/chunks/{LpPopover-8268UmlC.js → LpPopover-p_e_shN3.js} +4 -2
  48. package/dist/chunks/{LpProgress-Btq5W4Ut.js → LpProgress-CBU1iI69.js} +5 -4
  49. package/dist/chunks/{LpRadioGroup-xPIuVDO_.js → LpRadioGroup-DmEW0l78.js} +9 -2
  50. package/dist/chunks/{LpSegmented-fV0Sk92p.js → LpSegmented-Dejd7cIx.js} +7 -3
  51. package/dist/chunks/{LpSelect-ANIVtrpM.js → LpSelect-DrRD9I7L.js} +40 -18
  52. package/dist/chunks/{LpSidebar-ClBUwoEz.js → LpSidebar-BgwUNWpJ.js} +30 -22
  53. package/dist/chunks/{LpSidebarNav-llOcs_Fn.js → LpSidebarNav-Bqxz8aWT.js} +14 -9
  54. package/dist/chunks/{LpSkeleton-CTI-vBgW.js → LpSkeleton-BB6XIV-w.js} +1 -1
  55. package/dist/chunks/{LpSlider-BwNIBkAK.js → LpSlider-Cuzmrd7p.js} +7 -2
  56. package/dist/chunks/{LpSparkline-C6xQ2dwv.js → LpSparkline-CB80N8SJ.js} +11 -4
  57. package/dist/chunks/{LpStat-CQaW8R--.js → LpStat-dXtyrdX9.js} +1 -1
  58. package/dist/chunks/{LpSwitch-Blizvqyt.js → LpSwitch-B1aLKulj.js} +40 -26
  59. package/dist/chunks/{LpTableOfContents-CjvxPMpU.js → LpTableOfContents-BUcAfGPQ.js} +36 -41
  60. package/dist/chunks/{LpTabs-Cefe_lys.js → LpTabs-_qMVaZJ6.js} +19 -5
  61. package/dist/chunks/LpTextarea-yP8hGedR.js +77 -0
  62. package/dist/chunks/LpThemeProvider-Wjm7Tuit.js +35 -0
  63. package/dist/chunks/LpThemeSwitcher-CKLc10KZ.js +169 -0
  64. package/dist/chunks/{LpTilt-DuyKbpkA.js → LpTilt-B7KHX1zH.js} +2 -2
  65. package/dist/chunks/{LpTimeField-K6nAGboX.js → LpTimeField-C5HRgS5H.js} +13 -7
  66. package/dist/chunks/{LpToaster-gDB1EaU3.js → LpToaster--oT6lfhL.js} +57 -28
  67. package/dist/chunks/{LpTooltip-hCLPAqMG.js → LpTooltip-CBdzeKNf.js} +13 -6
  68. package/dist/chunks/LpTooltipProvider-CftjV7Xc.js +25 -0
  69. package/dist/chunks/{LpTopologyCanvas-Cm1L39uL.js → LpTopologyCanvas-DocsLal3.js} +6 -1
  70. package/dist/chunks/{LpUptimeBar-DvAMno0S.js → LpUptimeBar-DLtxOMRy.js} +43 -14
  71. package/dist/chunks/{LpThemeSwitcher-Cue-myST.js → useTheme-C8JHUlPM.js} +173 -215
  72. package/dist/component-names.d.ts +1 -1
  73. package/dist/components/ContextMenuItems.vue.d.ts +8 -0
  74. package/dist/components/ContextMenuItems.vue.js +4 -0
  75. package/dist/components/LayoutCanvas.vue.js +1 -1
  76. package/dist/components/LayoutNode.vue.d.ts +4 -0
  77. package/dist/components/LayoutNode.vue.js +1 -1
  78. package/dist/components/LpActionBar.vue.d.ts +3 -0
  79. package/dist/components/LpActionBar.vue.js +1 -1
  80. package/dist/components/LpAddressInput.vue.d.ts +1 -1
  81. package/dist/components/LpAddressInput.vue.js +1 -1
  82. package/dist/components/LpAlert.vue.d.ts +9 -0
  83. package/dist/components/LpAlert.vue.js +1 -1
  84. package/dist/components/LpAppShell.vue.d.ts +10 -0
  85. package/dist/components/LpAppShell.vue.js +1 -1
  86. package/dist/components/LpAutocomplete.vue.d.ts +31 -6
  87. package/dist/components/LpAutocomplete.vue.js +1 -1
  88. package/dist/components/LpAvatar.vue.d.ts +3 -3
  89. package/dist/components/LpAvatar.vue.js +1 -1
  90. package/dist/components/LpBadge.vue.d.ts +2 -2
  91. package/dist/components/LpBreadcrumbs.vue.d.ts +13 -9
  92. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  93. package/dist/components/LpButton.vue.d.ts +35 -98
  94. package/dist/components/LpButton.vue.js +1 -1
  95. package/dist/components/LpCard.vue.js +1 -1
  96. package/dist/components/LpChart.vue.d.ts +31 -2
  97. package/dist/components/LpChart.vue.js +317 -95
  98. package/dist/components/LpCheckbox.vue.js +1 -1
  99. package/dist/components/LpCodeBlock.vue.d.ts +2 -0
  100. package/dist/components/LpCodeBlock.vue.js +1 -1
  101. package/dist/components/LpColorPicker.vue.js +1 -1
  102. package/dist/components/LpCommandPalette.vue.d.ts +8 -3
  103. package/dist/components/LpCommandPalette.vue.js +86 -72
  104. package/dist/components/LpConfirmDialog.vue.d.ts +15 -3
  105. package/dist/components/LpConfirmDialog.vue.js +1 -1
  106. package/dist/components/LpConfirmHost.vue.d.ts +9 -0
  107. package/dist/components/LpConfirmHost.vue.js +4 -0
  108. package/dist/components/LpContextMenu.vue.d.ts +3 -5
  109. package/dist/components/LpContextMenu.vue.js +1 -1
  110. package/dist/components/LpDatePicker.vue.d.ts +24 -6
  111. package/dist/components/LpDatePicker.vue.js +1 -1
  112. package/dist/components/LpDrawer.vue.d.ts +10 -0
  113. package/dist/components/LpDrawer.vue.js +1 -1
  114. package/dist/components/LpDropzone.vue.js +22 -6
  115. package/dist/components/LpFileTree.vue.d.ts +11 -2
  116. package/dist/components/LpFileTree.vue.js +1 -1
  117. package/dist/components/LpFileTreeNode.vue.js +1 -1
  118. package/dist/components/LpFormField.vue.d.ts +9 -0
  119. package/dist/components/LpFormField.vue.js +1 -1
  120. package/dist/components/LpImageEditor.vue.js +92 -57
  121. package/dist/components/LpInput.vue.d.ts +3 -3
  122. package/dist/components/LpInput.vue.js +1 -1
  123. package/dist/components/LpLightbox.vue.d.ts +17 -0
  124. package/dist/components/LpLightbox.vue.js +1 -1
  125. package/dist/components/LpLink.vue.d.ts +11 -11
  126. package/dist/components/LpLink.vue.js +1 -1
  127. package/dist/components/LpLogViewer.vue.d.ts +20 -2
  128. package/dist/components/LpLogViewer.vue.js +1 -1
  129. package/dist/components/LpMapPicker.vue.d.ts +4 -4
  130. package/dist/components/LpMapPicker.vue.js +1 -1
  131. package/dist/components/LpMediaGallery.vue.js +1 -1
  132. package/dist/components/LpModal.vue.d.ts +16 -6
  133. package/dist/components/LpModal.vue.js +1 -1
  134. package/dist/components/LpModalNav.vue.d.ts +0 -1
  135. package/dist/components/LpModalNav.vue.js +1 -1
  136. package/dist/components/LpNotificationBell.vue.d.ts +11 -2
  137. package/dist/components/LpNotificationBell.vue.js +1 -1
  138. package/dist/components/LpNumberField.vue.d.ts +2 -0
  139. package/dist/components/LpNumberField.vue.js +1 -1
  140. package/dist/components/LpNumberFlow.vue.js +1 -1
  141. package/dist/components/LpOtpInput.vue.js +1 -1
  142. package/dist/components/LpPagination.vue.d.ts +2 -0
  143. package/dist/components/LpPagination.vue.js +1 -1
  144. package/dist/components/LpPasswordInput.vue.js +1 -1
  145. package/dist/components/LpPhoneInput.vue.js +1 -1
  146. package/dist/components/LpPie.vue.d.ts +21 -2
  147. package/dist/components/LpPie.vue.js +1 -1
  148. package/dist/components/LpPopover.vue.d.ts +2 -0
  149. package/dist/components/LpPopover.vue.js +1 -1
  150. package/dist/components/LpProgress.vue.js +1 -1
  151. package/dist/components/LpRadioGroup.vue.js +1 -1
  152. package/dist/components/LpSection.vue.d.ts +1 -1
  153. package/dist/components/LpSegmented.vue.d.ts +5 -0
  154. package/dist/components/LpSegmented.vue.js +1 -1
  155. package/dist/components/LpSelect.vue.d.ts +1 -0
  156. package/dist/components/LpSelect.vue.js +1 -1
  157. package/dist/components/LpShortcutsDialog.vue.d.ts +1 -1
  158. package/dist/components/LpShortcutsDialog.vue.js +11 -8
  159. package/dist/components/LpSidebar.vue.d.ts +6 -2
  160. package/dist/components/LpSidebar.vue.js +1 -1
  161. package/dist/components/LpSidebarNav.vue.js +1 -1
  162. package/dist/components/LpSkeleton.vue.js +1 -1
  163. package/dist/components/LpSlider.vue.js +1 -1
  164. package/dist/components/LpSparkline.vue.d.ts +1 -0
  165. package/dist/components/LpSparkline.vue.js +1 -1
  166. package/dist/components/LpStat.vue.js +1 -1
  167. package/dist/components/LpSwitch.vue.js +1 -1
  168. package/dist/components/LpTable.vue.d.ts +23 -1
  169. package/dist/components/LpTable.vue.js +74 -36
  170. package/dist/components/LpTableOfContents.vue.d.ts +4 -2
  171. package/dist/components/LpTableOfContents.vue.js +1 -1
  172. package/dist/components/LpTabs.vue.d.ts +2 -0
  173. package/dist/components/LpTabs.vue.js +1 -1
  174. package/dist/components/LpTextarea.vue.d.ts +19 -6
  175. package/dist/components/LpTextarea.vue.js +1 -1
  176. package/dist/components/LpThemeProvider.vue.d.ts +22 -0
  177. package/dist/components/LpThemeProvider.vue.js +4 -0
  178. package/dist/components/LpThemeSwitcher.vue.d.ts +13 -0
  179. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  180. package/dist/components/LpTilt.vue.js +1 -1
  181. package/dist/components/LpTimeField.vue.js +1 -1
  182. package/dist/components/LpToaster.vue.d.ts +6 -1
  183. package/dist/components/LpToaster.vue.js +1 -1
  184. package/dist/components/LpTooltip.vue.d.ts +1 -2
  185. package/dist/components/LpTooltip.vue.js +1 -1
  186. package/dist/components/LpTooltipProvider.vue.d.ts +22 -0
  187. package/dist/components/LpTooltipProvider.vue.js +4 -0
  188. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  189. package/dist/components/LpUptimeBar.vue.d.ts +4 -0
  190. package/dist/components/LpUptimeBar.vue.js +1 -1
  191. package/dist/components/chart.d.ts +32 -1
  192. package/dist/components/chart.js +39 -4
  193. package/dist/components/codeHighlight.d.ts +6 -0
  194. package/dist/components/codeHighlight.js +92 -20
  195. package/dist/components/color.d.ts +1 -1
  196. package/dist/components/color.js +20 -9
  197. package/dist/components/confirm.d.ts +25 -0
  198. package/dist/components/confirm.js +26 -0
  199. package/dist/components/countries.d.ts +6 -0
  200. package/dist/components/countries.js +7 -0
  201. package/dist/components/fileTree.js +16 -7
  202. package/dist/components/imageCrop.d.ts +42 -0
  203. package/dist/components/imageCrop.js +47 -0
  204. package/dist/components/link.d.ts +12 -0
  205. package/dist/components/link.js +19 -0
  206. package/dist/components/modalDepth.d.ts +18 -2
  207. package/dist/components/modalDepth.js +17 -5
  208. package/dist/components/otp.d.ts +5 -0
  209. package/dist/components/otp.js +10 -0
  210. package/dist/components/sidebar.d.ts +7 -1
  211. package/dist/components/tableSort.d.ts +8 -0
  212. package/dist/components/tableSort.js +31 -0
  213. package/dist/composables/useField.d.ts +34 -0
  214. package/dist/composables/useField.js +45 -0
  215. package/dist/composables/useFormDraft.d.ts +10 -3
  216. package/dist/composables/useFormDraft.js +9 -14
  217. package/dist/composables/useHotkeys.d.ts +17 -1
  218. package/dist/composables/useHotkeys.js +55 -18
  219. package/dist/composables/useInputFilter.d.ts +17 -0
  220. package/dist/composables/useInputFilter.js +47 -10
  221. package/dist/composables/useLpConfig.d.ts +24 -0
  222. package/dist/composables/useLpConfig.js +44 -0
  223. package/dist/composables/useRovingFocus.js +7 -0
  224. package/dist/composables/useTilt.d.ts +2 -2
  225. package/dist/composables/useTilt.js +5 -6
  226. package/dist/composables/useToast.d.ts +20 -0
  227. package/dist/composables/useToast.js +28 -4
  228. package/dist/composables/useZoomPan.d.ts +3 -3
  229. package/dist/composables/useZoomPan.js +4 -4
  230. package/dist/index.d.ts +32 -2
  231. package/dist/index.js +184 -137
  232. package/dist/layout/tree.d.ts +2 -0
  233. package/dist/layout/tree.js +16 -1
  234. package/dist/layout/useLayout.d.ts +15 -0
  235. package/dist/layout/useLayout.js +41 -14
  236. package/dist/theme/useTheme.d.ts +32 -9
  237. package/package.json +8 -7
  238. package/src/component-names.ts +5 -7
  239. package/src/components/ContextMenuItems.vue +88 -0
  240. package/src/components/LayoutCanvas.vue +26 -6
  241. package/src/components/LayoutNode.vue +19 -2
  242. package/src/components/LpActionBar.vue +30 -7
  243. package/src/components/LpAddressInput.vue +57 -41
  244. package/src/components/LpAlert.vue +16 -3
  245. package/src/components/LpAppShell.vue +45 -12
  246. package/src/components/LpAutocomplete.vue +46 -10
  247. package/src/components/LpAvatar.vue +19 -5
  248. package/src/components/LpBreadcrumbs.vue +65 -41
  249. package/src/components/LpButton.vue +59 -13
  250. package/src/components/LpChart.vue +272 -28
  251. package/src/components/LpCheckbox.vue +7 -3
  252. package/src/components/LpCodeBlock.vue +50 -14
  253. package/src/components/LpColorPicker.vue +39 -9
  254. package/src/components/LpCommandPalette.vue +34 -13
  255. package/src/components/LpConfirmDialog.vue +68 -17
  256. package/src/components/LpConfirmHost.vue +59 -0
  257. package/src/components/LpContextMenu.vue +14 -115
  258. package/src/components/LpDatePicker.vue +99 -68
  259. package/src/components/LpDrawer.vue +56 -19
  260. package/src/components/LpDropzone.vue +21 -4
  261. package/src/components/LpFileTree.vue +33 -7
  262. package/src/components/LpFormField.vue +30 -7
  263. package/src/components/LpImageEditor.vue +96 -57
  264. package/src/components/LpInput.vue +18 -7
  265. package/src/components/LpLightbox.vue +140 -29
  266. package/src/components/LpLink.vue +24 -13
  267. package/src/components/LpLogViewer.vue +86 -17
  268. package/src/components/LpMapPicker.vue +36 -13
  269. package/src/components/LpMediaGallery.vue +8 -2
  270. package/src/components/LpModal.vue +53 -27
  271. package/src/components/LpModalNav.vue +5 -2
  272. package/src/components/LpNotificationBell.vue +63 -17
  273. package/src/components/LpNumberField.vue +21 -6
  274. package/src/components/LpNumberFlow.vue +26 -10
  275. package/src/components/LpOtpInput.vue +43 -8
  276. package/src/components/LpPagination.vue +23 -7
  277. package/src/components/LpPasswordInput.vue +4 -1
  278. package/src/components/LpPhoneInput.vue +53 -27
  279. package/src/components/LpPie.vue +94 -15
  280. package/src/components/LpPopover.vue +3 -0
  281. package/src/components/LpProgress.vue +5 -3
  282. package/src/components/LpRadioGroup.vue +9 -2
  283. package/src/components/LpSegmented.vue +12 -2
  284. package/src/components/LpSelect.vue +0 -0
  285. package/src/components/LpShortcutsDialog.vue +8 -3
  286. package/src/components/LpSidebar.vue +14 -9
  287. package/src/components/LpSidebarNav.vue +17 -5
  288. package/src/components/LpSkeleton.vue +4 -1
  289. package/src/components/LpSlider.vue +7 -1
  290. package/src/components/LpSparkline.vue +11 -3
  291. package/src/components/LpSwitch.vue +30 -7
  292. package/src/components/LpTable.vue +91 -32
  293. package/src/components/LpTableOfContents.vue +50 -32
  294. package/src/components/LpTabs.vue +23 -6
  295. package/src/components/LpTextarea.vue +44 -6
  296. package/src/components/LpThemeProvider.vue +41 -0
  297. package/src/components/LpThemeSwitcher.vue +64 -17
  298. package/src/components/LpTilt.vue +2 -2
  299. package/src/components/LpTimeField.vue +14 -3
  300. package/src/components/LpToaster.vue +66 -26
  301. package/src/components/LpTooltip.vue +22 -5
  302. package/src/components/LpTooltipProvider.vue +24 -0
  303. package/src/components/LpTopologyCanvas.vue +12 -1
  304. package/src/components/LpUptimeBar.vue +46 -8
  305. package/src/components/chart.ts +77 -5
  306. package/src/components/codeHighlight.ts +111 -15
  307. package/src/components/color.ts +30 -9
  308. package/src/components/confirm.ts +58 -0
  309. package/src/components/countries.ts +12 -0
  310. package/src/components/fileTree.ts +19 -7
  311. package/src/components/imageCrop.ts +107 -0
  312. package/src/components/link.ts +34 -0
  313. package/src/components/modalDepth.ts +48 -6
  314. package/src/components/otp.ts +16 -0
  315. package/src/components/sidebar.ts +7 -1
  316. package/src/components/tableSort.ts +37 -0
  317. package/src/composables/useField.ts +79 -0
  318. package/src/composables/useFormDraft.ts +25 -20
  319. package/src/composables/useHotkeys.ts +89 -18
  320. package/src/composables/useInputFilter.ts +77 -14
  321. package/src/composables/useLpConfig.ts +87 -0
  322. package/src/composables/useRovingFocus.ts +11 -0
  323. package/src/composables/useTilt.ts +5 -6
  324. package/src/composables/useToast.ts +56 -4
  325. package/src/composables/useZoomPan.ts +6 -6
  326. package/src/index.ts +37 -2
  327. package/src/layout/tree.ts +30 -0
  328. package/src/layout/useLayout.ts +68 -18
  329. package/src/nuxt.ts +59 -17
  330. package/src/runtime/config.plugin.ts +14 -0
  331. package/src/runtime/theme.plugin.ts +42 -22
  332. package/src/theme/useTheme.ts +206 -100
  333. package/dist/chunks/LpBreadcrumbs-p4jHe_V-.js +0 -62
  334. package/dist/chunks/LpContextMenu-CwW1QXe_.js +0 -217
  335. package/dist/chunks/LpDatePicker-CwOToxGq.js +0 -164
  336. package/dist/chunks/LpFormField-CazBc2-a.js +0 -62
  337. package/dist/chunks/LpTextarea-t7F6gv8E.js +0 -47
@@ -50,7 +50,15 @@ export function clampHsv(c: Hsv): Hsv {
50
50
  }
51
51
  }
52
52
 
53
- export function hsvToRgb({ h, s, v, a }: Hsv): Rgb {
53
+ // NaN and Infinity read as 0: one bad channel must not throw out of a public
54
+ // conversion or poison the others.
55
+ const finite = (n: number) => (Number.isFinite(n) ? n : 0)
56
+
57
+ export function hsvToRgb(color: Hsv): Rgb {
58
+ const h = finite(color.h)
59
+ const s = clamp(finite(color.s), 0, 1)
60
+ const v = clamp(finite(color.v), 0, 1)
61
+ const a = Number.isFinite(color.a) ? color.a : 1
54
62
  const hue = normalizeHue(h) / 60
55
63
  const c = v * s
56
64
  const x = c * (1 - Math.abs((hue % 2) - 1))
@@ -99,10 +107,20 @@ export function rgbToHsv({ r, g, b, a }: Rgb): Hsv {
99
107
 
100
108
  const hex2 = (n: number) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0")
101
109
 
110
+ /**
111
+ * Alpha quantised to the one precision every format can carry, a byte, so hex,
112
+ * rgb and hsl agree on whether a colour is opaque: 0.996 used to print as an
113
+ * opaque rgb() and as "#…fe" in hex.
114
+ */
115
+ function alphaByte(a: number): number {
116
+ return Math.round(clamp(a, 0, 1) * 255)
117
+ }
118
+
102
119
  /** "#rrggbb", or "#rrggbbaa" when the colour is not fully opaque. */
103
120
  export function rgbToHex({ r, g, b, a }: Rgb): string {
104
121
  const base = `#${hex2(r)}${hex2(g)}${hex2(b)}`
105
- return a >= 1 ? base : base + hex2(a * 255)
122
+ const byte = alphaByte(a)
123
+ return byte >= 255 ? base : base + hex2(byte)
106
124
  }
107
125
 
108
126
  /**
@@ -199,20 +217,23 @@ export function hsvToHsl({ h, s, v }: Hsv): { h: number; s: number; l: number }
199
217
  /** The colour as a CSS string in the requested format. */
200
218
  export function formatColor(c: Hsv, format: ColorFormat): string {
201
219
  const rgb = hsvToRgb(c)
202
- const a = Math.round(clamp(c.a, 0, 1) * 100) / 100
220
+ const byte = alphaByte(rgb.a)
221
+ const a = Math.round((byte / 255) * 1000) / 1000
203
222
 
204
223
  if (format === "hex") return rgbToHex(rgb)
205
224
  if (format === "rgb") {
206
- return a >= 1
225
+ return byte >= 255
207
226
  ? `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`
208
227
  : `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a})`
209
228
  }
210
229
 
211
- const { h, s, l } = hsvToHsl(c)
212
- const hh = Math.round(h)
213
- const ss = Math.round(s * 100)
214
- const ll = Math.round(l * 100)
215
- return a >= 1 ? `hsl(${hh}, ${ss}%, ${ll}%)` : `hsla(${hh}, ${ss}%, ${ll}%, ${a})`
230
+ // One decimal: whole percentages drift up to 4 per channel on the way back.
231
+ const { h, s, l } = hsvToHsl({ ...c, h: finite(c.h), s: clamp(finite(c.s), 0, 1), v: clamp(finite(c.v), 0, 1) })
232
+ const dec = (n: number) => Math.round(n * 10) / 10
233
+ const hh = dec(h) % 360
234
+ const ss = dec(s * 100)
235
+ const ll = dec(l * 100)
236
+ return byte >= 255 ? `hsl(${hh}, ${ss}%, ${ll}%)` : `hsla(${hh}, ${ss}%, ${ll}%, ${a})`
216
237
  }
217
238
 
218
239
  /** Always opaque — for the swatch behind an alpha checkerboard, and for rails. */
@@ -0,0 +1,58 @@
1
+ import { reactive } from "vue"
2
+
3
+ /*
4
+ * Imperative confirmation: `if (await confirm({ title: "Delete?" })) ...`.
5
+ * Requests queue here and the one mounted LpConfirmHost shows them one at a
6
+ * time. Pinned to a global symbol like the toast queue, so a barrel and a deep
7
+ * import share one host.
8
+ */
9
+
10
+ export interface ConfirmOptions {
11
+ title: string
12
+ description?: string
13
+ danger?: boolean
14
+ icon?: string
15
+ confirmLabel?: string
16
+ cancelLabel?: string
17
+ loadingLabel?: string
18
+ /**
19
+ * Work to run on confirm. The dialog stays open with a loading state while it
20
+ * runs and shows the error's message if it rejects; the promise from
21
+ * `confirm()` resolves true only once it succeeded.
22
+ */
23
+ action?: () => unknown
24
+ }
25
+
26
+ export interface ConfirmRequest extends ConfirmOptions {
27
+ id: number
28
+ resolve: (confirmed: boolean) => void
29
+ }
30
+
31
+ const STATE_KEY = Symbol.for("@leavepulse/ui:confirm-queue")
32
+ type GlobalWithConfirm = typeof globalThis & {
33
+ [STATE_KEY]?: { queue: ConfirmRequest[]; nextId: number }
34
+ }
35
+ const g = globalThis as GlobalWithConfirm
36
+ const state = (g[STATE_KEY] ??= { queue: reactive<ConfirmRequest[]>([]), nextId: 0 })
37
+
38
+ export const confirmQueue = state.queue
39
+
40
+ /** Ask for confirmation. Resolves false when the user cancels or dismisses. */
41
+ export function confirm(options: ConfirmOptions): Promise<boolean> {
42
+ if (typeof window === "undefined") return Promise.resolve(false)
43
+ return new Promise((resolve) => {
44
+ state.queue.push({ ...options, id: state.nextId++, resolve })
45
+ })
46
+ }
47
+
48
+ /** Settle a request and drop it from the queue. Used by the host. */
49
+ export function settleConfirm(id: number, confirmed: boolean): void {
50
+ const i = state.queue.findIndex((r) => r.id === id)
51
+ if (i === -1) return
52
+ const [request] = state.queue.splice(i, 1)
53
+ request?.resolve(confirmed)
54
+ }
55
+
56
+ export function useConfirm(): typeof confirm {
57
+ return confirm
58
+ }
@@ -65,3 +65,15 @@ export function matchCountryByValue(
65
65
  }
66
66
  return best
67
67
  }
68
+
69
+ /**
70
+ * Countries matching a search query: by name, or by dial code when the query
71
+ * carries digits ("+44", "44"). A query without digits must not match on the
72
+ * dial code at all, or every country would match the empty string.
73
+ */
74
+ export function filterCountries(list: Country[], query: string): Country[] {
75
+ const q = query.trim().toLowerCase()
76
+ if (!q) return list
77
+ const digits = q.replace(/\D/g, "")
78
+ return list.filter((c) => c.name.toLowerCase().includes(q) || (digits !== "" && c.dial.includes(digits)))
79
+ }
@@ -126,7 +126,8 @@ export function formatSize(bytes: number): string {
126
126
  const units = ["KB", "MB", "GB", "TB", "PB"]
127
127
  let value = bytes / 1024
128
128
  let i = 0
129
- while (value >= 1024 && i < units.length - 1) {
129
+ // Compare the value as it will print, so 1 048 575 B is "1.0 MB", not "1024 KB".
130
+ while (Math.round(value) >= 1024 && i < units.length - 1) {
130
131
  value /= 1024
131
132
  i++
132
133
  }
@@ -191,14 +192,20 @@ export function checkStateOf(node: FileNode, checked: ReadonlySet<string>): Chec
191
192
  }
192
193
  let all = true
193
194
  let any = false
195
+ let considered = 0
194
196
  for (const child of node.children) {
195
197
  const state = checkStateOf(child, checked)
198
+ // A locked, unticked entry can never be ticked, so it must not hold its
199
+ // folder at "indeterminate" forever.
200
+ if (child.disabled && state === "unchecked") continue
201
+ considered++
196
202
  if (state === "checked") any = true
197
203
  else if (state === "indeterminate") {
198
204
  any = true
199
205
  all = false
200
206
  } else all = false
201
207
  }
208
+ if (!considered) return checked.has(node.id) ? "checked" : "unchecked"
202
209
  if (all) return "checked"
203
210
  return any ? "indeterminate" : "unchecked"
204
211
  }
@@ -271,14 +278,18 @@ export function computeStats(
271
278
  let total = 0
272
279
  let bytes = 0
273
280
  let sized = node.size !== undefined
281
+ let considered = 0
274
282
 
275
283
  for (const child of node.children) {
276
284
  const childStats = visit(child)
277
- if (childStats.state === "checked") any = true
278
- else if (childStats.state === "indeterminate") {
279
- any = true
280
- all = false
281
- } else all = false
285
+ if (!child.disabled || childStats.state !== "unchecked") {
286
+ considered++
287
+ if (childStats.state === "checked") any = true
288
+ else if (childStats.state === "indeterminate") {
289
+ any = true
290
+ all = false
291
+ } else all = false
292
+ }
282
293
  count += childStats.checked
283
294
  total += childStats.total
284
295
  if (childStats.size !== undefined) {
@@ -287,8 +298,9 @@ export function computeStats(
287
298
  }
288
299
  }
289
300
 
301
+ const own: CheckState = checked.has(node.id) ? "checked" : "unchecked"
290
302
  const self: NodeStats = {
291
- state: all ? "checked" : any ? "indeterminate" : "unchecked",
303
+ state: !considered ? own : all ? "checked" : any ? "indeterminate" : "unchecked",
292
304
  checked: count,
293
305
  total,
294
306
  // An explicit size on the directory wins: a server-side listing usually
@@ -0,0 +1,107 @@
1
+ /*
2
+ * Geometry shared by LpImageEditor's preview and its export, so what is drawn
3
+ * on screen and what is written to the Blob come from one calculation.
4
+ *
5
+ * Three spaces are involved:
6
+ * - source: the picture's own pixels, origin at its centre;
7
+ * - upright: the source after the rotation, as the person sees it, origin at
8
+ * its centre, still in source-pixel units;
9
+ * - frame: the crop box on screen, origin at its centre, in CSS pixels.
10
+ *
11
+ * A turn of θ (clockwise, as CSS and canvas both rotate in a y-down space)
12
+ * maps source to upright by u = R(θ)·q. The picture is drawn at `cover` CSS
13
+ * pixels per source pixel, then panned by `offset` and zoomed by `scale`, so a
14
+ * frame point f shows the upright point (f / scale − offset) / cover.
15
+ */
16
+
17
+ export interface Point {
18
+ x: number
19
+ y: number
20
+ }
21
+
22
+ export interface CropInput {
23
+ sourceWidth: number
24
+ sourceHeight: number
25
+ frameWidth: number
26
+ frameHeight: number
27
+ /** Accumulated rotation in degrees; any angle, not only quarter turns. */
28
+ rotation: number
29
+ /** Zoom factor, 1 = the picture just covers the frame. */
30
+ scale: number
31
+ /** Pan in frame pixels before the zoom, as useZoomPan reports it. */
32
+ offset: Point
33
+ }
34
+
35
+ export interface CropGeometry {
36
+ /** CSS pixels per source pixel at zoom 1. */
37
+ cover: number
38
+ /** Crop size in upright (source-pixel) units. */
39
+ width: number
40
+ height: number
41
+ /** Crop centre in upright space. */
42
+ centre: Point
43
+ }
44
+
45
+ const radians = (deg: number) => (deg * Math.PI) / 180
46
+
47
+ /** Rotate a point clockwise by `deg` in a y-down space. */
48
+ export function rotatePoint(p: Point, deg: number): Point {
49
+ const a = radians(deg)
50
+ const cos = Math.cos(a)
51
+ const sin = Math.sin(a)
52
+ return { x: p.x * cos - p.y * sin, y: p.x * sin + p.y * cos }
53
+ }
54
+
55
+ /** The source pixel (from the source's top-left) under an upright-space point. */
56
+ export function uprightToSource(u: Point, rotation: number, sourceWidth: number, sourceHeight: number): Point {
57
+ const q = rotatePoint(u, -rotation)
58
+ return { x: q.x + sourceWidth / 2, y: q.y + sourceHeight / 2 }
59
+ }
60
+
61
+ /**
62
+ * Smallest scale at which the turned picture still covers the whole frame.
63
+ *
64
+ * Covering the bounding box of a turned picture is not enough at a free angle:
65
+ * its corners are empty. The frame, turned back by the same angle, has to fit
66
+ * inside the unturned picture; for a quarter turn this reduces to the usual
67
+ * object-cover of the swapped dimensions.
68
+ */
69
+ export function coverScale(
70
+ sourceWidth: number,
71
+ sourceHeight: number,
72
+ frameWidth: number,
73
+ frameHeight: number,
74
+ rotation: number,
75
+ ): number {
76
+ const a = radians(rotation)
77
+ const cos = Math.abs(Math.cos(a))
78
+ const sin = Math.abs(Math.sin(a))
79
+ return Math.max(
80
+ (frameWidth * cos + frameHeight * sin) / sourceWidth,
81
+ (frameWidth * sin + frameHeight * cos) / sourceHeight,
82
+ )
83
+ }
84
+
85
+ /** What the frame holds, in upright space. */
86
+ export function cropGeometry(input: CropInput): CropGeometry {
87
+ const cover = coverScale(
88
+ input.sourceWidth,
89
+ input.sourceHeight,
90
+ input.frameWidth,
91
+ input.frameHeight,
92
+ input.rotation,
93
+ )
94
+ const shown = cover * input.scale
95
+ return {
96
+ cover,
97
+ width: input.frameWidth / shown,
98
+ height: input.frameHeight / shown,
99
+ centre: { x: -input.offset.x / cover, y: -input.offset.y / cover },
100
+ }
101
+ }
102
+
103
+ /** Whether the turn is closer to sideways than to upright. */
104
+ export function isSideways(rotation: number): boolean {
105
+ const quarters = Math.round(rotation / 90)
106
+ return ((quarters % 2) + 2) % 2 === 1
107
+ }
@@ -0,0 +1,34 @@
1
+ import type { Component } from "vue"
2
+
3
+ /*
4
+ * How a navigation target renders: an internal destination goes through the
5
+ * configured link component (RouterLink, NuxtLink) so it navigates client-side,
6
+ * anything else is a plain anchor. Shared by every kit piece that links.
7
+ */
8
+ export type LinkTarget = string | Record<string, unknown>
9
+
10
+ export interface ResolvedLink {
11
+ is: Component | string
12
+ props: Record<string, unknown>
13
+ }
14
+
15
+ /** An app-relative path: "/x", not "//host" or "https://…". */
16
+ export function isInternalHref(href: string): boolean {
17
+ return href.startsWith("/") && !href.startsWith("//")
18
+ }
19
+
20
+ export function resolveLink(
21
+ linkComponent: Component | string,
22
+ target: { to?: LinkTarget; href?: string },
23
+ ): ResolvedLink | null {
24
+ const router = linkComponent !== "a"
25
+ if (target.to != null) {
26
+ if (router) return { is: linkComponent, props: { to: target.to } }
27
+ return typeof target.to === "string" ? { is: "a", props: { href: target.to } } : null
28
+ }
29
+ if (target.href != null) {
30
+ if (router && isInternalHref(target.href)) return { is: linkComponent, props: { to: target.href } }
31
+ return { is: "a", props: { href: target.href } }
32
+ }
33
+ return null
34
+ }
@@ -1,16 +1,47 @@
1
- import { computed, ref, type ComputedRef } from "vue"
1
+ import { computed, ref, type ComputedRef, type Ref } from "vue"
2
2
 
3
3
  const STEP = 20
4
4
 
5
- const stack = ref<number[]>([])
6
- let nextId = 0
5
+ /*
6
+ * One opening-order stack for every modal surface. Pinned to a global symbol
7
+ * like the toast queue: a barrel import and a deep import can load this module
8
+ * twice, and two stacks would put a deep-imported dialog's scrim under a
9
+ * barrel-imported drawer's panel.
10
+ */
11
+ const STATE_KEY = Symbol.for("@leavepulse/ui:modal-stack")
12
+ type GlobalWithStack = typeof globalThis & {
13
+ [STATE_KEY]?: {
14
+ stack: Ref<number[]>
15
+ nextId: number
16
+ elements: Map<number, () => HTMLElement | null | undefined>
17
+ }
18
+ }
19
+ const g = globalThis as GlobalWithStack
20
+ const state = (g[STATE_KEY] ??= { stack: ref<number[]>([]), nextId: 0, elements: new Map() })
21
+ const stack = state.stack
7
22
 
8
- export function useModalLayer(): {
23
+ export interface ModalLayer {
24
+ /** Join the stack on top. Ignored on the server, where nothing would ever release it. */
9
25
  claim: () => void
10
26
  release: () => void
27
+ /** z-index values for this surface's scrim and panel, and whether another modal is under it. */
11
28
  layer: ComputedRef<{ scrim: string; panel: string; nested: boolean }>
12
- } {
13
- const id = nextId++
29
+ /** Whether this surface is the topmost open one. */
30
+ top: ComputedRef<boolean>
31
+ }
32
+
33
+ export interface ModalLayerOptions {
34
+ /** The surface's panel, so keyboard handling can tell what sits inside the top modal. */
35
+ element?: () => HTMLElement | null | undefined
36
+ }
37
+
38
+ /**
39
+ * Put an overlay on the shared modal stack, so a surface opened from another
40
+ * one (a dialog from a drawer, a lightbox from a dialog) lands above it with
41
+ * its scrim, whatever component either of them is.
42
+ */
43
+ export function useModalLayer(options: ModalLayerOptions = {}): ModalLayer {
44
+ const id = state.nextId++
14
45
 
15
46
  const fromTop = computed(() => {
16
47
  const at = stack.value.indexOf(id)
@@ -19,9 +50,12 @@ export function useModalLayer(): {
19
50
 
20
51
  return {
21
52
  claim: () => {
53
+ if (typeof window === "undefined") return
54
+ if (options.element) state.elements.set(id, options.element)
22
55
  if (!stack.value.includes(id)) stack.value = [...stack.value, id]
23
56
  },
24
57
  release: () => {
58
+ state.elements.delete(id)
25
59
  stack.value = stack.value.filter((x) => x !== id)
26
60
  },
27
61
  layer: computed(() => {
@@ -32,9 +66,17 @@ export function useModalLayer(): {
32
66
  nested: stack.value.indexOf(id) > 0,
33
67
  }
34
68
  }),
69
+ top: computed(() => stack.value[stack.value.length - 1] === id),
35
70
  }
36
71
  }
37
72
 
38
73
  export function anyModalOpen(): boolean {
39
74
  return stack.value.length > 0
40
75
  }
76
+
77
+ /** The panel of the topmost modal, when it registered one. */
78
+ export function topModalElement(): HTMLElement | null {
79
+ const id = stack.value[stack.value.length - 1]
80
+ if (id === undefined) return null
81
+ return state.elements.get(id)?.() ?? null
82
+ }
@@ -0,0 +1,16 @@
1
+ /*
2
+ * The bridge between reka PinInput's per-cell array and LpOtpInput's string
3
+ * model. reka empties a cell by deleting its slot (numeric mode) or setting it
4
+ * to "", so the array can have holes in the middle.
5
+ */
6
+ export type OtpCells = (string | number | undefined)[]
7
+
8
+ /** The code typed so far: the filled cells in order. */
9
+ export function joinCells(cells: OtpCells): string {
10
+ return Array.from(cells, (c) => (c === undefined ? "" : String(c))).join("")
11
+ }
12
+
13
+ /** Cells for a code given from outside, one character each. */
14
+ export function splitCode(code: string | undefined, length: number): string[] {
15
+ return (code ?? "").split("").slice(0, length)
16
+ }
@@ -10,8 +10,14 @@ export interface SidebarItem {
10
10
  id: string
11
11
  label: string
12
12
  icon?: string
13
- /** Render as a link instead of a button. */
13
+ /**
14
+ * Render as a link instead of a button. An app-relative path goes through
15
+ * the link component from provideLpConfig (NuxtLink under the Nuxt module),
16
+ * so it navigates client-side; other URLs render a plain anchor.
17
+ */
14
18
  href?: string
19
+ /** Router destination (rendered through the configured link component). */
20
+ to?: string | Record<string, unknown>
15
21
  /** Count chip on the right (number or short string). */
16
22
  badge?: number | string
17
23
  disabled?: boolean
@@ -0,0 +1,37 @@
1
+ const COLLATOR = new Intl.Collator(undefined, { numeric: true })
2
+
3
+ const rank = (v: unknown): number => {
4
+ if (v == null || (typeof v === "number" && Number.isNaN(v))) return 0
5
+ if (typeof v === "boolean") return 1
6
+ if (typeof v === "number" || typeof v === "bigint") return 2
7
+ if (v instanceof Date) return Number.isNaN(v.getTime()) ? 0 : 3
8
+ return 4
9
+ }
10
+
11
+ /**
12
+ * Ascending order for raw cell values. Empty values (null, undefined, NaN, an
13
+ * invalid Date) come first; numbers, bigints and Dates compare by magnitude,
14
+ * booleans false before true, everything else as text with numeric runs read as
15
+ * numbers ("file 2" before "file 10"). Mixed kinds group by kind so the order
16
+ * is total and a sort is stable from one run to the next.
17
+ */
18
+ export function compareValues(a: unknown, b: unknown): number {
19
+ const ra = rank(a)
20
+ const rb = rank(b)
21
+ if (ra !== rb) return ra - rb
22
+ switch (ra) {
23
+ case 0:
24
+ return 0
25
+ case 1:
26
+ return Number(a) - Number(b)
27
+ case 2: {
28
+ const x = a as number | bigint
29
+ const y = b as number | bigint
30
+ return x < y ? -1 : x > y ? 1 : 0
31
+ }
32
+ case 3:
33
+ return (a as Date).getTime() - (b as Date).getTime()
34
+ default:
35
+ return COLLATOR.compare(String(a), String(b))
36
+ }
37
+ }
@@ -0,0 +1,79 @@
1
+ import { computed, inject, provide, useId, type ComputedRef, type InjectionKey } from "vue"
2
+
3
+ /*
4
+ * What a form field tells the control inside it: the id its label points at,
5
+ * the id of the hint/error line the control should be described by, and the
6
+ * states the control should reflect. LpFormField provides it; every kit control
7
+ * consumes it, and so can a consumer's own control placed inside a field.
8
+ */
9
+ export interface FieldContext {
10
+ id: string
11
+ /** Id of the visible label, for controls a `<label for>` cannot name (groups). */
12
+ labelId?: string
13
+ describedBy?: string
14
+ invalid: boolean
15
+ required: boolean
16
+ disabled: boolean
17
+ }
18
+
19
+ export const LP_FIELD: InjectionKey<ComputedRef<FieldContext>> = Symbol.for("lp-field")
20
+
21
+ export function provideField(context: ComputedRef<FieldContext>): void {
22
+ provide(LP_FIELD, context)
23
+ }
24
+
25
+ /** The nearest LpFormField's context, or null outside one. */
26
+ export function useField(): ComputedRef<FieldContext> | null {
27
+ return inject(LP_FIELD, null)
28
+ }
29
+
30
+ export function fieldMessageId(id: string): string {
31
+ return `${id}-message`
32
+ }
33
+
34
+ export interface FieldControlState {
35
+ /** The control wraps itself in its own LpFormField (it has a label, hint or error prop). */
36
+ wrapped: boolean
37
+ /** That own field renders a hint or error line. */
38
+ hasMessage?: boolean
39
+ /** An id the consumer set on the control, which must win over a generated one. */
40
+ id?: string
41
+ invalid?: boolean
42
+ required?: boolean
43
+ disabled?: boolean
44
+ }
45
+
46
+ /**
47
+ * The field state a control renders with: its own wrapping field when it has
48
+ * one, otherwise the field around it, with the control's own flags or-ed in.
49
+ * `id` is undefined only for a bare control outside any field.
50
+ */
51
+ export function useFieldControl(
52
+ own: () => FieldControlState,
53
+ ): ComputedRef<Omit<FieldContext, "id"> & { id?: string }> {
54
+ const outer = useField()
55
+ const generated = useId()
56
+ return computed(() => {
57
+ const state = own()
58
+ if (state.wrapped) {
59
+ const id = state.id ?? generated
60
+ return {
61
+ id,
62
+ labelId: undefined,
63
+ describedBy: state.hasMessage ? fieldMessageId(id) : undefined,
64
+ invalid: !!state.invalid,
65
+ required: !!state.required,
66
+ disabled: !!state.disabled,
67
+ }
68
+ }
69
+ const field = outer?.value
70
+ return {
71
+ id: state.id ?? field?.id,
72
+ labelId: field?.labelId,
73
+ describedBy: field?.describedBy,
74
+ invalid: !!state.invalid || !!field?.invalid,
75
+ required: !!state.required || !!field?.required,
76
+ disabled: !!state.disabled || !!field?.disabled,
77
+ }
78
+ })
79
+ }