@leavepulse/ui 0.45.1 → 0.47.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 (125) hide show
  1. package/dist/assets/HighlightText-BwC5xbqn.css +6 -0
  2. package/dist/assets/{LpCommandPalette-HzAA4gri.css → LpCommandPalette-DmQUsJqA.css} +21 -21
  3. package/dist/assets/{LpDropzone-i4BdMi1H.css → LpDropzone-Ev6X03zj.css} +12 -12
  4. package/dist/assets/LpKeys-BxRhfQda.css +36 -0
  5. package/dist/assets/LpPaletteFacts-B9FdVmIO.css +93 -0
  6. package/dist/assets/LpPaletteHeader-BVOu1UX5.css +70 -0
  7. package/dist/assets/LpPalettePreview-CJmAKgYW.css +43 -0
  8. package/dist/assets/LpPaletteStats-ClE-37MK.css +33 -0
  9. package/dist/assets/LpPaletteSwatches-CA0B8y2E.css +21 -0
  10. package/dist/assets/{LpShortcutsDialog-CqYX3yy6.css → LpShortcutsDialog-BUp_BX4s.css} +7 -28
  11. package/dist/assets/{LpTable-CPwWxTJS.css → LpTable-xWwI4KPZ.css} +17 -17
  12. package/dist/chunks/HighlightText-BvnYZkhi.js +27 -0
  13. package/dist/chunks/{LpAddressInput-CJOYGZlI.js → LpAddressInput-iOJhmRzy.js} +1 -1
  14. package/dist/chunks/{LpAppShell-B07Lf48e.js → LpAppShell-BmMYcEVx.js} +2 -2
  15. package/dist/chunks/{LpAvatar-C1XStdOo.js → LpAvatar-Cb1jtOVv.js} +22 -4
  16. package/dist/chunks/{LpCard-C2IvucmX.js → LpCard-BdxkMXjo.js} +1 -1
  17. package/dist/chunks/{LpColorPicker-zz4VGkG4.js → LpColorPicker-BLqPX5Vw.js} +1 -1
  18. package/dist/chunks/{LpConfirmDialog-BTVAR4tF.js → LpConfirmDialog-Ca-CTlxT.js} +1 -1
  19. package/dist/chunks/{LpConfirmHost-Ci_uCHgQ.js → LpConfirmHost-BAaSaT2b.js} +1 -1
  20. package/dist/chunks/{LpContextMenu-BqkAYa9X.js → LpContextMenu-1cKew_Dc.js} +17 -4
  21. package/dist/chunks/{LpDrawer-CfRhHwmE.js → LpDrawer-CBFVD624.js} +1 -1
  22. package/dist/chunks/{LpDropdownMenu-DdJGQzPn.js → LpDropdownMenu-DrpJJBp0.js} +22 -8
  23. package/dist/chunks/{LpFileTree-CNurOvkV.js → LpFileTree-bk5VjBey.js} +2 -2
  24. package/dist/chunks/{LpFileTreeNode-D_qRRdsH.js → LpFileTreeNode-BvMSBM7X.js} +1 -1
  25. package/dist/chunks/{LpLightbox-DSDahF23.js → LpLightbox-EVXzAq62.js} +3 -3
  26. package/dist/chunks/{LpLogViewer-KypbgOyo.js → LpLogViewer-BDFXzvcl.js} +2 -2
  27. package/dist/chunks/{LpMediaGallery-YKr-cQPN.js → LpMediaGallery-BnlkN_1f.js} +1 -1
  28. package/dist/chunks/{LpModal-BzTVeNqe.js → LpModal-JNa3B_Lr.js} +12 -5
  29. package/dist/chunks/{LpModalNav-CjABSGCu.js → LpModalNav-BAxC9et7.js} +10 -3
  30. package/dist/chunks/{LpNotificationBell-CCwyJP5C.js → LpNotificationBell-DsqSyMwS.js} +2 -2
  31. package/dist/chunks/LpPalettePreview-CFav_7co.js +19 -0
  32. package/dist/chunks/{LpPie-BEJ2SfI6.js → LpPie-BdSnta11.js} +1 -1
  33. package/dist/chunks/{LpScrollArea-Bry1fdOM.js → LpScrollArea-Hz3-JWUG.js} +23 -5
  34. package/dist/chunks/{LpSidebar-Bwws_X29.js → LpSidebar-DpEiGi8L.js} +2 -2
  35. package/dist/chunks/{LpSidebarNav-kx-LdqNU.js → LpSidebarNav-BUuxipX-.js} +2 -2
  36. package/dist/chunks/{LpThemeSwitcher-B4aVPbNC.js → LpThemeSwitcher-BbYFFT5w.js} +1 -1
  37. package/dist/chunks/MenuTitle-J3ZDYjpV.js +56 -0
  38. package/dist/component-names.d.ts +1 -1
  39. package/dist/components/HighlightText.vue.d.ts +7 -0
  40. package/dist/components/HighlightText.vue.js +4 -0
  41. package/dist/components/LpAddressInput.vue.js +1 -1
  42. package/dist/components/LpAppShell.vue.js +1 -1
  43. package/dist/components/LpAvatar.vue.d.ts +12 -0
  44. package/dist/components/LpAvatar.vue.js +1 -1
  45. package/dist/components/LpCard.vue.js +1 -1
  46. package/dist/components/LpChart.vue.js +1 -1
  47. package/dist/components/LpColorPicker.vue.js +1 -1
  48. package/dist/components/LpCommandPalette.vue.d.ts +152 -15
  49. package/dist/components/LpCommandPalette.vue.js +181 -92
  50. package/dist/components/LpConfirmDialog.vue.js +1 -1
  51. package/dist/components/LpConfirmHost.vue.js +1 -1
  52. package/dist/components/LpContextMenu.vue.d.ts +11 -0
  53. package/dist/components/LpContextMenu.vue.js +1 -1
  54. package/dist/components/LpDrawer.vue.js +1 -1
  55. package/dist/components/LpDropdownMenu.vue.d.ts +6 -0
  56. package/dist/components/LpDropdownMenu.vue.js +1 -1
  57. package/dist/components/LpDropzone.vue.d.ts +14 -0
  58. package/dist/components/LpDropzone.vue.js +9 -7
  59. package/dist/components/LpFileTree.vue.js +1 -1
  60. package/dist/components/LpFileTreeNode.vue.js +1 -1
  61. package/dist/components/LpKeys.vue.d.ts +22 -0
  62. package/dist/components/LpKeys.vue.js +52 -0
  63. package/dist/components/LpLightbox.vue.js +1 -1
  64. package/dist/components/LpLogViewer.vue.js +1 -1
  65. package/dist/components/LpMediaGallery.vue.js +1 -1
  66. package/dist/components/LpModal.vue.d.ts +7 -5
  67. package/dist/components/LpModal.vue.js +1 -1
  68. package/dist/components/LpModalNav.vue.d.ts +5 -3
  69. package/dist/components/LpModalNav.vue.js +1 -1
  70. package/dist/components/LpNotificationBell.vue.js +1 -1
  71. package/dist/components/LpPaletteFacts.vue.d.ts +25 -0
  72. package/dist/components/LpPaletteFacts.vue.js +85 -0
  73. package/dist/components/LpPaletteHeader.vue.d.ts +32 -0
  74. package/dist/components/LpPaletteHeader.vue.js +103 -0
  75. package/dist/components/LpPalettePreview.vue.d.ts +19 -0
  76. package/dist/components/LpPalettePreview.vue.js +4 -0
  77. package/dist/components/LpPaletteStats.vue.d.ts +11 -0
  78. package/dist/components/LpPaletteStats.vue.js +47 -0
  79. package/dist/components/LpPaletteSwatches.vue.d.ts +10 -0
  80. package/dist/components/LpPaletteSwatches.vue.js +34 -0
  81. package/dist/components/LpPie.vue.js +1 -1
  82. package/dist/components/LpScrollArea.vue.d.ts +2 -2
  83. package/dist/components/LpScrollArea.vue.js +1 -1
  84. package/dist/components/LpShortcutsDialog.vue.js +9 -29
  85. package/dist/components/LpSidebar.vue.js +1 -1
  86. package/dist/components/LpSidebarNav.vue.js +1 -1
  87. package/dist/components/LpTable.vue.js +8 -6
  88. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  89. package/dist/components/MenuTitle.vue.d.ts +9 -0
  90. package/dist/components/MenuTitle.vue.js +4 -0
  91. package/dist/components/commandPalette.d.ts +157 -0
  92. package/dist/components/commandPalette.js +103 -0
  93. package/dist/components/commandSearch.d.ts +32 -0
  94. package/dist/components/commandSearch.js +48 -0
  95. package/dist/components/keys.d.ts +2 -0
  96. package/dist/components/keys.js +11 -0
  97. package/dist/composables/useClipboard.js +35 -2
  98. package/dist/index.d.ts +12 -3
  99. package/dist/index.js +161 -138
  100. package/package.json +1 -1
  101. package/src/component-names.ts +6 -0
  102. package/src/components/HighlightText.vue +28 -0
  103. package/src/components/LpAvatar.vue +21 -2
  104. package/src/components/LpCommandPalette.vue +392 -86
  105. package/src/components/LpContextMenu.vue +19 -1
  106. package/src/components/LpDropdownMenu.vue +14 -2
  107. package/src/components/LpDropzone.vue +13 -5
  108. package/src/components/LpKeys.vue +79 -0
  109. package/src/components/LpLightbox.vue +4 -1
  110. package/src/components/LpModal.vue +7 -1
  111. package/src/components/LpModalNav.vue +4 -0
  112. package/src/components/LpPaletteFacts.vue +165 -0
  113. package/src/components/LpPaletteHeader.vue +155 -0
  114. package/src/components/LpPalettePreview.vue +77 -0
  115. package/src/components/LpPaletteStats.vue +81 -0
  116. package/src/components/LpPaletteSwatches.vue +56 -0
  117. package/src/components/LpScrollArea.vue +30 -2
  118. package/src/components/LpShortcutsDialog.vue +2 -45
  119. package/src/components/LpTable.vue +7 -2
  120. package/src/components/MenuTitle.vue +34 -0
  121. package/src/components/commandPalette.ts +264 -0
  122. package/src/components/commandSearch.ts +79 -0
  123. package/src/components/keys.ts +13 -0
  124. package/src/composables/useClipboard.ts +49 -7
  125. package/src/index.ts +12 -3
@@ -1,16 +1,18 @@
1
- import { defineComponent, ref, useId, computed, shallowRef, watch, useSlots, nextTick, onBeforeUnmount, openBlock, createBlock, unref, withCtx, createVNode, normalizeStyle, normalizeClass, withModifiers, createTextVNode, toDisplayString, createElementVNode, createElementBlock, Fragment, renderList, createCommentVNode, withDirectives, withKeys, vModelText, resolveDynamicComponent, Transition, renderSlot } from "vue";
1
+ import { defineComponent, ref, useId, computed, shallowRef, watch, useSlots, onBeforeUnmount, nextTick, openBlock, createBlock, unref, withCtx, createVNode, normalizeStyle, normalizeClass, withModifiers, createTextVNode, toDisplayString, createElementVNode, createElementBlock, Fragment, renderList, createCommentVNode, withDirectives, withKeys, vModelText, resolveDynamicComponent, Transition, renderSlot } from "vue";
2
2
  import { DialogRoot, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription } from "reka-ui";
3
+ import { _ as _sfc_main$3 } from "../chunks/LpAvatar-Cb1jtOVv.js";
3
4
  import { _ as _sfc_main$1 } from "../chunks/LpIcon-DYtaid0K.js";
4
- import { _ as _sfc_main$2 } from "../chunks/LpScrollArea-Bry1fdOM.js";
5
+ import { _ as _sfc_main$2 } from "../chunks/LpScrollArea-Hz3-JWUG.js";
5
6
  import { useAsyncTask } from "../composables/useAsyncTask.js";
6
7
  import { useGlass } from "../composables/useGlass.js";
7
8
  import { useClipboard } from "../composables/useClipboard.js";
8
9
  import { useHotkeys } from "../composables/useHotkeys.js";
9
10
  import { useModalLayer } from "./modalDepth.js";
10
11
  import { useLabels } from "../composables/useLpConfig.js";
11
- import { parseCommandQuery, rankCommandItems } from "./commandSearch.js";
12
+ import { parseCommandQuery, rankCommandItems, applyQueryFilters, parsePaletteQuery, highlightParts } from "./commandSearch.js";
13
+ import { fallbackRows, readRecent, resolveRecentItems, recentVisible, commandHasPreview, matchModePrefix, modeChipLabel, toRecentItem, pushRecent } from "./commandPalette.js";
12
14
  import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
13
- import '../assets/LpCommandPalette-HzAA4gri.css';const _hoisted_1 = { class: "flex shrink-0 items-center gap-2.5 border-b border-line px-4" };
15
+ import '../assets/LpCommandPalette-DmQUsJqA.css';const _hoisted_1 = { class: "flex shrink-0 items-center gap-2.5 border-b border-line px-4" };
14
16
  const _hoisted_2 = ["title", "onClick"];
15
17
  const _hoisted_3 = ["aria-activedescendant", "placeholder", "onKeydown"];
16
18
  const _hoisted_4 = { class: "shrink-0 rounded border border-line px-1.5 py-0.5 text-[10px] text-muted" };
@@ -39,37 +41,42 @@ const _hoisted_13 = {
39
41
  class: "rounded-xs bg-brand/20 text-ink"
40
42
  };
41
43
  const _hoisted_14 = {
42
- key: 1,
44
+ key: 2,
43
45
  class: "shrink-0 text-xs text-muted"
44
46
  };
45
47
  const _hoisted_15 = {
48
+ key: 3,
49
+ class: "shrink-0 rounded border border-line px-1.5 font-mono text-[11px] whitespace-pre text-muted"
50
+ };
51
+ const _hoisted_16 = {
46
52
  key: 0,
47
53
  class: "lp-cmd-pane hidden min-h-0 shrink-0 flex-col border-l border-line md:flex"
48
54
  };
49
- const _hoisted_16 = { class: "lp-cmd-para flex items-center gap-2 text-ink" };
50
- const _hoisted_17 = { class: "min-w-0 text-base font-semibold" };
51
- const _hoisted_18 = {
55
+ const _hoisted_17 = { class: "lp-cmd-para flex items-center gap-2 text-ink" };
56
+ const _hoisted_18 = { class: "min-w-0 text-base font-semibold" };
57
+ const _hoisted_19 = {
52
58
  key: 0,
53
59
  class: "lp-cmd-para mt-0.5 text-xs text-muted"
54
60
  };
55
- const _hoisted_19 = {
61
+ const _hoisted_20 = {
56
62
  key: 0,
57
63
  class: "rounded-xs bg-brand/25 text-ink"
58
64
  };
59
- const _hoisted_20 = { 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" };
60
- const _hoisted_21 = { class: "flex items-center gap-1" };
65
+ const _hoisted_21 = { 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" };
61
66
  const _hoisted_22 = { class: "flex items-center gap-1" };
62
- const _hoisted_23 = {
67
+ const _hoisted_23 = { class: "flex items-center gap-1" };
68
+ const _hoisted_24 = {
63
69
  key: 0,
64
70
  class: "flex items-center gap-1"
65
71
  };
66
- const _hoisted_24 = {
72
+ const _hoisted_25 = {
67
73
  key: 1,
68
74
  class: "flex items-center gap-1"
69
75
  };
70
- const _hoisted_25 = { class: "ml-auto flex items-center gap-1" };
71
- const _hoisted_26 = { class: "rounded border border-line px-1 text-[10px]" };
76
+ const _hoisted_26 = { class: "ml-auto flex items-center gap-1" };
77
+ const _hoisted_27 = { class: "rounded border border-line px-1 text-[10px]" };
72
78
  const RECENT_KEEP = 20;
79
+ const PANE_LINGER_MS = 160;
73
80
  const STAGGER_MS = 45;
74
81
  const STAGGER_MAX = 10;
75
82
  const _sfc_main = /* @__PURE__ */ defineComponent({
@@ -85,6 +92,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
85
92
  minChars: { default: 1 },
86
93
  recent: {},
87
94
  recentKey: {},
95
+ resolveRecent: {},
88
96
  recentLimit: { default: 5 },
89
97
  recentLabel: { default: "Recent" },
90
98
  resultsLabel: { default: "Results" },
@@ -92,9 +100,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
92
100
  excerptLines: { default: 4 },
93
101
  groupLimit: { default: 0 },
94
102
  previewPane: { type: Boolean, default: false },
103
+ hasPreview: {},
95
104
  glass: { type: Boolean, default: void 0 }
96
105
  },
97
- emits: ["update:open", "select", "run"],
106
+ emits: ["update:open", "select", "run", "remember"],
98
107
  setup(__props, { emit: __emit }) {
99
108
  const props = __props;
100
109
  const frosted = useGlass(() => props.glass);
@@ -165,7 +174,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
165
174
  if (!c || !actions.length) return;
166
175
  event.preventDefault();
167
176
  event.stopPropagation();
168
- enter({ id: `${c.id}:actions`, label: c.label, icon: c.icon, children: actions });
177
+ const actionsMode = { id: `${c.id}:actions`, label: c.label, icon: c.icon, children: actions };
178
+ syntheticModes.add(actionsMode);
179
+ enter(actionsMode);
169
180
  }
170
181
  watch(
171
182
  () => props.commands,
@@ -184,8 +195,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
184
195
  if (last && Array.isArray(last.children)) modeItems.value = last.children;
185
196
  }
186
197
  );
187
- function enter(c) {
188
- trailState.value = [...trailState.value, { query: query.value, selectedId: selectedId.value }];
198
+ function enter(c, parentQuery = query.value) {
199
+ trailState.value = [...trailState.value, { query: parentQuery, selectedId: selectedId.value }];
189
200
  trail.value = [...trail.value, c];
190
201
  query.value = "";
191
202
  select(0);
@@ -234,10 +245,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
234
245
  failed.value = false;
235
246
  const run2 = fetcher();
236
247
  if (!run2) return;
237
- const q = query.value.trim();
248
+ const parsed = parsePaletteQuery(query.value.trim());
249
+ const q = parsed.text;
238
250
  const inMode = mode.value !== null;
239
251
  if (!inMode && q.length < Math.max(1, props.minChars)) return;
240
- task.run((signal) => run2(q, signal), {
252
+ task.run((signal) => run2(q, signal, parsed), {
241
253
  delay: props.debounce,
242
254
  apply: (hits) => found.value = hits,
243
255
  fail: () => {
@@ -249,7 +261,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
249
261
  const source = computed(() => mode.value ? modeItems.value : props.commands);
250
262
  const local = computed(() => {
251
263
  const terms = parseCommandQuery(query.value);
252
- const filtered = query.value.trim() ? source.value : source.value.filter((c) => !c.hidden);
264
+ const filtered = query.value.trim() ? source.value.filter((c) => !c.fallback) : source.value.filter((c) => !c.hidden && !c.fallback);
253
265
  if (!terms.include.length && !terms.exclude.length && !terms.groups.length) return { items: filtered, terms: [] };
254
266
  const items = rankCommandItems(filtered, terms);
255
267
  return { items, terms: terms.include };
@@ -258,39 +270,63 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
258
270
  const fetched = computed(() => {
259
271
  if (!found.value.length) return [];
260
272
  const seen = new Set(ranked.value.map((c) => c.id));
261
- return found.value.filter((c) => !seen.has(c.id)).map((c) => c.group ? c : { ...c, group: props.resultsLabel });
273
+ const rows = found.value.filter((c) => !seen.has(c.id)).map((c) => c.group ? c : { ...c, group: props.resultsLabel });
274
+ return applyQueryFilters(rows, parseCommandQuery(query.value));
262
275
  });
276
+ const fallbackOf = /* @__PURE__ */ new WeakMap();
277
+ const fallbacks = computed(
278
+ () => fallbackRows(source.value, query.value).map(({ command, label }) => {
279
+ const row = { ...command, label };
280
+ fallbackOf.set(row, command);
281
+ return row;
282
+ })
283
+ );
263
284
  const ownRecent = ref([]);
264
285
  watch(
265
286
  () => props.recentKey,
266
287
  (key) => {
267
288
  if (!key || typeof localStorage === "undefined") return;
268
289
  try {
269
- const stored = JSON.parse(localStorage.getItem(key) ?? "[]");
270
- ownRecent.value = Array.isArray(stored) ? stored.filter((v) => typeof v === "string") : [];
290
+ ownRecent.value = readRecent(JSON.parse(localStorage.getItem(key) ?? "[]"));
271
291
  } catch {
272
292
  ownRecent.value = [];
273
293
  }
274
294
  },
275
295
  { immediate: true }
276
296
  );
277
- function remember(id) {
297
+ const recentPath = /* @__PURE__ */ new WeakMap();
298
+ const syntheticModes = /* @__PURE__ */ new WeakSet();
299
+ function remember(c) {
300
+ if (c.remember === false || trail.value.some((step) => syntheticModes.has(step))) return;
301
+ const path = recentPath.get(c) ?? trail.value.map((step) => step.id);
302
+ const item = toRecentItem(c, path);
303
+ emit("remember", item, c);
278
304
  const key = props.recentKey;
279
305
  if (!key || props.recent) return;
280
- ownRecent.value = [id, ...ownRecent.value.filter((v) => v !== id)].slice(0, RECENT_KEEP);
306
+ ownRecent.value = pushRecent(ownRecent.value, item, RECENT_KEEP);
281
307
  try {
282
308
  localStorage.setItem(key, JSON.stringify(ownRecent.value));
283
309
  } catch {
284
310
  }
285
311
  }
286
- const recentIds = computed(() => props.recent ?? (props.recentKey ? ownRecent.value : []));
312
+ const recentList = computed(() => props.recent ?? (props.recentKey ? ownRecent.value : []));
313
+ function fromEntry(entry) {
314
+ return { id: entry.id, label: entry.label, description: entry.description, icon: entry.icon, image: entry.image, href: entry.href };
315
+ }
287
316
  const recentItems = computed(() => {
288
- if (mode.value || query.value.trim() || !recentIds.value.length) return [];
289
- const byId = new Map(props.commands.map((c) => [c.id, c]));
317
+ if (mode.value || !recentList.value.length) return [];
318
+ const terms = parseCommandQuery(query.value);
319
+ if (terms.include.length) return [];
320
+ const found2 = resolveRecentItems(recentList.value, props.commands, {
321
+ limit: Number.POSITIVE_INFINITY,
322
+ resolve: props.resolveRecent,
323
+ fromEntry
324
+ });
290
325
  const out = [];
291
- for (const id of recentIds.value) {
292
- const c = byId.get(id);
293
- if (c && !c.disabled && !out.includes(c)) out.push(c);
326
+ for (const { command, path } of found2) {
327
+ if (out.includes(command) || !recentVisible(command, terms, props.recentLabel)) continue;
328
+ recentPath.set(command, path);
329
+ out.push(command);
294
330
  if (out.length >= props.recentLimit) break;
295
331
  }
296
332
  return out;
@@ -309,10 +345,21 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
309
345
  for (const c of recent) push(c, props.recentLabel);
310
346
  for (const c of ranked.value) if (!recent.includes(c)) push(c, c.group ?? "");
311
347
  for (const c of fetched.value) push(c, c.group ?? "");
348
+ const tail = /* @__PURE__ */ new Map();
349
+ for (const c of fallbacks.value) {
350
+ const group = c.group ?? "";
351
+ if (map.has(group)) map.get(group).push(c);
352
+ else tail.set(group, [...tail.get(group) ?? [], c]);
353
+ }
354
+ for (const [group, items] of tail) {
355
+ map.set(group, items);
356
+ order.push(group);
357
+ }
312
358
  return order.map((name) => ({ name, items: folded(name, map.get(name)) }));
313
359
  });
314
360
  const expanded = ref(/* @__PURE__ */ new Set());
315
361
  const moreOf = /* @__PURE__ */ new WeakMap();
362
+ const firstHidden = /* @__PURE__ */ new WeakMap();
316
363
  function folded(name, items) {
317
364
  const limit = props.groupLimit;
318
365
  if (!limit || items.length <= limit + 1 || expanded.value.has(name)) return items;
@@ -323,6 +370,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
323
370
  keepOpen: true
324
371
  };
325
372
  moreOf.set(more, name);
373
+ firstHidden.set(more, items[limit].id);
326
374
  return [...items.slice(0, limit), more];
327
375
  }
328
376
  const flat = computed(() => grouped.value.flatMap((g) => g.items));
@@ -332,20 +380,45 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
332
380
  return c && !moreOf.has(c) ? c : void 0;
333
381
  });
334
382
  const previewRef = ref(null);
335
- const paneOpen = computed(
336
- () => props.previewPane && !!active.value && (!!slots.preview || !!(active.value.preview || active.value.excerpt))
383
+ const previewable = computed(
384
+ () => props.previewPane && commandHasPreview(active.value, { slot: !!slots.preview, hasPreview: props.hasPreview })
385
+ );
386
+ const paneCommand = shallowRef();
387
+ let lingerTimer;
388
+ watch(
389
+ [active, previewable],
390
+ ([command, show]) => {
391
+ clearTimeout(lingerTimer);
392
+ if (show) {
393
+ paneCommand.value = command;
394
+ return;
395
+ }
396
+ if (!paneCommand.value) return;
397
+ lingerTimer = setTimeout(() => paneCommand.value = void 0, PANE_LINGER_MS);
398
+ },
399
+ { immediate: true }
337
400
  );
401
+ onBeforeUnmount(() => clearTimeout(lingerTimer));
402
+ const paneOpen = computed(() => props.previewPane && !!paneCommand.value);
403
+ function paneHighlight(text2) {
404
+ return highlight(text2, paneBody.value?.highlight);
405
+ }
406
+ const paneQuery = computed(() => parsePaletteQuery(query.value.trim()));
407
+ const paneBody = shallowRef();
408
+ watch(paneCommand, (command) => {
409
+ if (command) paneBody.value = command;
410
+ });
338
411
  const paragraphs = computed(
339
- () => (active.value?.preview ?? active.value?.excerpt ?? "").split(/\n{2,}/).map((part) => part.trim()).filter(Boolean)
412
+ () => (paneBody.value?.preview ?? paneBody.value?.excerpt ?? "").split(/\n{2,}/).map((part) => part.trim()).filter(Boolean)
340
413
  );
341
414
  const paneDescription = computed(() => {
342
- const description = active.value?.description?.trim();
415
+ const description = paneBody.value?.description?.trim();
343
416
  if (!description) return void 0;
344
417
  const body = paragraphs.value.join(" ");
345
418
  return body.startsWith(description) ? void 0 : description;
346
419
  });
347
420
  watch(
348
- () => active.value?.id,
421
+ () => paneCommand.value?.id,
349
422
  async () => {
350
423
  if (!paneOpen.value) return;
351
424
  await nextTick();
@@ -361,37 +434,26 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
361
434
  return flatIndex.value.get(c) ?? -1;
362
435
  }
363
436
  function highlight(value, extra = []) {
364
- const terms = [...new Set([...local.value.terms, ...extra.map((t) => t.toLowerCase())].filter((t) => t.length >= 2))].sort(
365
- (a, b) => b.length - a.length
366
- );
367
- if (!value || terms.length === 0) return value ? [{ text: value, hit: false }] : [];
368
- const lower = value.toLowerCase();
369
- const parts = [];
370
- let i = 0;
371
- while (i < value.length) {
372
- let at = -1;
373
- let term = "";
374
- for (const t of terms) {
375
- const found2 = lower.indexOf(t, i);
376
- if (found2 !== -1 && (at === -1 || found2 < at || found2 === at && t.length > term.length)) {
377
- at = found2;
378
- term = t;
379
- }
380
- }
381
- if (at === -1) {
382
- parts.push({ text: value.slice(i), hit: false });
383
- break;
384
- }
385
- if (at > i) parts.push({ text: value.slice(i, at), hit: false });
386
- parts.push({ text: value.slice(at, at + term.length), hit: true });
387
- i = at + term.length;
388
- }
389
- return parts;
437
+ return highlightParts(value, [...local.value.terms, ...extra]);
390
438
  }
391
- watch(query, () => {
439
+ const IMAGE_RADIUS = { circle: "rounded-pill", soft: "rounded-[6px]", square: "rounded-[3px]" };
440
+ function imageOf(c) {
441
+ if (!c.image) return null;
442
+ const image = typeof c.image === "string" ? { src: c.image } : c.image;
443
+ return { ...image, icon: image.icon ?? c.icon };
444
+ }
445
+ watch(query, (value) => {
446
+ if (enterByPrefix(value)) return;
392
447
  select(0);
393
448
  refresh();
394
449
  });
450
+ function enterByPrefix(value) {
451
+ const hit = matchModePrefix(value, source.value);
452
+ if (!hit) return false;
453
+ enter(hit.command, "");
454
+ query.value = hit.rest;
455
+ return true;
456
+ }
395
457
  watch([query, trail, () => props.open], () => {
396
458
  expanded.value = /* @__PURE__ */ new Set();
397
459
  });
@@ -461,6 +523,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
461
523
  if (!c.href) return;
462
524
  window.open(c.href, "_blank", "noopener");
463
525
  emit("run", c.id, c);
526
+ remember(c);
464
527
  emit("update:open", false);
465
528
  }
466
529
  function onRowClick(event, c) {
@@ -468,10 +531,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
468
531
  event.preventDefault();
469
532
  run(c);
470
533
  }
471
- function run(c) {
472
- if (!c || c.disabled) return;
473
- const group = moreOf.get(c);
534
+ function run(row) {
535
+ if (!row || row.disabled) return;
536
+ const c = fallbackOf.get(row) ?? row;
537
+ const group = moreOf.get(row);
474
538
  if (group !== void 0) {
539
+ selectedId.value = firstHidden.get(row) ?? null;
475
540
  expanded.value = /* @__PURE__ */ new Set([...expanded.value, group]);
476
541
  inputRef.value?.focus();
477
542
  return;
@@ -480,11 +545,13 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
480
545
  c.onSelect?.();
481
546
  if (!c.onSelect && c.href && !c.children) window.location.assign(c.href);
482
547
  if (c.children) {
548
+ const carried = query.value;
483
549
  enter(c);
550
+ if (c.carryQuery) query.value = carried;
484
551
  return;
485
552
  }
486
553
  emit("run", c.id, c);
487
- if (!mode.value) remember(c.id);
554
+ remember(c);
488
555
  if (c.keepOpen) {
489
556
  query.value = "";
490
557
  if (mode.value) void nextTick(() => select(Math.max(0, flat.value.findIndex((x) => x.id === c.id))));
@@ -599,7 +666,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
599
666
  name: step.icon,
600
667
  size: 13
601
668
  }, null, 8, ["name"])) : createCommentVNode("", true),
602
- createTextVNode(" " + toDisplayString(step.label) + " ", 1),
669
+ createTextVNode(" " + toDisplayString(unref(modeChipLabel)(step)) + " ", 1),
603
670
  createVNode(_sfc_main$1, {
604
671
  name: "lucide:x",
605
672
  size: 12,
@@ -677,8 +744,17 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
677
744
  onPointerenter: ($event) => onPointerEnter(c)
678
745
  }, {
679
746
  default: withCtx(() => [
680
- c.icon ? (openBlock(), createBlock(_sfc_main$1, {
747
+ imageOf(c) ? (openBlock(), createBlock(_sfc_main$3, {
681
748
  key: 0,
749
+ src: imageOf(c).src ?? void 0,
750
+ alt: imageOf(c).alt ?? "",
751
+ fallback: imageOf(c).fallback,
752
+ icon: imageOf(c).icon,
753
+ shape: imageOf(c).shape ?? "soft",
754
+ "pixel-size": 20,
755
+ class: normalizeClass(["-my-0.5 -ml-0.5 shrink-0 text-[9px]", IMAGE_RADIUS[imageOf(c).shape ?? "soft"]])
756
+ }, null, 8, ["src", "alt", "fallback", "icon", "shape", "class"])) : c.icon ? (openBlock(), createBlock(_sfc_main$1, {
757
+ key: 1,
682
758
  name: c.icon,
683
759
  size: 16,
684
760
  class: normalizeClass(["shrink-0", { "mt-0.5": c.excerpt }])
@@ -722,8 +798,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
722
798
  }), 128))
723
799
  ], 4)) : createCommentVNode("", true)
724
800
  ]),
725
- c.shortcut ? (openBlock(), createElementBlock("kbd", _hoisted_14, toDisplayString(c.shortcut), 1)) : c.children ? (openBlock(), createBlock(_sfc_main$1, {
726
- key: 2,
801
+ c.shortcut ? (openBlock(), createElementBlock("kbd", _hoisted_14, toDisplayString(c.shortcut), 1)) : c.prefix && c.children ? (openBlock(), createElementBlock("kbd", _hoisted_15, toDisplayString(c.prefix), 1)) : c.children ? (openBlock(), createBlock(_sfc_main$1, {
802
+ key: 4,
727
803
  name: "lucide:chevron-right",
728
804
  size: 14,
729
805
  class: "shrink-0 text-muted"
@@ -760,7 +836,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
760
836
  }, 512),
761
837
  createVNode(Transition, { name: "lp-cmd-pane" }, {
762
838
  default: withCtx(() => [
763
- paneOpen.value ? (openBlock(), createElementBlock("aside", _hoisted_15, [
839
+ paneOpen.value ? (openBlock(), createElementBlock("aside", _hoisted_16, [
764
840
  createVNode(_sfc_main$2, {
765
841
  ref_key: "previewRef",
766
842
  ref: previewRef,
@@ -768,31 +844,44 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
768
844
  "content-class": "px-5 py-4"
769
845
  }, {
770
846
  default: withCtx(() => [
771
- active.value ? (openBlock(), createElementBlock("div", {
772
- key: active.value.id
847
+ paneBody.value ? (openBlock(), createElementBlock("div", {
848
+ key: paneBody.value.id
773
849
  }, [
774
- renderSlot(_ctx.$slots, "preview", { command: active.value }, () => [
775
- createElementVNode("div", _hoisted_16, [
776
- active.value.icon ? (openBlock(), createBlock(_sfc_main$1, {
850
+ renderSlot(_ctx.$slots, "preview", {
851
+ command: paneBody.value,
852
+ highlight: paneHighlight,
853
+ query: paneQuery.value
854
+ }, () => [
855
+ createElementVNode("div", _hoisted_17, [
856
+ imageOf(paneBody.value) ? (openBlock(), createBlock(_sfc_main$3, {
777
857
  key: 0,
778
- name: active.value.icon,
858
+ src: imageOf(paneBody.value).src ?? void 0,
859
+ alt: imageOf(paneBody.value).alt ?? "",
860
+ fallback: imageOf(paneBody.value).fallback,
861
+ icon: imageOf(paneBody.value).icon,
862
+ shape: imageOf(paneBody.value).shape ?? "soft",
863
+ "pixel-size": 28,
864
+ class: normalizeClass(["shrink-0", IMAGE_RADIUS[imageOf(paneBody.value).shape ?? "soft"]])
865
+ }, null, 8, ["src", "alt", "fallback", "icon", "shape", "class"])) : paneBody.value.icon ? (openBlock(), createBlock(_sfc_main$1, {
866
+ key: 1,
867
+ name: paneBody.value.icon,
779
868
  size: 16,
780
869
  class: "shrink-0 text-muted"
781
870
  }, null, 8, ["name"])) : createCommentVNode("", true),
782
- createElementVNode("h3", _hoisted_17, toDisplayString(active.value.label), 1)
871
+ createElementVNode("h3", _hoisted_18, toDisplayString(paneBody.value.label), 1)
783
872
  ]),
784
- paneDescription.value ? (openBlock(), createElementBlock("p", _hoisted_18, toDisplayString(paneDescription.value), 1)) : createCommentVNode("", true),
873
+ paneDescription.value ? (openBlock(), createElementBlock("p", _hoisted_19, toDisplayString(paneDescription.value), 1)) : createCommentVNode("", true),
785
874
  (openBlock(true), createElementBlock(Fragment, null, renderList(paragraphs.value, (para, i) => {
786
875
  return openBlock(), createElementBlock("p", {
787
876
  key: i,
788
877
  class: "lp-cmd-para mt-3 whitespace-pre-line text-sm leading-relaxed text-ink/90",
789
878
  style: normalizeStyle({ animationDelay: `${Math.min(i + 1, STAGGER_MAX) * STAGGER_MS}ms` })
790
879
  }, [
791
- (openBlock(true), createElementBlock(Fragment, null, renderList(highlight(para, active.value.highlight), (p, pi) => {
880
+ (openBlock(true), createElementBlock(Fragment, null, renderList(highlight(para, paneBody.value.highlight), (p, pi) => {
792
881
  return openBlock(), createElementBlock(Fragment, {
793
882
  key: `p${pi}`
794
883
  }, [
795
- p.hit ? (openBlock(), createElementBlock("mark", _hoisted_19, toDisplayString(p.text), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
884
+ p.hit ? (openBlock(), createElementBlock("mark", _hoisted_20, toDisplayString(p.text), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
796
885
  createTextVNode(toDisplayString(p.text), 1)
797
886
  ], 64))
798
887
  ], 64);
@@ -809,26 +898,26 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
809
898
  _: 3
810
899
  })
811
900
  ], 32),
812
- createElementVNode("div", _hoisted_20, [
813
- createElementVNode("span", _hoisted_21, [
901
+ createElementVNode("div", _hoisted_21, [
902
+ createElementVNode("span", _hoisted_22, [
814
903
  _cache[4] || (_cache[4] = createElementVNode("kbd", { class: "rounded border border-line px-1 text-[10px]" }, "↑", -1)),
815
904
  _cache[5] || (_cache[5] = createElementVNode("kbd", { class: "rounded border border-line px-1 text-[10px]" }, "↓", -1)),
816
905
  createTextVNode(" " + toDisplayString(unref(text).navigate), 1)
817
906
  ]),
818
- createElementVNode("span", _hoisted_22, [
907
+ createElementVNode("span", _hoisted_23, [
819
908
  _cache[6] || (_cache[6] = createElementVNode("kbd", { class: "rounded border border-line px-1 text-[10px]" }, "↵", -1)),
820
909
  createTextVNode(" " + toDisplayString(unref(text).select), 1)
821
910
  ]),
822
- selectedActions.value.length ? (openBlock(), createElementBlock("span", _hoisted_23, [
911
+ selectedActions.value.length ? (openBlock(), createElementBlock("span", _hoisted_24, [
823
912
  _cache[7] || (_cache[7] = createElementVNode("kbd", { class: "rounded border border-line px-1 text-[10px]" }, "tab", -1)),
824
913
  createTextVNode(" " + toDisplayString(unref(text).actions), 1)
825
914
  ])) : createCommentVNode("", true),
826
- trail.value.length ? (openBlock(), createElementBlock("span", _hoisted_24, [
915
+ trail.value.length ? (openBlock(), createElementBlock("span", _hoisted_25, [
827
916
  _cache[8] || (_cache[8] = createElementVNode("kbd", { class: "rounded border border-line px-1 text-[10px]" }, "⌫", -1)),
828
917
  createTextVNode(" " + toDisplayString(unref(text).back), 1)
829
918
  ])) : createCommentVNode("", true),
830
- createElementVNode("span", _hoisted_25, [
831
- createElementVNode("kbd", _hoisted_26, toDisplayString(unref(text).esc), 1),
919
+ createElementVNode("span", _hoisted_26, [
920
+ createElementVNode("kbd", _hoisted_27, toDisplayString(unref(text).esc), 1),
832
921
  createTextVNode(" " + toDisplayString(unref(text).close), 1)
833
922
  ])
834
923
  ])
@@ -844,7 +933,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
844
933
  };
845
934
  }
846
935
  });
847
- const LpCommandPalette = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-bb9a3f4c"]]);
936
+ const LpCommandPalette = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-6da7fe88"]]);
848
937
  export {
849
938
  LpCommandPalette as default
850
939
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpConfirmDialog-BTVAR4tF.js";
1
+ import { _ as _sfc_main } from "../chunks/LpConfirmDialog-Ca-CTlxT.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpConfirmHost-Ci_uCHgQ.js";
1
+ import { _ as _sfc_main } from "../chunks/LpConfirmHost-BAaSaT2b.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -26,6 +26,17 @@ type __VLS_Props = {
26
26
  * native-menu mode on that first click and swallow it.
27
27
  */
28
28
  always?: boolean;
29
+ /**
30
+ * What the menu acts on, shown as a heading above the items: the row's name
31
+ * in a list of similar rows, so the user sees which one the right-click hit.
32
+ * Also names the menu for assistive tech.
33
+ */
34
+ title?: string;
35
+ /** A muted second line under `title`, e.g. a version or a path. */
36
+ subtitle?: string;
37
+ /** An icon beside the title, or `titleImage` for a picture (an avatar, a logo). */
38
+ titleIcon?: string;
39
+ titleImage?: string;
29
40
  };
30
41
  declare var __VLS_16: {};
31
42
  type __VLS_Slots = {} & {
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpContextMenu-BqkAYa9X.js";
1
+ import { _ as _sfc_main } from "../chunks/LpContextMenu-1cKew_Dc.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpDrawer-CfRhHwmE.js";
1
+ import { _ as _sfc_main } from "../chunks/LpDrawer-CBFVD624.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -3,10 +3,16 @@ export interface MenuItem {
3
3
  icon?: string;
4
4
  danger?: boolean;
5
5
  separatorBefore?: boolean;
6
+ disabled?: boolean;
6
7
  onSelect?: () => void;
7
8
  }
8
9
  type __VLS_Props = {
9
10
  items: MenuItem[];
11
+ /** A heading naming what the menu acts on; see LpContextMenu's `title`. */
12
+ title?: string;
13
+ subtitle?: string;
14
+ titleIcon?: string;
15
+ titleImage?: string;
10
16
  };
11
17
  declare var __VLS_14: {};
12
18
  type __VLS_Slots = {} & {
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpDropdownMenu-DdJGQzPn.js";
1
+ import { _ as _sfc_main } from "../chunks/LpDropdownMenu-DrpJJBp0.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -6,6 +6,13 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
6
6
  /** Take more than one at a time. */
7
7
  multiple?: boolean;
8
8
  disabled?: boolean;
9
+ /**
10
+ * A drag the page itself cannot see is over the zone. Desktop shells
11
+ * (Tauri, Electron) often take file drags natively, and the page then gets
12
+ * no dragenter or dragover at all, only the shell's own events; the app
13
+ * hit-tests those and passes the answer here so the zone lights up the same.
14
+ */
15
+ dragOver?: boolean;
9
16
  /**
10
17
  * Also accept a paste anywhere on the page, not only while focused.
11
18
  * Off by default: two zones both listening would each take the same
@@ -42,6 +49,13 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
42
49
  /** Take more than one at a time. */
43
50
  multiple?: boolean;
44
51
  disabled?: boolean;
52
+ /**
53
+ * A drag the page itself cannot see is over the zone. Desktop shells
54
+ * (Tauri, Electron) often take file drags natively, and the page then gets
55
+ * no dragenter or dragover at all, only the shell's own events; the app
56
+ * hit-tests those and passes the answer here so the zone lights up the same.
57
+ */
58
+ dragOver?: boolean;
45
59
  /**
46
60
  * Also accept a paste anywhere on the page, not only while focused.
47
61
  * Off by default: two zones both listening would each take the same