@leavepulse/ui 0.70.0 → 0.71.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 (345) hide show
  1. package/README.md +103 -0
  2. package/dist/assets/{LpCommandPalette-u-zOxKWa.css → LpCommandPalette-DgPZX0us.css} +24 -17
  3. package/dist/assets/{LpPaletteFacts-B9FdVmIO.css → LpPaletteFacts-CVNh5ICn.css} +16 -16
  4. package/dist/assets/{LpPaletteStats-ClE-37MK.css → LpPaletteStats-ZiZmR6vz.css} +5 -5
  5. package/dist/assets/LpRepeater-C1Cw27hj.css +21 -0
  6. package/dist/assets/{LpRollingText-BGnE6q9j.css → LpRollingText-dyOXedGO.css} +7 -0
  7. package/dist/assets/{LpTable-DHxEQ_-3.css → LpTable-DHvorDiU.css} +19 -19
  8. package/dist/assets/LpTabs-CO3EcsNV.css +18 -0
  9. package/dist/chunks/{ContextMenuItems-B5uE8Znw.js → ContextMenuItems-eS7qx9gD.js} +11 -5
  10. package/dist/chunks/{DropzoneLinkField-Bw8Flkhk.js → DropzoneLinkField-DkFhdfEJ.js} +2 -2
  11. package/dist/chunks/{FieldWrapper-BiennROB.js → FieldWrapper-YzbvvC3M.js} +1 -1
  12. package/dist/chunks/{LayoutCanvas-BsJIzhdd.js → LayoutCanvas-Ba4A9QC5.js} +1 -1
  13. package/dist/chunks/{LayoutNode-DJVhkSGk.js → LayoutNode-CTTTLMDM.js} +1 -1
  14. package/dist/chunks/{LpActionBar-CzOFar5t.js → LpActionBar-B7anGlkI.js} +2 -2
  15. package/dist/chunks/{LpAddressInput-CNnLTDZg.js → LpAddressInput-CD0bzrt-.js} +7 -7
  16. package/dist/chunks/{LpAlert-C5igj802.js → LpAlert-BHi7ARqu.js} +1 -1
  17. package/dist/chunks/{LpAppShell-CEUAB8kK.js → LpAppShell-1RmBp6xQ.js} +3 -3
  18. package/dist/chunks/{LpAutocomplete-D92niDHE.js → LpAutocomplete-DDqe2M5g.js} +5 -2
  19. package/dist/chunks/{LpAvatar-CLPl-qOF.js → LpAvatar-DqfKCIdr.js} +1 -1
  20. package/dist/chunks/{LpBadge-HOEPBYmi.js → LpBadge-C8fKEmTF.js} +2 -2
  21. package/dist/chunks/{LpBreadcrumbs-DYoPc3P4.js → LpBreadcrumbs-DhcoStFa.js} +2 -2
  22. package/dist/chunks/{LpButton-CVxBfGjd.js → LpButton-BQDAFXBd.js} +3 -0
  23. package/dist/chunks/{LpCalendar-pVL91-Pl.js → LpCalendar-NmrA47J9.js} +1 -1
  24. package/dist/chunks/{LpCheckbox-DHc2bHDe.js → LpCheckbox-BoOuLnGB.js} +5 -2
  25. package/dist/chunks/{LpCodeBlock-BGsQhxw8.js → LpCodeBlock-Czu-fq1M.js} +2 -2
  26. package/dist/chunks/{LpCodeDisplay-CIKKT2iW.js → LpCodeDisplay-BK2FHbtM.js} +6 -6
  27. package/dist/chunks/{LpColorPicker-DFugXNUi.js → LpColorPicker-Ee1DZiRd.js} +3 -3
  28. package/dist/chunks/LpCommandScope-ubtWDMa0.js +53 -0
  29. package/dist/chunks/{LpConfirmDialog-CfGCux8m.js → LpConfirmDialog-i15pulMT.js} +3 -3
  30. package/dist/chunks/{LpConfirmHost-exB7JmcO.js → LpConfirmHost-D0oGavtd.js} +1 -1
  31. package/dist/chunks/{LpContextMenu-COwOwRgI.js → LpContextMenu-DXpFJjlO.js} +17 -6
  32. package/dist/chunks/{LpCopyButton-DzHjnFs0.js → LpCopyButton-Dmq7DmFw.js} +3 -3
  33. package/dist/chunks/{LpCountdown-Dtq1ihlJ.js → LpCountdown-DTdMF8DL.js} +2 -2
  34. package/dist/chunks/{LpDataList-B-5nHfNw.js → LpDataList-CFPtSsBB.js} +7 -6
  35. package/dist/chunks/{LpDatePicker-BUe1VUC0.js → LpDatePicker-cC9K-Pk8.js} +7 -6
  36. package/dist/chunks/{LpDateRangePicker-C_k65vGR.js → LpDateRangePicker-BVQKPbvo.js} +5 -4
  37. package/dist/chunks/{LpDisclosure-DOarzlAG.js → LpDisclosure-hRnX3FWL.js} +4 -1
  38. package/dist/chunks/{LpDrawer-DGNKRsGq.js → LpDrawer-u4rxnuR-.js} +1 -1
  39. package/dist/chunks/{LpDropdownMenu-CRgrhdAH.js → LpDropdownMenu-BgZ0Z_Im.js} +17 -4
  40. package/dist/chunks/{LpEditable-DTqLt2lT.js → LpEditable-CjxuOVh_.js} +7 -5
  41. package/dist/chunks/{LpEmptyState-KTKipws2.js → LpEmptyState-CKFTtQgJ.js} +1 -1
  42. package/dist/chunks/{LpFieldNotice-CurbNuXI.js → LpFieldNotice-CPI8EwtU.js} +1 -1
  43. package/dist/chunks/{LpFileTree-DMISXVHC.js → LpFileTree-BH0gNlSJ.js} +2 -2
  44. package/dist/chunks/{LpFileTreeNode-CPXB_Yy3.js → LpFileTreeNode-gPVB1oXM.js} +4 -4
  45. package/dist/chunks/{LpFormField-BWwgm1E4.js → LpFormField-gRASfyj2.js} +4 -1
  46. package/dist/chunks/{LpGauge-Bk8TwJj0.js → LpGauge-BdjPGnqw.js} +2 -2
  47. package/dist/chunks/{LpHintLayer-p4hqzdyA.js → LpHintLayer-C2asBIfs.js} +2 -2
  48. package/dist/chunks/{LpHints-CCrhRfy9.js → LpHints-CNZQ76JV.js} +3 -3
  49. package/dist/chunks/{LpIcon-HJP5xMz4.js → LpIcon-DkP-9mzK.js} +1 -1
  50. package/dist/chunks/{LpInfraNode-CUC3t4na.js → LpInfraNode-yTh4-5qa.js} +2 -2
  51. package/dist/chunks/{LpInput-CDuwODJG.js → LpInput-2H-5ILOB.js} +5 -2
  52. package/dist/chunks/{LpLightbox-XJ0LpTcZ.js → LpLightbox-lAt5uIfx.js} +2 -2
  53. package/dist/chunks/{LpLogViewer-elZHbj85.js → LpLogViewer-x78YfhJf.js} +5 -5
  54. package/dist/chunks/{LpMapPicker-BtIl7NOM.js → LpMapPicker-Dp5RSCi9.js} +1 -1
  55. package/dist/chunks/{LpMediaGallery-D5swlmZ_.js → LpMediaGallery-D92oJfY_.js} +3 -3
  56. package/dist/chunks/{LpModal-eBMt2Fqn.js → LpModal-CpH0bNo7.js} +1 -1
  57. package/dist/chunks/{LpModalNav-D4Z0wVHR.js → LpModalNav-BH4ANQGJ.js} +4 -4
  58. package/dist/chunks/{LpNotificationBell-DPZmP5dM.js → LpNotificationBell-BdUQqZhs.js} +8 -8
  59. package/dist/chunks/{LpNumberField-B9k-h2HE.js → LpNumberField-DZY3Z670.js} +4 -1
  60. package/dist/chunks/{LpNumberFlow-Cfyh033Q.js → LpNumberFlow-5khYxsfX.js} +6 -3
  61. package/dist/chunks/{LpPagination-VNCX6pFC.js → LpPagination-DbeACavM.js} +1 -1
  62. package/dist/chunks/LpPaletteTheme-CqWNRwHE.js +104 -0
  63. package/dist/chunks/{LpPasswordInput-DTV18FIh.js → LpPasswordInput-BzAZsf5V.js} +14 -8
  64. package/dist/chunks/{LpPhoneInput-C60tx1tC.js → LpPhoneInput-BMpd-0QR.js} +2 -2
  65. package/dist/chunks/{LpPie-CFuV7pZv.js → LpPie-DdFSTbGE.js} +5 -5
  66. package/dist/chunks/{LpPipeline-D81yh_NX.js → LpPipeline-zjEM1Lwc.js} +4 -4
  67. package/dist/chunks/{LpPopover-BEIcqb9p.js → LpPopover-DmZ3wUtP.js} +6 -5
  68. package/dist/chunks/{LpProgress-CE-m5OpD.js → LpProgress-BX33EKkN.js} +1 -1
  69. package/dist/chunks/{LpQuota-2Sw_gqcJ.js → LpQuota-DqNv1yjs.js} +3 -3
  70. package/dist/chunks/{LpRadioGroup-CGBr5nIp.js → LpRadioGroup-CsXeV9Ct.js} +4 -1
  71. package/dist/chunks/{LpRangeCalendar-c5qj7YcC.js → LpRangeCalendar-l-0nSFmY.js} +1 -1
  72. package/dist/chunks/{LpRangeSlider-Dv6lNvHY.js → LpRangeSlider-CM-0ZITZ.js} +3 -0
  73. package/dist/chunks/{LpRelativeTime-CKzVqAo9.js → LpRelativeTime-UM8EkfrN.js} +2 -2
  74. package/dist/chunks/LpRepeater-OvzByE5L.js +180 -0
  75. package/dist/chunks/{LpRollingText-DB7upf-5.js → LpRollingText-DiL717LK.js} +4 -3
  76. package/dist/chunks/{LpSecretField-DMfdJ1cQ.js → LpSecretField-DwQiPOko.js} +27 -16
  77. package/dist/chunks/{LpSection-BW4xJ-Li.js → LpSection-DcQsieHG.js} +1 -1
  78. package/dist/chunks/{LpSegmented-BO99Sr4m.js → LpSegmented-D2VST0o3.js} +4 -1
  79. package/dist/chunks/{LpSelect-B4f_yRi5.js → LpSelect-DYX2-u5A.js} +43 -18
  80. package/dist/chunks/{LpSidebar-D-M0Y_7U.js → LpSidebar-D44peKq2.js} +3 -3
  81. package/dist/chunks/{LpSidebarNav-DF-TYxOV.js → LpSidebarNav--VwkXO_a.js} +3 -3
  82. package/dist/chunks/{LpSlider-DuoRpOrH.js → LpSlider-DxYoCJJO.js} +3 -0
  83. package/dist/chunks/{LpStat-B2ix1Q7t.js → LpStat-DIsQfbeD.js} +3 -3
  84. package/dist/chunks/{LpStatus-C7kx5PKE.js → LpStatus-BQU1TUsv.js} +2 -2
  85. package/dist/chunks/{LpStepMark-DK-5BKAt.js → LpStepMark-CXa7DJy_.js} +2 -2
  86. package/dist/chunks/{LpStepper-DRFfdJaq.js → LpStepper-C9RM_yTG.js} +1 -1
  87. package/dist/chunks/{LpSwitch-DrzKrZZV.js → LpSwitch-bGZxUDSx.js} +3 -0
  88. package/dist/chunks/{LpTableFilter-DyvHGbOk.js → LpTableFilter-CTZMqFDU.js} +2 -2
  89. package/dist/chunks/{LpTableOfContents-rYvo8z5T.js → LpTableOfContents-CraicEs4.js} +2 -2
  90. package/dist/chunks/{LpTagsInput-DgpBFRV2.js → LpTagsInput-DrjmrOge.js} +8 -5
  91. package/dist/chunks/{LpTextarea-DiWjeVoJ.js → LpTextarea-BbYqmG1S.js} +4 -1
  92. package/dist/chunks/{LpThemeStudio-DguwHdGt.js → LpThemeStudio-B2nPaG-h.js} +18 -14
  93. package/dist/chunks/{LpThemeSwitcher-D5uz0MhK.js → LpThemeSwitcher-C0A5sg_E.js} +6 -4
  94. package/dist/chunks/{LpTimeField-BFQVf_PW.js → LpTimeField-DQ8WvPr2.js} +4 -1
  95. package/dist/chunks/{LpToaster-Wv9ksj4q.js → LpToaster-BTTl_wuy.js} +2 -2
  96. package/dist/chunks/{LpTooltip-Cz8SQwRt.js → LpTooltip-BcBvIx4z.js} +23 -5
  97. package/dist/chunks/{LpTopologyCanvas-ZhKthfgz.js → LpTopologyCanvas-B0wEvAoq.js} +1 -1
  98. package/dist/chunks/{LpTour-NfRJNEIP.js → LpTour-BgCfNlaW.js} +1 -1
  99. package/dist/chunks/{LpTruncate-D_5Pfv5B.js → LpTruncate-TcqfkyJM.js} +2 -2
  100. package/dist/chunks/{LpUptimeBar-DWegV2p6.js → LpUptimeBar-DRadGq4Y.js} +136 -47
  101. package/dist/chunks/{MenuTitle-BjQZX8tQ.js → MenuTitle-Dab3EGOp.js} +1 -1
  102. package/dist/chunks/{OptionContent-CmYUUeWj.js → OptionContent-DPzNsybd.js} +1 -1
  103. package/dist/chunks/PalettePreviewBoundary-B0Hhu3Yw.js +20 -0
  104. package/dist/chunks/{PickerField-CHRS1h7K.js → PickerField-DwLw7j0L.js} +26 -17
  105. package/dist/chunks/{RevealToggle-C-KzaQGJ.js → RevealToggle-BVFM_FAU.js} +3 -3
  106. package/dist/chunks/{TourCard-BXuVHsfn.js → TourCard--2IfgnUU.js} +3 -3
  107. package/dist/chunks/icons-CLu1iR1j.js +43 -0
  108. package/dist/chunks/palette-BiY7yQZL.js +29 -0
  109. package/dist/component-names.d.ts +1 -1
  110. package/dist/components/ContextMenuItems.vue.js +1 -1
  111. package/dist/components/DropzoneLinkField.vue.js +1 -1
  112. package/dist/components/FieldWrapper.vue.js +1 -1
  113. package/dist/components/LayoutCanvas.vue.js +1 -1
  114. package/dist/components/LayoutNode.vue.js +1 -1
  115. package/dist/components/LpActionBar.vue.js +1 -1
  116. package/dist/components/LpAddressInput.vue.d.ts +1 -1
  117. package/dist/components/LpAddressInput.vue.js +1 -1
  118. package/dist/components/LpAlert.vue.d.ts +3 -3
  119. package/dist/components/LpAlert.vue.js +1 -1
  120. package/dist/components/LpAppShell.vue.js +1 -1
  121. package/dist/components/LpAutocomplete.vue.d.ts +7 -1
  122. package/dist/components/LpAutocomplete.vue.js +1 -1
  123. package/dist/components/LpAvatar.vue.js +1 -1
  124. package/dist/components/LpBadge.vue.d.ts +2 -2
  125. package/dist/components/LpBadge.vue.js +1 -1
  126. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  127. package/dist/components/LpButton.vue.d.ts +4 -0
  128. package/dist/components/LpButton.vue.js +1 -1
  129. package/dist/components/LpCalendar.vue.js +1 -1
  130. package/dist/components/LpChart.vue.js +27 -25
  131. package/dist/components/LpCheckbox.vue.d.ts +8 -1
  132. package/dist/components/LpCheckbox.vue.js +1 -1
  133. package/dist/components/LpCodeBlock.vue.js +1 -1
  134. package/dist/components/LpCodeDisplay.vue.js +1 -1
  135. package/dist/components/LpColorPicker.vue.d.ts +2 -2
  136. package/dist/components/LpColorPicker.vue.js +1 -1
  137. package/dist/components/LpCommandPalette.vue.d.ts +43 -7
  138. package/dist/components/LpCommandPalette.vue.js +451 -126
  139. package/dist/components/LpCommandScope.vue.d.ts +28 -0
  140. package/dist/components/LpCommandScope.vue.js +4 -0
  141. package/dist/components/LpConfirmDialog.vue.js +1 -1
  142. package/dist/components/LpConfirmHost.vue.js +1 -1
  143. package/dist/components/LpContextMenu.vue.d.ts +5 -1
  144. package/dist/components/LpContextMenu.vue.js +1 -1
  145. package/dist/components/LpCopyButton.vue.d.ts +1 -1
  146. package/dist/components/LpCopyButton.vue.js +1 -1
  147. package/dist/components/LpCountdown.vue.js +1 -1
  148. package/dist/components/LpDataList.vue.js +1 -1
  149. package/dist/components/LpDatePicker.vue.d.ts +2 -0
  150. package/dist/components/LpDatePicker.vue.js +1 -1
  151. package/dist/components/LpDateRangePicker.vue.d.ts +2 -0
  152. package/dist/components/LpDateRangePicker.vue.js +1 -1
  153. package/dist/components/LpDisclosure.vue.d.ts +4 -0
  154. package/dist/components/LpDisclosure.vue.js +1 -1
  155. package/dist/components/LpDock.vue.js +2 -2
  156. package/dist/components/LpDrawer.vue.js +1 -1
  157. package/dist/components/LpDropdownMenu.vue.d.ts +5 -1
  158. package/dist/components/LpDropdownMenu.vue.js +1 -1
  159. package/dist/components/LpDropzone.vue.js +2 -2
  160. package/dist/components/LpEditable.vue.js +1 -1
  161. package/dist/components/LpEmptyState.vue.js +1 -1
  162. package/dist/components/LpFieldNotice.vue.js +1 -1
  163. package/dist/components/LpFileTree.vue.d.ts +1 -1
  164. package/dist/components/LpFileTree.vue.js +1 -1
  165. package/dist/components/LpFileTreeNode.vue.js +1 -1
  166. package/dist/components/LpFormField.vue.js +1 -1
  167. package/dist/components/LpGauge.vue.js +1 -1
  168. package/dist/components/LpHintLayer.vue.js +1 -1
  169. package/dist/components/LpHints.vue.js +1 -1
  170. package/dist/components/LpIcon.vue.js +1 -1
  171. package/dist/components/LpImageEditor.vue.js +3 -2
  172. package/dist/components/LpInfraNode.vue.js +1 -1
  173. package/dist/components/LpInput.vue.d.ts +6 -0
  174. package/dist/components/LpInput.vue.js +1 -1
  175. package/dist/components/LpLightbox.vue.js +1 -1
  176. package/dist/components/LpLoader.vue.d.ts +1 -1
  177. package/dist/components/LpLogViewer.vue.js +1 -1
  178. package/dist/components/LpMapPicker.vue.js +1 -1
  179. package/dist/components/LpMediaGallery.vue.d.ts +2 -2
  180. package/dist/components/LpMediaGallery.vue.js +1 -1
  181. package/dist/components/LpModal.vue.js +1 -1
  182. package/dist/components/LpModalNav.vue.js +1 -1
  183. package/dist/components/LpNotificationBell.vue.js +1 -1
  184. package/dist/components/LpNumberField.vue.d.ts +6 -0
  185. package/dist/components/LpNumberField.vue.js +1 -1
  186. package/dist/components/LpNumberFlow.vue.js +1 -1
  187. package/dist/components/LpPagination.vue.js +1 -1
  188. package/dist/components/LpPaletteFacts.vue.d.ts +1 -0
  189. package/dist/components/LpPaletteFacts.vue.js +40 -26
  190. package/dist/components/LpPaletteHeader.vue.js +2 -2
  191. package/dist/components/LpPaletteStats.vue.d.ts +1 -0
  192. package/dist/components/LpPaletteStats.vue.js +45 -21
  193. package/dist/components/LpPaletteTheme.vue.d.ts +7 -0
  194. package/dist/components/LpPaletteTheme.vue.js +4 -0
  195. package/dist/components/LpPasswordInput.vue.d.ts +7 -1
  196. package/dist/components/LpPasswordInput.vue.js +1 -1
  197. package/dist/components/LpPhoneInput.vue.js +1 -1
  198. package/dist/components/LpPie.vue.js +1 -1
  199. package/dist/components/LpPipeline.vue.js +1 -1
  200. package/dist/components/LpPopover.vue.js +1 -1
  201. package/dist/components/LpProgress.vue.d.ts +1 -1
  202. package/dist/components/LpProgress.vue.js +1 -1
  203. package/dist/components/LpQuota.vue.js +1 -1
  204. package/dist/components/LpRadioGroup.vue.d.ts +6 -1
  205. package/dist/components/LpRadioGroup.vue.js +1 -1
  206. package/dist/components/LpRangeCalendar.vue.js +1 -1
  207. package/dist/components/LpRangeSlider.vue.d.ts +6 -0
  208. package/dist/components/LpRangeSlider.vue.js +1 -1
  209. package/dist/components/LpRelativeTime.vue.js +1 -1
  210. package/dist/components/LpRepeater.vue.js +1 -1
  211. package/dist/components/LpRollingText.vue.d.ts +2 -0
  212. package/dist/components/LpRollingText.vue.js +1 -1
  213. package/dist/components/LpSecretField.vue.js +1 -1
  214. package/dist/components/LpSection.vue.js +1 -1
  215. package/dist/components/LpSegmented.vue.d.ts +6 -0
  216. package/dist/components/LpSegmented.vue.js +1 -1
  217. package/dist/components/LpSelect.vue.d.ts +3 -0
  218. package/dist/components/LpSelect.vue.js +1 -1
  219. package/dist/components/LpShinyText.vue.d.ts +1 -1
  220. package/dist/components/LpShortcutsDialog.vue.js +1 -1
  221. package/dist/components/LpSidebar.vue.js +1 -1
  222. package/dist/components/LpSidebarNav.vue.js +1 -1
  223. package/dist/components/LpSlider.vue.d.ts +6 -0
  224. package/dist/components/LpSlider.vue.js +1 -1
  225. package/dist/components/LpStat.vue.js +1 -1
  226. package/dist/components/LpStatus.vue.js +1 -1
  227. package/dist/components/LpStepMark.vue.js +1 -1
  228. package/dist/components/LpStepper.vue.js +1 -1
  229. package/dist/components/LpSwitch.vue.d.ts +6 -0
  230. package/dist/components/LpSwitch.vue.js +1 -1
  231. package/dist/components/LpTable.vue.d.ts +6 -0
  232. package/dist/components/LpTable.vue.js +74 -35
  233. package/dist/components/LpTableFilter.vue.js +1 -1
  234. package/dist/components/LpTableOfContents.vue.js +1 -1
  235. package/dist/components/LpTabs.vue.d.ts +1 -1
  236. package/dist/components/LpTabs.vue.js +15 -6
  237. package/dist/components/LpTagsInput.vue.d.ts +6 -0
  238. package/dist/components/LpTagsInput.vue.js +1 -1
  239. package/dist/components/LpTextarea.vue.d.ts +8 -1
  240. package/dist/components/LpTextarea.vue.js +1 -1
  241. package/dist/components/LpThemeStudio.vue.js +1 -1
  242. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  243. package/dist/components/LpTimeField.vue.d.ts +6 -0
  244. package/dist/components/LpTimeField.vue.js +1 -1
  245. package/dist/components/LpToaster.vue.js +1 -1
  246. package/dist/components/LpTooltip.vue.js +1 -1
  247. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  248. package/dist/components/LpTour.vue.js +1 -1
  249. package/dist/components/LpTruncate.vue.js +1 -1
  250. package/dist/components/LpUptimeBar.vue.d.ts +18 -9
  251. package/dist/components/LpUptimeBar.vue.js +1 -1
  252. package/dist/components/MenuTitle.vue.js +1 -1
  253. package/dist/components/OptionContent.vue.js +1 -1
  254. package/dist/components/PalettePreviewBoundary.vue.d.ts +13 -0
  255. package/dist/components/PalettePreviewBoundary.vue.js +4 -0
  256. package/dist/components/PickerField.vue.d.ts +5 -3
  257. package/dist/components/PickerField.vue.js +1 -1
  258. package/dist/components/RevealToggle.vue.js +1 -1
  259. package/dist/components/TourCard.vue.js +1 -1
  260. package/dist/components/commandPalette.d.ts +33 -0
  261. package/dist/components/commandPalette.js +21 -1
  262. package/dist/components/commandSearch.d.ts +5 -0
  263. package/dist/components/commandSearch.js +22 -2
  264. package/dist/components/format.d.ts +7 -0
  265. package/dist/components/format.js +4 -0
  266. package/dist/components/menuWhen.js +1 -1
  267. package/dist/components/uptimeBar.d.ts +45 -0
  268. package/dist/components/uptimeBar.js +51 -0
  269. package/dist/composables/scroll.d.ts +5 -0
  270. package/dist/composables/scroll.js +7 -0
  271. package/dist/composables/useField.d.ts +9 -0
  272. package/dist/composables/useField.js +24 -1
  273. package/dist/composables/useForm.js +13 -4
  274. package/dist/composables/usePaletteCommands.d.ts +61 -0
  275. package/dist/composables/usePaletteCommands.js +220 -0
  276. package/dist/composables/useReveal.js +21 -0
  277. package/dist/composables/useView.js +9 -2
  278. package/dist/icons-all.js +1 -1
  279. package/dist/index.d.ts +14 -1
  280. package/dist/index.js +103 -87
  281. package/dist/theme/palette.d.ts +9 -0
  282. package/dist/theme/studio.d.ts +12 -1
  283. package/package.json +4 -1
  284. package/src/component-names.ts +2 -0
  285. package/src/components/ContextMenuItems.vue +5 -2
  286. package/src/components/LpAutocomplete.vue +6 -1
  287. package/src/components/LpButton.vue +5 -1
  288. package/src/components/LpChart.vue +19 -17
  289. package/src/components/LpCheckbox.vue +7 -2
  290. package/src/components/LpCommandPalette.vue +263 -29
  291. package/src/components/LpCommandScope.vue +35 -0
  292. package/src/components/LpContextMenu.vue +17 -5
  293. package/src/components/LpDataList.vue +2 -2
  294. package/src/components/LpDatePicker.vue +2 -0
  295. package/src/components/LpDateRangePicker.vue +2 -0
  296. package/src/components/LpDisclosure.vue +5 -1
  297. package/src/components/LpDropdownMenu.vue +14 -3
  298. package/src/components/LpEditable.vue +3 -1
  299. package/src/components/LpFormField.vue +3 -0
  300. package/src/components/LpInput.vue +6 -1
  301. package/src/components/LpNumberField.vue +6 -1
  302. package/src/components/LpNumberFlow.vue +12 -2
  303. package/src/components/LpPaletteFacts.vue +11 -6
  304. package/src/components/LpPaletteStats.vue +12 -5
  305. package/src/components/LpPaletteTheme.vue +48 -0
  306. package/src/components/LpPasswordInput.vue +13 -5
  307. package/src/components/LpPopover.vue +4 -0
  308. package/src/components/LpRadioGroup.vue +4 -1
  309. package/src/components/LpRangeSlider.vue +6 -1
  310. package/src/components/LpRepeater.vue +64 -13
  311. package/src/components/LpRollingText.vue +12 -1
  312. package/src/components/LpSecretField.vue +18 -8
  313. package/src/components/LpSegmented.vue +6 -1
  314. package/src/components/LpSelect.vue +44 -10
  315. package/src/components/LpSlider.vue +6 -1
  316. package/src/components/LpSwitch.vue +6 -1
  317. package/src/components/LpTable.vue +35 -2
  318. package/src/components/LpTableOfContents.vue +2 -4
  319. package/src/components/LpTabs.vue +7 -0
  320. package/src/components/LpTagsInput.vue +6 -1
  321. package/src/components/LpTextarea.vue +7 -2
  322. package/src/components/LpThemeStudio.vue +7 -5
  323. package/src/components/LpThemeSwitcher.vue +3 -0
  324. package/src/components/LpTimeField.vue +6 -1
  325. package/src/components/LpTooltip.vue +23 -1
  326. package/src/components/LpUptimeBar.vue +69 -41
  327. package/src/components/PalettePreviewBoundary.vue +5 -0
  328. package/src/components/PickerField.vue +5 -4
  329. package/src/components/commandPalette.ts +22 -1
  330. package/src/components/commandSearch.ts +23 -1
  331. package/src/components/format.ts +10 -0
  332. package/src/components/uptimeBar.ts +98 -0
  333. package/src/composables/scroll.ts +11 -0
  334. package/src/composables/useField.ts +33 -1
  335. package/src/composables/useForm.ts +14 -5
  336. package/src/composables/usePaletteCommands.ts +220 -0
  337. package/src/composables/useReveal.ts +29 -0
  338. package/src/composables/useView.ts +8 -1
  339. package/src/index.ts +18 -1
  340. package/src/kit-icons.ts +1 -1
  341. package/src/theme/palette.ts +26 -0
  342. package/src/theme/studio.ts +15 -8
  343. package/dist/assets/LpTabs-BJ-nrJuI.css +0 -18
  344. package/dist/chunks/LpRepeater-D0VjmD6y.js +0 -140
  345. package/dist/chunks/icons-4IywzkHk.js +0 -43
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { usePaletteTheme } from "../theme/palette"
2
3
  /*
3
4
  * The theme, tuned in place: a base theme, any brand colour, corners, control
4
5
  * size and motion speed, all applied live, and the result ready to take away
@@ -102,8 +103,9 @@ const TILE =
102
103
  "flex min-w-0 flex-col items-center gap-1 rounded-control p-1.5 text-[11px] text-muted transition-colors duration-(--duration-fast) hover:bg-surface-soft hover:text-ink"
103
104
  const SWATCH = "relative size-8 overflow-hidden rounded-full ring-2 transition-[box-shadow] duration-(--duration-fast)"
104
105
 
105
- const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K) =>
106
- (value: string) => studio.tune({ [key]: value } as Partial<StudioState>)
106
+ const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K, value: string) => studio.tune({ [key]: value } as Partial<StudioState>)
107
+
108
+ usePaletteTheme(() => studio ?? undefined)
107
109
  </script>
108
110
 
109
111
  <template>
@@ -171,7 +173,7 @@ const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K) =>
171
173
  :model-value="state.corners"
172
174
  size="sm"
173
175
  :options="[{ value: 'sharp', label: text.sharp }, { value: 'default', label: text.default }, { value: 'round', label: text.round }]"
174
- @update:model-value="tuneTo('corners')"
176
+ @update:model-value="tuneTo('corners', $event)"
175
177
  />
176
178
  </LpThemeStudioSection>
177
179
 
@@ -180,7 +182,7 @@ const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K) =>
180
182
  :model-value="state.size"
181
183
  size="sm"
182
184
  :options="[{ value: 'compact', label: text.compact }, { value: 'default', label: text.default }, { value: 'comfortable', label: text.comfortable }]"
183
- @update:model-value="tuneTo('size')"
185
+ @update:model-value="tuneTo('size', $event)"
184
186
  />
185
187
  </LpThemeStudioSection>
186
188
 
@@ -189,7 +191,7 @@ const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K) =>
189
191
  :model-value="state.motion"
190
192
  size="sm"
191
193
  :options="[{ value: 'fast', label: text.fast }, { value: 'normal', label: text.normal }, { value: 'slow', label: text.slow }]"
192
- @update:model-value="tuneTo('motion')"
194
+ @update:model-value="tuneTo('motion', $event)"
193
195
  />
194
196
  </LpThemeStudioSection>
195
197
 
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { usePaletteTheme } from "../theme/palette"
2
3
  /*
3
4
  * Theme switcher. The kit convention for theming, so consumers stop hand-rolling
4
5
  * their own. Default behaviour mirrors LeaveHosting: LEFT-click cycles to the
@@ -298,6 +299,8 @@ const labelName = computed(() =>
298
299
  const triggerLabel = computed(
299
300
  () => props["aria-label"] ?? text.value.trigger.replace("{name}", labelName.value),
300
301
  )
302
+
303
+ usePaletteTheme(() => tuner ?? undefined)
301
304
  </script>
302
305
 
303
306
  <template>
@@ -6,6 +6,7 @@ export default { inheritAttrs: false }
6
6
  </script>
7
7
 
8
8
  <script setup lang="ts">
9
+ import { usePaletteControl, type PaletteControlOptions } from "../composables/usePaletteCommands"
9
10
  /*
10
11
  * Time-of-day field: hour and minute segments, typed or stepped with the arrow
11
12
  * keys (reka TimeField). The model is "HH:mm" (v-model), the same shape a
@@ -26,6 +27,8 @@ import LpIcon from "./LpIcon.vue"
26
27
 
27
28
  const props = withDefaults(
28
29
  defineProps<{
30
+ /** Contextual palette metadata; false excludes this control. */
31
+ palette?: PaletteControlOptions
29
32
  /** Selected time "HH:mm" (v-model). */
30
33
  modelValue?: string
31
34
  disabled?: boolean
@@ -38,7 +41,7 @@ const props = withDefaults(
38
41
  /** Show the clock icon at the start of the field. */
39
42
  icon?: boolean
40
43
  }>(),
41
- { size: "md", hourCycle: 24, icon: true },
44
+ { palette: undefined, size: "md", hourCycle: 24, icon: true },
42
45
  )
43
46
 
44
47
  const emit = defineEmits<{
@@ -84,6 +87,8 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
84
87
  field.value.invalid ? "border-danger focus-within:ring-danger-soft" : "border-line focus-within:border-brand",
85
88
  ].join(" "),
86
89
  )
90
+
91
+ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.value.invalid })
87
92
  </script>
88
93
 
89
94
  <template>
@@ -120,6 +120,27 @@ const PassThrough: FunctionalComponent = (_, { slots }) => slots.default?.()
120
120
 
121
121
  const target = { x: 0, y: 0 }
122
122
  const offset = ref({ x: 0, y: 0 })
123
+ const tipBody = ref<HTMLElement | null>(null)
124
+
125
+ /* reka's own collision padding, which the follow offset would otherwise undo. */
126
+ const EDGE = 8
127
+
128
+ /*
129
+ * reka keeps the tip inside the window for the box it was placed against; the
130
+ * follow offset is added after that, so near the right edge a wide tip (an
131
+ * uptime slice with its breakdown) slid half off screen. The offset is held to
132
+ * the room the window has on each side of where reka put the tip.
133
+ */
134
+ function inView(x: number): number {
135
+ const panel = tipBody.value?.parentElement
136
+ if (!panel) return x
137
+ const rect = panel.getBoundingClientRect()
138
+ const placed = rect.left - offset.value.x
139
+ const width = panel.ownerDocument.defaultView?.innerWidth ?? window.innerWidth
140
+ const min = EDGE - placed
141
+ const max = width - EDGE - rect.width - placed
142
+ return max < min ? x : Math.min(max, Math.max(min, x))
143
+ }
123
144
 
124
145
  /*
125
146
  * The spring constants come from the kit's shared "float" feel — the same ones
@@ -172,6 +193,7 @@ const onPointerMove = (e: PointerEvent) => {
172
193
  const half = props.side === "top" || props.side === "bottom" ? rect.height / 2 : 0
173
194
  const dir = props.side === "bottom" ? -1 : 1
174
195
  target.y = e.clientY - (rect.top + rect.height / 2) + half * dir
196
+ target.x = inView(target.x)
175
197
  // Without motion the tip goes straight to the reading instead of trailing.
176
198
  if (prefersReducedMotion()) {
177
199
  offset.value = { x: target.x, y: target.y }
@@ -272,7 +294,7 @@ const guardOutside = useOutsideGuard()
272
294
  ]"
273
295
  :style="followCursor ? { translate: `${offset.x}px ${offset.y}px` } : undefined"
274
296
  >
275
- <slot name="content">{{ content }}</slot>
297
+ <div ref="tipBody"><slot name="content">{{ content }}</slot></div>
276
298
  </TooltipContent>
277
299
  </TooltipPortal>
278
300
  </TooltipRoot>
@@ -3,27 +3,24 @@
3
3
  // appended to it, so opt out of the automatic pass-through that would add it
4
4
  // a second time, unmerged.
5
5
  export default { inheritAttrs: false }
6
+ export type { UptimeEvent, UptimeSegment, UptimeStatus } from "./uptimeBar"
6
7
  </script>
7
8
 
8
9
  <script setup lang="ts">
9
10
  import { computed, ref } from "vue"
10
11
  import LpNumberFlow from "./LpNumberFlow.vue"
11
12
  import LpRollingText from "./LpRollingText.vue"
13
+ import LpShift from "./LpShift.vue"
12
14
  import LpSkeleton from "./LpSkeleton.vue"
13
15
  import LpSwap from "./LpSwap.vue"
14
16
  import LpTooltip from "./LpTooltip.vue"
15
17
  import { TONES } from "./tone"
16
18
  import { useMergedAttrs } from "../composables/useMergedClass"
17
19
  import { useLabels } from "../composables/useLpConfig"
20
+ import { useLocale } from "../composables/useLocale"
21
+ import { DEFAULT_UNIT_LABELS, formatTime, toEpoch, type TimeUnitLabels } from "./timeSpan"
22
+ import { UPTIME_SEVERITY, uptimeFigure, uptimeRange, uptimeShare, type UptimeSegment, type UptimeStatus } from "./uptimeBar"
18
23
 
19
- export type UptimeStatus = "operational" | "degraded" | "down" | "maintenance" | "empty"
20
-
21
- export interface UptimeSegment {
22
- /** Health of this slice. `empty` renders a faded placeholder (no data). */
23
- status: UptimeStatus
24
- /** Optional rich tooltip body; falls back to a humanised status. */
25
- label?: string
26
- }
27
24
 
28
25
  const props = withDefaults(
29
26
  defineProps<{
@@ -35,6 +32,13 @@ const props = withDefaults(
35
32
  endLabel?: string
36
33
  /** Show the computed uptime % above the bar. */
37
34
  showUptime?: boolean
35
+ /**
36
+ * The uptime figure, when the app has a more exact one than the slices
37
+ * (a server that counted every minute). Otherwise it is worked out from
38
+ * the slices: their `durations`, else their spans, else their count.
39
+ */
40
+ uptime?: number | null
41
+ locale?: string
38
42
  /** Override the headline shown next to the uptime %. */
39
43
  title?: string
40
44
  /**
@@ -44,7 +48,7 @@ const props = withDefaults(
44
48
  */
45
49
  colors?: Partial<Record<UptimeStatus, string>>
46
50
  /** Status names, for the tooltip and screen readers. */
47
- labels?: Partial<Record<UptimeStatus | "loading", string>>
51
+ labels?: Partial<Record<UptimeStatus | "loading" | "uptime", string>>
48
52
  /**
49
53
  * The history is on its way. The row of ticks pulses in place of the
50
54
  * slices (as many as `segments` already holds, else `skeletonSlices`) at
@@ -88,26 +92,18 @@ const statusLabel = useLabels(
88
92
  maintenance: "Maintenance",
89
93
  empty: "No data",
90
94
  loading: "Loading",
95
+ uptime: "uptime",
91
96
  },
92
97
  () => props.labels,
93
98
  )
94
99
 
95
100
  const skeletonCount = computed(() => props.segments.length || props.skeletonSlices)
96
101
 
97
- // Uptime counts only slices with data; operational + maintenance are "up".
98
- const uptimePct = computed(() => {
99
- const tracked = props.segments.filter((s) => s.status !== "empty")
100
- if (!tracked.length) return null
101
- const up = tracked.filter((s) => s.status === "operational" || s.status === "maintenance").length
102
- return (up / tracked.length) * 100
103
- })
102
+ const locale = useLocale(() => props.locale)
103
+ const units = useLabels<TimeUnitLabels>("time", DEFAULT_UNIT_LABELS)
104
+
105
+ const uptimePct = computed(() => (props.uptime ?? null) !== null ? props.uptime! : uptimeShare(props.segments))
104
106
 
105
- /*
106
- * Places actually needed, so trailing zeros stay trimmed: 100 rather than
107
- * 100.00, 99.9 rather than 99.90. The counter needs a fixed count per render —
108
- * it cannot "trim" mid-roll without changing the row's shape — so it is derived
109
- * from the value instead of hard-coded at two.
110
- */
111
107
  /*
112
108
  * The gap is drawn INSIDE each slice, not between slices.
113
109
  *
@@ -136,7 +132,24 @@ const tip = computed(() => {
136
132
  if (i == null) return null
137
133
  const seg = props.segments[i]
138
134
  if (!seg) return null
139
- return { status: seg.status, text: seg.label ?? statusLabel.value[seg.status] }
135
+ return {
136
+ status: seg.status,
137
+ text: seg.label ?? statusLabel.value[seg.status],
138
+ range: seg.start !== undefined ? uptimeRange(seg.start, seg.end, locale.value) : "",
139
+ parts: UPTIME_SEVERITY.filter((st) => (seg.durations?.[st] ?? 0) > 0).map((st) => ({
140
+ status: st,
141
+ text: statusLabel.value[st],
142
+ time: formatTime(seg.durations![st]!, "units", units.value, 2),
143
+ })),
144
+ events: (seg.events ?? []).slice(0, 4).map((e) => ({
145
+ status: e.status ?? seg.status,
146
+ text: e.text,
147
+ at: e.at !== undefined && Number.isFinite(toEpoch(e.at))
148
+ ? new Intl.DateTimeFormat(locale.value, { hour: "2-digit", minute: "2-digit" }).format(toEpoch(e.at))
149
+ : "",
150
+ })),
151
+ more: Math.max(0, (seg.events?.length ?? 0) - 4),
152
+ }
140
153
  })
141
154
 
142
155
  /*
@@ -165,12 +178,7 @@ function onSlotKeydown(e: KeyboardEvent, i: number) {
165
178
  slots.value[next]?.focus()
166
179
  }
167
180
 
168
- const uptimeDecimals = computed(() => {
169
- const v = uptimePct.value
170
- if (v == null) return 0
171
- const trimmed = Number(v.toFixed(2))
172
- return Number.isInteger(trimmed) ? 0 : String(trimmed).split(".")[1].length
173
- })
181
+ const figure = computed(() => (uptimePct.value == null ? null : uptimeFigure(uptimePct.value)))
174
182
 
175
183
  // The bar sets its own gap between the strip and its caption, which a compact status row has to close.
176
184
  const { class: rootClass, attrs: rest } = useMergedAttrs(
@@ -210,13 +218,32 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
210
218
  </LpSkeleton>
211
219
  <LpTooltip v-else :delay="80" follow-cursor side="top" :disabled="hovered === null">
212
220
  <template #content>
213
- <span v-if="tip" class="flex items-center gap-1.5">
214
- <span
215
- class="size-1.5 shrink-0 rounded-full"
216
- :style="{ backgroundColor: colorFor(tip.status) }"
217
- />
218
- <LpRollingText :value="tip.text" :duration="180" align="start" />
219
- </span>
221
+ <LpShift v-if="tip" axis="both" class="flex">
222
+ <span class="flex min-w-36 flex-col gap-1">
223
+ <span class="flex items-center gap-1.5">
224
+ <span
225
+ class="size-1.5 shrink-0 rounded-full transition-colors duration-[var(--duration-fast)]"
226
+ :style="{ backgroundColor: colorFor(tip.status) }"
227
+ />
228
+ <LpRollingText :value="tip.text" :duration="180" align="start" class="font-medium" />
229
+ </span>
230
+ <LpRollingText v-if="tip.range" :value="tip.range" :duration="180" align="start" class="text-[0.7rem] opacity-70 tabular-nums" />
231
+ <span v-if="tip.parts.length > 1 || (tip.parts.length === 1 && tip.parts[0].status !== tip.status)" class="mt-0.5 flex flex-col gap-0.5">
232
+ <span v-for="part in tip.parts" :key="part.status" class="flex items-center gap-1.5 tabular-nums">
233
+ <span class="size-1.5 shrink-0 rounded-full" :style="{ backgroundColor: colorFor(part.status) }" />
234
+ <span class="flex-1">{{ part.text }}</span>
235
+ <LpRollingText :value="part.time" :duration="180" align="end" class="opacity-80" />
236
+ </span>
237
+ </span>
238
+ <span v-if="tip.events.length" class="mt-0.5 flex flex-col gap-0.5 border-t border-current/15 pt-1">
239
+ <span v-for="(event, n) in tip.events" :key="n" class="flex items-baseline gap-1.5">
240
+ <LpRollingText v-if="event.at" :value="event.at" :duration="180" align="start" class="shrink-0 tabular-nums opacity-70" />
241
+ <LpRollingText :value="event.text" :duration="180" align="start" wrap />
242
+ </span>
243
+ <span v-if="tip.more" class="opacity-70">+{{ tip.more }}</span>
244
+ </span>
245
+ </span>
246
+ </LpShift>
220
247
  </template>
221
248
 
222
249
  <!--
@@ -235,7 +262,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
235
262
  type="button"
236
263
  class="lp-uptime__slot min-w-0 flex-1 outline-none focus-visible:ring-2 focus-visible:ring-ring"
237
264
  :tabindex="i === tabStop ? 0 : -1"
238
- :aria-label="seg.label ?? statusLabel[seg.status]"
265
+ :aria-label="[seg.label ?? statusLabel[seg.status], seg.start !== undefined ? uptimeRange(seg.start, seg.end, locale) : ''].filter(Boolean).join(', ')"
239
266
  @pointerenter="hovered = i"
240
267
  @focus="focusIndex = i; hovered = i"
241
268
  @blur="hovered === i && (hovered = null)"
@@ -275,13 +302,14 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
275
302
  class="inline-block h-[0.9em] w-8 align-middle"
276
303
  />
277
304
  <LpNumberFlow
278
- v-else-if="uptimePct != null"
279
- :value="uptimePct"
280
- :decimals="uptimeDecimals"
305
+ v-else-if="figure"
306
+ :value="figure.value"
307
+ :decimals="figure.decimals"
308
+ grouped
281
309
  suffix="%"
282
310
  />
283
311
  <template v-else>—</template>
284
- <span class="text-muted"> uptime</span>
312
+ <span class="ml-1 text-muted">{{ statusLabel.uptime }}</span>
285
313
  </span>
286
314
  <span class="h-px flex-1 bg-line" />
287
315
  <span class="shrink-0">{{ endLabel }}</span>
@@ -0,0 +1,5 @@
1
+ <script setup lang="ts">
2
+ import { suppressPaletteDiscovery } from "../composables/usePaletteCommands"
3
+ suppressPaletteDiscovery()
4
+ </script>
5
+ <template><div data-lp-command-preview class="contents"><slot /></div></template>
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { useAttrs } from "vue"
3
- import type { useFieldControl } from "../composables/useField"
3
+ import { useFieldBlur, type useFieldControl } from "../composables/useField"
4
4
  import { mergeClass } from "../composables/useMergedClass"
5
5
  import { PICKER_CLEAR, PICKER_PRESET, PICKER_PRESETS, PICKER_SIZE, PICKER_TRIGGER, pickerTone } from "./calendar"
6
6
  import LpIcon from "./LpIcon.vue"
@@ -21,13 +21,14 @@ withDefaults(defineProps<{
21
21
  presetsClass: string
22
22
  }>(), { size: "md" })
23
23
  const open = defineModel<boolean>("open")
24
- defineEmits<{ clear: [] }>()
24
+ const emit = defineEmits<{ clear: []; blur: [event: FocusEvent] }>()
25
+ const { partId, onFocusOut } = useFieldBlur((event) => emit("blur", event))
25
26
  const attrs = useAttrs()
26
27
  </script>
27
28
 
28
29
  <template>
29
- <div :class="mergeClass('relative w-full', attrs.class as string)" :style="attrs.style as never">
30
- <LpPopover v-model:open="open" align="start" panel-class="flex w-auto max-w-[calc(100vw-1rem)] flex-col rounded-card p-0 sm:flex-row">
30
+ <div :data-lp-field-part="partId" @focusout="onFocusOut" :class="mergeClass('relative w-full', attrs.class as string)" :style="attrs.style as never">
31
+ <LpPopover v-model:open="open" :data-lp-field-part="partId" @focusout="onFocusOut" align="start" panel-class="flex w-auto max-w-[calc(100vw-1rem)] flex-col rounded-card p-0 sm:flex-row">
31
32
  <template #trigger>
32
33
  <button
33
34
  v-bind="{ ...attrs, class: undefined, style: undefined }"
@@ -1,4 +1,15 @@
1
1
  import { applyQueryFilters, parsePaletteQuery, type CommandSearchTerms, type HighlightPart, type SearchableCommand } from "./commandSearch"
2
+ import type { Command } from "./LpCommandPalette.vue"
3
+ import type { ContextMenuItemDef } from "./LpContextMenu.vue"
4
+
5
+ /** The same action definitions serve a row menu and the command palette. */
6
+ export function menuPaletteCommands(items: ContextMenuItemDef[], prefix: string): Command[] {
7
+ return items.map((item, index) => ({
8
+ id: `${prefix}/${index}`, label: item.label, icon: item.icon, disabled: item.disabled,
9
+ onSelect: item.onSelect, keepOpen: item.checked !== undefined,
10
+ children: item.children ? menuPaletteCommands(item.children, `${prefix}/${index}`) : undefined,
11
+ }))
12
+ }
2
13
 
3
14
  /*
4
15
  * The parts of LpCommandPalette that are plain logic, kept out of the SFC so
@@ -7,6 +18,7 @@ import { applyQueryFilters, parsePaletteQuery, type CommandSearchTerms, type Hig
7
18
 
8
19
  /** The fields of a command that decide whether the preview pane opens for it. */
9
20
  export interface PreviewableCommand {
21
+ previewComponent?: unknown
10
22
  preview?: string
11
23
  excerpt?: string
12
24
  noPreview?: boolean
@@ -23,9 +35,18 @@ export function commandHasPreview<T extends PreviewableCommand>(
23
35
  ): boolean {
24
36
  if (!command || command.noPreview) return false
25
37
  if (options.hasPreview) return options.hasPreview(command)
26
- return options.slot || !!(command.preview || command.excerpt)
38
+ return options.slot || !!(command.previewComponent || command.preview || command.excerpt)
27
39
  }
28
40
 
41
+ export const PALETTE_MODES = {
42
+ people: { prefix: "@", label: "People", icon: "lucide:users" },
43
+ channels: { prefix: "#", label: "Channels", icon: "lucide:hash" },
44
+ roles: { prefix: "&", label: "Roles", icon: "lucide:shield" },
45
+ commands: { prefix: ">", label: "Commands", icon: "lucide:terminal" },
46
+ help: { prefix: "?", label: "Help", icon: "lucide:circle-help" },
47
+ } as const
48
+ export type PaletteMode = keyof typeof PALETTE_MODES
49
+
29
50
  /** One line of LpPaletteFacts: what it is, and what it is set to now. */
30
51
  export interface PaletteFact {
31
52
  /** Text, or the preview slot's `highlight(text)` to mark the query. */
@@ -72,7 +72,7 @@ function score(command: SearchableCommand, terms: CommandSearchTerms): number {
72
72
  return total
73
73
  }
74
74
 
75
- export function rankCommandItems<T extends SearchableCommand>(items: T[], terms: CommandSearchTerms): T[] {
75
+ function rankExact<T extends SearchableCommand>(items: T[], terms: CommandSearchTerms): T[] {
76
76
  return items
77
77
  .map((command) => ({ command, score: score(command, terms) }))
78
78
  .filter((entry) => entry.score > 0)
@@ -80,6 +80,28 @@ export function rankCommandItems<T extends SearchableCommand>(items: T[], terms:
80
80
  .map((entry) => entry.command)
81
81
  }
82
82
 
83
+ const LATIN_KEYS = "`qwertyuiop[]asdfghjkl;'zxcvbnm,."
84
+ const CYRILLIC_KEYS = "ёйцукенгшщзхъфывапролджэячсмитьбю"
85
+ const LAYOUT_KEYS = new Map([...LATIN_KEYS].flatMap((key, i) => [[key, CYRILLIC_KEYS[i]!], [CYRILLIC_KEYS[i]!, key]]))
86
+
87
+ export function swapCommandLayout(value: string): string {
88
+ return [...value].map(char => {
89
+ const replacement = LAYOUT_KEYS.get(char.toLowerCase())
90
+ return replacement ? char === char.toUpperCase() ? replacement.toUpperCase() : replacement : char
91
+ }).join("")
92
+ }
93
+
94
+ export function searchCommandItems<T extends SearchableCommand>(items: T[], terms: CommandSearchTerms): { items: T[]; terms: string[] } {
95
+ const exact = rankExact(items, terms)
96
+ if (exact.length || !terms.include.length) return { items: exact, terms: terms.include }
97
+ const alternate = { include: terms.include.map(swapCommandLayout), exclude: terms.exclude, groups: terms.groups }
98
+ return { items: rankExact(items, alternate), terms: alternate.include }
99
+ }
100
+
101
+ export function rankCommandItems<T extends SearchableCommand>(items: T[], terms: CommandSearchTerms): T[] {
102
+ return searchCommandItems(items, terms).items
103
+ }
104
+
83
105
  /**
84
106
  * A query as a fetcher receives it. `text` is what a remote search should be
85
107
  * sent: the positive words and phrases, in the case they were typed, without
@@ -134,3 +134,13 @@ export function compactNumber(value: number, options: CompactNumberOptions = {})
134
134
  }).format(scaled)
135
135
  return `${number}${COMPACT_UNITS[i][1]}`
136
136
  }
137
+
138
+ /**
139
+ * The character a locale writes between the whole and the fractional part:
140
+ * "." in English, "," in Russian and German. A counter that splits a grouped
141
+ * figure into columns has to find the fraction by this mark, not by ".",
142
+ * because in German "." is the thousands separator.
143
+ */
144
+ export function decimalMark(locale?: string): string {
145
+ return numberFormat(locale, {}).formatToParts(1.5).find((p) => p.type === "decimal")?.value ?? "."
146
+ }
@@ -0,0 +1,98 @@
1
+ /*
2
+ * The arithmetic behind LpUptimeBar, kept pure so the corners are tested
3
+ * without a DOM: what share of the time was up, how that figure is printed,
4
+ * and how a slice's span reads in its tooltip.
5
+ */
6
+ import { toEpoch } from "./timeSpan"
7
+
8
+ export type UptimeStatus = "operational" | "degraded" | "down" | "maintenance" | "empty"
9
+
10
+ export type UptimeMoment = Date | string | number
11
+
12
+ /** Something that happened inside a slice: a restart, a deploy, an alert. */
13
+ export interface UptimeEvent {
14
+ text: string
15
+ at?: UptimeMoment
16
+ status?: UptimeStatus
17
+ }
18
+
19
+ export interface UptimeSegment {
20
+ /** Health of this slice. `empty` renders a faded placeholder (no data). */
21
+ status: UptimeStatus
22
+ /** Optional rich tooltip body; falls back to a humanised status. */
23
+ label?: string
24
+ /** When the slice begins and ends. Shown in the tooltip, and weights the uptime figure. */
25
+ start?: UptimeMoment
26
+ end?: UptimeMoment
27
+ /**
28
+ * Time spent in each state inside the slice, in ms. With it the figure
29
+ * counts real time rather than slices (one bad minute in a day no longer
30
+ * costs the whole day), and the tooltip says how long each state lasted.
31
+ */
32
+ durations?: Partial<Record<Exclude<UptimeStatus, "empty">, number>>
33
+ /** What happened in the slice, a line each in the tooltip. */
34
+ events?: UptimeEvent[]
35
+ }
36
+
37
+ const UP: ReadonlySet<UptimeStatus> = new Set(["operational", "maintenance"])
38
+
39
+ /** Worst first: the order a tooltip lists the time a slice spent in each state. */
40
+ export const UPTIME_SEVERITY: readonly Exclude<UptimeStatus, "empty">[] = ["down", "degraded", "maintenance", "operational"]
41
+
42
+ function span(seg: UptimeSegment): number | null {
43
+ if (seg.start === undefined || seg.end === undefined) return null
44
+ const ms = toEpoch(seg.end) - toEpoch(seg.start)
45
+ return Number.isFinite(ms) && ms > 0 ? ms : null
46
+ }
47
+
48
+ /**
49
+ * Percent of the tracked time that was up (operational or planned
50
+ * maintenance). Slices with `durations` count their real minutes; the rest
51
+ * count by their span when every one of them has a span, and as one slice
52
+ * each otherwise. Empty slices are not tracked. Null when nothing is.
53
+ */
54
+ export function uptimeShare(segments: readonly UptimeSegment[]): number | null {
55
+ const tracked = segments.filter((s) => s.status !== "empty")
56
+ const bySpan = tracked.every((s) => s.durations || span(s) !== null)
57
+ let up = 0
58
+ let total = 0
59
+ for (const seg of tracked) {
60
+ if (seg.durations) {
61
+ for (const [status, ms] of Object.entries(seg.durations)) {
62
+ if (!ms || ms < 0) continue
63
+ total += ms
64
+ if (UP.has(status as UptimeStatus)) up += ms
65
+ }
66
+ continue
67
+ }
68
+ const weight = bySpan ? (span(seg) ?? 0) : 1
69
+ total += weight
70
+ if (UP.has(seg.status)) up += weight
71
+ }
72
+ return total > 0 ? (up / total) * 100 : null
73
+ }
74
+
75
+ /**
76
+ * The figure and its decimal places: trailing zeros trimmed (100, 99.9), and
77
+ * never rounded up to 100 while some time was down — 99.999 prints as 99.99,
78
+ * because "100%" over an outage reads as no outage at all.
79
+ */
80
+ export function uptimeFigure(pct: number): { value: number; decimals: number } {
81
+ let value = Number(pct.toFixed(2))
82
+ if (pct < 100 && value >= 100) value = 99.99
83
+ const decimals = Number.isInteger(value) ? 0 : String(value).split(".")[1].length
84
+ return { value, decimals }
85
+ }
86
+
87
+ /** "26 Oct, 12:00 – 12:15", the date written once when both ends share a day. */
88
+ export function uptimeRange(start: UptimeMoment, end: UptimeMoment | undefined, locale?: string): string {
89
+ const from = toEpoch(start)
90
+ if (!Number.isFinite(from)) return ""
91
+ const full = new Intl.DateTimeFormat(locale, { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" })
92
+ if (end === undefined) return full.format(from)
93
+ const to = toEpoch(end)
94
+ if (!Number.isFinite(to)) return full.format(from)
95
+ const sameDay = new Date(from).toDateString() === new Date(to).toDateString()
96
+ const tail = sameDay ? new Intl.DateTimeFormat(locale, { hour: "2-digit", minute: "2-digit" }) : full
97
+ return `${full.format(from)} – ${tail.format(to)}`
98
+ }
@@ -26,6 +26,17 @@ export function scrollParent(el: Element | null | undefined, { axis = "y", self
26
26
  return null
27
27
  }
28
28
 
29
+ /**
30
+ * Whether `el`'s scroller (or the page) has no more room to scroll down.
31
+ * `slack` absorbs the pixel a rounded-up scrollHeight can sit above the sum.
32
+ */
33
+ export function atScrollEnd(el: Element | null | undefined, slack = 2): boolean {
34
+ if (!el) return false
35
+ const box = scrollParent(el) ?? (el.ownerDocument.scrollingElement as HTMLElement | null)
36
+ if (!box) return true
37
+ return box.scrollTop + box.clientHeight >= box.scrollHeight - slack
38
+ }
39
+
29
40
  export interface ScrollToElementOptions {
30
41
  /** `nearest` scrolls only when the element is out of view. */
31
42
  block?: "nearest" | "center" | "start"
@@ -1,4 +1,4 @@
1
- import { computed, inject, provide, useId, type ComputedRef, type InjectionKey } from "vue"
1
+ import { computed, inject, onScopeDispose, provide, useId, type ComputedRef, type InjectionKey } from "vue"
2
2
  import type { FieldNoticeOptions } from "./useFieldNotice"
3
3
 
4
4
  /*
@@ -9,6 +9,10 @@ import type { FieldNoticeOptions } from "./useFieldNotice"
9
9
  */
10
10
  export interface FieldContext {
11
11
  id: string
12
+ /** Human-readable name shared by controls and contextual commands. */
13
+ label?: string
14
+ hint?: string
15
+ error?: string
12
16
  /** Id of the visible label, for controls a `<label for>` cannot name (groups). */
13
17
  labelId?: string
14
18
  describedBy?: string
@@ -74,6 +78,9 @@ export function useFieldControl(
74
78
  return {
75
79
  id: state.id ?? field?.id,
76
80
  labelId: field?.labelId,
81
+ label: field?.label,
82
+ hint: field?.hint,
83
+ error: field?.error,
77
84
  describedBy: field?.describedBy,
78
85
  invalid: !!state.invalid || !!field?.invalid,
79
86
  required: !!state.required || !!field?.required,
@@ -93,3 +100,28 @@ export function focusLeft(event: FocusEvent): boolean {
93
100
  const next = event.relatedTarget
94
101
  return !(next instanceof Node && (event.currentTarget as Node | null)?.contains(next))
95
102
  }
103
+
104
+ /** Trigger and portalled content share a boundary for the control's public blur. */
105
+ export function useFieldBlur(blur: (event: FocusEvent) => void) {
106
+ const partId = `lp-field-${useId()}`
107
+ let cancelPending: (() => void) | undefined
108
+ const inside = (target: EventTarget | null) =>
109
+ !!(target as Element | null)?.closest?.(`[data-lp-field-part="${partId}"]`)
110
+
111
+ function onFocusOut(event: FocusEvent) {
112
+ cancelPending?.()
113
+ if (inside(event.relatedTarget)) return
114
+ if (event.relatedTarget) return blur(event)
115
+ // A removed calendar cell can lose focus before its trigger gets it back.
116
+ const doc = (event.target as Node).ownerDocument!
117
+ const view = doc.defaultView!
118
+ const frame = view.requestAnimationFrame(() => {
119
+ cancelPending = undefined
120
+ if (!inside(doc.activeElement)) blur(event)
121
+ })
122
+ cancelPending = () => view.cancelAnimationFrame(frame)
123
+ }
124
+
125
+ onScopeDispose(() => cancelPending?.())
126
+ return { partId, onFocusOut }
127
+ }