@leavepulse/ui 0.43.4 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (437) hide show
  1. package/README.md +121 -7
  2. package/dist/assets/{LpCommandPalette-B-0Est4Z.css → LpCommandPalette-nGNnzXqQ.css} +21 -21
  3. package/dist/assets/{LpDropzone-BBoWvi-J.css → LpDropzone-i4BdMi1H.css} +12 -12
  4. package/dist/assets/{LpImageEditor-Ct7Y3aNu.css → LpImageEditor-CbMA82g8.css} +6 -6
  5. package/dist/assets/{LpRollingText-CR1mb0Vt.css → LpRollingText-BGnE6q9j.css} +3 -0
  6. package/dist/assets/{LpShortcutsDialog-C6YoQ8_M.css → LpShortcutsDialog-CqYX3yy6.css} +28 -7
  7. package/dist/assets/{LpSkeleton-D7xU8IPV.css → LpSkeleton-H3Z5dEIk.css} +4 -1
  8. package/dist/assets/{LpTable-BgPLJOXa.css → LpTable-CPwWxTJS.css} +49 -9
  9. package/dist/chunks/ContextMenuItems-BtKU7074.js +124 -0
  10. package/dist/chunks/{LayoutCanvas-Dd20boUG.js → LayoutCanvas-BkaGXTjx.js} +26 -9
  11. package/dist/chunks/{LayoutNode-DGpZA9LX.js → LayoutNode-B3_9E1Xa.js} +28 -12
  12. package/dist/chunks/{LpActionBar-BlvtGB2P.js → LpActionBar-CnfFVIwC.js} +26 -12
  13. package/dist/chunks/{LpAddressInput-B-Zvxsn7.js → LpAddressInput-BwX8uo4_.js} +54 -49
  14. package/dist/chunks/{LpAlert-BAYHDSS1.js → LpAlert-B6ilrM7T.js} +16 -9
  15. package/dist/chunks/{LpAppShell-BGQVIY-3.js → LpAppShell-C53_RaGF.js} +53 -26
  16. package/dist/chunks/{LpAutocomplete-DExlIZe_.js → LpAutocomplete-C8IgSMsi.js} +52 -24
  17. package/dist/chunks/{LpAvatar-B3-xAOH3.js → LpAvatar-C1XStdOo.js} +19 -10
  18. package/dist/chunks/LpBreadcrumbs-CQJd73Gm.js +95 -0
  19. package/dist/chunks/{LpButton-5VXXCjwF.js → LpButton-CKSwC6Mu.js} +45 -15
  20. package/dist/chunks/{LpCalendar-B7Ko5RdR.js → LpCalendar-DrA2McTU.js} +1 -1
  21. package/dist/chunks/{LpCard-BhI1N_vL.js → LpCard-C2IvucmX.js} +1 -1
  22. package/dist/chunks/{LpCheckbox-CVccKYaH.js → LpCheckbox-Bkj13CbW.js} +8 -4
  23. package/dist/chunks/{LpCodeBlock-Bo0B4TFR.js → LpCodeBlock-ca7ANFyW.js} +49 -17
  24. package/dist/chunks/{LpColorPicker-BlXea7LM.js → LpColorPicker-zz4VGkG4.js} +75 -26
  25. package/dist/chunks/{LpConfirmDialog-Q-bxff8z.js → LpConfirmDialog-DCw5jR1t.js} +59 -17
  26. package/dist/chunks/LpConfirmHost-CbywMpMP.js +49 -0
  27. package/dist/chunks/LpContextMenu-BqkAYa9X.js +69 -0
  28. package/dist/chunks/LpDatePicker-DuusgT69.js +195 -0
  29. package/dist/chunks/LpDateRangePicker-DHeuznfC.js +208 -0
  30. package/dist/chunks/{LpDisclosure-ChQYmjlR.js → LpDisclosure-Cc23-JOT.js} +1 -1
  31. package/dist/chunks/{LpDrawer-BZ6k3hvo.js → LpDrawer-Dt4aB4Fr.js} +65 -28
  32. package/dist/chunks/{LpDropdownMenu-DJpoqRCb.js → LpDropdownMenu-DdJGQzPn.js} +2 -1
  33. package/dist/chunks/{LpEmptyState-Dsdqz1Wj.js → LpEmptyState-BJbZXC8D.js} +1 -1
  34. package/dist/chunks/LpFieldNotice-wtieLUTj.js +64 -0
  35. package/dist/chunks/{LpFileTree-_5vFBgaK.js → LpFileTree-CNurOvkV.js} +25 -10
  36. package/dist/chunks/{LpFileTreeNode-BdcIYKIE.js → LpFileTreeNode-D_qRRdsH.js} +3 -3
  37. package/dist/chunks/LpFormField-CtVYj6tO.js +101 -0
  38. package/dist/chunks/{LpIcon-CCnX5_2j.js → LpIcon-DYtaid0K.js} +20 -6
  39. package/dist/chunks/{LpInput-9EXWUM9B.js → LpInput-D-YGQbEv.js} +58 -20
  40. package/dist/chunks/{LpLightbox-2sYVMY-S.js → LpLightbox-DSDahF23.js} +148 -61
  41. package/dist/chunks/{LpLink-DCND-zyu.js → LpLink-Krkjhe_y.js} +15 -6
  42. package/dist/chunks/{LpLogViewer-CaRy5MeS.js → LpLogViewer-KypbgOyo.js} +300 -72
  43. package/dist/chunks/{LpMapPicker-ul4oYno6.js → LpMapPicker-Cc84LaM3.js} +39 -14
  44. package/dist/chunks/{LpMediaGallery-FF-NquIo.js → LpMediaGallery-YKr-cQPN.js} +7 -4
  45. package/dist/chunks/{LpModal-Bt0pQCI_.js → LpModal-DMMDQHAu.js} +52 -29
  46. package/dist/chunks/{LpModalNav-YaifHrXC.js → LpModalNav-BVPDyIup.js} +10 -7
  47. package/dist/chunks/{LpNotificationBell-DyGGsMAM.js → LpNotificationBell-CCwyJP5C.js} +79 -33
  48. package/dist/chunks/{LpNumberField-CWuX08iX.js → LpNumberField-B77O-z28.js} +21 -8
  49. package/dist/chunks/{LpNumberFlow-amWYaeuA.js → LpNumberFlow-z4WPZcZu.js} +22 -12
  50. package/dist/chunks/{LpOtpInput-y2v-pmiN.js → LpOtpInput-CziPF3Oq.js} +37 -8
  51. package/dist/chunks/{LpPagination-5FxjmmB5.js → LpPagination-BcDlo3r_.js} +31 -20
  52. package/dist/chunks/{LpPasswordInput--sjYCkLS.js → LpPasswordInput-eKzQhbQP.js} +11 -8
  53. package/dist/chunks/LpPhoneInput-BCbrrhgE.js +336 -0
  54. package/dist/chunks/{LpPie-D6OnzwlJ.js → LpPie-BEJ2SfI6.js} +84 -28
  55. package/dist/chunks/{LpPopover-8268UmlC.js → LpPopover-DCKeqiVn.js} +5 -2
  56. package/dist/chunks/{LpProgress-Btq5W4Ut.js → LpProgress-CBU1iI69.js} +5 -4
  57. package/dist/chunks/{LpRadioGroup-xPIuVDO_.js → LpRadioGroup-DmEW0l78.js} +9 -2
  58. package/dist/chunks/LpRangeCalendar-DZuaRV0W.js +331 -0
  59. package/dist/chunks/{LpRollingText-CM5gkSeF.js → LpRollingText-DB7upf-5.js} +1 -1
  60. package/dist/chunks/{LpScrollArea-D8xyKXcF.js → LpScrollArea-Bry1fdOM.js} +14 -2
  61. package/dist/chunks/{LpSection-aM6J_u51.js → LpSection-tKEH0jYf.js} +1 -1
  62. package/dist/chunks/{LpSegmented-fV0Sk92p.js → LpSegmented-C09EEam2.js} +8 -4
  63. package/dist/chunks/{LpSelect-ANIVtrpM.js → LpSelect-DNIL6MB9.js} +50 -31
  64. package/dist/chunks/{LpSidebar-ClBUwoEz.js → LpSidebar-CQNB0-yU.js} +30 -22
  65. package/dist/chunks/{LpSidebarNav-llOcs_Fn.js → LpSidebarNav-kx-LdqNU.js} +16 -11
  66. package/dist/chunks/{LpSkeleton-CTI-vBgW.js → LpSkeleton-BB6XIV-w.js} +1 -1
  67. package/dist/chunks/{LpSlider-BwNIBkAK.js → LpSlider-Cuzmrd7p.js} +7 -2
  68. package/dist/chunks/{LpSparkline-C6xQ2dwv.js → LpSparkline-CB80N8SJ.js} +11 -4
  69. package/dist/chunks/{LpStat-CQaW8R--.js → LpStat-B-7udBRP.js} +2 -2
  70. package/dist/chunks/{LpStepper-Bk-XaQYU.js → LpStepper-C7XiVKZj.js} +34 -19
  71. package/dist/chunks/{LpSwitch-Blizvqyt.js → LpSwitch-OrcOJX9m.js} +41 -27
  72. package/dist/chunks/LpTableFilter-BV8PpLM9.js +158 -0
  73. package/dist/chunks/{LpTableOfContents-CjvxPMpU.js → LpTableOfContents-BUcAfGPQ.js} +36 -41
  74. package/dist/chunks/{LpTabs-Cefe_lys.js → LpTabs-29iW1XOx.js} +20 -6
  75. package/dist/chunks/LpTagsInput-D1SuqbhB.js +300 -0
  76. package/dist/chunks/LpTextarea-uuScWo6-.js +77 -0
  77. package/dist/chunks/LpThemeProvider-Be4r2TQX.js +35 -0
  78. package/dist/chunks/LpThemeSwitcher-B4aVPbNC.js +166 -0
  79. package/dist/chunks/{LpTilt-DuyKbpkA.js → LpTilt-B7KHX1zH.js} +2 -2
  80. package/dist/chunks/{LpTimeField-K6nAGboX.js → LpTimeField-Cqe7fXnh.js} +14 -8
  81. package/dist/chunks/{LpToaster-gDB1EaU3.js → LpToaster-DpJvoCOh.js} +94 -31
  82. package/dist/chunks/{LpTooltip-hCLPAqMG.js → LpTooltip-CXVKcOXT.js} +14 -6
  83. package/dist/chunks/LpTooltipProvider-CftjV7Xc.js +25 -0
  84. package/dist/chunks/{LpTopologyCanvas-Cm1L39uL.js → LpTopologyCanvas-6ozNrQKh.js} +8 -2
  85. package/dist/chunks/{LpUptimeBar-DvAMno0S.js → LpUptimeBar-ugcv25xj.js} +44 -15
  86. package/dist/chunks/icons-CkA0njzm.js +43 -0
  87. package/dist/chunks/{LpThemeSwitcher-Cue-myST.js → useTheme-DJm1JSsY.js} +215 -260
  88. package/dist/component-names.d.ts +1 -1
  89. package/dist/components/ContextMenuItems.vue.d.ts +8 -0
  90. package/dist/components/ContextMenuItems.vue.js +4 -0
  91. package/dist/components/LayoutCanvas.vue.js +1 -1
  92. package/dist/components/LayoutNode.vue.d.ts +4 -0
  93. package/dist/components/LayoutNode.vue.js +1 -1
  94. package/dist/components/LpActionBar.vue.d.ts +3 -0
  95. package/dist/components/LpActionBar.vue.js +1 -1
  96. package/dist/components/LpAddressInput.vue.d.ts +1 -1
  97. package/dist/components/LpAddressInput.vue.js +1 -1
  98. package/dist/components/LpAlert.vue.d.ts +9 -0
  99. package/dist/components/LpAlert.vue.js +1 -1
  100. package/dist/components/LpAppShell.vue.d.ts +10 -0
  101. package/dist/components/LpAppShell.vue.js +1 -1
  102. package/dist/components/LpAutocomplete.vue.d.ts +31 -6
  103. package/dist/components/LpAutocomplete.vue.js +1 -1
  104. package/dist/components/LpAvatar.vue.d.ts +3 -3
  105. package/dist/components/LpAvatar.vue.js +1 -1
  106. package/dist/components/LpBadge.vue.d.ts +2 -2
  107. package/dist/components/LpBreadcrumbs.vue.d.ts +13 -9
  108. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  109. package/dist/components/LpButton.vue.d.ts +35 -98
  110. package/dist/components/LpButton.vue.js +1 -1
  111. package/dist/components/LpCalendar.vue.js +1 -1
  112. package/dist/components/LpCard.vue.js +1 -1
  113. package/dist/components/LpChart.vue.d.ts +47 -3
  114. package/dist/components/LpChart.vue.js +377 -97
  115. package/dist/components/LpCheckbox.vue.js +1 -1
  116. package/dist/components/LpCodeBlock.vue.d.ts +2 -0
  117. package/dist/components/LpCodeBlock.vue.js +1 -1
  118. package/dist/components/LpColorPicker.vue.d.ts +2 -2
  119. package/dist/components/LpColorPicker.vue.js +1 -1
  120. package/dist/components/LpCommandPalette.vue.d.ts +14 -3
  121. package/dist/components/LpCommandPalette.vue.js +142 -77
  122. package/dist/components/LpConfirmDialog.vue.d.ts +15 -3
  123. package/dist/components/LpConfirmDialog.vue.js +1 -1
  124. package/dist/components/LpConfirmHost.vue.d.ts +9 -0
  125. package/dist/components/LpConfirmHost.vue.js +4 -0
  126. package/dist/components/LpContextMenu.vue.d.ts +3 -5
  127. package/dist/components/LpContextMenu.vue.js +1 -1
  128. package/dist/components/LpDatePicker.vue.d.ts +24 -6
  129. package/dist/components/LpDatePicker.vue.js +1 -1
  130. package/dist/components/LpDateRangePicker.vue.d.ts +60 -0
  131. package/dist/components/LpDateRangePicker.vue.js +4 -0
  132. package/dist/components/LpDisclosure.vue.js +1 -1
  133. package/dist/components/LpDrawer.vue.d.ts +10 -0
  134. package/dist/components/LpDrawer.vue.js +1 -1
  135. package/dist/components/LpDropdownMenu.vue.js +1 -1
  136. package/dist/components/LpDropzone.vue.js +23 -7
  137. package/dist/components/LpEmptyState.vue.js +1 -1
  138. package/dist/components/LpFieldNotice.vue.d.ts +8 -0
  139. package/dist/components/LpFieldNotice.vue.js +4 -0
  140. package/dist/components/LpFileTree.vue.d.ts +11 -2
  141. package/dist/components/LpFileTree.vue.js +1 -1
  142. package/dist/components/LpFileTreeNode.vue.js +1 -1
  143. package/dist/components/LpFormField.vue.d.ts +13 -1
  144. package/dist/components/LpFormField.vue.js +1 -1
  145. package/dist/components/LpIcon.vue.d.ts +0 -1
  146. package/dist/components/LpIcon.vue.js +1 -1
  147. package/dist/components/LpImageEditor.vue.js +93 -58
  148. package/dist/components/LpInput.vue.d.ts +22 -3
  149. package/dist/components/LpInput.vue.js +1 -1
  150. package/dist/components/LpLightbox.vue.d.ts +17 -0
  151. package/dist/components/LpLightbox.vue.js +1 -1
  152. package/dist/components/LpLink.vue.d.ts +11 -11
  153. package/dist/components/LpLink.vue.js +1 -1
  154. package/dist/components/LpLogViewer.vue.d.ts +72 -3
  155. package/dist/components/LpLogViewer.vue.js +1 -1
  156. package/dist/components/LpMapPicker.vue.d.ts +4 -4
  157. package/dist/components/LpMapPicker.vue.js +1 -1
  158. package/dist/components/LpMediaGallery.vue.js +1 -1
  159. package/dist/components/LpModal.vue.d.ts +16 -6
  160. package/dist/components/LpModal.vue.js +1 -1
  161. package/dist/components/LpModalNav.vue.d.ts +0 -1
  162. package/dist/components/LpModalNav.vue.js +1 -1
  163. package/dist/components/LpNotificationBell.vue.d.ts +11 -2
  164. package/dist/components/LpNotificationBell.vue.js +1 -1
  165. package/dist/components/LpNumberField.vue.d.ts +2 -0
  166. package/dist/components/LpNumberField.vue.js +1 -1
  167. package/dist/components/LpNumberFlow.vue.js +1 -1
  168. package/dist/components/LpOtpInput.vue.js +1 -1
  169. package/dist/components/LpPagination.vue.d.ts +2 -0
  170. package/dist/components/LpPagination.vue.js +1 -1
  171. package/dist/components/LpPasswordInput.vue.js +1 -1
  172. package/dist/components/LpPhoneInput.vue.d.ts +13 -0
  173. package/dist/components/LpPhoneInput.vue.js +1 -1
  174. package/dist/components/LpPie.vue.d.ts +21 -2
  175. package/dist/components/LpPie.vue.js +1 -1
  176. package/dist/components/LpPopover.vue.d.ts +2 -0
  177. package/dist/components/LpPopover.vue.js +1 -1
  178. package/dist/components/LpProgress.vue.d.ts +1 -1
  179. package/dist/components/LpProgress.vue.js +1 -1
  180. package/dist/components/LpRadioGroup.vue.js +1 -1
  181. package/dist/components/LpRangeCalendar.vue.d.ts +51 -0
  182. package/dist/components/LpRangeCalendar.vue.js +4 -0
  183. package/dist/components/LpRollingText.vue.d.ts +1 -1
  184. package/dist/components/LpRollingText.vue.js +1 -1
  185. package/dist/components/LpScrollArea.vue.d.ts +2 -2
  186. package/dist/components/LpScrollArea.vue.js +1 -1
  187. package/dist/components/LpSection.vue.d.ts +1 -1
  188. package/dist/components/LpSection.vue.js +1 -1
  189. package/dist/components/LpSegmented.vue.d.ts +5 -0
  190. package/dist/components/LpSegmented.vue.js +1 -1
  191. package/dist/components/LpSelect.vue.d.ts +1 -0
  192. package/dist/components/LpSelect.vue.js +1 -1
  193. package/dist/components/LpShortcutsDialog.vue.d.ts +7 -1
  194. package/dist/components/LpShortcutsDialog.vue.js +60 -15
  195. package/dist/components/LpSidebar.vue.d.ts +6 -2
  196. package/dist/components/LpSidebar.vue.js +1 -1
  197. package/dist/components/LpSidebarNav.vue.js +1 -1
  198. package/dist/components/LpSkeleton.vue.js +1 -1
  199. package/dist/components/LpSlider.vue.js +1 -1
  200. package/dist/components/LpSparkline.vue.d.ts +1 -0
  201. package/dist/components/LpSparkline.vue.js +1 -1
  202. package/dist/components/LpStat.vue.js +1 -1
  203. package/dist/components/LpStepper.vue.js +1 -1
  204. package/dist/components/LpSwitch.vue.js +1 -1
  205. package/dist/components/LpTable.vue.d.ts +107 -3
  206. package/dist/components/LpTable.vue.js +540 -128
  207. package/dist/components/LpTableFilter.vue.d.ts +31 -0
  208. package/dist/components/LpTableFilter.vue.js +4 -0
  209. package/dist/components/LpTableOfContents.vue.d.ts +4 -2
  210. package/dist/components/LpTableOfContents.vue.js +1 -1
  211. package/dist/components/LpTabs.vue.d.ts +2 -0
  212. package/dist/components/LpTabs.vue.js +1 -1
  213. package/dist/components/LpTagsInput.vue.d.ts +77 -0
  214. package/dist/components/LpTagsInput.vue.js +4 -0
  215. package/dist/components/LpTextarea.vue.d.ts +19 -6
  216. package/dist/components/LpTextarea.vue.js +1 -1
  217. package/dist/components/LpThemeProvider.vue.d.ts +22 -0
  218. package/dist/components/LpThemeProvider.vue.js +4 -0
  219. package/dist/components/LpThemeSwitcher.vue.d.ts +15 -2
  220. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  221. package/dist/components/LpTilt.vue.js +1 -1
  222. package/dist/components/LpTimeField.vue.js +1 -1
  223. package/dist/components/LpToaster.vue.d.ts +6 -1
  224. package/dist/components/LpToaster.vue.js +1 -1
  225. package/dist/components/LpTooltip.vue.d.ts +1 -2
  226. package/dist/components/LpTooltip.vue.js +1 -1
  227. package/dist/components/LpTooltipProvider.vue.d.ts +22 -0
  228. package/dist/components/LpTooltipProvider.vue.js +4 -0
  229. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  230. package/dist/components/LpUptimeBar.vue.d.ts +4 -0
  231. package/dist/components/LpUptimeBar.vue.js +1 -1
  232. package/dist/components/chart.d.ts +32 -1
  233. package/dist/components/chart.js +42 -6
  234. package/dist/components/codeHighlight.d.ts +6 -0
  235. package/dist/components/codeHighlight.js +92 -20
  236. package/dist/components/color.d.ts +1 -1
  237. package/dist/components/color.js +20 -9
  238. package/dist/components/commandSearch.js +3 -0
  239. package/dist/components/confirm.d.ts +25 -0
  240. package/dist/components/confirm.js +26 -0
  241. package/dist/components/countries.d.ts +6 -0
  242. package/dist/components/countries.js +7 -0
  243. package/dist/components/dateRange.d.ts +43 -0
  244. package/dist/components/dateRange.js +76 -0
  245. package/dist/components/exportData.d.ts +58 -0
  246. package/dist/components/exportData.js +165 -0
  247. package/dist/components/fileTree.js +16 -7
  248. package/dist/components/imageCrop.d.ts +42 -0
  249. package/dist/components/imageCrop.js +47 -0
  250. package/dist/components/link.d.ts +12 -0
  251. package/dist/components/link.js +19 -0
  252. package/dist/components/logSearch.d.ts +18 -0
  253. package/dist/components/logSearch.js +36 -0
  254. package/dist/components/modalDepth.d.ts +18 -2
  255. package/dist/components/modalDepth.js +17 -5
  256. package/dist/components/otp.d.ts +5 -0
  257. package/dist/components/otp.js +10 -0
  258. package/dist/components/phone.d.ts +41 -0
  259. package/dist/components/phone.js +80 -0
  260. package/dist/components/sidebar.d.ts +7 -1
  261. package/dist/components/tableFilter.d.ts +36 -0
  262. package/dist/components/tableFilter.js +84 -0
  263. package/dist/components/tableSort.d.ts +8 -0
  264. package/dist/components/tableSort.js +31 -0
  265. package/dist/components/tags.d.ts +20 -0
  266. package/dist/components/tags.js +37 -0
  267. package/dist/composables/useField.d.ts +38 -0
  268. package/dist/composables/useField.js +45 -0
  269. package/dist/composables/useFieldNotice.d.ts +17 -0
  270. package/dist/composables/useFieldNotice.js +28 -0
  271. package/dist/composables/useForm.d.ts +53 -0
  272. package/dist/composables/useForm.js +207 -0
  273. package/dist/composables/useFormDraft.d.ts +10 -3
  274. package/dist/composables/useFormDraft.js +9 -14
  275. package/dist/composables/useHotkeys.d.ts +81 -5
  276. package/dist/composables/useHotkeys.js +223 -25
  277. package/dist/composables/useInputFilter.d.ts +17 -0
  278. package/dist/composables/useInputFilter.js +47 -10
  279. package/dist/composables/useInputMask.d.ts +59 -0
  280. package/dist/composables/useInputMask.js +170 -0
  281. package/dist/composables/useLpConfig.d.ts +24 -0
  282. package/dist/composables/useLpConfig.js +44 -0
  283. package/dist/composables/usePaintSelect.d.ts +22 -0
  284. package/dist/composables/usePaintSelect.js +169 -0
  285. package/dist/composables/useRovingFocus.js +7 -0
  286. package/dist/composables/useShift.js +7 -2
  287. package/dist/composables/useTilt.d.ts +2 -2
  288. package/dist/composables/useTilt.js +5 -6
  289. package/dist/composables/useToast.d.ts +20 -0
  290. package/dist/composables/useToast.js +28 -4
  291. package/dist/composables/useZoomPan.d.ts +3 -3
  292. package/dist/composables/useZoomPan.js +4 -4
  293. package/dist/icons-all.d.ts +1 -0
  294. package/dist/icons-all.js +3 -0
  295. package/dist/icons.d.ts +14 -1
  296. package/dist/index.d.ts +60 -4
  297. package/dist/index.js +245 -141
  298. package/dist/kit-icons.d.ts +2 -0
  299. package/dist/layout/tree.d.ts +2 -0
  300. package/dist/layout/tree.js +16 -1
  301. package/dist/layout/useLayout.d.ts +15 -0
  302. package/dist/layout/useLayout.js +41 -14
  303. package/dist/theme/backdrop.d.ts +12 -0
  304. package/dist/theme/presets.d.ts +0 -2
  305. package/dist/theme/useTheme.d.ts +32 -9
  306. package/package.json +18 -8
  307. package/src/component-names.ts +9 -7
  308. package/src/components/ContextMenuItems.vue +88 -0
  309. package/src/components/LayoutCanvas.vue +26 -6
  310. package/src/components/LayoutNode.vue +30 -4
  311. package/src/components/LpActionBar.vue +31 -8
  312. package/src/components/LpAddressInput.vue +57 -41
  313. package/src/components/LpAlert.vue +16 -3
  314. package/src/components/LpAppShell.vue +45 -12
  315. package/src/components/LpAutocomplete.vue +47 -10
  316. package/src/components/LpAvatar.vue +20 -6
  317. package/src/components/LpBreadcrumbs.vue +65 -41
  318. package/src/components/LpButton.vue +60 -14
  319. package/src/components/LpChart.vue +351 -28
  320. package/src/components/LpCheckbox.vue +7 -3
  321. package/src/components/LpCodeBlock.vue +50 -14
  322. package/src/components/LpColorPicker.vue +73 -10
  323. package/src/components/LpCommandPalette.vue +115 -20
  324. package/src/components/LpConfirmDialog.vue +68 -17
  325. package/src/components/LpConfirmHost.vue +59 -0
  326. package/src/components/LpContextMenu.vue +15 -116
  327. package/src/components/LpDatePicker.vue +104 -68
  328. package/src/components/LpDateRangePicker.vue +224 -0
  329. package/src/components/LpDrawer.vue +56 -19
  330. package/src/components/LpDropdownMenu.vue +1 -0
  331. package/src/components/LpDropzone.vue +21 -4
  332. package/src/components/LpFieldNotice.vue +65 -0
  333. package/src/components/LpFileTree.vue +33 -7
  334. package/src/components/LpFormField.vue +59 -7
  335. package/src/components/LpIcon.vue +22 -4
  336. package/src/components/LpImageEditor.vue +96 -57
  337. package/src/components/LpInput.vue +62 -11
  338. package/src/components/LpLightbox.vue +140 -29
  339. package/src/components/LpLink.vue +24 -13
  340. package/src/components/LpLogViewer.vue +311 -39
  341. package/src/components/LpMapPicker.vue +36 -13
  342. package/src/components/LpMediaGallery.vue +8 -2
  343. package/src/components/LpModal.vue +54 -28
  344. package/src/components/LpModalNav.vue +5 -2
  345. package/src/components/LpNotificationBell.vue +77 -23
  346. package/src/components/LpNumberField.vue +21 -6
  347. package/src/components/LpNumberFlow.vue +26 -10
  348. package/src/components/LpOtpInput.vue +43 -8
  349. package/src/components/LpPagination.vue +23 -7
  350. package/src/components/LpPasswordInput.vue +4 -1
  351. package/src/components/LpPhoneInput.vue +224 -39
  352. package/src/components/LpPie.vue +102 -20
  353. package/src/components/LpPopover.vue +4 -0
  354. package/src/components/LpProgress.vue +5 -3
  355. package/src/components/LpRadioGroup.vue +9 -2
  356. package/src/components/LpRangeCalendar.vue +365 -0
  357. package/src/components/LpRollingText.vue +3 -0
  358. package/src/components/LpScrollArea.vue +22 -2
  359. package/src/components/LpSegmented.vue +12 -2
  360. package/src/components/LpSelect.vue +0 -0
  361. package/src/components/LpShortcutsDialog.vue +83 -9
  362. package/src/components/LpSidebar.vue +14 -9
  363. package/src/components/LpSidebarNav.vue +17 -5
  364. package/src/components/LpSkeleton.vue +4 -1
  365. package/src/components/LpSlider.vue +7 -1
  366. package/src/components/LpSparkline.vue +11 -3
  367. package/src/components/LpStepper.vue +23 -10
  368. package/src/components/LpSwitch.vue +31 -8
  369. package/src/components/LpTable.vue +668 -90
  370. package/src/components/LpTableFilter.vue +175 -0
  371. package/src/components/LpTableOfContents.vue +50 -32
  372. package/src/components/LpTabs.vue +23 -6
  373. package/src/components/LpTagsInput.vue +364 -0
  374. package/src/components/LpTextarea.vue +44 -6
  375. package/src/components/LpThemeProvider.vue +41 -0
  376. package/src/components/LpThemeSwitcher.vue +76 -33
  377. package/src/components/LpTilt.vue +2 -2
  378. package/src/components/LpTimeField.vue +14 -3
  379. package/src/components/LpToaster.vue +115 -27
  380. package/src/components/LpTooltip.vue +29 -5
  381. package/src/components/LpTooltipProvider.vue +24 -0
  382. package/src/components/LpTopologyCanvas.vue +19 -2
  383. package/src/components/LpUptimeBar.vue +46 -8
  384. package/src/components/chart.ts +82 -8
  385. package/src/components/codeHighlight.ts +111 -15
  386. package/src/components/color.ts +30 -9
  387. package/src/components/commandSearch.ts +6 -0
  388. package/src/components/confirm.ts +58 -0
  389. package/src/components/countries.ts +12 -0
  390. package/src/components/dateRange.ts +134 -0
  391. package/src/components/exportData.ts +228 -0
  392. package/src/components/fileTree.ts +19 -7
  393. package/src/components/imageCrop.ts +107 -0
  394. package/src/components/link.ts +34 -0
  395. package/src/components/logSearch.ts +46 -0
  396. package/src/components/modalDepth.ts +48 -6
  397. package/src/components/otp.ts +16 -0
  398. package/src/components/phone.ts +140 -0
  399. package/src/components/sidebar.ts +7 -1
  400. package/src/components/tableFilter.ts +122 -0
  401. package/src/components/tableSort.ts +37 -0
  402. package/src/components/tags.ts +62 -0
  403. package/src/composables/useField.ts +83 -0
  404. package/src/composables/useFieldNotice.ts +59 -0
  405. package/src/composables/useForm.ts +314 -0
  406. package/src/composables/useFormDraft.ts +25 -20
  407. package/src/composables/useHotkeys.ts +355 -28
  408. package/src/composables/useInputFilter.ts +77 -14
  409. package/src/composables/useInputMask.ts +254 -0
  410. package/src/composables/useLpConfig.ts +87 -0
  411. package/src/composables/usePaintSelect.ts +243 -0
  412. package/src/composables/useRovingFocus.ts +11 -0
  413. package/src/composables/useShift.ts +13 -2
  414. package/src/composables/useTilt.ts +5 -6
  415. package/src/composables/useToast.ts +56 -4
  416. package/src/composables/useZoomPan.ts +6 -6
  417. package/src/iconSubset.d.mts +10 -0
  418. package/src/iconSubset.mjs +51 -0
  419. package/src/icons-all.ts +10 -0
  420. package/src/icons.ts +59 -8
  421. package/src/index.ts +73 -4
  422. package/src/kit-icons.ts +5 -0
  423. package/src/layout/tree.ts +30 -0
  424. package/src/layout/useLayout.ts +68 -18
  425. package/src/nuxt.ts +87 -17
  426. package/src/runtime/config.plugin.ts +14 -0
  427. package/src/runtime/theme.plugin.ts +43 -23
  428. package/src/theme/backdrop.ts +69 -0
  429. package/src/theme/presets.ts +6 -64
  430. package/src/theme/useTheme.ts +206 -100
  431. package/src/tokens/tokens.css +46 -0
  432. package/dist/chunks/LpBreadcrumbs-p4jHe_V-.js +0 -62
  433. package/dist/chunks/LpContextMenu-CwW1QXe_.js +0 -217
  434. package/dist/chunks/LpDatePicker-CwOToxGq.js +0 -164
  435. package/dist/chunks/LpFormField-CazBc2-a.js +0 -62
  436. package/dist/chunks/LpPhoneInput-BDSKVl_z.js +0 -209
  437. package/dist/chunks/LpTextarea-t7F6gv8E.js +0 -47
@@ -0,0 +1,364 @@
1
+ <script lang="ts">
2
+ export default { inheritAttrs: false }
3
+
4
+ export type TagsInputLabels = {
5
+ remove: string
6
+ duplicate: string
7
+ max: string
8
+ added: string
9
+ removed: string
10
+ }
11
+ </script>
12
+
13
+ <script setup lang="ts">
14
+ /*
15
+ * Free-form list of short values: emails, labels, hostnames. Typing a
16
+ * delimiter or Enter turns the text into a tag, a paste is split into many,
17
+ * Backspace in an empty field selects the last tag and removes it on the next
18
+ * press, and the arrow keys walk the tags (reka TagsInput).
19
+ *
20
+ * Adding is the kit's own (see tags.ts) rather than reka's, so a tag that
21
+ * fails `validate`, the limit or the duplicate policy stays in the field for
22
+ * fixing instead of vanishing. The reason floats under the field
23
+ * (LpFieldNotice) rather than taking a line, so a refused paste never moves
24
+ * the form, and a repeat makes the tag it repeats pulse.
25
+ */
26
+ import {
27
+ TagsInputInput,
28
+ TagsInputItem,
29
+ TagsInputItemDelete,
30
+ TagsInputItemText,
31
+ TagsInputRoot,
32
+ } from "reka-ui"
33
+ import { computed, ref, useAttrs, useId } from "vue"
34
+ import { fieldMessageId, useField, useFieldControl } from "../composables/useField"
35
+ import { prefersReducedMotion } from "../composables/easing"
36
+ import { useFieldNotice, type FieldNoticeOptions } from "../composables/useFieldNotice"
37
+ import { useShift } from "../composables/useShift"
38
+ import { useLabels } from "../composables/useLpConfig"
39
+ import { mergeClass } from "../composables/useMergedClass"
40
+ import LpFieldNotice from "./LpFieldNotice.vue"
41
+ import LpFormField from "./LpFormField.vue"
42
+ import LpIcon from "./LpIcon.vue"
43
+ import { addTags, splitTags, type TagDuplicates, type TagRejection, type TagValidator } from "./tags"
44
+
45
+ const props = withDefaults(
46
+ defineProps<{
47
+ modelValue?: string[]
48
+ placeholder?: string
49
+ /** Characters (or a RegExp) that end a tag while typing and split a paste. */
50
+ delimiter?: string | readonly string[] | RegExp
51
+ /** At most this many tags. */
52
+ max?: number
53
+ /**
54
+ * "reject" keeps a repeat typed with a note, "ignore" drops it with a passing
55
+ * note, "allow" keeps it. Either way the tag it repeats pulses.
56
+ */
57
+ duplicates?: TagDuplicates
58
+ /** Return an error message to refuse a tag. */
59
+ validate?: TagValidator
60
+ /** Applied to each tag before it is checked and stored (trim is always applied). */
61
+ normalize?: (tag: string) => string
62
+ /** Turn leftover text into a tag when the field loses focus. */
63
+ addOnBlur?: boolean
64
+ disabled?: boolean
65
+ invalid?: boolean
66
+ required?: boolean
67
+ label?: string
68
+ hint?: string
69
+ error?: string
70
+ labels?: Partial<TagsInputLabels>
71
+ }>(),
72
+ { modelValue: () => [], delimiter: () => [",", "\n", "\t"], duplicates: "reject", addOnBlur: true },
73
+ )
74
+
75
+ const emit = defineEmits<{
76
+ (e: "update:modelValue", value: string[]): void
77
+ (e: "reject", rejected: TagRejection[]): void
78
+ (e: "blur", event: FocusEvent): void
79
+ }>()
80
+
81
+ const text = useLabels<TagsInputLabels>(
82
+ "tagsInput",
83
+ {
84
+ remove: "Remove {tag}",
85
+ duplicate: "{tag} is already added",
86
+ max: "Up to {max} items",
87
+ added: "{tag} added",
88
+ removed: "{tag} removed",
89
+ },
90
+ () => props.labels,
91
+ )
92
+
93
+ const attrs = useAttrs()
94
+ const hasField = computed(() => !!props.label || !!props.hint || !!props.error)
95
+ /*
96
+ * Notes go to the field that frames the control, which fades its own hint out
97
+ * under them: this component's own LpFormField when it has a label or hint,
98
+ * the one around it otherwise, and a notice of its own only when it stands
99
+ * alone.
100
+ */
101
+ const notice = useFieldNotice()
102
+ const outerField = useField()
103
+ type FieldHandle = { notify: (text: string, options?: FieldNoticeOptions) => void; clearNotice: () => void }
104
+ const ownField = ref<FieldHandle | null>(null)
105
+ function bindField(c: unknown): void {
106
+ ownField.value = hasField.value && c ? (c as FieldHandle) : null
107
+ }
108
+ const standalone = computed(() => !hasField.value && !outerField?.value.notify)
109
+ function say(message: string, options?: FieldNoticeOptions): void {
110
+ if (hasField.value) ownField.value?.notify(message, options)
111
+ else if (outerField?.value.notify) outerField.value.notify(message, options)
112
+ else notice.show(message, options)
113
+ }
114
+ function unsay(): void {
115
+ if (hasField.value) ownField.value?.clearNotice()
116
+ else if (outerField?.value.clearNotice) outerField.value.clearNotice()
117
+ else notice.clear()
118
+ }
119
+ /** A refusal the user still has to act on: the text stays typed and the field reads invalid. */
120
+ const stuck = ref(false)
121
+ const announcement = ref("")
122
+ const noticeId = `${useId()}-notice`
123
+
124
+ const field = useFieldControl(() => ({
125
+ wrapped: hasField.value,
126
+ hasMessage: !!props.error || !!props.hint,
127
+ id: typeof attrs.id === "string" ? attrs.id : undefined,
128
+ invalid: props.invalid || !!props.error || stuck.value,
129
+ required: props.required,
130
+ disabled: props.disabled,
131
+ }))
132
+
133
+ const describedBy = computed(
134
+ () =>
135
+ [
136
+ field.value.describedBy,
137
+ hasField.value && field.value.id ? `${field.value.id}-notice` : undefined,
138
+ standalone.value && notice.message.value ? noticeId : undefined,
139
+ ]
140
+ .filter(Boolean)
141
+ .join(" ") || undefined,
142
+ )
143
+
144
+ const rootAttrs = computed(() => {
145
+ const { class: _c, style: _s, id: _i, ...rest } = attrs
146
+ return rest
147
+ })
148
+
149
+ function explain(rejected: TagRejection): string {
150
+ if (rejected.reason === "invalid") return rejected.message ?? ""
151
+ if (rejected.reason === "max") return text.value.max.replace("{max}", String(props.max))
152
+ return text.value.duplicate.replace("{tag}", rejected.value)
153
+ }
154
+
155
+ const inputEl = ref<HTMLInputElement | null>(null)
156
+
157
+ /*
158
+ * The box grows a row at a time as tags wrap, and shrinks back when they go:
159
+ * eased rather than snapped, so the fields below glide instead of jumping.
160
+ */
161
+ const { el: boxEl, tweening, resizing } = useShift({ axis: "height" })
162
+
163
+ /*
164
+ * Keys follow the tag, not its position: keyed by index, removing the first
165
+ * tag re-keyed every later one and they all replayed their entry. A repeated
166
+ * tag (duplicates "allow") is told apart by which occurrence it is.
167
+ */
168
+ const keyed = computed(() => {
169
+ const seen = new Map<string, number>()
170
+ return props.modelValue.map((tag) => {
171
+ const n = seen.get(tag) ?? 0
172
+ seen.set(tag, n + 1)
173
+ return { tag, key: `${n}:${tag}` }
174
+ })
175
+ })
176
+
177
+ /** Draw the eye to a tag that was just typed again. */
178
+ function pulse(tag: string): void {
179
+ if (prefersReducedMotion()) return
180
+ const chip = boxEl.value?.querySelector<HTMLElement>(`[data-tag-key="${CSS.escape(`0:${tag}`)}"]`)
181
+ chip?.animate(
182
+ [
183
+ { transform: "scale(1)", boxShadow: "0 0 0 0 transparent" },
184
+ { transform: "scale(1.08)", boxShadow: "0 0 0 2px var(--color-ring)" },
185
+ { transform: "scale(1)", boxShadow: "0 0 0 0 transparent" },
186
+ ],
187
+ { duration: 420, easing: "ease-out" },
188
+ )
189
+ }
190
+
191
+ /** Add whatever `raw` splits into; returns the text that should stay typed. */
192
+ function add(raw: string): string {
193
+ const parts = splitTags(raw, props.delimiter)
194
+ if (!parts.length) return ""
195
+ const { tags, rejected, ignored } = addTags(props.modelValue, parts, {
196
+ max: props.max,
197
+ duplicates: props.duplicates,
198
+ validate: props.validate,
199
+ normalize: props.normalize,
200
+ })
201
+ if (tags.length !== props.modelValue.length) {
202
+ const added = tags.slice(props.modelValue.length)
203
+ announcement.value = added.map((t) => text.value.added.replace("{tag}", t)).join(", ")
204
+ emit("update:modelValue", tags)
205
+ }
206
+ stuck.value = rejected.length > 0
207
+ const first = rejected[0]
208
+ if (first) {
209
+ say(explain(first), { tone: first.reason === "invalid" ? "danger" : "info", duration: 0 })
210
+ emit("reject", rejected)
211
+ } else if (ignored.length) {
212
+ say(text.value.duplicate.replace("{tag}", ignored[0]!))
213
+ } else {
214
+ unsay()
215
+ }
216
+ for (const r of rejected) if (r.reason === "duplicate") pulse(props.normalize ? props.normalize(r.value) : r.value)
217
+ for (const tag of ignored) pulse(tag)
218
+ return rejected.map((r) => r.value).join(joiner.value)
219
+ }
220
+
221
+ // Rejected parts go back into the field joined by a delimiter, so committing
222
+ // them again splits them the same way.
223
+ const joiner = computed(() => {
224
+ const d = props.delimiter
225
+ const visible = d instanceof RegExp ? undefined : (typeof d === "string" ? [d] : d).find((x) => x.trim())
226
+ return visible ? `${visible} ` : " "
227
+ })
228
+
229
+ function commit(el: HTMLInputElement): void {
230
+ el.value = add(el.value)
231
+ }
232
+
233
+ function isDelimiter(data: string): boolean {
234
+ const d = props.delimiter
235
+ if (d instanceof RegExp) return new RegExp(d.source, d.flags.replace(/[gy]/g, "")).test(data)
236
+ return (typeof d === "string" ? [d] : d).includes(data)
237
+ }
238
+
239
+ function onKeydown(event: KeyboardEvent): void {
240
+ if (event.isComposing || !(event.target instanceof HTMLInputElement)) return
241
+ if (event.key === "Enter" && event.target.value.trim()) {
242
+ event.preventDefault()
243
+ commit(event.target)
244
+ }
245
+ }
246
+
247
+ function onBeforeInput(event: InputEvent): void {
248
+ if (event.isComposing || !(event.target instanceof HTMLInputElement)) return
249
+ if (event.inputType === "insertText" && event.data && isDelimiter(event.data)) {
250
+ event.preventDefault()
251
+ commit(event.target)
252
+ }
253
+ }
254
+
255
+ function onInput(): void {
256
+ if (!stuck.value) return
257
+ stuck.value = false
258
+ unsay()
259
+ }
260
+
261
+ function onPaste(event: ClipboardEvent): void {
262
+ const pasted = event.clipboardData?.getData("text") ?? ""
263
+ if (!(event.target instanceof HTMLInputElement) || !pasted) return
264
+ if (splitTags(pasted, props.delimiter).length < 2) return
265
+ event.preventDefault()
266
+ const el = event.target
267
+ const typed = el.value.slice(0, el.selectionStart ?? el.value.length) + pasted + el.value.slice(el.selectionEnd ?? el.value.length)
268
+ el.value = add(typed)
269
+ }
270
+
271
+ function onBlur(event: FocusEvent): void {
272
+ emit("blur", event)
273
+ if (props.addOnBlur && event.target instanceof HTMLInputElement && event.target.value.trim()) commit(event.target)
274
+ }
275
+
276
+ function onModel(value: (string | Record<string, unknown>)[] | null): void {
277
+ const next = (value ?? []).map(String)
278
+ const removed = props.modelValue.filter((t) => !next.includes(t))
279
+ if (removed.length) announcement.value = removed.map((t) => text.value.removed.replace("{tag}", t)).join(", ")
280
+ stuck.value = false
281
+ unsay()
282
+ emit("update:modelValue", next)
283
+ }
284
+
285
+ defineExpose({ focus: () => inputEl.value?.focus(), add: (value: string) => add(value) })
286
+ </script>
287
+
288
+ <template>
289
+ <component
290
+ :is="hasField ? LpFormField : 'div'"
291
+ :ref="bindField"
292
+ :label="hasField ? label : undefined"
293
+ :hint="hasField ? hint : undefined"
294
+ :error="hasField ? error : undefined"
295
+ :required="hasField ? required : undefined"
296
+ :id="hasField ? field.id : undefined"
297
+ :class="hasField ? attrs.class : 'contents'"
298
+ :style="hasField ? attrs.style : undefined"
299
+ >
300
+ <div class="relative">
301
+ <TagsInputRoot
302
+ v-bind="rootAttrs"
303
+ :model-value="modelValue"
304
+ :disabled="field.disabled"
305
+ :required="field.required"
306
+ :id="field.id"
307
+ :delimiter="/(?!)/"
308
+ data-lp-ring-owner
309
+ :ref="(c) => (boxEl = (c as { $el?: HTMLElement } | null)?.$el ?? null)"
310
+ :class="
311
+ mergeClass(
312
+ 'flex min-h-(--size-control-md) w-full flex-wrap items-center gap-1.5 rounded-control border border-line bg-surface-soft px-2 py-1.5 text-sm text-ink transition-colors duration-[var(--duration-fast)] focus-within:border-brand focus-within:ring-2 focus-within:ring-ring has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-55',
313
+ field.invalid ? 'border-danger focus-within:border-danger focus-within:ring-danger-soft' : '',
314
+ hasField ? '' : (attrs.class as string),
315
+ tweening
316
+ ? 'transition-[height,border-color,box-shadow] duration-fast ease-[var(--ease-emphasized)] motion-reduce:transition-none'
317
+ : '',
318
+ resizing ? 'overflow-hidden' : '',
319
+ )
320
+ "
321
+ :style="hasField ? undefined : attrs.style"
322
+ @update:model-value="onModel"
323
+ >
324
+ <!-- Tags grow in when added and shrink out when removed; the ones
325
+ already there on mount stay still (no `appear`). -->
326
+ <TransitionGroup
327
+ enter-active-class="animate-[chip-in_var(--duration-fast)_var(--ease-emphasized)] motion-reduce:animate-none"
328
+ leave-active-class="animate-[chip-out_var(--duration-fast)_ease] motion-reduce:animate-none"
329
+ >
330
+ <TagsInputItem
331
+ v-for="{ tag, key } in keyed"
332
+ :key="key"
333
+ :data-tag-key="key"
334
+ :value="tag"
335
+ class="inline-flex max-w-full items-center gap-1 rounded-pill bg-surface-raised py-0.5 pr-1 pl-2.5 text-xs font-medium text-ink outline-none data-[state=active]:ring-2 data-[state=active]:ring-ring"
336
+ >
337
+ <TagsInputItemText class="truncate" />
338
+ <TagsInputItemDelete
339
+ :aria-labelledby="undefined"
340
+ :aria-label="text.remove.replace('{tag}', tag)"
341
+ class="grid size-4 shrink-0 place-items-center rounded-pill text-muted transition-colors hover:bg-ink/10 hover:text-ink"
342
+ >
343
+ <LpIcon name="lucide:x" :size="12" />
344
+ </TagsInputItemDelete>
345
+ </TagsInputItem>
346
+ </TransitionGroup>
347
+ <TagsInputInput
348
+ :ref="(c) => (inputEl = (c as { $el?: HTMLInputElement } | null)?.$el ?? null)"
349
+ :placeholder="modelValue.length ? undefined : placeholder"
350
+ :aria-invalid="field.invalid || undefined"
351
+ :aria-describedby="describedBy"
352
+ class="min-w-24 flex-1 bg-transparent px-1 outline-none placeholder:text-muted"
353
+ @keydown="onKeydown"
354
+ @beforeinput="onBeforeInput"
355
+ @input="onInput"
356
+ @paste="onPaste"
357
+ @blur="onBlur"
358
+ />
359
+ </TagsInputRoot>
360
+ <LpFieldNotice v-if="standalone" :notice="notice" :id="noticeId" />
361
+ </div>
362
+ <span class="sr-only" aria-live="polite">{{ announcement }}</span>
363
+ </component>
364
+ </template>
@@ -1,5 +1,11 @@
1
+ <script lang="ts">
2
+ export default { inheritAttrs: false }
3
+ </script>
4
+
1
5
  <script setup lang="ts">
2
- import { computed } from "vue"
6
+ import { computed, useAttrs } from "vue"
7
+ import { useFieldControl } from "../composables/useField"
8
+ import { mergeClass } from "../composables/useMergedClass"
3
9
  import LpFormField from "./LpFormField.vue"
4
10
 
5
11
  const props = defineProps<{
@@ -21,25 +27,57 @@ const props = defineProps<{
21
27
  defineEmits<{ (e: "update:modelValue", value: string): void }>()
22
28
 
23
29
  const hasField = computed(() => !!props.label || !!props.hint || !!props.error)
30
+
31
+ // class/style lay out the field when there is one, and style the <textarea>
32
+ // when it stands alone; every other attr (name, maxlength, autofocus, @blur…)
33
+ // belongs to the <textarea> itself.
34
+ const attrs = useAttrs()
35
+ const textareaAttrs = computed(() => {
36
+ const { class: _c, style: _s, ...rest } = attrs
37
+ return rest
38
+ })
39
+
40
+ const field = useFieldControl(() => ({
41
+ wrapped: hasField.value,
42
+ hasMessage: !!props.error || !!props.hint,
43
+ id: typeof attrs.id === "string" ? attrs.id : undefined,
44
+ invalid: props.invalid || !!props.error,
45
+ required: props.required,
46
+ disabled: props.disabled,
47
+ }))
48
+
49
+ const textareaClass = computed(() =>
50
+ mergeClass(
51
+ "w-full resize-y rounded-control border bg-surface-soft px-3 py-2 text-sm text-ink outline-none transition-colors duration-[var(--duration-fast)] placeholder:text-muted focus:border-brand focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-55",
52
+ field.value.invalid ? "border-danger focus:border-danger focus:ring-danger-soft" : "border-line",
53
+ hasField.value ? "" : (attrs.class as string),
54
+ ),
55
+ )
24
56
  </script>
25
57
 
26
58
  <template>
27
59
  <component
28
60
  :is="hasField ? LpFormField : 'div'"
61
+ :id="hasField ? field.id : undefined"
29
62
  :label="hasField ? label : undefined"
30
63
  :hint="hasField ? hint : undefined"
31
64
  :error="hasField ? error : undefined"
32
65
  :required="hasField ? required : undefined"
33
- :class="hasField ? undefined : 'contents'"
66
+ :class="hasField ? attrs.class : 'contents'"
67
+ :style="hasField ? attrs.style : undefined"
34
68
  >
35
69
  <textarea
70
+ v-bind="textareaAttrs"
71
+ :id="field.id"
36
72
  :value="modelValue"
37
73
  :placeholder="placeholder"
38
74
  :rows="rows ?? 4"
39
- :disabled="disabled"
40
- :required="required || undefined"
41
- class="w-full resize-y rounded-control border bg-surface-soft px-3 py-2 text-sm text-ink outline-none transition-colors duration-[var(--duration-fast)] placeholder:text-muted focus:border-brand focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-55"
42
- :class="(invalid || error) ? 'border-danger focus:border-danger focus:ring-danger-soft' : 'border-line'"
75
+ :disabled="field.disabled"
76
+ :required="field.required || undefined"
77
+ :aria-invalid="field.invalid || undefined"
78
+ :aria-describedby="field.describedBy"
79
+ :class="textareaClass"
80
+ :style="hasField ? undefined : (attrs.style as never)"
43
81
  @input="$emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)"
44
82
  />
45
83
  </component>
@@ -0,0 +1,41 @@
1
+ <script lang="ts">
2
+ export default { inheritAttrs: false }
3
+ </script>
4
+
5
+ <script setup lang="ts">
6
+ /*
7
+ * Applies a theme to a subtree instead of the whole page: a dark sidebar in a
8
+ * light app, a live preview of a theme being edited, an embedded widget that
9
+ * keeps its own look. Every token is declared on this element, derived ones
10
+ * included, so kit components inside re-skin while the page around them keeps
11
+ * the global theme. Nothing is persisted; this is presentation only.
12
+ */
13
+ import { computed, type Component } from "vue"
14
+ import type { TokenSet } from "../theme/tokens"
15
+ import { themeEntries } from "../theme/useTheme"
16
+ import { useMergedAttrs } from "../composables/useMergedClass"
17
+
18
+ const props = withDefaults(
19
+ defineProps<{
20
+ theme: TokenSet
21
+ /** Root element. A wrapper that should not affect layout can use `class="contents"`. */
22
+ as?: string | Component
23
+ }>(),
24
+ { as: "div" },
25
+ )
26
+
27
+ // Paints its own surface and text so the scoped theme reads as one; a class
28
+ // like `bg-transparent` overrides that.
29
+ const { class: rootClass, attrs: rest } = useMergedAttrs("bg-surface text-ink")
30
+
31
+ const style = computed(() => ({
32
+ ...Object.fromEntries(themeEntries(props.theme)),
33
+ colorScheme: props.theme.mode,
34
+ }))
35
+ </script>
36
+
37
+ <template>
38
+ <component :is="as" :style="style" :data-theme-mode="theme.mode" :class="rootClass" v-bind="rest">
39
+ <slot />
40
+ </component>
41
+ </template>
@@ -12,6 +12,7 @@
12
12
  * fetches; it just drives the theme engine.
13
13
  */
14
14
  import { computed, onMounted, ref } from "vue"
15
+ import { useLabels } from "../composables/useLpConfig"
15
16
  import { presets } from "../theme/presets"
16
17
  import type { TokenSet } from "../theme/tokens"
17
18
  import { useTheme } from "../theme/useTheme"
@@ -33,9 +34,9 @@ const props = withDefaults(
33
34
  cycleThemes?: (string | TokenSet)[]
34
35
  /**
35
36
  * Trigger style:
36
- * - "swatch" (default): a square showing the active theme's brand colour.
37
+ * - "swatch" (default): a round chip previewing the theme's surface and brand.
37
38
  * - "icon": a sun/moon icon following the active theme's mode.
38
- * - "pill": swatch + the theme name in a pill (always shows the label).
39
+ * - "pill": the chip and the theme name in a rounded pill.
39
40
  */
40
41
  variant?: "swatch" | "icon" | "pill"
41
42
  /** Show the active theme's name next to the trigger (forced on for "pill"). */
@@ -51,6 +52,13 @@ const props = withDefaults(
51
52
  transition?: boolean
52
53
  size?: "sm" | "md" | "lg"
53
54
  "aria-label"?: string
55
+ /**
56
+ * Offer a "System" entry in the menu that follows the OS colour scheme,
57
+ * switching between these two themes (names from `themes`, or TokenSets).
58
+ */
59
+ system?: { light: string | TokenSet; dark: string | TokenSet }
60
+ /** Text overrides. `trigger` may contain `{name}`. */
61
+ labels?: Partial<{ trigger: string; system: string }>
54
62
  }>(),
55
63
  {
56
64
  variant: "swatch",
@@ -66,6 +74,12 @@ const emit = defineEmits<{
66
74
 
67
75
  const theme = useTheme()
68
76
 
77
+ const text = useLabels(
78
+ "themeSwitcher",
79
+ { trigger: "Theme: {name}. Click to switch, right-click to choose.", system: "System" },
80
+ () => props.labels,
81
+ )
82
+
69
83
  const list = computed<TokenSet[]>(() =>
70
84
  props.themes && props.themes.length ? props.themes : Object.values(presets),
71
85
  )
@@ -75,9 +89,6 @@ const list = computed<TokenSet[]>(() =>
75
89
  const activeName = computed(
76
90
  () => props.modelValue ?? (theme.currentName.value || list.value[0]?.name || ""),
77
91
  )
78
- const active = computed<TokenSet | undefined>(
79
- () => list.value.find((t) => t.name === activeName.value) ?? list.value[0],
80
- )
81
92
 
82
93
  // Apply the consumer's default on mount when nothing is cached yet. init()
83
94
  // hydrates from the cache first (a saved choice wins), so this only takes effect
@@ -93,14 +104,12 @@ onMounted(() => {
93
104
  })
94
105
 
95
106
  const SIZES = { sm: "size-7", md: "size-8", lg: "size-9" } as const
107
+ const HEIGHTS = { sm: "h-7", md: "h-8", lg: "h-9" } as const
96
108
  const ICON_SIZE = { sm: 15, md: 16, lg: 18 } as const
109
+ const CHIP = { sm: "size-3.5", md: "size-4", lg: "size-[18px]" } as const
97
110
 
98
111
  const wantsLabel = computed(() => props.showLabel || props.variant === "pill")
99
112
 
100
- // Labelless icon variant renders as a plain square icon button (no border/fill),
101
- // so it sits inline with other toolbar icon buttons. Other variants stay framed.
102
- const bareIcon = computed(() => props.variant === "icon" && !wantsLabel.value)
103
-
104
113
  // Apply a theme through the kit's self-managed setter (it persists + updates
105
114
  // the shared `current`), then notify. The reveal animates from the click point.
106
115
  function applyTheme(t: TokenSet, ev?: MouseEvent) {
@@ -140,18 +149,49 @@ function cycle(ev: MouseEvent) {
140
149
  // Right-click menu: every theme, the active one checked. A circular-reveal from
141
150
  // the (saved) click point isn't available here, so the menu picks reveal from
142
151
  // the trigger centre via applyWithTransition's default origin.
143
- const menu = computed<ContextMenuItemDef[]>(() =>
144
- list.value.map((t) => ({
152
+ function resolve(t: string | TokenSet): TokenSet | undefined {
153
+ return typeof t === "string" ? list.value.find((x) => x.name === t) : t
154
+ }
155
+
156
+ const systemPair = computed(() => {
157
+ if (!props.system) return null
158
+ const light = resolve(props.system.light)
159
+ const dark = resolve(props.system.dark)
160
+ return light && dark ? { light, dark } : null
161
+ })
162
+
163
+ function followSystem() {
164
+ const pair = systemPair.value
165
+ if (!pair) return
166
+ theme.setSystem(pair)
167
+ const next = theme.current.value as TokenSet | null
168
+ if (next) {
169
+ emit("update:modelValue", next.name)
170
+ emit("change", next)
171
+ }
172
+ }
173
+
174
+ const menu = computed<ContextMenuItemDef[]>(() => {
175
+ const following = theme.system.value
176
+ const items: ContextMenuItemDef[] = list.value.map((t) => ({
145
177
  label: t.name,
146
178
  icon:
147
- t.name === activeName.value
179
+ !following && t.name === activeName.value
148
180
  ? "lucide:check"
149
181
  : t.mode === "dark"
150
182
  ? "lucide:moon"
151
183
  : "lucide:sun",
152
184
  onSelect: () => applyTheme(t),
153
- })),
154
- )
185
+ }))
186
+ if (systemPair.value) {
187
+ items.unshift({
188
+ label: text.value.system,
189
+ icon: following ? "lucide:check" : "lucide:monitor",
190
+ onSelect: followSystem,
191
+ })
192
+ }
193
+ return items
194
+ })
155
195
 
156
196
  /*
157
197
  * The theme the SERVER rendered is not necessarily the one the client restores:
@@ -176,7 +216,7 @@ const labelName = computed(() =>
176
216
  )
177
217
 
178
218
  const triggerLabel = computed(
179
- () => props["aria-label"] ?? `Theme: ${labelName.value}. Click to switch, right-click to choose.`,
219
+ () => props["aria-label"] ?? text.value.trigger.replace("{name}", labelName.value),
180
220
  )
181
221
  </script>
182
222
 
@@ -186,34 +226,37 @@ const triggerLabel = computed(
186
226
  type="button"
187
227
  :aria-label="triggerLabel"
188
228
  :title="triggerLabel"
189
- class="inline-flex items-center justify-center gap-2 rounded-control outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
229
+ class="inline-flex shrink-0 items-center justify-center gap-2 outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring"
190
230
  :class="
191
- bareIcon
192
- ? [SIZES[size], 'text-muted hover:bg-surface-soft hover:text-ink']
193
- : [
194
- 'border border-line bg-surface-raised px-1.5 text-ink hover:border-line-strong',
195
- wantsLabel ? 'py-1 pr-2.5' : '',
231
+ wantsLabel
232
+ ? [
233
+ HEIGHTS[size],
234
+ 'rounded-full border border-line bg-surface-raised pl-2 pr-3 text-ink hover:border-line-strong hover:bg-surface-soft',
196
235
  ]
236
+ : [SIZES[size], 'rounded-control text-muted hover:bg-surface-soft hover:text-ink']
197
237
  "
198
238
  @click="cycle"
199
239
  >
200
- <!-- swatch: brand over surface, hinting the theme at a glance -->
240
+ <!-- A preview of the applied theme: its surface and brand halves. Read from
241
+ the applied variables rather than the theme object, so the server's
242
+ render (painted from the cookie) and the client's agree. -->
201
243
  <span
202
244
  v-if="variant !== 'icon'"
203
- class="grid shrink-0 place-items-center rounded-control border border-line/60"
204
- :class="SIZES[size]"
205
- :style="{ background: active?.colors.surface }"
206
- >
207
- <span
208
- class="size-3 rounded-full"
209
- :style="{ background: active?.colors.brand }"
245
+ class="shrink-0 rounded-full shadow-[inset_0_0_0_1px_color-mix(in_srgb,var(--color-ink)_22%,transparent)]"
246
+ :class="CHIP[size]"
247
+ style="background: linear-gradient(135deg, var(--color-surface) 50%, var(--color-brand) 50%)"
248
+ />
249
+ <!-- sun/moon by mode, chosen by CSS from the applied theme's mode -->
250
+ <span v-else class="grid shrink-0 place-items-center">
251
+ <LpIcon
252
+ name="lucide:moon"
253
+ :size="ICON_SIZE[size]"
254
+ class="in-data-[theme-mode=light]:hidden"
210
255
  />
211
- </span>
212
- <!-- icon: sun/moon by mode -->
213
- <span v-else class="grid shrink-0 place-items-center" :class="SIZES[size]">
214
256
  <LpIcon
215
- :name="active?.mode === 'dark' ? 'lucide:moon' : 'lucide:sun'"
257
+ name="lucide:sun"
216
258
  :size="ICON_SIZE[size]"
259
+ class="hidden in-data-[theme-mode=light]:block"
217
260
  />
218
261
  </span>
219
262