@leavepulse/ui 0.43.3 → 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 (340) hide show
  1. package/README.md +75 -7
  2. package/dist/assets/{LpCommandPalette-DH7sPcKP.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 -10
  103. package/dist/components/LpCommandPalette.vue.js +88 -78
  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/commandSearch.d.ts +0 -1
  198. package/dist/components/commandSearch.js +1 -17
  199. package/dist/components/confirm.d.ts +25 -0
  200. package/dist/components/confirm.js +26 -0
  201. package/dist/components/countries.d.ts +6 -0
  202. package/dist/components/countries.js +7 -0
  203. package/dist/components/fileTree.js +16 -7
  204. package/dist/components/imageCrop.d.ts +42 -0
  205. package/dist/components/imageCrop.js +47 -0
  206. package/dist/components/link.d.ts +12 -0
  207. package/dist/components/link.js +19 -0
  208. package/dist/components/modalDepth.d.ts +18 -2
  209. package/dist/components/modalDepth.js +17 -5
  210. package/dist/components/otp.d.ts +5 -0
  211. package/dist/components/otp.js +10 -0
  212. package/dist/components/sidebar.d.ts +7 -1
  213. package/dist/components/tableSort.d.ts +8 -0
  214. package/dist/components/tableSort.js +31 -0
  215. package/dist/composables/useField.d.ts +34 -0
  216. package/dist/composables/useField.js +45 -0
  217. package/dist/composables/useFormDraft.d.ts +10 -3
  218. package/dist/composables/useFormDraft.js +9 -14
  219. package/dist/composables/useHotkeys.d.ts +17 -1
  220. package/dist/composables/useHotkeys.js +55 -18
  221. package/dist/composables/useInputFilter.d.ts +17 -0
  222. package/dist/composables/useInputFilter.js +47 -10
  223. package/dist/composables/useLpConfig.d.ts +24 -0
  224. package/dist/composables/useLpConfig.js +44 -0
  225. package/dist/composables/useRovingFocus.js +7 -0
  226. package/dist/composables/useTilt.d.ts +2 -2
  227. package/dist/composables/useTilt.js +5 -6
  228. package/dist/composables/useToast.d.ts +20 -0
  229. package/dist/composables/useToast.js +28 -4
  230. package/dist/composables/useZoomPan.d.ts +3 -3
  231. package/dist/composables/useZoomPan.js +4 -4
  232. package/dist/index.d.ts +33 -3
  233. package/dist/index.js +185 -139
  234. package/dist/layout/tree.d.ts +2 -0
  235. package/dist/layout/tree.js +16 -1
  236. package/dist/layout/useLayout.d.ts +15 -0
  237. package/dist/layout/useLayout.js +41 -14
  238. package/dist/theme/useTheme.d.ts +32 -9
  239. package/package.json +8 -7
  240. package/src/component-names.ts +5 -7
  241. package/src/components/ContextMenuItems.vue +88 -0
  242. package/src/components/LayoutCanvas.vue +26 -6
  243. package/src/components/LayoutNode.vue +19 -2
  244. package/src/components/LpActionBar.vue +30 -7
  245. package/src/components/LpAddressInput.vue +57 -41
  246. package/src/components/LpAlert.vue +16 -3
  247. package/src/components/LpAppShell.vue +45 -12
  248. package/src/components/LpAutocomplete.vue +46 -10
  249. package/src/components/LpAvatar.vue +19 -5
  250. package/src/components/LpBreadcrumbs.vue +65 -41
  251. package/src/components/LpButton.vue +59 -13
  252. package/src/components/LpChart.vue +272 -28
  253. package/src/components/LpCheckbox.vue +7 -3
  254. package/src/components/LpCodeBlock.vue +50 -14
  255. package/src/components/LpColorPicker.vue +39 -9
  256. package/src/components/LpCommandPalette.vue +36 -25
  257. package/src/components/LpConfirmDialog.vue +68 -17
  258. package/src/components/LpConfirmHost.vue +59 -0
  259. package/src/components/LpContextMenu.vue +14 -115
  260. package/src/components/LpDatePicker.vue +99 -68
  261. package/src/components/LpDrawer.vue +56 -19
  262. package/src/components/LpDropzone.vue +21 -4
  263. package/src/components/LpFileTree.vue +33 -7
  264. package/src/components/LpFormField.vue +30 -7
  265. package/src/components/LpImageEditor.vue +96 -57
  266. package/src/components/LpInput.vue +18 -7
  267. package/src/components/LpLightbox.vue +140 -29
  268. package/src/components/LpLink.vue +24 -13
  269. package/src/components/LpLogViewer.vue +86 -17
  270. package/src/components/LpMapPicker.vue +36 -13
  271. package/src/components/LpMediaGallery.vue +8 -2
  272. package/src/components/LpModal.vue +53 -27
  273. package/src/components/LpModalNav.vue +5 -2
  274. package/src/components/LpNotificationBell.vue +63 -17
  275. package/src/components/LpNumberField.vue +21 -6
  276. package/src/components/LpNumberFlow.vue +26 -10
  277. package/src/components/LpOtpInput.vue +43 -8
  278. package/src/components/LpPagination.vue +23 -7
  279. package/src/components/LpPasswordInput.vue +4 -1
  280. package/src/components/LpPhoneInput.vue +53 -27
  281. package/src/components/LpPie.vue +94 -15
  282. package/src/components/LpPopover.vue +3 -0
  283. package/src/components/LpProgress.vue +5 -3
  284. package/src/components/LpRadioGroup.vue +9 -2
  285. package/src/components/LpSegmented.vue +12 -2
  286. package/src/components/LpSelect.vue +0 -0
  287. package/src/components/LpShortcutsDialog.vue +8 -3
  288. package/src/components/LpSidebar.vue +14 -9
  289. package/src/components/LpSidebarNav.vue +17 -5
  290. package/src/components/LpSkeleton.vue +4 -1
  291. package/src/components/LpSlider.vue +7 -1
  292. package/src/components/LpSparkline.vue +11 -3
  293. package/src/components/LpSwitch.vue +30 -7
  294. package/src/components/LpTable.vue +91 -32
  295. package/src/components/LpTableOfContents.vue +50 -32
  296. package/src/components/LpTabs.vue +23 -6
  297. package/src/components/LpTextarea.vue +44 -6
  298. package/src/components/LpThemeProvider.vue +41 -0
  299. package/src/components/LpThemeSwitcher.vue +64 -17
  300. package/src/components/LpTilt.vue +2 -2
  301. package/src/components/LpTimeField.vue +14 -3
  302. package/src/components/LpToaster.vue +66 -26
  303. package/src/components/LpTooltip.vue +22 -5
  304. package/src/components/LpTooltipProvider.vue +24 -0
  305. package/src/components/LpTopologyCanvas.vue +12 -1
  306. package/src/components/LpUptimeBar.vue +46 -8
  307. package/src/components/chart.ts +77 -5
  308. package/src/components/codeHighlight.ts +111 -15
  309. package/src/components/color.ts +30 -9
  310. package/src/components/commandSearch.ts +0 -17
  311. package/src/components/confirm.ts +58 -0
  312. package/src/components/countries.ts +12 -0
  313. package/src/components/fileTree.ts +19 -7
  314. package/src/components/imageCrop.ts +107 -0
  315. package/src/components/link.ts +34 -0
  316. package/src/components/modalDepth.ts +48 -6
  317. package/src/components/otp.ts +16 -0
  318. package/src/components/sidebar.ts +7 -1
  319. package/src/components/tableSort.ts +37 -0
  320. package/src/composables/useField.ts +79 -0
  321. package/src/composables/useFormDraft.ts +25 -20
  322. package/src/composables/useHotkeys.ts +89 -18
  323. package/src/composables/useInputFilter.ts +77 -14
  324. package/src/composables/useLpConfig.ts +87 -0
  325. package/src/composables/useRovingFocus.ts +11 -0
  326. package/src/composables/useTilt.ts +5 -6
  327. package/src/composables/useToast.ts +56 -4
  328. package/src/composables/useZoomPan.ts +6 -6
  329. package/src/index.ts +38 -3
  330. package/src/layout/tree.ts +30 -0
  331. package/src/layout/useLayout.ts +68 -18
  332. package/src/nuxt.ts +59 -17
  333. package/src/runtime/config.plugin.ts +14 -0
  334. package/src/runtime/theme.plugin.ts +42 -22
  335. package/src/theme/useTheme.ts +206 -100
  336. package/dist/chunks/LpBreadcrumbs-p4jHe_V-.js +0 -62
  337. package/dist/chunks/LpContextMenu-CwW1QXe_.js +0 -217
  338. package/dist/chunks/LpDatePicker-CwOToxGq.js +0 -164
  339. package/dist/chunks/LpFormField-CazBc2-a.js +0 -62
  340. package/dist/chunks/LpTextarea-t7F6gv8E.js +0 -47
@@ -1,4 +1,4 @@
1
- import { computed, ref, type ComputedRef, type CSSProperties, type Ref } from "vue"
1
+ import { computed, ref, toValue, type ComputedRef, type CSSProperties, type MaybeRefOrGetter, type Ref } from "vue"
2
2
 
3
3
  /*
4
4
  * useZoomPan — zoom + pan for a single element inside a viewport, the way an
@@ -15,8 +15,8 @@ import { computed, ref, type ComputedRef, type CSSProperties, type Ref } from "v
15
15
  export interface UseZoomPanOptions {
16
16
  /** Smallest allowed scale. Default 1 (fit — never shrink past the viewport). */
17
17
  min?: number
18
- /** Largest allowed scale. Default 8. */
19
- max?: number
18
+ /** Largest allowed scale. Default 8. Read on every change, so it may be a getter. */
19
+ max?: MaybeRefOrGetter<number | undefined>
20
20
  /** Multiplier per wheel notch. Default 1.15. */
21
21
  step?: number
22
22
  /** Scale that a double-click/tap toggles to, from `min`. Default 2. */
@@ -62,7 +62,7 @@ export interface UseZoomPan {
62
62
 
63
63
  export function useZoomPan(options: UseZoomPanOptions = {}): UseZoomPan {
64
64
  const min = options.min ?? 1
65
- const max = options.max ?? 8
65
+ const maxOf = () => toValue(options.max) ?? 8
66
66
  const step = options.step ?? 1.15
67
67
  const zoomedScale = options.zoomedScale ?? 2
68
68
 
@@ -104,7 +104,7 @@ export function useZoomPan(options: UseZoomPanOptions = {}): UseZoomPan {
104
104
 
105
105
  /** Zoom to `next`, holding the viewport-relative point `px,py` in place. */
106
106
  function zoomAbout(next: number, px: number, py: number) {
107
- const clamped = clamp(next, min, max)
107
+ const clamped = clamp(next, min, maxOf())
108
108
  if (clamped === scale.value) return
109
109
  // Where the cursor sits in content space before the change; solving for the
110
110
  // same content point after gives the translate that keeps it under the
@@ -215,7 +215,7 @@ export function useZoomPan(options: UseZoomPanOptions = {}): UseZoomPan {
215
215
 
216
216
  function setView(view: { x: number; y: number; scale?: number }) {
217
217
  if (view.scale !== undefined) {
218
- scale.value = Math.min(max, Math.max(min, view.scale))
218
+ scale.value = Math.min(maxOf(), Math.max(min, view.scale))
219
219
  }
220
220
  x.value = view.x
221
221
  y.value = view.y
package/src/index.ts CHANGED
@@ -36,6 +36,7 @@ export { default as LpCalendar } from "./components/LpCalendar.vue"
36
36
  export { default as LpCard } from "./components/LpCard.vue"
37
37
  export { default as LpChart } from "./components/LpChart.vue"
38
38
  export type { ChartPointEvent } from "./components/LpChart.vue"
39
+ export type { ChartText } from "./components/LpChart.vue"
39
40
  export {
40
41
  CHART_PALETTE,
41
42
  axisTicks,
@@ -43,9 +44,12 @@ export {
43
44
  niceScale,
44
45
  } from "./components/chart"
45
46
  export type { ChartAxis, ChartPoint, ChartSeries } from "./components/chart"
47
+ export { chartSummary, markerIndex } from "./components/chart"
48
+ export type { ChartMarker, ChartThreshold } from "./components/chart"
46
49
  export { default as LpCheckbox } from "./components/LpCheckbox.vue"
47
50
  export { default as LpCodeBlock } from "./components/LpCodeBlock.vue"
48
51
  export type { CodeLang } from "./components/codeHighlight"
52
+ export { tokenizeLines } from "./components/codeHighlight"
49
53
  export { default as LpColorPicker } from "./components/LpColorPicker.vue"
50
54
  export type { ColorPickerLabels } from "./components/LpColorPicker.vue"
51
55
  export {
@@ -65,9 +69,13 @@ export {
65
69
  export type { ColorFormat, Hsv, Rgb } from "./components/color"
66
70
  export { default as LpCommandPalette } from "./components/LpCommandPalette.vue"
67
71
  export type { Command, PaletteLabels } from "./components/LpCommandPalette.vue"
68
- export { parseCommandQuery, rankCommandItems, swapCommandTerms } from "./components/commandSearch"
72
+ export { parseCommandQuery, rankCommandItems } from "./components/commandSearch"
69
73
  export type { CommandSearchTerms, SearchableCommand } from "./components/commandSearch"
70
74
  export { default as LpConfirmDialog } from "./components/LpConfirmDialog.vue"
75
+ export type { ConfirmLabels } from "./components/LpConfirmDialog.vue"
76
+ export { default as LpConfirmHost } from "./components/LpConfirmHost.vue"
77
+ export { confirm, useConfirm } from "./components/confirm"
78
+ export type { ConfirmOptions } from "./components/confirm"
71
79
  export { default as LpContextMenu } from "./components/LpContextMenu.vue"
72
80
  export type { ContextMenuItemDef } from "./components/LpContextMenu.vue"
73
81
  export { default as LpDatePicker } from "./components/LpDatePicker.vue"
@@ -80,6 +88,7 @@ export { default as LpDropzone } from "./components/LpDropzone.vue"
80
88
  export { default as LpEmptyState } from "./components/LpEmptyState.vue"
81
89
  export { default as LpFileTree } from "./components/LpFileTree.vue"
82
90
  export type { FileTreeSummary } from "./components/LpFileTree.vue"
91
+ export type { FileTreeLabels } from "./components/LpFileTree.vue"
83
92
  export type { CheckState, FileNode, NodeStats } from "./components/fileTree"
84
93
  export {
85
94
  ancestorIds,
@@ -100,11 +109,13 @@ export { default as LpInfraNode } from "./components/LpInfraNode.vue"
100
109
  export type { InfraNodeData } from "./components/LpInfraNode.vue"
101
110
  export { default as LpInput } from "./components/LpInput.vue"
102
111
  export { default as LpLightbox } from "./components/LpLightbox.vue"
112
+ export type { LightboxLabels } from "./components/LpLightbox.vue"
103
113
  export type { LightboxItem } from "./components/lightbox"
104
114
  export { fileNameOf } from "./components/lightbox"
105
115
  export { default as LpLink } from "./components/LpLink.vue"
106
116
  export { default as LpLogViewer } from "./components/LpLogViewer.vue"
107
117
  export type { LogLevel, LogLine } from "./components/LpLogViewer.vue"
118
+ export type { LogViewerLabels } from "./components/LpLogViewer.vue"
108
119
  export { default as LpMediaGallery } from "./components/LpMediaGallery.vue"
109
120
  export { default as LpModal } from "./components/LpModal.vue"
110
121
  export { default as LpModalNav } from "./components/LpModalNav.vue"
@@ -121,11 +132,12 @@ export { default as LpPagination } from "./components/LpPagination.vue"
121
132
  export { default as LpPasswordInput } from "./components/LpPasswordInput.vue"
122
133
  export { default as LpPie } from "./components/LpPie.vue"
123
134
  export type { SliceEvent } from "./components/LpPie.vue"
135
+ export type { PieText } from "./components/LpPie.vue"
124
136
  export { resolveSlices } from "./components/chart"
125
137
  export type { PieSlice, ResolvedSlice } from "./components/chart"
126
138
  export { default as LpPhoneInput } from "./components/LpPhoneInput.vue"
127
139
  export type { PhoneDetail } from "./components/LpPhoneInput.vue"
128
- export { flagEmoji, loadCountries, matchCountryByValue } from "./components/countries"
140
+ export { filterCountries, flagEmoji, loadCountries, matchCountryByValue } from "./components/countries"
129
141
  export type { Country } from "./components/countries"
130
142
  export { default as LpPopover } from "./components/LpPopover.vue"
131
143
  export { default as LpProgress } from "./components/LpProgress.vue"
@@ -154,6 +166,8 @@ export type { Step } from "./components/LpStepper.vue"
154
166
  export { default as LpSwitch } from "./components/LpSwitch.vue"
155
167
  export { default as LpTable } from "./components/LpTable.vue"
156
168
  export type { SortState, TableColumn, TablePage, TableQuery } from "./components/LpTable.vue"
169
+ export type { TableLabels } from "./components/LpTable.vue"
170
+ export { compareValues } from "./components/tableSort"
157
171
  export { default as LpTableOfContents } from "./components/LpTableOfContents.vue"
158
172
  export type { TocLink } from "./components/LpTableOfContents.vue"
159
173
  export { default as LpTabs } from "./components/LpTabs.vue"
@@ -163,6 +177,7 @@ export { default as LpThemeSwitcher } from "./components/LpThemeSwitcher.vue"
163
177
  export { default as LpTilt } from "./components/LpTilt.vue"
164
178
  export { default as LpToaster } from "./components/LpToaster.vue"
165
179
  export { default as LpTooltip } from "./components/LpTooltip.vue"
180
+ export { default as LpTooltipProvider } from "./components/LpTooltipProvider.vue"
166
181
  export { default as LpTopologyCanvas } from "./components/LpTopologyCanvas.vue"
167
182
  export type {
168
183
  CanvasViewport,
@@ -237,9 +252,16 @@ export {
237
252
  mergeShortcutGroups,
238
253
  } from "./composables/useHotkeys"
239
254
  export type { HotkeyBinding, HotkeyHelp, ShortcutEntry, ShortcutGroup } from "./composables/useHotkeys"
255
+ export { matchesHotkey, useAppleKeyboard } from "./composables/useHotkeys"
256
+ export { anyModalOpen, topModalElement, useModalLayer } from "./components/modalDepth"
257
+ export type { ModalLayer, ModalLayerOptions } from "./components/modalDepth"
240
258
  export { provideGlass, useGlass } from "./composables/useGlass"
259
+ export { LP_CONFIG, lpConfig, mergeConfig, provideLpConfig, useLabels, useLinkComponent, useLpConfig } from "./composables/useLpConfig"
260
+ export type { LabelScope, LpConfig } from "./composables/useLpConfig"
241
261
  export { useMergedClass, useMergedAttrs, mergeClass } from "./composables/useMergedClass"
242
- export { useInputFilter } from "./composables/useInputFilter"
262
+ export { anchorPattern, stripDisallowed, useInputFilter } from "./composables/useInputFilter"
263
+ export { LP_FIELD, fieldMessageId, provideField, useField, useFieldControl } from "./composables/useField"
264
+ export type { FieldContext, FieldControlState } from "./composables/useField"
243
265
  export type { UseInputFilterOptions } from "./composables/useInputFilter"
244
266
  export { easeOut, easeTravel, easingToken, prefersReducedMotion } from "./composables/easing"
245
267
  export { useReveal } from "./composables/useReveal"
@@ -265,3 +287,16 @@ export type {
265
287
  ToastOptions,
266
288
  ToastVariant,
267
289
  } from "./composables/useToast"
290
+ export type { ToastPromiseMessages } from "./composables/useToast"
291
+ export {
292
+ cacheSystemThemes,
293
+ normalizeTheme,
294
+ prefersDarkScheme,
295
+ themeEntries,
296
+ } from "./theme/useTheme"
297
+ export type { SystemThemes, UseTheme } from "./theme/useTheme"
298
+ export { layoutController } from "./layout/useLayout"
299
+ export type { LayoutController } from "./layout/useLayout"
300
+ export { default as LpThemeProvider } from "./components/LpThemeProvider.vue"
301
+ export { isInternalHref, resolveLink } from "./components/link"
302
+ export type { LinkTarget, ResolvedLink } from "./components/link"
@@ -37,6 +37,13 @@ export type LayoutNode = Leaf | Split
37
37
  let uid = 0
38
38
  const nid = () => `lp-n${uid++}`
39
39
 
40
+ // Keep fresh ids clear of ids restored from a snapshot.
41
+ function claimId(id: string): string {
42
+ const n = /^lp-n(\d+)$/.exec(id)
43
+ if (n) uid = Math.max(uid, Number(n[1]) + 1)
44
+ return id
45
+ }
46
+
40
47
  function leaf(block: string): Leaf {
41
48
  return { kind: "leaf", id: nid(), blocks: [block], active: 0, size: 1 }
42
49
  }
@@ -286,3 +293,26 @@ export function deserializeLayout(data: SerializedNode): Split {
286
293
  }
287
294
  return reactive(hydrate(data) as Split)
288
295
  }
296
+
297
+ // ── history snapshots ────────────────────────────────────────
298
+ // Like serializeLayout, but keeping node ids: restoring an undo step with
299
+ // fresh ids remounted every block and threw away its local state.
300
+ type SnapshotNode =
301
+ | { kind: "leaf"; id: string; blocks: string[]; active: number; size: number }
302
+ | { kind: "split"; id: string; dir: Dir; size: number; children: SnapshotNode[] }
303
+
304
+ export function snapshotLayout(root: Split): string {
305
+ const strip = (n: LayoutNode): SnapshotNode =>
306
+ n.kind === "leaf"
307
+ ? { kind: "leaf", id: n.id, blocks: [...n.blocks], active: n.active, size: n.size }
308
+ : { kind: "split", id: n.id, dir: n.dir, size: n.size, children: n.children.map(strip) }
309
+ return JSON.stringify(strip(root))
310
+ }
311
+
312
+ export function restoreLayout(snapshot: string): Split {
313
+ const hydrate = (n: SnapshotNode): LayoutNode =>
314
+ n.kind === "leaf"
315
+ ? { kind: "leaf", id: claimId(n.id), blocks: [...n.blocks], active: n.active, size: n.size }
316
+ : { kind: "split", id: claimId(n.id), dir: n.dir, size: n.size, children: n.children.map(hydrate) }
317
+ return reactive(hydrate(JSON.parse(snapshot) as SnapshotNode) as Split)
318
+ }
@@ -1,4 +1,4 @@
1
- import { computed, ref, shallowRef, type Ref } from "vue"
1
+ import { computed, ref, shallowRef, watch, type Ref } from "vue"
2
2
  import {
3
3
  addLeaf,
4
4
  addTab,
@@ -10,8 +10,10 @@ import {
10
10
  removeLeaf,
11
11
  reorderTab,
12
12
  resizeAt,
13
+ restoreLayout,
13
14
  setActiveTab,
14
15
  serializeLayout,
16
+ snapshotLayout,
15
17
  type Side,
16
18
  type Split,
17
19
  } from "./tree"
@@ -22,13 +24,34 @@ import {
22
24
  * tree.ts is a pile of pure-ish mutators; this composable is the ergonomic
23
25
  * surface a consumer actually wires to UI: it owns the reactive tree, snapshots
24
26
  * every structural edit for undo/redo, and exposes save/load/reset against a
25
- * serialized form. The canvas calls the same mutators for drag/drop/resize, so
26
- * those go through `record()` too and land in history.
27
+ * serialized form. A <LayoutCanvas v-model> bound to this tree finds the
28
+ * controller through `layoutController`, so drag-move, tab close/reorder and
29
+ * divider resizes land in history with no extra wiring.
27
30
  *
28
- * History holds serialized snapshots (plain JSON), not live trees, so an undo
29
- * can't smuggle stale reactive nodes back in.
31
+ * History holds snapshots (plain JSON with node ids), not live trees, so an
32
+ * undo can't smuggle stale reactive nodes back in, yet blocks keep their
33
+ * identity and are not remounted.
30
34
  */
31
35
 
36
+ /** The edits a canvas performs, routed through history when a controller owns the tree. */
37
+ export interface LayoutController {
38
+ move: (movingId: string, targetId: string, side: Side) => void
39
+ removeBlock: (leafId: string, index: number) => void
40
+ reorderTab: (leafId: string, from: number, to: number) => void
41
+ setActive: (leafId: string, index: number) => void
42
+ resize: (parent: Split, index: number, deltaFraction: number) => void
43
+ /** Bracket a continuous resize gesture so it records one history step. */
44
+ beginResize: () => void
45
+ endResize: () => void
46
+ }
47
+
48
+ const controllers = new WeakMap<Split, LayoutController>()
49
+
50
+ /** The controller managing `tree`, if a useLayout() owns it. */
51
+ export function layoutController(tree: Split): LayoutController | undefined {
52
+ return controllers.get(tree)
53
+ }
54
+
32
55
  export interface UseLayoutOptions {
33
56
  /** Initial block ids, left-to-right, when no saved layout is loaded. */
34
57
  initial: string[]
@@ -57,6 +80,8 @@ export interface UseLayout {
57
80
  remove: (leafId: string) => void
58
81
  move: (movingId: string, targetId: string, side: Side) => void
59
82
  resize: (parent: Split, index: number, deltaFraction: number) => void
83
+ beginResize: () => void
84
+ endResize: () => void
60
85
  undo: () => void
61
86
  redo: () => void
62
87
  /** Replace the whole layout from a flat block list. */
@@ -77,19 +102,43 @@ export function useLayout(options: UseLayoutOptions): UseLayout {
77
102
  const canUndo = computed(() => past.value.length > 0)
78
103
  const canRedo = computed(() => future.value.length > 0)
79
104
 
80
- const snapshot = () => JSON.stringify(serializeLayout(layout.value))
105
+ const snapshot = () => snapshotLayout(layout.value)
106
+
107
+ function push(before: string) {
108
+ if (before === snapshot()) return
109
+ past.value.push(before)
110
+ if (past.value.length > limit) past.value.shift()
111
+ future.value = []
112
+ }
81
113
 
82
114
  // Capture the pre-edit state, run the structural change, drop any redo branch.
83
115
  function record(mutate: () => void) {
84
116
  const before = snapshot()
85
117
  mutate()
86
- past.value.push(before)
87
- if (past.value.length > limit) past.value.shift()
88
- future.value = []
118
+ push(before)
119
+ }
120
+
121
+ let resizeStart: string | null = null
122
+
123
+ const controller: LayoutController = {
124
+ move: (movingId, targetId, side) => record(() => moveLeaf(layout.value, movingId, targetId, side)),
125
+ removeBlock: (leafId, index) => record(() => removeBlock(layout.value, leafId, index)),
126
+ reorderTab: (leafId, from, to) => record(() => reorderTab(layout.value, leafId, from, to)),
127
+ setActive: (leafId, index) => setActiveTab(layout.value, leafId, index),
128
+ resize: (parent, index, delta) => resizeAt(parent, index, delta),
129
+ beginResize: () => {
130
+ resizeStart = snapshot()
131
+ },
132
+ endResize: () => {
133
+ if (resizeStart !== null) push(resizeStart)
134
+ resizeStart = null
135
+ },
89
136
  }
90
137
 
138
+ watch(layout, (tree) => controllers.set(tree, controller), { immediate: true, flush: "sync" })
139
+
91
140
  function restore(serialized: string) {
92
- layout.value = deserializeLayout(JSON.parse(serialized))
141
+ layout.value = restoreLayout(serialized)
93
142
  }
94
143
 
95
144
  return {
@@ -99,16 +148,17 @@ export function useLayout(options: UseLayoutOptions): UseLayout {
99
148
  canRedo,
100
149
  add: (block, side) => record(() => addLeaf(layout.value, block, side)),
101
150
  addTab: (leafId, block) => record(() => addTab(layout.value, leafId, block)),
102
- setActive: (leafId, index) => setActiveTab(layout.value, leafId, index),
103
- reorderTab: (leafId, from, to) => record(() => reorderTab(layout.value, leafId, from, to)),
104
- removeBlock: (leafId, index) => record(() => removeBlock(layout.value, leafId, index)),
151
+ setActive: controller.setActive,
152
+ reorderTab: controller.reorderTab,
153
+ removeBlock: controller.removeBlock,
105
154
  remove: (leafId) => record(() => removeLeaf(layout.value, leafId)),
106
- move: (movingId, targetId, side) =>
107
- record(() => moveLeaf(layout.value, movingId, targetId, side)),
155
+ move: controller.move,
108
156
  // Resize fires continuously during a divider drag; recording every frame
109
- // would flood history, so it mutates without a snapshot. (A consumer that
110
- // wants resize in history can wrap a single record() around the gesture.)
111
- resize: (parent, index, delta) => resizeAt(parent, index, delta),
157
+ // would flood history. The canvas brackets its gestures with
158
+ // beginResize/endResize, which records the whole drag as one step.
159
+ resize: controller.resize,
160
+ beginResize: controller.beginResize,
161
+ endResize: controller.endResize,
112
162
  undo: () => {
113
163
  const prev = past.value.pop()
114
164
  if (prev === undefined) return
package/src/nuxt.ts CHANGED
@@ -11,28 +11,52 @@
11
11
  *
12
12
  * Options (nuxt.config `leavepulseUi`):
13
13
  * theme false to skip the theme plugin entirely (default: true)
14
- * defaultTheme preset name used when the visitor has no saved theme
14
+ * defaultTheme theme name used when the visitor has no saved theme
15
+ * themes path of a module whose default export is the app's own
16
+ * TokenSet[] (e.g. "~/themes"), so SSR can paint and fall back
17
+ * to custom themes, not only the built-in presets
18
+ * link false to skip providing NuxtLink as the kit's link component
19
+ * exclude component names not to auto-register (e.g. ["LayoutCanvas"]
20
+ * when the app has its own component of that name)
15
21
  */
16
- import { addComponent, addPlugin, createResolver, defineNuxtModule } from "@nuxt/kit"
22
+ import { addComponent, addPlugin, addTemplate, createResolver, defineNuxtModule } from "@nuxt/kit"
17
23
  import { COMPONENT_NAMES } from "./component-names"
18
24
 
19
25
  export interface LeavepulseUiOptions {
20
26
  /** Register the SSR theme plugin. Disable to wire theming yourself. */
21
27
  theme?: boolean
22
- /** Preset name to fall back to when nothing is saved (default "LeavePulse"). */
28
+ /** Theme name to fall back to when nothing is saved (default "Dark"). */
23
29
  defaultTheme?: string
30
+ /** Module path whose default export is the app's TokenSet[]. */
31
+ themes?: string
32
+ /** Provide NuxtLink as the link component for kit navigation (default true). */
33
+ link?: boolean
34
+ /** Component names to leave unregistered. */
35
+ exclude?: string[]
24
36
  }
25
37
 
26
38
  export default defineNuxtModule<LeavepulseUiOptions>({
27
39
  meta: { name: "@leavepulse/ui", configKey: "leavepulseUi" },
28
- defaults: { theme: true },
29
- setup(options: LeavepulseUiOptions, nuxt: { options: { runtimeConfig: { public: Record<string, unknown> } } }) {
40
+ defaults: { theme: true, link: true },
41
+ setup(
42
+ options: LeavepulseUiOptions,
43
+ nuxt: {
44
+ options: {
45
+ runtimeConfig: { public: Record<string, unknown> }
46
+ alias: Record<string, string>
47
+ }
48
+ hook: (
49
+ name: "prepare:types",
50
+ fn: (options: { references: { path: string }[] }) => void,
51
+ ) => void
52
+ },
53
+ ) {
54
+ const resolver = createResolver(import.meta.url)
55
+ const skip = new Set(options.exclude ?? [])
30
56
  for (const name of COMPONENT_NAMES) {
31
- // Point each component at its own dist chunk rather than the barrel
32
- // ("@leavepulse/ui"). Importing through the barrel makes the bundler pull
33
- // the whole kit; a per-component path lets it ship only the components an
34
- // app actually uses (plus their real deps — e.g. @vue-flow only when a
35
- // canvas component is used).
57
+ if (skip.has(name)) continue
58
+ // Each component's own dist chunk rather than the barrel, so the app
59
+ // ships only the components it uses (and their real deps).
36
60
  addComponent({
37
61
  name,
38
62
  export: "default",
@@ -40,18 +64,36 @@ export default defineNuxtModule<LeavepulseUiOptions>({
40
64
  })
41
65
  }
42
66
 
67
+ if (options.link !== false) {
68
+ addPlugin({ src: resolver.resolve("./runtime/config.plugin"), mode: "all" })
69
+ }
70
+
43
71
  if (options.theme === false) return
44
72
 
45
- // The plugin reads its options from runtimeConfig rather than closing over
46
- // them, so they survive the build into the server bundle.
73
+ const themes = addTemplate({
74
+ filename: "leavepulse-ui-themes.mjs",
75
+ getContents: () =>
76
+ options.themes
77
+ ? `export { default } from ${JSON.stringify(options.themes)}\n`
78
+ : "export default []\n",
79
+ })
80
+ nuxt.options.alias["#lp-ui-themes"] = themes.dst
81
+ const themeTypes = addTemplate({
82
+ filename: "types/leavepulse-ui-themes.d.ts",
83
+ getContents: () =>
84
+ 'declare module "#lp-ui-themes" {\n const themes: import("@leavepulse/ui").TokenSet[]\n export default themes\n}\n',
85
+ })
86
+ nuxt.hook("prepare:types", ({ references }) => {
87
+ references.push({ path: themeTypes.dst })
88
+ })
89
+
90
+ // Read from runtimeConfig rather than closed over, so the options survive
91
+ // the build into the server bundle.
47
92
  nuxt.options.runtimeConfig.public.leavepulseUi = {
48
- ...(nuxt.options.runtimeConfig.public.leavepulseUi ?? {}),
93
+ ...((nuxt.options.runtimeConfig.public.leavepulseUi as Record<string, unknown> | undefined) ?? {}),
49
94
  defaultTheme: options.defaultTheme,
50
95
  }
51
96
 
52
- addPlugin({
53
- src: createResolver(import.meta.url).resolve("./runtime/theme.plugin"),
54
- mode: "all",
55
- })
97
+ addPlugin({ src: resolver.resolve("./runtime/theme.plugin"), mode: "all" })
56
98
  },
57
99
  })
@@ -0,0 +1,14 @@
1
+ /*
2
+ * Nuxt runtime plugin: provides the kit configuration app-wide, with NuxtLink
3
+ * as the link component, so navigation pieces (LpLink, LpButton with `to`,
4
+ * LpBreadcrumbs, LpSidebar items) navigate client-side without an `as` on
5
+ * every call site. Registered by the kit's Nuxt module.
6
+ */
7
+ import { LP_CONFIG, type LpConfig } from "@leavepulse/ui"
8
+ import { NuxtLink } from "#components"
9
+ import { defineNuxtPlugin } from "#imports"
10
+
11
+ export default defineNuxtPlugin((nuxtApp) => {
12
+ const config: LpConfig = { linkComponent: NuxtLink }
13
+ nuxtApp.vueApp.provide(LP_CONFIG, config)
14
+ })
@@ -1,54 +1,74 @@
1
1
  /*
2
- * Nuxt runtime plugin — paints the user's theme during SSR, so a reload shows
2
+ * Nuxt runtime plugin: paints the user's theme during SSR, so a reload shows
3
3
  * the right colours in the first frame instead of flashing the default.
4
4
  *
5
- * Registered automatically by the kit's Nuxt module; a consuming app needs no
6
- * plugin of its own. Opt out (or point it at custom themes) via nuxt.config:
5
+ * Registered automatically by the kit's Nuxt module. Options (nuxt.config):
7
6
  *
8
7
  * leavepulseUi: { theme: false } // no theme handling at all
9
- * leavepulseUi: { defaultTheme: "Nord" } // different fallback preset
8
+ * leavepulseUi: { defaultTheme: "Nord" } // different fallback theme
9
+ * leavepulseUi: { themes: "~/themes" } // module exporting the app's own themes
10
10
  *
11
- * How it avoids the flash: the kit mirrors the chosen theme's NAME into the
12
- * `lp-theme` cookie (see cacheTheme), which unlike localStorage travels with the
13
- * request. Here the server reads it, resolves it against the built-in presets,
14
- * and inlines that theme's CSS variables into <head>. On the client
15
- * bootstrapTheme() still runs before mount, so a localStorage choice — which
16
- * holds the full token values, including themes the server cannot name — keeps
17
- * winning.
11
+ * The kit mirrors the chosen theme's NAME into the `lp-theme` cookie, which
12
+ * unlike localStorage travels with the request. The server resolves it against
13
+ * the built-in presets plus the app's themes and inlines that theme's variables
14
+ * into <head>. On the client useTheme().init() still restores localStorage,
15
+ * which holds the full token values and keeps winning.
18
16
  */
19
17
  // Through the barrel, not "../theme/…": this file is compiled by the consuming
20
18
  // app, where the barrel resolves to the same dist instance the components use.
21
- // A relative path would load a second copy of the theme singleton.
22
19
  import {
23
- bootstrapTheme,
24
20
  leavepulse,
25
21
  presets,
26
22
  THEME_COOKIE,
27
23
  themeFromCookie,
28
24
  themeToCssRule,
25
+ useTheme,
29
26
  type TokenSet,
30
27
  } from "@leavepulse/ui"
31
- import { defineNuxtPlugin, useCookie, useHead, useRuntimeConfig } from "#imports"
28
+ import appThemes from "#lp-ui-themes"
29
+ import {
30
+ defineNuxtPlugin,
31
+ useCookie,
32
+ useHead,
33
+ useRequestEvent,
34
+ useRequestHeaders,
35
+ useRuntimeConfig,
36
+ } from "#imports"
37
+
38
+ const HINT = "sec-ch-prefers-color-scheme"
32
39
 
33
40
  export default defineNuxtPlugin(() => {
34
41
  const options = (useRuntimeConfig().public.leavepulseUi ?? {}) as {
35
42
  defaultTheme?: string
36
43
  }
37
44
 
38
- const themes = Object.values(presets) as TokenSet[]
39
- const fallback =
40
- themes.find((theme) => theme.name === options.defaultTheme) ?? leavepulse
45
+ const themes = [...(appThemes as TokenSet[]), ...(Object.values(presets) as TokenSet[])]
46
+ const fallback = themes.find((theme) => theme.name === options.defaultTheme) ?? leavepulse
41
47
 
42
- const cookie = useCookie<string | null>(THEME_COOKIE, { readonly: true })
43
- const initial = themeFromCookie(cookie.value, themes) ?? fallback
48
+ // Raw value: themeFromCookie decodes safely itself, a throwing decode here
49
+ // would turn a malformed cookie into a 500 on every request.
50
+ const cookie = useCookie<string | null>(THEME_COOKIE, {
51
+ readonly: true,
52
+ decode: (value: string) => value,
53
+ })
54
+ let prefersDark: boolean | undefined
55
+ if (import.meta.server) {
56
+ const hint = useRequestHeaders([HINT])[HINT]
57
+ if (hint) prefersDark = hint.includes("dark")
58
+ // Ask the browser for the OS colour scheme from the next request on, so a
59
+ // visitor following the OS gets the right theme server-side as well.
60
+ useRequestEvent()?.node.res.setHeader("Accept-CH", "Sec-CH-Prefers-Color-Scheme")
61
+ }
62
+ const initial = themeFromCookie(cookie.value, themes, { prefersDark }) ?? fallback
44
63
 
45
64
  useHead({
65
+ htmlAttrs: { "data-theme-mode": initial.mode },
46
66
  style: [{ id: "lp-ui-theme", innerHTML: themeToCssRule(initial) }],
47
67
  })
48
68
 
49
- // `initial` as the fallback (not the preset default) so a visitor who only
50
- // has the cookie keeps the theme the server just painted.
69
+ // `initial` as the fallback, so a visitor who only has the cookie keeps the
70
+ // theme the server just painted and the shared state starts from it.
51
71
  if (import.meta.client) {
52
- bootstrapTheme(initial)
72
+ useTheme().init(initial)
53
73
  }
54
74
  })