@leavepulse/ui 0.58.1 → 0.60.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 (173) hide show
  1. package/dist/assets/{LpCommandPalette-DmQUsJqA.css → LpCommandPalette-DA5pmuPi.css} +21 -21
  2. package/dist/assets/{LpTable-BNzavdlv.css → LpTable-BZZf_rrt.css} +19 -19
  3. package/dist/chunks/{ContextMenuItems-BY5sTVQF.js → ContextMenuItems-qXReFtQW.js} +2 -2
  4. package/dist/chunks/{LayoutCanvas-BcGLJVBq.js → LayoutCanvas-t7LWFCEn.js} +1 -1
  5. package/dist/chunks/{LayoutNode-BkBn6dvM.js → LayoutNode-DHsE6GwM.js} +1 -1
  6. package/dist/chunks/{LpActionBar-DCN-_pZP.js → LpActionBar-BsSp66S_.js} +2 -2
  7. package/dist/chunks/{LpAddressInput-Dio6Sqed.js → LpAddressInput-DD4zTN4P.js} +7 -7
  8. package/dist/chunks/{LpAlert-CXMn1GYE.js → LpAlert--KLL4NLk.js} +1 -1
  9. package/dist/chunks/{LpAppShell-Be6T96td.js → LpAppShell-Cv-Y9qNA.js} +3 -3
  10. package/dist/chunks/{LpAutocomplete-hJ_6CHlh.js → LpAutocomplete-PnL2ntKl.js} +1 -1
  11. package/dist/chunks/{LpAvatar-BxNs23Xu.js → LpAvatar-CwGs1JCQ.js} +2 -2
  12. package/dist/chunks/{LpBreadcrumbs-BLzOEgAF.js → LpBreadcrumbs-CndzPYiG.js} +1 -1
  13. package/dist/chunks/{LpButton-vhlwmpBy.js → LpButton-CJSD7N0Z.js} +1 -1
  14. package/dist/chunks/{LpCalendar-BLoE8Qm7.js → LpCalendar-CXVDbb2M.js} +1 -1
  15. package/dist/chunks/{LpCard-DhR7u0d8.js → LpCard-B2D0GIUU.js} +1 -1
  16. package/dist/chunks/{LpCheckbox-B_C8sOoT.js → LpCheckbox-DV9W6T3H.js} +1 -1
  17. package/dist/chunks/{LpCodeBlock-CXsqJwDy.js → LpCodeBlock-t6MLA3fb.js} +5 -5
  18. package/dist/chunks/{LpColorPicker-DQ88niXq.js → LpColorPicker-CvBF8TeL.js} +6 -6
  19. package/dist/chunks/{LpConfirmDialog-DxlSdjmk.js → LpConfirmDialog-tkgyY3eu.js} +3 -3
  20. package/dist/chunks/{LpConfirmHost-BcrmTNu_.js → LpConfirmHost-CoYIcUdl.js} +1 -1
  21. package/dist/chunks/{LpContextMenu-B1NGfpp1.js → LpContextMenu-BnN1k7ga.js} +17 -5
  22. package/dist/chunks/{LpDatePicker-B-VMHMgy.js → LpDatePicker-DxdrVNCG.js} +3 -3
  23. package/dist/chunks/{LpDateRangePicker-B68KPKPl.js → LpDateRangePicker-DjeaqikC.js} +2 -2
  24. package/dist/chunks/{LpDisclosure-Ct-VBHxt.js → LpDisclosure-s8cQ7KQq.js} +1 -1
  25. package/dist/chunks/{LpDrawer-DSknigxH.js → LpDrawer-C97yoWmh.js} +2 -2
  26. package/dist/chunks/{LpDropdownMenu-C7oHVlKY.js → LpDropdownMenu-CbyYu4Rw.js} +12 -5
  27. package/dist/chunks/{LpEmptyState-CBwwkkdA.js → LpEmptyState-CX8fdGSU.js} +1 -1
  28. package/dist/chunks/{LpFieldNotice-9hH4ITZa.js → LpFieldNotice-B--RzKfj.js} +1 -1
  29. package/dist/chunks/{LpFileTree-CMA0_rsB.js → LpFileTree-DEqh2ySW.js} +2 -2
  30. package/dist/chunks/{LpFileTreeNode-B4T258hF.js → LpFileTreeNode-DCZA7sQ-.js} +3 -3
  31. package/dist/chunks/{LpFormField-BpQ6WJPe.js → LpFormField-DMPPL1jF.js} +1 -1
  32. package/dist/chunks/{LpHintLayer-B5-6FyEf.js → LpHintLayer-DsclGYcD.js} +1 -1
  33. package/dist/chunks/{LpHints-BqoZXHZk.js → LpHints-BTpvisgP.js} +10 -4
  34. package/dist/chunks/{LpIcon-B5guGKrW.js → LpIcon-CTuDVTYv.js} +1 -1
  35. package/dist/chunks/{LpInput-WvyP02ID.js → LpInput-C7FHxzj2.js} +2 -2
  36. package/dist/chunks/{LpLightbox-Dq-e36uT.js → LpLightbox-DiksFa4L.js} +1 -1
  37. package/dist/chunks/{LpLogViewer-DwiYE32z.js → LpLogViewer-BuPe23sZ.js} +2 -2
  38. package/dist/chunks/{LpMapPicker-BH5csaBt.js → LpMapPicker-DybIlWnF.js} +1 -1
  39. package/dist/chunks/{LpMediaGallery-B1kDypJ-.js → LpMediaGallery-Cic9UHsa.js} +2 -2
  40. package/dist/chunks/{LpModal-BxYssnXg.js → LpModal-QFfctZsj.js} +2 -2
  41. package/dist/chunks/{LpModalNav-Bn2ws64b.js → LpModalNav-Cw7m-eks.js} +4 -4
  42. package/dist/chunks/{LpNotificationBell-DJfs4JjQ.js → LpNotificationBell-xH_JphpS.js} +6 -6
  43. package/dist/chunks/{LpNumberField-CJPjmoDz.js → LpNumberField-BFSRVGkJ.js} +1 -1
  44. package/dist/chunks/{LpPagination-DehAaJLY.js → LpPagination-BFl0I7tL.js} +1 -1
  45. package/dist/chunks/{LpPasswordInput-Bg5uTZbx.js → LpPasswordInput-C1G4k_r0.js} +3 -3
  46. package/dist/chunks/{LpPhoneInput-DStwcbnQ.js → LpPhoneInput-CZrpVxla.js} +3 -3
  47. package/dist/chunks/{LpPie-DeTkbbOK.js → LpPie-BD5Lsh-2.js} +3 -3
  48. package/dist/chunks/{LpRangeCalendar-D5sDID9W.js → LpRangeCalendar-COeOYF52.js} +1 -1
  49. package/dist/chunks/{LpSection-B19VPYmr.js → LpSection-BAkbf0OK.js} +1 -1
  50. package/dist/chunks/{LpSegmented-D3Eam8sa.js → LpSegmented-DslFku0N.js} +3 -3
  51. package/dist/chunks/{LpSelect-DynjWK1A.js → LpSelect-C4OXN_q3.js} +1 -1
  52. package/dist/chunks/{LpSidebar-CshW0fqC.js → LpSidebar-B3E_iViL.js} +3 -3
  53. package/dist/chunks/{LpSidebarNav-C9b3i9G2.js → LpSidebarNav-32sAXNM-.js} +4 -4
  54. package/dist/chunks/{LpStat-BS2TEXYK.js → LpStat-oYk6V1LZ.js} +1 -1
  55. package/dist/chunks/{LpStepper-BSglWF45.js → LpStepper-YgHGzgfD.js} +1 -1
  56. package/dist/chunks/{LpTableFilter-CwHXVxJt.js → LpTableFilter-Ba-rS4H1.js} +2 -2
  57. package/dist/chunks/{LpTabs-BBGRHY38.js → LpTabs-00v3VQx4.js} +7 -6
  58. package/dist/chunks/{LpTagsInput-CHbQoAAA.js → LpTagsInput-Sc56iy_j.js} +3 -3
  59. package/dist/chunks/{LpTextarea-Bz2IWhTH.js → LpTextarea-CQZccQI3.js} +1 -1
  60. package/dist/chunks/{LpThemeSwitcher-B_qhSl7q.js → LpThemeSwitcher-CeBG0e_D.js} +2 -2
  61. package/dist/chunks/{LpTimeField-DvGXgdES.js → LpTimeField-BTEt0kvL.js} +1 -1
  62. package/dist/chunks/{LpToaster-Bo9Q4QBP.js → LpToaster-BcKs7vX7.js} +3 -3
  63. package/dist/chunks/{LpTour-D7m9ZxNk.js → LpTour-C29-1wha.js} +4 -1
  64. package/dist/chunks/{MenuTitle-DQP6hc2N.js → MenuTitle-FeC6I24N.js} +3 -3
  65. package/dist/chunks/{TourCard-DCHOjr6e.js → TourCard-AjN0uhs1.js} +2 -2
  66. package/dist/chunks/{icons-QqBscG5I.js → icons-CuO3W46H.js} +1 -1
  67. package/dist/components/ContextMenuItems.vue.js +1 -1
  68. package/dist/components/LayoutCanvas.vue.js +1 -1
  69. package/dist/components/LayoutNode.vue.js +1 -1
  70. package/dist/components/LpActionBar.vue.js +1 -1
  71. package/dist/components/LpAddressInput.vue.js +1 -1
  72. package/dist/components/LpAlert.vue.js +1 -1
  73. package/dist/components/LpAppShell.vue.js +1 -1
  74. package/dist/components/LpAutocomplete.vue.js +1 -1
  75. package/dist/components/LpAvatar.vue.js +1 -1
  76. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  77. package/dist/components/LpButton.vue.js +1 -1
  78. package/dist/components/LpCalendar.vue.js +1 -1
  79. package/dist/components/LpCard.vue.js +1 -1
  80. package/dist/components/LpChart.vue.js +4 -4
  81. package/dist/components/LpCheckbox.vue.js +1 -1
  82. package/dist/components/LpCodeBlock.vue.js +1 -1
  83. package/dist/components/LpColorPicker.vue.js +1 -1
  84. package/dist/components/LpCommandPalette.vue.js +38 -27
  85. package/dist/components/LpConfirmDialog.vue.js +1 -1
  86. package/dist/components/LpConfirmHost.vue.js +1 -1
  87. package/dist/components/LpContextMenu.vue.d.ts +7 -2
  88. package/dist/components/LpContextMenu.vue.js +1 -1
  89. package/dist/components/LpDatePicker.vue.js +1 -1
  90. package/dist/components/LpDateRangePicker.vue.js +1 -1
  91. package/dist/components/LpDisclosure.vue.js +1 -1
  92. package/dist/components/LpDrawer.vue.js +1 -1
  93. package/dist/components/LpDropdownMenu.vue.d.ts +7 -2
  94. package/dist/components/LpDropdownMenu.vue.js +1 -1
  95. package/dist/components/LpDropzone.vue.js +1 -1
  96. package/dist/components/LpEmptyState.vue.js +1 -1
  97. package/dist/components/LpFieldNotice.vue.js +1 -1
  98. package/dist/components/LpFileTree.vue.js +1 -1
  99. package/dist/components/LpFileTreeNode.vue.js +1 -1
  100. package/dist/components/LpFormField.vue.js +1 -1
  101. package/dist/components/LpHintLayer.vue.js +1 -1
  102. package/dist/components/LpHints.vue.js +1 -1
  103. package/dist/components/LpIcon.vue.js +1 -1
  104. package/dist/components/LpImageEditor.vue.js +4 -2
  105. package/dist/components/LpInput.vue.js +1 -1
  106. package/dist/components/LpLightbox.vue.js +1 -1
  107. package/dist/components/LpLogViewer.vue.js +1 -1
  108. package/dist/components/LpMapPicker.vue.js +1 -1
  109. package/dist/components/LpMediaGallery.vue.js +1 -1
  110. package/dist/components/LpModal.vue.js +1 -1
  111. package/dist/components/LpModalNav.vue.js +1 -1
  112. package/dist/components/LpNotificationBell.vue.js +1 -1
  113. package/dist/components/LpNumberField.vue.js +1 -1
  114. package/dist/components/LpPagination.vue.js +1 -1
  115. package/dist/components/LpPaletteFacts.vue.js +1 -1
  116. package/dist/components/LpPaletteHeader.vue.js +2 -2
  117. package/dist/components/LpPasswordInput.vue.js +1 -1
  118. package/dist/components/LpPhoneInput.vue.js +1 -1
  119. package/dist/components/LpPie.vue.js +1 -1
  120. package/dist/components/LpRangeCalendar.vue.js +1 -1
  121. package/dist/components/LpSection.vue.js +1 -1
  122. package/dist/components/LpSegmented.vue.js +1 -1
  123. package/dist/components/LpSelect.vue.js +1 -1
  124. package/dist/components/LpShortcutsDialog.vue.js +1 -1
  125. package/dist/components/LpSidebar.vue.js +1 -1
  126. package/dist/components/LpSidebarNav.vue.js +1 -1
  127. package/dist/components/LpStat.vue.js +1 -1
  128. package/dist/components/LpStepper.vue.js +1 -1
  129. package/dist/components/LpTable.vue.js +13 -12
  130. package/dist/components/LpTableFilter.vue.js +1 -1
  131. package/dist/components/LpTabs.vue.d.ts +7 -0
  132. package/dist/components/LpTabs.vue.js +1 -1
  133. package/dist/components/LpTagsInput.vue.js +1 -1
  134. package/dist/components/LpTextarea.vue.js +1 -1
  135. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  136. package/dist/components/LpTimeField.vue.js +1 -1
  137. package/dist/components/LpToaster.vue.js +1 -1
  138. package/dist/components/LpTour.vue.js +1 -1
  139. package/dist/components/MenuTitle.vue.js +1 -1
  140. package/dist/components/TourCard.vue.js +1 -1
  141. package/dist/components/dropdown.js +1 -1
  142. package/dist/composables/useHintSystem.d.ts +65 -0
  143. package/dist/composables/useHintSystem.js +98 -12
  144. package/dist/icons-all.js +1 -1
  145. package/dist/index.d.ts +2 -2
  146. package/dist/index.js +62 -58
  147. package/package.json +1 -1
  148. package/src/components/ContextMenuItems.vue +1 -1
  149. package/src/components/LpChart.vue +1 -1
  150. package/src/components/LpCodeBlock.vue +4 -4
  151. package/src/components/LpColorPicker.vue +4 -4
  152. package/src/components/LpCommandPalette.vue +36 -14
  153. package/src/components/LpContextMenu.vue +19 -1
  154. package/src/components/LpDrawer.vue +1 -1
  155. package/src/components/LpDropdownMenu.vue +14 -3
  156. package/src/components/LpHints.vue +7 -1
  157. package/src/components/LpModal.vue +1 -1
  158. package/src/components/LpNotificationBell.vue +2 -2
  159. package/src/components/LpPasswordInput.vue +1 -1
  160. package/src/components/LpPhoneInput.vue +1 -1
  161. package/src/components/LpPie.vue +1 -1
  162. package/src/components/LpSegmented.vue +2 -2
  163. package/src/components/LpSidebarNav.vue +2 -2
  164. package/src/components/LpTable.vue +3 -3
  165. package/src/components/LpTabs.vue +11 -5
  166. package/src/components/LpToaster.vue +2 -2
  167. package/src/components/LpTour.vue +3 -0
  168. package/src/components/MenuTitle.vue +2 -2
  169. package/src/components/dropdown.ts +1 -1
  170. package/src/composables/useHintSystem.ts +130 -12
  171. package/src/index.ts +2 -1
  172. package/src/kit-icons.ts +1 -1
  173. package/src/tokens/tokens.css +6 -0
@@ -1,6 +1,44 @@
1
- import { provide, reactive, computed, inject, watch } from "vue";
1
+ import { shallowRef, provide, onBeforeUnmount, reactive, computed, inject, watch } from "vue";
2
2
  import { anyModalOpen } from "../components/modalDepth.js";
3
3
  import { hints } from "./useHints.js";
4
+ const HINT_SIGNALS = {
5
+ /** A context menu was opened (right click or long press). */
6
+ contextMenu: "lp.context-menu",
7
+ /** A "…" menu marked `rightClickToo` was opened: its items were one right click away. */
8
+ menuButton: "lp.menu-button",
9
+ /** A left click on something that also has a context menu. */
10
+ contextTarget: "lp.context-target",
11
+ /** The command palette was opened. */
12
+ palette: "lp.palette",
13
+ /** The current hint page changed: a navigation. */
14
+ navigate: "lp.navigate",
15
+ /** An item's actions were opened in the palette (Tab). */
16
+ paletteActions: "lp.palette-actions"
17
+ };
18
+ function contextMenuTip(text = {}) {
19
+ return {
20
+ id: "lp.tip.context-menu",
21
+ title: text.title ?? "Right-click works here",
22
+ text: text.text ?? "Rows and cards open their actions on right-click, even where no … button shows it.",
23
+ when: (count) => count(HINT_SIGNALS.contextMenu) === 0 && (count(HINT_SIGNALS.menuButton) >= 5 || count(HINT_SIGNALS.contextTarget) >= 25),
24
+ step: () => {
25
+ const target = [...document.querySelectorAll("[data-lp-context-menu]")].find((el) => {
26
+ const box = el.getBoundingClientRect();
27
+ return box.width > 0 && box.bottom > 0 && box.top < window.innerHeight;
28
+ });
29
+ return target ? { target, title: text.title ?? "Right-click works here", text: text.stepText ?? "Right-click this to see what it can do.", try: true } : null;
30
+ }
31
+ };
32
+ }
33
+ function paletteTip(keys = "Ctrl+K", text = {}) {
34
+ return {
35
+ id: "lp.tip.palette",
36
+ title: text.title ?? "Jump anywhere",
37
+ text: text.text ?? "Search pages, people and settings from anywhere instead of the menu.",
38
+ keys,
39
+ when: (count) => count(HINT_SIGNALS.navigate) >= 15 && count(HINT_SIGNALS.palette) === 0
40
+ };
41
+ }
4
42
  const RANK = { seen: 0, explained: 1, used: 2 };
5
43
  const DAY = 24 * 3600 * 1e3;
6
44
  const DEFAULT_TIMING = {
@@ -69,12 +107,15 @@ function createHintSystem(options) {
69
107
  const features = options.features;
70
108
  const pages = options.pages ?? {};
71
109
  const news = options.news ?? [];
110
+ const tips = options.tips ?? [];
111
+ const TIP_MAX = 3;
72
112
  const storage = options.storage ?? localHintStorage();
73
113
  const sessionKey = options.sessionKey ?? "lp-hints:interrupted";
74
114
  const state = reactive({
75
115
  records: /* @__PURE__ */ new Map(),
76
116
  loaded: false,
77
117
  page: "",
118
+ overlay: null,
78
119
  tour: { open: false, step: 0, steps: [], ids: [], page: "" },
79
120
  offer: null,
80
121
  candidate: null,
@@ -87,6 +128,9 @@ function createHintSystem(options) {
87
128
  const pageKey = hintKey.page;
88
129
  const newsKey = hintKey.news;
89
130
  const nudgeKey = hintKey.nudge;
131
+ const signalKey = (name) => `u:${name}`;
132
+ const tipKey = (id) => `p:${id}`;
133
+ const count = (name) => state.records.get(signalKey(name))?.count ?? 0;
90
134
  function interrupted() {
91
135
  try {
92
136
  return sessionStorage.getItem(sessionKey) === "1";
@@ -213,21 +257,35 @@ function createHintSystem(options) {
213
257
  return;
214
258
  }
215
259
  const n = state.candidate;
216
- if (!n || n.page !== page) return;
217
- const seen = state.records.get(nudgeKey(n.id));
218
- if (seen?.dismissed || state.records.get(featureKey(n.feature))?.state === "used" || !hints.present(n.feature)) return;
219
- if (seen && Date.now() - Date.parse(seen.updatedAt) < timing.nudgeAgain) return;
220
- state.candidate = null;
221
- spend();
222
- record(nudgeKey(n.id), { add: 1 });
223
- state.offer = { kind: "nudge", page, nudge: n };
260
+ if (n && n.page === page) {
261
+ const seen = state.records.get(nudgeKey(n.id));
262
+ const f = features[n.feature];
263
+ const visible = !!f && (!!f.target || !!f.enter || hints.present(n.feature));
264
+ if (!seen?.dismissed && state.records.get(featureKey(n.feature))?.state !== "used" && visible && !(seen && Date.now() - Date.parse(seen.updatedAt) < timing.nudgeAgain)) {
265
+ state.candidate = null;
266
+ spend();
267
+ record(nudgeKey(n.id), { add: 1 });
268
+ state.offer = { kind: "nudge", page, nudge: n };
269
+ return;
270
+ }
271
+ }
272
+ for (const tip of tips) {
273
+ const seen = state.records.get(tipKey(tip.id));
274
+ if (seen?.dismissed || (seen?.count ?? 0) >= (tip.max ?? TIP_MAX)) continue;
275
+ if (seen && Date.now() - Date.parse(seen.updatedAt) < timing.nudgeAgain) continue;
276
+ if (!tip.when(count)) continue;
277
+ spend();
278
+ state.offer = { kind: "tip", page, tip };
279
+ return;
280
+ }
224
281
  }
225
282
  let stops = [];
226
283
  function start() {
227
284
  if (stops.length) return;
228
285
  const stopPage = watch(
229
- options.page,
230
- (page) => {
286
+ () => state.overlay ?? options.page(),
287
+ (page, previous) => {
288
+ if (previous !== void 0 && page !== previous && !state.overlay) record(signalKey(HINT_SIGNALS.navigate), { add: 1 });
231
289
  state.page = page;
232
290
  state.settledAt = Date.now();
233
291
  if (state.offer && state.offer.page !== page) state.offer = null;
@@ -249,6 +307,7 @@ function createHintSystem(options) {
249
307
  stopPage,
250
308
  hints.on("used", (id) => {
251
309
  if (features[id]) record(featureKey(id), { state: "used", add: 1 });
310
+ if (id.startsWith("lp.") || !features[id]) record(signalKey(id), { add: 1 });
252
311
  }),
253
312
  () => clearInterval(poll),
254
313
  () => window.removeEventListener("pagehide", flushOnLeave)
@@ -279,6 +338,16 @@ function createHintSystem(options) {
279
338
  nudge(n) {
280
339
  state.candidate = { ...n, page: state.page };
281
340
  },
341
+ /**
342
+ * A dialog acts as its own page while open (its dots and tour), then hands
343
+ * back. Pass null on close, and on unmount: a dialog left open by a route
344
+ * change would otherwise keep the page.
345
+ */
346
+ overlay(page) {
347
+ state.overlay = page;
348
+ },
349
+ /** How many times a signal was reported (for an app's own tips or reports). */
350
+ count,
282
351
  /** The user navigated on purpose (palette, notification): do not interrupt this arrival. */
283
352
  markIntent() {
284
353
  state.intent = true;
@@ -294,6 +363,7 @@ function createHintSystem(options) {
294
363
  if (id) record(featureKey(id), { state: "explained" });
295
364
  },
296
365
  tourEnded(finished) {
366
+ if (!state.tour.ids.length) return;
297
367
  record(pageKey(state.tour.page), finished ? { state: "explained" } : { add: 1 });
298
368
  baseline(state.tour.page);
299
369
  },
@@ -305,6 +375,10 @@ function createHintSystem(options) {
305
375
  const ids = offer.items.map((n) => n.feature).filter((id) => !!id);
306
376
  readNews(offer.page);
307
377
  if (choice === "show" && ids.length) startTour(offer.page, ids);
378
+ } else if (offer.kind === "tip") {
379
+ record(tipKey(offer.tip.id), { add: 1, dismissed: choice === "never" });
380
+ const step = choice === "show" ? offer.tip.step?.() : null;
381
+ if (step) Object.assign(state.tour, { open: true, step: 0, page: offer.page, ids: [], steps: [step] });
308
382
  } else if (offer.kind === "nudge") {
309
383
  if (choice === "show") startTour(features[offer.nudge.feature].page, [offer.nudge.feature]);
310
384
  else record(nudgeKey(offer.nudge.id), { dismissed: true });
@@ -318,17 +392,29 @@ function createHintSystem(options) {
318
392
  };
319
393
  }
320
394
  const HINT_SYSTEM = /* @__PURE__ */ Symbol.for("lp-hint-system");
395
+ const running = shallowRef(null);
321
396
  function provideHintSystem(system) {
322
397
  provide(HINT_SYSTEM, system);
398
+ running.value = system;
399
+ onBeforeUnmount(() => {
400
+ if (running.value === system) running.value = null;
401
+ });
323
402
  }
324
403
  function useHintSystem() {
325
- return inject(HINT_SYSTEM, null);
404
+ return inject(HINT_SYSTEM, null) ?? running.value;
405
+ }
406
+ function runningHintSystem() {
407
+ return running.value;
326
408
  }
327
409
  export {
410
+ HINT_SIGNALS,
411
+ contextMenuTip,
328
412
  createHintSystem,
329
413
  hintKey,
330
414
  hintSummary,
331
415
  localHintStorage,
416
+ paletteTip,
332
417
  provideHintSystem,
418
+ runningHintSystem,
333
419
  useHintSystem
334
420
  };
package/dist/icons-all.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import lucide from "@iconify-json/lucide/icons.json";
2
- import { a as registerIconCollection } from "./chunks/icons-QqBscG5I.js";
2
+ import { a as registerIconCollection } from "./chunks/icons-CuO3W46H.js";
3
3
  registerIconCollection(lucide);
package/dist/index.d.ts CHANGED
@@ -162,8 +162,8 @@ export type { TourContext, TourStep } from "./components/LpTour.vue";
162
162
  export { holeOf, openContextMenu, panelsAround } from "./components/tour";
163
163
  export { default as LpHintLayer } from "./components/LpHintLayer.vue";
164
164
  export { default as LpHints } from "./components/LpHints.vue";
165
- export { createHintSystem, hintKey, hintSummary, localHintStorage, provideHintSystem, useHintSystem } from "./composables/useHintSystem";
166
- export type { HintDelta, HintFeature, HintLevel, HintNews, HintNudge, HintOffer, HintPage, HintRecord, HintStorage, HintSummary, HintSystem, HintSystemOptions, HintTiming, } from "./composables/useHintSystem";
165
+ export { HINT_SIGNALS, contextMenuTip, createHintSystem, hintKey, hintSummary, localHintStorage, paletteTip, provideHintSystem, runningHintSystem, useHintSystem } from "./composables/useHintSystem";
166
+ export type { HintDelta, HintFeature, HintLevel, HintNews, HintNudge, HintOffer, HintPage, HintRecord, HintStorage, HintSummary, HintSystem, HintTip, HintSystemOptions, HintTiming, } from "./composables/useHintSystem";
167
167
  export type { HintNote } from "./components/LpHintLayer.vue";
168
168
  export { default as LpTooltipProvider } from "./components/LpTooltipProvider.vue";
169
169
  export { default as LpTopologyCanvas } from "./components/LpTopologyCanvas.vue";
package/dist/index.js CHANGED
@@ -1,24 +1,24 @@
1
- import { _ } from "./chunks/LayoutCanvas-BcGLJVBq.js";
2
- import { _ as _2 } from "./chunks/LpAddressInput-Dio6Sqed.js";
3
- import { _ as _3 } from "./chunks/LpMapPicker-BH5csaBt.js";
1
+ import { _ } from "./chunks/LayoutCanvas-t7LWFCEn.js";
2
+ import { _ as _2 } from "./chunks/LpAddressInput-DD4zTN4P.js";
3
+ import { _ as _3 } from "./chunks/LpMapPicker-DybIlWnF.js";
4
4
  import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
5
- import { _ as _4 } from "./chunks/LpActionBar-DCN-_pZP.js";
6
- import { _ as _5 } from "./chunks/LpAlert-CXMn1GYE.js";
7
- import { _ as _6 } from "./chunks/LpAppShell-Be6T96td.js";
8
- import { _ as _7 } from "./chunks/LpAutocomplete-hJ_6CHlh.js";
9
- import { _ as _8 } from "./chunks/LpAvatar-BxNs23Xu.js";
5
+ import { _ as _4 } from "./chunks/LpActionBar-BsSp66S_.js";
6
+ import { _ as _5 } from "./chunks/LpAlert--KLL4NLk.js";
7
+ import { _ as _6 } from "./chunks/LpAppShell-Cv-Y9qNA.js";
8
+ import { _ as _7 } from "./chunks/LpAutocomplete-PnL2ntKl.js";
9
+ import { _ as _8 } from "./chunks/LpAvatar-CwGs1JCQ.js";
10
10
  import { _ as _9 } from "./chunks/LpBadge-Fe0D1bYx.js";
11
- import { _ as _10 } from "./chunks/LpBreadcrumbs-BLzOEgAF.js";
12
- import { _ as _11 } from "./chunks/LpButton-vhlwmpBy.js";
13
- import { _ as _12 } from "./chunks/LpCalendar-BLoE8Qm7.js";
14
- import { _ as _13 } from "./chunks/LpCard-DhR7u0d8.js";
11
+ import { _ as _10 } from "./chunks/LpBreadcrumbs-CndzPYiG.js";
12
+ import { _ as _11 } from "./chunks/LpButton-CJSD7N0Z.js";
13
+ import { _ as _12 } from "./chunks/LpCalendar-CXVDbb2M.js";
14
+ import { _ as _13 } from "./chunks/LpCard-B2D0GIUU.js";
15
15
  import { default as default2 } from "./components/LpChart.vue.js";
16
16
  import { backgroundOf, csvField, downloadFile, fileStem, inlineSvg, resolveCssColor, svgToPng, toCsv } from "./components/exportData.js";
17
17
  import { CHART_PALETTE, axisTicks, chartSummary, formatTick, markerIndex, niceScale, resolveSlices } from "./components/chart.js";
18
- import { _ as _14 } from "./chunks/LpCheckbox-B_C8sOoT.js";
19
- import { _ as _15 } from "./chunks/LpCodeBlock-CXsqJwDy.js";
18
+ import { _ as _14 } from "./chunks/LpCheckbox-DV9W6T3H.js";
19
+ import { _ as _15 } from "./chunks/LpCodeBlock-t6MLA3fb.js";
20
20
  import { tokenizeLines } from "./components/codeHighlight.js";
21
- import { _ as _16 } from "./chunks/LpColorPicker-DQ88niXq.js";
21
+ import { _ as _16 } from "./chunks/LpColorPicker-CvBF8TeL.js";
22
22
  import { DEFAULT_SWATCHES, clampHsv, formatColor, hsvToHsl, hsvToRgb, isLight, normalizeHue, opaqueCss, parseColor, parseHex, rgbToHex, rgbToHsv } from "./components/color.js";
23
23
  import { default as default3 } from "./components/LpCommandPalette.vue.js";
24
24
  import { applyQueryFilters, highlightParts, parseCommandQuery, parsePaletteQuery, rankCommandItems } from "./components/commandSearch.js";
@@ -30,46 +30,46 @@ import { default as default6 } from "./components/LpPaletteStats.vue.js";
30
30
  import { default as default7 } from "./components/LpPaletteSwatches.vue.js";
31
31
  import { default as default8 } from "./components/LpKeys.vue.js";
32
32
  import { comboKeys, comboSteps } from "./components/keys.js";
33
- import { _ as _18 } from "./chunks/LpConfirmDialog-DxlSdjmk.js";
34
- import { _ as _19 } from "./chunks/LpConfirmHost-BcrmTNu_.js";
33
+ import { _ as _18 } from "./chunks/LpConfirmDialog-tkgyY3eu.js";
34
+ import { _ as _19 } from "./chunks/LpConfirmHost-CoYIcUdl.js";
35
35
  import { confirm, useConfirm } from "./components/confirm.js";
36
- import { _ as _20 } from "./chunks/LpContextMenu-B1NGfpp1.js";
37
- import { _ as _21 } from "./chunks/LpDatePicker-B-VMHMgy.js";
38
- import { _ as _22 } from "./chunks/LpDateRangePicker-B68KPKPl.js";
39
- import { _ as _23 } from "./chunks/LpRangeCalendar-D5sDID9W.js";
36
+ import { _ as _20 } from "./chunks/LpContextMenu-BnN1k7ga.js";
37
+ import { _ as _21 } from "./chunks/LpDatePicker-DxdrVNCG.js";
38
+ import { _ as _22 } from "./chunks/LpDateRangePicker-DjeaqikC.js";
39
+ import { _ as _23 } from "./chunks/LpRangeCalendar-COeOYF52.js";
40
40
  import { clampRange, defaultRangePresets, formatRange, normalizeRange, sameRange } from "./components/dateRange.js";
41
- import { _ as _24 } from "./chunks/LpDisclosure-Ct-VBHxt.js";
41
+ import { _ as _24 } from "./chunks/LpDisclosure-s8cQ7KQq.js";
42
42
  import { _ as _25 } from "./chunks/LpDivider-CUX46KR0.js";
43
- import { _ as _26 } from "./chunks/LpDrawer-DSknigxH.js";
44
- import { _ as _27 } from "./chunks/LpDropdownMenu-C7oHVlKY.js";
43
+ import { _ as _26 } from "./chunks/LpDrawer-C97yoWmh.js";
44
+ import { _ as _27 } from "./chunks/LpDropdownMenu-CbyYu4Rw.js";
45
45
  import { default as default9 } from "./components/LpDropzone.vue.js";
46
- import { _ as _28 } from "./chunks/LpEmptyState-CBwwkkdA.js";
47
- import { _ as _29 } from "./chunks/LpFileTree-CMA0_rsB.js";
46
+ import { _ as _28 } from "./chunks/LpEmptyState-CX8fdGSU.js";
47
+ import { _ as _29 } from "./chunks/LpFileTree-DEqh2ySW.js";
48
48
  import { ancestorIds, checkStateOf, checkedCount, computeStats, fileIcon, formatModified, formatSize, sortNodes, subtreeIds, subtreeSize } from "./components/fileTree.js";
49
- import { _ as _30 } from "./chunks/LpFieldNotice-9hH4ITZa.js";
50
- import { _ as _31 } from "./chunks/LpFormField-BpQ6WJPe.js";
51
- import { _ as _32 } from "./chunks/LpIcon-B5guGKrW.js";
52
- import { i, b, a, c } from "./chunks/icons-QqBscG5I.js";
49
+ import { _ as _30 } from "./chunks/LpFieldNotice-B--RzKfj.js";
50
+ import { _ as _31 } from "./chunks/LpFormField-DMPPL1jF.js";
51
+ import { _ as _32 } from "./chunks/LpIcon-CTuDVTYv.js";
52
+ import { i, b, a, c } from "./chunks/icons-CuO3W46H.js";
53
53
  import { default as default10 } from "./components/LpImageEditor.vue.js";
54
54
  import { _ as _33 } from "./chunks/LpInfraNode-Ds_8SZgN.js";
55
- import { _ as _34 } from "./chunks/LpInput-WvyP02ID.js";
56
- import { _ as _35 } from "./chunks/LpLightbox-Dq-e36uT.js";
55
+ import { _ as _34 } from "./chunks/LpInput-C7FHxzj2.js";
56
+ import { _ as _35 } from "./chunks/LpLightbox-DiksFa4L.js";
57
57
  import { fileNameOf } from "./components/lightbox.js";
58
58
  import { _ as _36 } from "./chunks/LpLink-Krkjhe_y.js";
59
- import { _ as _37 } from "./chunks/LpLogViewer-DwiYE32z.js";
59
+ import { _ as _37 } from "./chunks/LpLogViewer-BuPe23sZ.js";
60
60
  import { countLevels, matchPositions, matchesTerm, ringWindow, stepMatch } from "./components/logSearch.js";
61
- import { _ as _38 } from "./chunks/LpMediaGallery-B1kDypJ-.js";
62
- import { _ as _39 } from "./chunks/LpModal-BxYssnXg.js";
63
- import { _ as _40 } from "./chunks/LpModalNav-Bn2ws64b.js";
64
- import { _ as _41 } from "./chunks/LpNotificationBell-DJfs4JjQ.js";
65
- import { _ as _42 } from "./chunks/LpNumberField-CJPjmoDz.js";
66
- import { _ as _43 } from "./chunks/LpTimeField-DvGXgdES.js";
61
+ import { _ as _38 } from "./chunks/LpMediaGallery-Cic9UHsa.js";
62
+ import { _ as _39 } from "./chunks/LpModal-QFfctZsj.js";
63
+ import { _ as _40 } from "./chunks/LpModalNav-Cw7m-eks.js";
64
+ import { _ as _41 } from "./chunks/LpNotificationBell-xH_JphpS.js";
65
+ import { _ as _42 } from "./chunks/LpNumberField-BFSRVGkJ.js";
66
+ import { _ as _43 } from "./chunks/LpTimeField-BTEt0kvL.js";
67
67
  import { _ as _44 } from "./chunks/LpNumberFlow-z4WPZcZu.js";
68
68
  import { _ as _45 } from "./chunks/LpOtpInput-PbeNJ5In.js";
69
- import { _ as _46 } from "./chunks/LpPagination-DehAaJLY.js";
70
- import { _ as _47 } from "./chunks/LpPasswordInput-Bg5uTZbx.js";
71
- import { _ as _48 } from "./chunks/LpPie-DeTkbbOK.js";
72
- import { _ as _49 } from "./chunks/LpPhoneInput-DStwcbnQ.js";
69
+ import { _ as _46 } from "./chunks/LpPagination-BFl0I7tL.js";
70
+ import { _ as _47 } from "./chunks/LpPasswordInput-C1G4k_r0.js";
71
+ import { _ as _48 } from "./chunks/LpPie-BD5Lsh-2.js";
72
+ import { _ as _49 } from "./chunks/LpPhoneInput-CZrpVxla.js";
73
73
  import { filterCountries, flagEmoji, loadCountries, matchCountryByValue } from "./components/countries.js";
74
74
  import { _ as _50 } from "./chunks/LpPopover-DCKeqiVn.js";
75
75
  import { _ as _51 } from "./chunks/LpProgress-CBU1iI69.js";
@@ -77,38 +77,38 @@ import { _ as _52 } from "./chunks/LpRadio-CWA5Eb3M.js";
77
77
  import { _ as _53 } from "./chunks/LpRadioGroup-CCn-Q2_c.js";
78
78
  import { _ as _54 } from "./chunks/LpRollingText-DB7upf-5.js";
79
79
  import { _ as _55 } from "./chunks/LpScrollArea-Hz3-JWUG.js";
80
- import { _ as _56 } from "./chunks/LpSegmented-D3Eam8sa.js";
81
- import { _ as _57 } from "./chunks/LpSection-B19VPYmr.js";
82
- import { _ as _58 } from "./chunks/LpSelect-DynjWK1A.js";
80
+ import { _ as _56 } from "./chunks/LpSegmented-DslFku0N.js";
81
+ import { _ as _57 } from "./chunks/LpSection-BAkbf0OK.js";
82
+ import { _ as _58 } from "./chunks/LpSelect-C4OXN_q3.js";
83
83
  import { _ as _59 } from "./chunks/LpServiceNode-D8uJ8sGk.js";
84
84
  import { _ as _60 } from "./chunks/LpShift-DXNsauQZ.js";
85
85
  import { default as default11 } from "./components/LpShortcutsDialog.vue.js";
86
- import { _ as _61 } from "./chunks/LpSidebar-CshW0fqC.js";
86
+ import { _ as _61 } from "./chunks/LpSidebar-B3E_iViL.js";
87
87
  import { _ as _62 } from "./chunks/LpSkeleton-BB6XIV-w.js";
88
88
  import { _ as _63 } from "./chunks/LpSlider-Cuzmrd7p.js";
89
89
  import { _ as _64 } from "./chunks/LpRangeSlider-C1yF9-nP.js";
90
90
  import { _ as _65 } from "./chunks/LpVideoBackdrop-qWRYBOTQ.js";
91
91
  import { formatClipTime, isNativeLoop, planLoop, seamProgress, seamReached } from "./components/videoBackdrop.js";
92
92
  import { _ as _66 } from "./chunks/LpSparkline-CB80N8SJ.js";
93
- import { _ as _67 } from "./chunks/LpStat-BS2TEXYK.js";
94
- import { _ as _68 } from "./chunks/LpStepper-BSglWF45.js";
93
+ import { _ as _67 } from "./chunks/LpStat-oYk6V1LZ.js";
94
+ import { _ as _68 } from "./chunks/LpStepper-YgHGzgfD.js";
95
95
  import { _ as _69 } from "./chunks/LpSwitch-DG2AhXIL.js";
96
96
  import { default as default12 } from "./components/LpTable.vue.js";
97
97
  import { compareValues } from "./components/tableSort.js";
98
98
  import { _ as _70 } from "./chunks/LpTableOfContents-DYuqkByy.js";
99
- import { _ as _71 } from "./chunks/LpTabs-BBGRHY38.js";
100
- import { _ as _72 } from "./chunks/LpTagsInput-CHbQoAAA.js";
99
+ import { _ as _71 } from "./chunks/LpTabs-00v3VQx4.js";
100
+ import { _ as _72 } from "./chunks/LpTagsInput-Sc56iy_j.js";
101
101
  import { addTags, splitTags } from "./components/tags.js";
102
- import { _ as _73 } from "./chunks/LpTextarea-Bz2IWhTH.js";
103
- import { _ as _74 } from "./chunks/LpThemeSwitcher-B_qhSl7q.js";
102
+ import { _ as _73 } from "./chunks/LpTextarea-CQZccQI3.js";
103
+ import { _ as _74 } from "./chunks/LpThemeSwitcher-CeBG0e_D.js";
104
104
  import { _ as _75 } from "./chunks/LpTilt-B7KHX1zH.js";
105
- import { _ as _76 } from "./chunks/LpToaster-Bo9Q4QBP.js";
105
+ import { _ as _76 } from "./chunks/LpToaster-BcKs7vX7.js";
106
106
  import { _ as _77 } from "./chunks/LpTooltip-suFCM34F.js";
107
- import { _ as _78 } from "./chunks/LpTour-D7m9ZxNk.js";
107
+ import { _ as _78 } from "./chunks/LpTour-C29-1wha.js";
108
108
  import { holeOf, openContextMenu, panelsAround } from "./components/tour.js";
109
- import { _ as _79 } from "./chunks/LpHintLayer-B5-6FyEf.js";
110
- import { _ as _80 } from "./chunks/LpHints-BqoZXHZk.js";
111
- import { createHintSystem, hintKey, hintSummary, localHintStorage, provideHintSystem, useHintSystem } from "./composables/useHintSystem.js";
109
+ import { _ as _79 } from "./chunks/LpHintLayer-DsclGYcD.js";
110
+ import { _ as _80 } from "./chunks/LpHints-BTpvisgP.js";
111
+ import { HINT_SIGNALS, contextMenuTip, createHintSystem, hintKey, hintSummary, localHintStorage, paletteTip, provideHintSystem, runningHintSystem, useHintSystem } from "./composables/useHintSystem.js";
112
112
  import { _ as _81 } from "./chunks/LpTooltipProvider-CftjV7Xc.js";
113
113
  import { _ as _82 } from "./chunks/LpTopologyCanvas-6ozNrQKh.js";
114
114
  import { _ as _83 } from "./chunks/LpUptimeBar-B4ltCfno.js";
@@ -184,6 +184,7 @@ export {
184
184
  DEFAULT_SWATCHES,
185
185
  FIELD_NOTICE_MS,
186
186
  G as GLASS_BACKDROP_ATTR,
187
+ HINT_SIGNALS,
187
188
  LP_CONFIG,
188
189
  LP_FIELD,
189
190
  _ as LayoutCanvas,
@@ -314,6 +315,7 @@ export {
314
315
  compareValues,
315
316
  computeStats,
316
317
  confirm,
318
+ contextMenuTip,
317
319
  countBlocks,
318
320
  countLeaves,
319
321
  countLevels,
@@ -388,6 +390,7 @@ export {
388
390
  k as normalizeTheme,
389
391
  opaqueCss,
390
392
  openContextMenu,
393
+ paletteTip,
391
394
  panelsAround,
392
395
  parseColor,
393
396
  parseCommandQuery,
@@ -424,6 +427,7 @@ export {
424
427
  rgbToHsv,
425
428
  ringWindow,
426
429
  o as rose,
430
+ runningHintSystem,
427
431
  sameRange,
428
432
  scrollParent,
429
433
  scrollToElement,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.58.1",
3
+ "version": "0.60.0",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -23,7 +23,7 @@ defineProps<{
23
23
  }>()
24
24
 
25
25
  const ITEM =
26
- "group/item flex cursor-pointer select-none items-center gap-2 rounded-md px-2.5 py-1.5 text-sm outline-none transition-[background-color,color] duration-[var(--duration-fast)] data-[disabled]:pointer-events-none data-[disabled]:opacity-45 data-[highlighted]:bg-brand-soft"
26
+ "group/item flex cursor-pointer select-none items-center gap-2 rounded-inner px-2.5 py-1.5 text-sm outline-none transition-[background-color,color] duration-[var(--duration-fast)] data-[disabled]:pointer-events-none data-[disabled]:opacity-45 data-[highlighted]:bg-brand-soft"
27
27
  const ICON =
28
28
  "transition-transform duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] group-data-[highlighted]/item:translate-x-0.5"
29
29
  </script>
@@ -1116,7 +1116,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
1116
1116
  v-for="s in allSeries"
1117
1117
  :key="s.id"
1118
1118
  type="button"
1119
- class="group flex cursor-pointer items-center gap-1.5 rounded-md text-xs outline-none transition-opacity duration-fast focus-visible:ring-2 focus-visible:ring-ring"
1119
+ class="group flex cursor-pointer items-center gap-1.5 rounded-inner text-xs outline-none transition-opacity duration-fast focus-visible:ring-2 focus-visible:ring-ring"
1120
1120
  :class="hidden.has(s.id) ? 'opacity-40' : ''"
1121
1121
  :aria-pressed="!hidden.has(s.id)"
1122
1122
  @click="toggleSeries(s.id)"
@@ -236,14 +236,14 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
236
236
  class="flex items-center gap-2 border-b border-line bg-surface-soft px-3 py-1.5 text-muted"
237
237
  >
238
238
  <span v-if="title" class="truncate text-ink">{{ title }}</span>
239
- <span v-if="lang !== 'plain'" class="rounded bg-surface px-1.5 py-0.5 text-[10px] uppercase tracking-wide">
239
+ <span v-if="lang !== 'plain'" class="rounded-small bg-surface px-1.5 py-0.5 text-[10px] uppercase tracking-wide">
240
240
  {{ lang }}
241
241
  </span>
242
242
  <div class="ml-auto flex items-center gap-1">
243
243
  <button
244
244
  v-if="lockToggle"
245
245
  type="button"
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"
246
+ class="group/lock flex items-center gap-1 rounded-small px-1.5 py-1 outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
247
247
  :aria-label="locked ? text.unlock : text.lock"
248
248
  @click="toggleLock"
249
249
  >
@@ -256,7 +256,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
256
256
  <button
257
257
  v-if="copyable"
258
258
  type="button"
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"
259
+ class="group/copy flex items-center gap-1 rounded-small px-1.5 py-1 outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
260
260
  :aria-label="copied ? text.copied : text.copy"
261
261
  @click="copy"
262
262
  >
@@ -314,7 +314,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
314
314
  v-else
315
315
  ><button
316
316
  type="button"
317
- class="lp-fold-toggle flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
317
+ class="lp-fold-toggle flex w-full items-center gap-1.5 rounded-small px-1 py-0.5 text-left text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
318
318
  :aria-expanded="openState[seg.id]"
319
319
  @click="toggleFold(seg.id)"
320
320
  ><LpIcon
@@ -654,11 +654,11 @@ defineExpose({ open })
654
654
  would be painted over the checkerboard and, being translucent
655
655
  white, would show it through as a see-through outline. -->
656
656
  <span
657
- class="relative size-5 shrink-0 overflow-hidden rounded-md"
657
+ class="relative size-5 shrink-0 overflow-hidden rounded-inner"
658
658
  :style="{ background: CHECKERBOARD }"
659
659
  >
660
660
  <span class="block size-full" :style="{ backgroundColor: previewCss }" />
661
- <span class="pointer-events-none absolute inset-0 rounded-md ring-1 ring-inset ring-line" />
661
+ <span class="pointer-events-none absolute inset-0 rounded-inner ring-1 ring-inset ring-line" />
662
662
  </span>
663
663
  <span class="min-w-0 flex-1 truncate font-mono text-xs text-muted-strong">
664
664
  {{ label ?? current }}
@@ -814,7 +814,7 @@ defineExpose({ open })
814
814
  :disabled="disabled"
815
815
  :aria-label="c"
816
816
  :aria-pressed="isActiveSwatch(c)"
817
- class="relative flex items-center justify-center overflow-hidden rounded-md outline-none transition-transform duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] hover:scale-110 active:scale-95 focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed motion-reduce:hover:scale-100 motion-reduce:active:scale-95"
817
+ class="relative flex items-center justify-center overflow-hidden rounded-inner outline-none transition-transform duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] hover:scale-110 active:scale-95 focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed motion-reduce:hover:scale-100 motion-reduce:active:scale-95"
818
818
  :class="row.size"
819
819
  :style="{ background: CHECKERBOARD }"
820
820
  @click="pickSwatch(c)"
@@ -826,7 +826,7 @@ defineExpose({ open })
826
826
  inset ring painted OVER the colour instead, so it reads
827
827
  as one fixed hairline rather than a translucent gap. -->
828
828
  <span class="absolute inset-0" :style="{ backgroundColor: c }" />
829
- <span class="pointer-events-none absolute inset-0 rounded-md ring-1 ring-inset ring-line" />
829
+ <span class="pointer-events-none absolute inset-0 rounded-inner ring-1 ring-inset ring-line" />
830
830
  <LpIcon
831
831
  v-if="isActiveSwatch(c)"
832
832
  name="lucide:check"
@@ -44,6 +44,8 @@ import { useAsyncTask } from "../composables/useAsyncTask"
44
44
  import { useGlass } from "../composables/useGlass"
45
45
  import { useClipboard } from "../composables/useClipboard"
46
46
  import { useHotkeys } from "../composables/useHotkeys"
47
+ import { hints } from "../composables/useHints"
48
+ import { HINT_SIGNALS, runningHintSystem } from "../composables/useHintSystem"
47
49
  import { useModalLayer } from "./modalDepth"
48
50
  import { useLabels } from "../composables/useLpConfig"
49
51
  import {
@@ -516,9 +518,20 @@ function onTab(event: KeyboardEvent) {
516
518
  event.stopPropagation()
517
519
  const actionsMode: Command = { id: `${c.id}:actions`, label: c.label, icon: c.icon, children: actions }
518
520
  syntheticModes.add(actionsMode)
521
+ hints.use(HINT_SIGNALS.paletteActions)
519
522
  enter(actionsMode)
520
523
  }
521
524
 
525
+ // A returning user who never pressed Tab gets the footer key lit up, nothing
526
+ // more: the palette is the one place they already came to on purpose.
527
+ const PALETTE_ACTIONS_AFTER = 3
528
+ const showActionsKey = computed(() => {
529
+ const system = runningHintSystem()
530
+ return !!system?.state.loaded
531
+ && system.count(HINT_SIGNALS.palette) >= PALETTE_ACTIONS_AFTER
532
+ && system.count(HINT_SIGNALS.paletteActions) === 0
533
+ })
534
+
522
535
  /*
523
536
  * A mode holds the command it was entered through, and a static `children`
524
537
  * list is a snapshot of that moment. When the app rebuilds its commands (a
@@ -1162,8 +1175,10 @@ function onEscape(event: KeyboardEvent) {
1162
1175
  watch(
1163
1176
  () => props.open,
1164
1177
  async (o) => {
1165
- if (o) claim()
1166
- else release()
1178
+ if (o) {
1179
+ claim()
1180
+ hints.use(HINT_SIGNALS.palette)
1181
+ } else release()
1167
1182
  cancel()
1168
1183
  query.value = ""
1169
1184
  select(0)
@@ -1239,7 +1254,7 @@ useHotkeys(() =>
1239
1254
  v-for="(step, i) in trail"
1240
1255
  :key="step.id"
1241
1256
  type="button"
1242
- class="flex shrink-0 items-center gap-1.5 rounded-md bg-brand-soft px-2 py-1 text-xs font-medium text-brand transition-opacity hover:opacity-80"
1257
+ class="flex shrink-0 items-center gap-1.5 rounded-inner bg-brand-soft px-2 py-1 text-xs font-medium text-brand transition-opacity hover:opacity-80"
1243
1258
  :title="text.back"
1244
1259
  @click="popTo(i)"
1245
1260
  >
@@ -1264,7 +1279,7 @@ useHotkeys(() =>
1264
1279
  @keydown.esc="onEscape"
1265
1280
  @keydown.tab="onTab"
1266
1281
  />
1267
- <kbd class="shrink-0 rounded border border-line px-1.5 py-0.5 text-[10px] text-muted">{{ text.esc }}</kbd>
1282
+ <kbd class="shrink-0 rounded-small border border-line px-1.5 py-0.5 text-[10px] text-muted">{{ text.esc }}</kbd>
1268
1283
  </div>
1269
1284
 
1270
1285
  <!-- Results -->
@@ -1305,7 +1320,7 @@ useHotkeys(() =>
1305
1320
  :aria-disabled="c.disabled || undefined"
1306
1321
  :data-cmd-index="flatIndexOf(c)"
1307
1322
  :disabled="c.href ? undefined : c.disabled"
1308
- class="lp-cmd-item flex w-full gap-3 rounded-md px-2.5 py-2 text-left text-sm outline-none transition-colors duration-[var(--duration-fast)] disabled:pointer-events-none disabled:opacity-45 aria-disabled:pointer-events-none aria-disabled:opacity-45"
1323
+ class="lp-cmd-item flex w-full gap-3 rounded-inner px-2.5 py-2 text-left text-sm outline-none transition-colors duration-[var(--duration-fast)] disabled:pointer-events-none disabled:opacity-45 aria-disabled:pointer-events-none aria-disabled:opacity-45"
1309
1324
  :class="[
1310
1325
  c.excerpt ? 'items-start' : 'items-center',
1311
1326
  flatIndexOf(c) === selectedIndex
@@ -1365,7 +1380,7 @@ useHotkeys(() =>
1365
1380
  shortcut is learned by seeing it rather than reading docs. -->
1366
1381
  <kbd
1367
1382
  v-else-if="c.prefix && c.children"
1368
- class="shrink-0 rounded border border-line px-1.5 font-mono text-[11px] whitespace-pre text-muted"
1383
+ class="shrink-0 rounded-small border border-line px-1.5 font-mono text-[11px] whitespace-pre text-muted"
1369
1384
  >{{ c.prefix }}</kbd>
1370
1385
  <!-- Drills in rather than runs: worth saying before the click,
1371
1386
  since the two look identical otherwise. -->
@@ -1390,7 +1405,7 @@ useHotkeys(() =>
1390
1405
  <span>{{ text.searchFailed }}</span>
1391
1406
  <button
1392
1407
  type="button"
1393
- class="rounded-md px-1.5 py-0.5 font-medium text-brand outline-none transition-colors hover:bg-brand-soft focus-visible:ring-2 focus-visible:ring-ring"
1408
+ class="rounded-inner px-1.5 py-0.5 font-medium text-brand outline-none transition-colors hover:bg-brand-soft focus-visible:ring-2 focus-visible:ring-ring"
1394
1409
  @click="refresh(); inputRef?.focus()"
1395
1410
  >
1396
1411
  {{ text.retry }}
@@ -1451,26 +1466,33 @@ useHotkeys(() =>
1451
1466
  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"
1452
1467
  >
1453
1468
  <span class="flex items-center gap-1">
1454
- <kbd class="rounded border border-line px-1 text-[10px]">↑</kbd>
1455
- <kbd class="rounded border border-line px-1 text-[10px]">↓</kbd>
1469
+ <kbd class="rounded-small border border-line px-1 text-[10px]">↑</kbd>
1470
+ <kbd class="rounded-small border border-line px-1 text-[10px]">↓</kbd>
1456
1471
  {{ text.navigate }}
1457
1472
  </span>
1458
1473
  <span class="flex items-center gap-1">
1459
- <kbd class="rounded border border-line px-1 text-[10px]">↵</kbd>
1474
+ <kbd class="rounded-small border border-line px-1 text-[10px]">↵</kbd>
1460
1475
  {{ text.select }}
1461
1476
  </span>
1462
1477
  <!-- Only inside a mode: at the root Backspace just edits the query,
1463
1478
  and advertising it there would be a lie. -->
1464
- <span v-if="selectedActions.length" class="flex items-center gap-1">
1465
- <kbd class="rounded border border-line px-1 text-[10px]">tab</kbd>
1479
+ <span
1480
+ v-if="selectedActions.length"
1481
+ class="flex items-center gap-1 transition-colors"
1482
+ :class="showActionsKey && 'font-medium text-brand'"
1483
+ >
1484
+ <kbd
1485
+ class="rounded-small border px-1 text-[10px] transition-colors"
1486
+ :class="showActionsKey ? 'border-brand' : 'border-line'"
1487
+ >tab</kbd>
1466
1488
  {{ text.actions }}
1467
1489
  </span>
1468
1490
  <span v-if="trail.length" class="flex items-center gap-1">
1469
- <kbd class="rounded border border-line px-1 text-[10px]">⌫</kbd>
1491
+ <kbd class="rounded-small border border-line px-1 text-[10px]">⌫</kbd>
1470
1492
  {{ text.back }}
1471
1493
  </span>
1472
1494
  <span class="ml-auto flex items-center gap-1">
1473
- <kbd class="rounded border border-line px-1 text-[10px]">{{ text.esc }}</kbd>
1495
+ <kbd class="rounded-small border border-line px-1 text-[10px]">{{ text.esc }}</kbd>
1474
1496
  {{ text.close }}
1475
1497
  </span>
1476
1498
  </div>