@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
@@ -24,7 +24,8 @@ export default { inheritAttrs: false }
24
24
  */
25
25
  import { computed, reactive, ref, watch } from "vue"
26
26
  import { useClipboard } from "../composables/useClipboard"
27
- import { type CodeLang, tokenizeLine } from "./codeHighlight"
27
+ import { type CodeLang, tokenizeLines } from "./codeHighlight"
28
+ import { useLabels } from "../composables/useLpConfig"
28
29
  import LpIcon from "./LpIcon.vue"
29
30
  import { useMergedAttrs } from "../composables/useMergedClass"
30
31
 
@@ -49,6 +50,7 @@ const props = withDefaults(
49
50
  height?: string
50
51
  /** Auto-collapse a leading import/package block (no markers needed). */
51
52
  foldImports?: boolean
53
+ labels?: Partial<Record<"unlock" | "lock" | "copy" | "copied" | "imports" | "details" | "lines", string>>
52
54
  }>(),
53
55
  {
54
56
  lang: "plain",
@@ -66,6 +68,21 @@ const emit = defineEmits<{
66
68
  (e: "update:locked", value: boolean): void
67
69
  }>()
68
70
 
71
+ const text = useLabels(
72
+ "codeBlock",
73
+ {
74
+ unlock: "Unlock to edit",
75
+ lock: "Lock (read-only)",
76
+ copy: "Copy code",
77
+ copied: "Copied",
78
+ imports: "imports",
79
+ details: "details",
80
+ /** `{n}` is replaced by the count. */
81
+ lines: "({n} lines)",
82
+ },
83
+ () => props.labels,
84
+ )
85
+
69
86
  const code = computed(() => props.modelValue ?? "")
70
87
  const rawLines = computed(() => code.value.replace(/\n$/, "").split("\n"))
71
88
 
@@ -114,7 +131,7 @@ const segments = computed<Segment[]>(() => {
114
131
  flushPlain()
115
132
  const lines: NumberedLine[] = []
116
133
  for (let k = start; k < realEnd; k++) lines.push({ n: k + 1, text: src[k] })
117
- out.push({ kind: "fold", id: foldId++, label: "imports", defaultOpen: false, lines })
134
+ out.push({ kind: "fold", id: foldId++, label: text.value.imports, defaultOpen: false, lines })
118
135
  i = realEnd
119
136
  }
120
137
  }
@@ -126,7 +143,7 @@ const segments = computed<Segment[]>(() => {
126
143
  flushPlain()
127
144
  const rest = startMatch[1] ?? ""
128
145
  const defaultOpen = /\bopen\b/.test(rest)
129
- const label = rest.replace(/\bopen\b/, "").trim() || "details"
146
+ const label = rest.replace(/\bopen\b/, "").trim() || text.value.details
130
147
  const lines: NumberedLine[] = []
131
148
  i++
132
149
  for (; i < src.length; i++) {
@@ -143,6 +160,7 @@ const segments = computed<Segment[]>(() => {
143
160
  })
144
161
 
145
162
  const hasFolds = computed(() => segments.value.some((s) => s.kind === "fold"))
163
+ const editing = computed(() => !props.locked && !hasFolds.value)
146
164
 
147
165
  // Per-region open state, keyed by fold id, defaulting to each region's declared
148
166
  // open flag. Reset when the set of folds changes.
@@ -159,9 +177,21 @@ function toggleFold(id: number) {
159
177
  openState[id] = !openState[id]
160
178
  }
161
179
 
162
- /** Highlight one line's text into tokens. */
163
- function tokens(text: string) {
164
- return tokenizeLine(text, props.lang)
180
+ /* Highlighted once for the whole snippet, so a block comment that spans lines
181
+ is coloured as one; looked up by the line's 1-based number. */
182
+ const highlighted = computed(() => tokenizeLines(rawLines.value, props.lang))
183
+ function tokens(n: number) {
184
+ return highlighted.value[n - 1] ?? []
185
+ }
186
+
187
+ /* The textarea is the scroller while editing; the highlighted copy under it
188
+ follows, or the caret drifts over the wrong text on a long line. */
189
+ const preEl = ref<HTMLElement | null>(null)
190
+ function syncScroll(e: Event) {
191
+ const area = e.target as HTMLTextAreaElement
192
+ if (!preEl.value) return
193
+ preEl.value.scrollLeft = area.scrollLeft
194
+ preEl.value.scrollTop = area.scrollTop
165
195
  }
166
196
 
167
197
  // Copied text is the real code with marker lines removed (so it pastes clean),
@@ -214,7 +244,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
214
244
  v-if="lockToggle"
215
245
  type="button"
216
246
  class="group/lock flex items-center gap-1 rounded px-1.5 py-1 outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
217
- :aria-label="locked ? 'Unlock to edit' : 'Lock (read-only)'"
247
+ :aria-label="locked ? text.unlock : text.lock"
218
248
  @click="toggleLock"
219
249
  >
220
250
  <LpIcon
@@ -227,7 +257,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
227
257
  v-if="copyable"
228
258
  type="button"
229
259
  class="group/copy flex items-center gap-1 rounded px-1.5 py-1 outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
230
- :aria-label="copied ? 'Copied' : 'Copy code'"
260
+ :aria-label="copied ? text.copied : text.copy"
231
261
  @click="copy"
232
262
  >
233
263
  <LpIcon
@@ -245,7 +275,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
245
275
  <div
246
276
  class="lp-scrollbar-none relative"
247
277
  :style="height ? { height } : undefined"
248
- :class="height ? 'overflow-auto' : ''"
278
+ :class="height ? (editing ? 'overflow-hidden' : 'overflow-auto') : ''"
249
279
  >
250
280
  <!-- Editable overlay: only when unlocked and there are no folds. -->
251
281
  <textarea
@@ -253,12 +283,18 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
253
283
  :value="code"
254
284
  spellcheck="false"
255
285
  class="lp-scrollbar-none absolute inset-0 size-full resize-none overflow-auto whitespace-pre bg-transparent p-3 leading-relaxed text-transparent caret-brand outline-none"
256
- :class="[wrap ? 'whitespace-pre-wrap break-words' : 'whitespace-pre', lineNumbers ? 'pl-12' : '']"
286
+ :class="[wrap ? 'whitespace-pre-wrap break-words' : 'whitespace-pre', lineNumbers ? 'pl-13' : '']"
257
287
  @input="onInput"
288
+ @scroll="syncScroll"
258
289
  />
259
290
  <pre
291
+ ref="preEl"
260
292
  class="lp-scrollbar-none m-0 overflow-auto p-3 leading-relaxed"
261
- :class="[wrap ? 'whitespace-pre-wrap break-words' : 'whitespace-pre', !locked && !hasFolds ? 'pointer-events-none' : '']"
293
+ :class="[
294
+ wrap ? 'whitespace-pre-wrap break-words' : 'whitespace-pre',
295
+ editing ? 'pointer-events-none' : '',
296
+ editing && height ? 'h-full overflow-hidden' : '',
297
+ ]"
262
298
  ><code class="block"><template
263
299
  v-for="(seg, s) in segments"
264
300
  :key="s"
@@ -271,7 +307,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
271
307
  class="mr-3 inline-block w-7 select-none text-right text-muted/60"
272
308
  aria-hidden="true"
273
309
  >{{ ln.n }}</span><span
274
- v-for="(tok, j) in tokens(ln.text)"
310
+ v-for="(tok, j) in tokens(ln.n)"
275
311
  :key="j"
276
312
  :class="tok.cls"
277
313
  >{{ tok.text }}</span></span></template><!-- Fold region --><template
@@ -288,7 +324,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
288
324
  /><span class="select-none">{{ openState[seg.id] ? seg.label : seg.label + ' …' }}</span><span
289
325
  v-if="!openState[seg.id]"
290
326
  class="select-none text-muted/50"
291
- >({{ seg.lines.length }} lines)</span></button><template
327
+ >{{ text.lines.replace("{n}", String(seg.lines.length)) }}</span></button><template
292
328
  v-if="openState[seg.id]"
293
329
  ><span
294
330
  v-for="ln in seg.lines"
@@ -299,7 +335,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
299
335
  class="mr-3 inline-block w-7 select-none text-right text-muted/60"
300
336
  aria-hidden="true"
301
337
  >{{ ln.n }}</span><span
302
- v-for="(tok, j) in tokens(ln.text)"
338
+ v-for="(tok, j) in tokens(ln.n)"
303
339
  :key="j"
304
340
  :class="tok.cls"
305
341
  >{{ tok.text }}</span></span></template></template></template></code></pre>
@@ -36,7 +36,9 @@ import {
36
36
  PopoverRoot,
37
37
  PopoverTrigger,
38
38
  } from "reka-ui"
39
- import { computed, ref, watch } from "vue"
39
+ import { computed, nextTick, ref, watch } from "vue"
40
+ import { prefersReducedMotion } from "../composables/easing"
41
+ import { useFieldControl } from "../composables/useField"
40
42
  import {
41
43
  clampHsv,
42
44
  DEFAULT_SWATCHES,
@@ -52,6 +54,7 @@ import { useClipboard } from "../composables/useClipboard"
52
54
  import { POPOVER_PANEL } from "./dropdown"
53
55
  import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
54
56
  import LpIcon from "./LpIcon.vue"
57
+ import LpRollingText from "./LpRollingText.vue"
55
58
 
56
59
  /**
57
60
  * Every user-facing string the picker renders on its own — the menu items, the
@@ -271,7 +274,9 @@ function trackPointer(e: PointerEvent, onMove: (x: number, y: number) => void) {
271
274
 
272
275
  const onFieldPointer = (e: PointerEvent) =>
273
276
  trackPointer(e, (x, y) => set({ s: x, v: 1 - y }))
274
- const onHuePointer = (e: PointerEvent) => trackPointer(e, (x) => set({ h: x * 360 }))
277
+ // 360° is 0° again, so the far edge of the rail would throw the thumb back to
278
+ // the start; it stops just short instead, as End does.
279
+ const onHuePointer = (e: PointerEvent) => trackPointer(e, (x) => set({ h: Math.min(x * 360, 359.9) }))
275
280
  const onAlphaPointer = (e: PointerEvent) => trackPointer(e, (x) => set({ a: x }))
276
281
 
277
282
  /* ── keyboard ─────────────────────────────────────
@@ -303,11 +308,13 @@ function step(e: KeyboardEvent, axis: "sv" | "h" | "a") {
303
308
  }
304
309
 
305
310
  /* ── text field ───────────────────────────────────
306
- Typed text is parsed on every keystroke but only applied when it resolves to
307
- a real colour, so a half-written "#3b8" never snaps the picker somewhere the
308
- user is still on their way past. The field shows what is typed while it has
309
- focus and the committed value once it doesn't. */
311
+ Typed text is parsed on every keystroke and previews as soon as it resolves
312
+ to a real colour, but is only committed (`change`) on Enter or when the field
313
+ loses focus: "#3b8" and "#3b82" are valid colours on the way to "#3b82f6",
314
+ and committing each one handed listeners three colours nobody chose. The
315
+ field shows what is typed while it has focus and the value once it doesn't. */
310
316
  const typed = ref<string | null>(null)
317
+ let typedChange = false
311
318
  const inputValue = computed(() => typed.value ?? current.value)
312
319
 
313
320
  function onInput(e: Event) {
@@ -318,11 +325,19 @@ function onInput(e: Event) {
318
325
  const next = rgbToHsv(rgb)
319
326
  if (next.s === 0) next.h = hsv.value.h
320
327
  if (next.v === 0) next.s = hsv.value.s
321
- set(props.alpha ? next : { ...next, a: 1 }, true)
328
+ set(props.alpha ? next : { ...next, a: 1 })
329
+ typedChange = true
330
+ }
331
+
332
+ function commitTyped() {
333
+ if (!typedChange) return
334
+ typedChange = false
335
+ emit("change", current.value)
322
336
  }
323
337
 
324
338
  function onInputBlur() {
325
339
  typed.value = null
340
+ commitTyped()
326
341
  }
327
342
 
328
343
  function pickSwatch(value: string) {
@@ -463,8 +478,12 @@ function remember(value: string) {
463
478
  // Remember on close rather than on every commit: the colour a person leaves the
464
479
  // popover on is the one they chose, while the ones they passed through on the
465
480
  // way are not history, they are the search for it.
481
+ // Only a colour that changed while the popover was open: opening it to look,
482
+ // or to copy the value out, is not a choice worth recording.
483
+ let openedOn = ""
466
484
  watch(open, (isOpen, was) => {
467
- if (was && !isOpen) remember(current.value)
485
+ if (isOpen) openedOn = current.value
486
+ else if (was && current.value !== openedOn) remember(current.value)
468
487
  })
469
488
 
470
489
  /**
@@ -537,9 +556,28 @@ async function pasteColor() {
537
556
  * emit the picker would display rgb() while the app still stored hex, and the
538
557
  * next thing to read that value would disagree with the screen.
539
558
  */
559
+ /*
560
+ * A format switch rewrites the whole text in place ("#3b82f6" becomes
561
+ * "rgb(59 130 246)"), so the new value rolls in over the field the way the
562
+ * kit's other changing labels do. An <input> cannot animate its own text, so
563
+ * a rolling copy lies over it for the length of the roll; it only ever moves
564
+ * on a format switch, never while a colour is being dragged.
565
+ */
566
+ const ROLL_MS = 320
567
+ const rollText = ref(current.value)
568
+ const rolling = ref(false)
569
+ let rollTimer: ReturnType<typeof setTimeout> | undefined
570
+
540
571
  function setFormat(f: ColorFormat) {
541
572
  if (activeFormat.value === f) return
573
+ rollText.value = inputValue.value
542
574
  activeFormat.value = f
575
+ if (!prefersReducedMotion()) {
576
+ rolling.value = true
577
+ void nextTick(() => (rollText.value = current.value))
578
+ if (rollTimer) clearTimeout(rollTimer)
579
+ rollTimer = setTimeout(() => (rolling.value = false), ROLL_MS + 40)
580
+ }
543
581
  typed.value = null
544
582
  emit("update:modelValue", current.value)
545
583
  emit("change", current.value)
@@ -573,6 +611,12 @@ const menuItems = computed<ContextMenuItemDef[]>(() => {
573
611
  : builtin
574
612
  })
575
613
 
614
+ const field = useFieldControl(() => ({
615
+ wrapped: false,
616
+ invalid: props.invalid,
617
+ disabled: props.disabled,
618
+ }))
619
+
576
620
  defineExpose({ open })
577
621
  </script>
578
622
 
@@ -592,12 +636,15 @@ defineExpose({ open })
592
636
  <PopoverTrigger as-child>
593
637
  <button
594
638
  type="button"
595
- :disabled="disabled"
639
+ :id="field.id"
640
+ :disabled="field.disabled"
596
641
  :aria-label="ariaLabel"
642
+ :aria-invalid="field.invalid || undefined"
643
+ :aria-describedby="field.describedBy"
597
644
  class="group inline-flex w-full items-center gap-2 rounded-control border bg-surface-soft px-2 text-left text-ink outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:border-brand disabled:cursor-not-allowed disabled:opacity-55"
598
645
  :class="[
599
646
  triggerSize[size],
600
- invalid ? 'border-danger focus-visible:ring-danger-soft' : 'border-line focus-visible:border-brand',
647
+ field.invalid ? 'border-danger focus-visible:ring-danger-soft' : 'border-line focus-visible:border-brand',
601
648
  ]"
602
649
  >
603
650
  <!-- The swatch sits on the checkerboard so a transparent colour looks
@@ -628,6 +675,7 @@ defineExpose({ open })
628
675
 
629
676
  <PopoverPortal>
630
677
  <PopoverContent
678
+ :collision-padding="8"
631
679
  :side="side"
632
680
  :align="align"
633
681
  :side-offset="6"
@@ -645,6 +693,9 @@ defineExpose({ open })
645
693
  role="slider"
646
694
  :tabindex="disabled ? -1 : 0"
647
695
  :aria-label="l.saturation"
696
+ :aria-valuemin="0"
697
+ :aria-valuemax="100"
698
+ :aria-valuenow="Math.round(hsv.s * 100)"
648
699
  :aria-valuetext="fill(l.saturationValueText, { s: Math.round(hsv.s * 100), v: Math.round(hsv.v * 100) })"
649
700
  class="relative h-36 w-full cursor-crosshair touch-none overflow-hidden rounded-control outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
650
701
  :style="fieldStyle"
@@ -719,6 +770,15 @@ defineExpose({ open })
719
770
  class="size-4 shrink-0 rounded-[5px] border border-line"
720
771
  :style="{ backgroundColor: previewCss }"
721
772
  />
773
+ <span class="relative flex min-w-0 flex-1 items-center">
774
+ <LpRollingText
775
+ v-show="rolling"
776
+ :value="rollText"
777
+ :duration="ROLL_MS"
778
+ align="start"
779
+ aria-hidden="true"
780
+ class="pointer-events-none absolute inset-0 font-mono text-xs text-ink"
781
+ />
722
782
  <input
723
783
  :value="inputValue"
724
784
  :disabled="disabled"
@@ -727,9 +787,12 @@ defineExpose({ open })
727
787
  autocomplete="off"
728
788
  :aria-label="l.value"
729
789
  class="min-w-0 flex-1 select-text bg-transparent font-mono text-xs text-ink outline-none placeholder:text-muted"
790
+ :class="rolling ? 'text-transparent' : ''"
730
791
  @input="onInput"
792
+ @keydown.enter="commitTyped"
731
793
  @blur="onInputBlur"
732
794
  />
795
+ </span>
733
796
  </div>
734
797
 
735
798
  <!-- Recent sits ABOVE the presets because it is the row that
@@ -44,6 +44,7 @@ import { useGlass } from "../composables/useGlass"
44
44
  import { useClipboard } from "../composables/useClipboard"
45
45
  import { useHotkeys } from "../composables/useHotkeys"
46
46
  import { useModalLayer } from "./modalDepth"
47
+ import { useLabels } from "../composables/useLpConfig"
47
48
  import { parseCommandQuery, rankCommandItems } from "./commandSearch"
48
49
 
49
50
  export interface Command {
@@ -87,7 +88,7 @@ export interface Command {
87
88
  * `onSelect` still runs when the mode opens, so a command can both act and
88
89
  * drill in.
89
90
  */
90
- children?: Command[] | ((query: string) => Command[] | Promise<Command[]>)
91
+ children?: Command[] | ((query: string, signal: AbortSignal) => Command[] | Promise<Command[]>)
91
92
  /** Placeholder while inside this command's mode. */
92
93
  childPlaceholder?: string
93
94
  /**
@@ -121,7 +122,7 @@ export interface Command {
121
122
  }
122
123
 
123
124
  /** Footer hints and the mode chip, spelled out so an app can translate them. */
124
- export interface PaletteLabels {
125
+ export type PaletteLabels = {
125
126
  navigate: string
126
127
  select: string
127
128
  close: string
@@ -144,6 +145,11 @@ export interface PaletteLabels {
144
145
  * text is never displayed. Kept so existing `labels` objects still type-check.
145
146
  */
146
147
  noPreview?: string
148
+ /** Accessible name and description of the dialog, read by screen readers only. */
149
+ title?: string
150
+ description?: string
151
+ /** The key hint printed beside the box and in the footer. */
152
+ esc?: string
147
153
  }
148
154
 
149
155
  const props = withDefaults(
@@ -175,6 +181,12 @@ const props = withDefaults(
175
181
  * so a stored history never resurrects a command that has gone away.
176
182
  */
177
183
  recent?: string[]
184
+ /**
185
+ * Let the palette keep the history itself, in localStorage under this key:
186
+ * every top-level command run is remembered, newest first. Ignored while
187
+ * `recent` is bound, which stays the way to store it somewhere else.
188
+ */
189
+ recentKey?: string
178
190
  /** How many recent commands to surface. */
179
191
  recentLimit?: number
180
192
  /** Headings the palette adds itself. */
@@ -229,9 +241,10 @@ const props = withDefaults(
229
241
  )
230
242
 
231
243
  const frosted = useGlass(() => props.glass)
232
- const { claim, release, layer } = useModalLayer()
244
+ const contentRef = ref<{ $el: HTMLElement } | null>(null)
245
+ const { claim, release, layer } = useModalLayer({ element: () => contentRef.value?.$el })
233
246
 
234
- const DEFAULT_LABELS: PaletteLabels = {
247
+ const DEFAULT_LABELS: Required<PaletteLabels> = {
235
248
  navigate: "navigate",
236
249
  select: "select",
237
250
  close: "close",
@@ -245,9 +258,12 @@ const DEFAULT_LABELS: PaletteLabels = {
245
258
  copyLink: "Copy link",
246
259
  linkCopied: "Link copied",
247
260
  noPreview: "No preview",
261
+ title: "Command palette",
262
+ description: "Search and run a command.",
263
+ esc: "esc",
248
264
  }
249
265
 
250
- const text = computed<PaletteLabels>(() => ({ ...DEFAULT_LABELS, ...props.labels }))
266
+ const text = useLabels("palette", DEFAULT_LABELS, () => props.labels)
251
267
 
252
268
  const emit = defineEmits<{
253
269
  (e: "update:open", value: boolean): void
@@ -330,15 +346,42 @@ const selectedActions = computed(() => actionsOf(flat.value[selectedIndex.value]
330
346
  * actions it keeps that default.
331
347
  */
332
348
  function onTab(event: KeyboardEvent) {
333
- event.preventDefault()
334
- event.stopPropagation()
349
+ // Without actions Tab stays Tab, so the trail chips and a Retry button are
350
+ // still reachable from the keyboard.
335
351
  if (event.shiftKey) return
336
352
  const c = flat.value[selectedIndex.value]
337
353
  const actions = selectedActions.value
338
354
  if (!c || !actions.length) return
355
+ event.preventDefault()
356
+ event.stopPropagation()
339
357
  enter({ id: `${c.id}:actions`, label: c.label, icon: c.icon, children: actions })
340
358
  }
341
359
 
360
+ /*
361
+ * A mode holds the command it was entered through, and a static `children`
362
+ * list is a snapshot of that moment. When the app rebuilds its commands (a
363
+ * theme list whose tick follows the active theme), the open mode is matched
364
+ * to the new commands by id along its path and takes their children, so a
365
+ * `keepOpen` pick inside it shows its own effect.
366
+ */
367
+ watch(
368
+ () => props.commands,
369
+ (commands) => {
370
+ if (!trail.value.length) return
371
+ let level: Command[] = commands
372
+ const next: Command[] = []
373
+ for (const step of trail.value) {
374
+ const fresh = level.find((c) => c.id === step.id)
375
+ if (!fresh) return
376
+ next.push(fresh)
377
+ level = Array.isArray(fresh.children) ? fresh.children : []
378
+ }
379
+ trail.value = next
380
+ const last = next[next.length - 1]
381
+ if (last && Array.isArray(last.children)) modeItems.value = last.children
382
+ },
383
+ )
384
+
342
385
  function enter(c: Command) {
343
386
  trailState.value = [...trailState.value, { query: query.value, selectedId: selectedId.value }]
344
387
  trail.value = [...trail.value, c]
@@ -392,7 +435,7 @@ function fetcher(): ((q: string, signal: AbortSignal) => Command[] | Promise<Com
392
435
  const current = mode.value
393
436
  if (current) {
394
437
  return typeof current.children === "function"
395
- ? (q: string) => (current.children as (q: string) => Command[] | Promise<Command[]>)(q)
438
+ ? current.children
396
439
  : null
397
440
  }
398
441
  return props.search ?? null
@@ -486,11 +529,36 @@ const fetched = computed<Command[]>(() => {
486
529
  * Only on the root's empty query: once someone types, or drills into a mode,
487
530
  * relevance is the better order and a duplicated row is just noise.
488
531
  */
532
+ const RECENT_KEEP = 20
533
+ const ownRecent = ref<string[]>([])
534
+ watch(
535
+ () => props.recentKey,
536
+ (key) => {
537
+ if (!key || typeof localStorage === "undefined") return
538
+ try {
539
+ const stored: unknown = JSON.parse(localStorage.getItem(key) ?? "[]")
540
+ ownRecent.value = Array.isArray(stored) ? stored.filter((v): v is string => typeof v === "string") : []
541
+ } catch {
542
+ ownRecent.value = []
543
+ }
544
+ },
545
+ { immediate: true },
546
+ )
547
+ function remember(id: string) {
548
+ const key = props.recentKey
549
+ if (!key || props.recent) return
550
+ ownRecent.value = [id, ...ownRecent.value.filter((v) => v !== id)].slice(0, RECENT_KEEP)
551
+ try {
552
+ localStorage.setItem(key, JSON.stringify(ownRecent.value))
553
+ } catch {}
554
+ }
555
+ const recentIds = computed(() => props.recent ?? (props.recentKey ? ownRecent.value : []))
556
+
489
557
  const recentItems = computed<Command[]>(() => {
490
- if (mode.value || query.value.trim() || !props.recent?.length) return []
558
+ if (mode.value || query.value.trim() || !recentIds.value.length) return []
491
559
  const byId = new Map(props.commands.map((c) => [c.id, c]))
492
560
  const out: Command[] = []
493
- for (const id of props.recent) {
561
+ for (const id of recentIds.value) {
494
562
  const c = byId.get(id)
495
563
  if (c && !c.disabled && !out.includes(c)) out.push(c)
496
564
  if (out.length >= props.recentLimit) break
@@ -578,6 +646,18 @@ const paragraphs = computed(() =>
578
646
  .filter(Boolean),
579
647
  )
580
648
 
649
+ /*
650
+ * The row's description heads the pane only when it says something the body
651
+ * does not: a command whose full text opens with its description (or is it)
652
+ * would otherwise print the same sentences twice, once small and once large.
653
+ */
654
+ const paneDescription = computed(() => {
655
+ const description = active.value?.description?.trim()
656
+ if (!description) return undefined
657
+ const body = paragraphs.value.join(" ")
658
+ return body.startsWith(description) ? undefined : description
659
+ })
660
+
581
661
  /** Stagger between paragraphs, capped so a long section does not trickle in. */
582
662
  const STAGGER_MS = 45
583
663
  const STAGGER_MAX = 10
@@ -599,8 +679,11 @@ watch(
599
679
  else viewport.scrollTop = 0
600
680
  },
601
681
  )
682
+ // Rows ask for their index several times per render; a lookup keeps that O(1)
683
+ // instead of a scan of the whole list per row.
684
+ const flatIndex = computed(() => new Map(flat.value.map((c, i) => [c, i])))
602
685
  function flatIndexOf(c: Command): number {
603
- return flat.value.indexOf(c)
686
+ return flatIndex.value.get(c) ?? -1
604
687
  }
605
688
 
606
689
  // ── highlight ────────────────────────────────────────────────
@@ -689,6 +772,9 @@ function movePage(direction: -1 | 1) {
689
772
  function onPaletteKeydown(event: KeyboardEvent) {
690
773
  if (event.isComposing) return
691
774
  if (!(["ArrowDown", "ArrowUp", "Home", "End", "PageDown", "PageUp"] as string[]).includes(event.key)) return
775
+ // Home and End belong to the caret while there is text to move it through.
776
+ const typing = event.target instanceof HTMLInputElement && event.target.value !== ""
777
+ if (typing && (event.key === "Home" || event.key === "End")) return
692
778
  event.preventDefault()
693
779
  event.stopPropagation()
694
780
  if (event.key === "ArrowDown") moveDown()
@@ -750,11 +836,16 @@ function run(c: Command | undefined) {
750
836
  return
751
837
  }
752
838
  emit("run", c.id, c)
839
+ if (!mode.value) remember(c.id)
753
840
  if (c.keepOpen) {
754
841
  // Back to the top of a fresh list: the query that found this command is
755
842
  // spent, and leaving it would hide whatever the action just changed.
756
843
  query.value = ""
757
- select(0)
844
+ // Inside a mode the highlight stays on what was just picked, so trying
845
+ // themes one after another is a matter of the arrow keys; at the root the
846
+ // list it came from is gone and the top is the honest place to land.
847
+ if (mode.value) void nextTick(() => select(Math.max(0, flat.value.findIndex((x) => x.id === c.id))))
848
+ else select(0)
758
849
  inputRef.value?.focus()
759
850
  return
760
851
  }
@@ -828,6 +919,8 @@ useHotkeys(() =>
828
919
  code: "KeyK",
829
920
  ctrl: true,
830
921
  allowInInput: true,
922
+ overLayers: true,
923
+ repeat: false,
831
924
  handler: () => emit("update:open", !props.open),
832
925
  },
833
926
  ]
@@ -844,6 +937,7 @@ useHotkeys(() =>
844
937
  :style="{ zIndex: layer.scrim }"
845
938
  />
846
939
  <DialogContent
940
+ ref="contentRef"
847
941
  class="lp-cmd-dialog fixed left-1/2 top-[12vh] flex max-h-[76vh] -translate-x-1/2 flex-col overflow-hidden rounded-card border border-line shadow-panel outline-none data-[state=open]:animate-[pop-in_160ms_var(--ease-emphasized)] data-[state=closed]:animate-[pop-out_130ms_ease]"
848
942
  :class="[frosted ? (layer.nested ? 'lp-glass-overlay--nested' : 'lp-glass-overlay') : 'bg-surface-raised', previewPane && 'lp-cmd-split']"
849
943
  :style="{ zIndex: layer.panel }"
@@ -851,8 +945,8 @@ useHotkeys(() =>
851
945
  @open-auto-focus.prevent
852
946
  @keydown.capture="onPaletteKeydown"
853
947
  >
854
- <DialogTitle class="sr-only">Command palette</DialogTitle>
855
- <DialogDescription class="sr-only">Search and run a command.</DialogDescription>
948
+ <DialogTitle class="sr-only">{{ text.title }}</DialogTitle>
949
+ <DialogDescription class="sr-only">{{ text.description }}</DialogDescription>
856
950
 
857
951
  <!-- Filter. The trail sits inside the box as chips, the way a scoped
858
952
  search reads everywhere else: what you type is filtered by what is
@@ -895,7 +989,7 @@ useHotkeys(() =>
895
989
  @keydown.esc="onEscape"
896
990
  @keydown.tab="onTab"
897
991
  />
898
- <kbd class="shrink-0 rounded border border-line px-1.5 py-0.5 text-[10px] text-muted">esc</kbd>
992
+ <kbd class="shrink-0 rounded border border-line px-1.5 py-0.5 text-[10px] text-muted">{{ text.esc }}</kbd>
899
993
  </div>
900
994
 
901
995
  <!-- Results -->
@@ -1024,8 +1118,8 @@ useHotkeys(() =>
1024
1118
  <LpIcon v-if="active.icon" :name="active.icon" :size="16" class="shrink-0 text-muted" />
1025
1119
  <h3 class="min-w-0 text-base font-semibold">{{ active.label }}</h3>
1026
1120
  </div>
1027
- <p v-if="active.description" class="lp-cmd-para mt-0.5 text-xs text-muted">
1028
- {{ active.description }}
1121
+ <p v-if="paneDescription" class="lp-cmd-para mt-0.5 text-xs text-muted">
1122
+ {{ paneDescription }}
1029
1123
  </p>
1030
1124
  <p
1031
1125
  v-for="(para, i) in paragraphs"
@@ -1045,9 +1139,10 @@ useHotkeys(() =>
1045
1139
  </Transition>
1046
1140
  </div>
1047
1141
 
1048
- <!-- Footer hints -->
1142
+ <!-- Footer hints. Keyboard-only, so a touch screen without a keyboard
1143
+ gets the row back for results. -->
1049
1144
  <div
1050
- class="flex shrink-0 items-center gap-4 border-t border-line px-4 py-2.5 text-[11px] text-muted"
1145
+ class="flex shrink-0 items-center gap-4 border-t border-line px-4 py-2.5 text-[11px] text-muted [@media(hover:none)_and_(pointer:coarse)]:hidden"
1051
1146
  >
1052
1147
  <span class="flex items-center gap-1">
1053
1148
  <kbd class="rounded border border-line px-1 text-[10px]">↑</kbd>
@@ -1069,7 +1164,7 @@ useHotkeys(() =>
1069
1164
  {{ text.back }}
1070
1165
  </span>
1071
1166
  <span class="ml-auto flex items-center gap-1">
1072
- <kbd class="rounded border border-line px-1 text-[10px]">esc</kbd>
1167
+ <kbd class="rounded border border-line px-1 text-[10px]">{{ text.esc }}</kbd>
1073
1168
  {{ text.close }}
1074
1169
  </span>
1075
1170
  </div>