@leavepulse/ui 0.39.1 → 0.40.1
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.
- package/dist/assets/LpCommandPalette-DDGFwOom.css +85 -0
- package/dist/components/LpCommandPalette.vue.d.ts +11 -8
- package/dist/components/LpCommandPalette.vue.js +81 -70
- package/package.json +1 -1
- package/src/components/LpCommandPalette.vue +139 -44
- package/dist/assets/LpCommandPalette-DKL4O4JP.css +0 -21
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
* Sizes for the split layout. Only the dialog's width animates: the list and
|
|
4
|
+
* the pane keep fixed widths, and the dialog — overflow hidden — uncovers the
|
|
5
|
+
* pane from its right edge as it grows. Animating the columns themselves made
|
|
6
|
+
* every line of the list and the pane re-wrap on every frame.
|
|
7
|
+
*/
|
|
8
|
+
.lp-cmd-dialog[data-v-27071a60] {
|
|
9
|
+
--lp-cmd-list: min(92vw, 40rem);
|
|
10
|
+
--lp-cmd-pane: max(0px, min(26rem, 96vw - var(--lp-cmd-list)));
|
|
11
|
+
width: var(--lp-cmd-list);
|
|
12
|
+
}
|
|
13
|
+
.lp-cmd-split[data-v-27071a60] {
|
|
14
|
+
transition:
|
|
15
|
+
width 300ms var(--ease-emphasized, ease),
|
|
16
|
+
min-height 300ms var(--ease-emphasized, ease);
|
|
17
|
+
}
|
|
18
|
+
@media (min-width: 768px) {
|
|
19
|
+
.lp-cmd-split .lp-cmd-list[data-v-27071a60] {
|
|
20
|
+
flex: 0 0 auto;
|
|
21
|
+
/* the dialog's 1px border on each side */
|
|
22
|
+
width: calc(var(--lp-cmd-list) - 2px);
|
|
23
|
+
}
|
|
24
|
+
.lp-cmd-dialog[data-pane="open"][data-v-27071a60] {
|
|
25
|
+
width: calc(var(--lp-cmd-list) + var(--lp-cmd-pane));
|
|
26
|
+
min-height: min(76vh, 36rem);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
.lp-cmd-pane[data-v-27071a60] {
|
|
30
|
+
width: var(--lp-cmd-pane);
|
|
31
|
+
}
|
|
32
|
+
/*
|
|
33
|
+
* The pane rides in from the right edge while the dialog opens up to it, and
|
|
34
|
+
* leaves the same way, for as long as the dialog takes to close over it.
|
|
35
|
+
*/
|
|
36
|
+
.lp-cmd-pane-enter-active[data-v-27071a60],
|
|
37
|
+
.lp-cmd-pane-leave-active[data-v-27071a60] {
|
|
38
|
+
transition:
|
|
39
|
+
transform 300ms var(--ease-emphasized, ease),
|
|
40
|
+
opacity 300ms ease;
|
|
41
|
+
}
|
|
42
|
+
.lp-cmd-pane-enter-from[data-v-27071a60],
|
|
43
|
+
.lp-cmd-pane-leave-to[data-v-27071a60] {
|
|
44
|
+
opacity: 0;
|
|
45
|
+
transform: translateX(40%);
|
|
46
|
+
}
|
|
47
|
+
.lp-cmd-para[data-v-27071a60] {
|
|
48
|
+
animation: lp-cmd-para-in-27071a60 260ms ease-out both;
|
|
49
|
+
}
|
|
50
|
+
@keyframes lp-cmd-para-in-27071a60 {
|
|
51
|
+
from {
|
|
52
|
+
opacity: 0;
|
|
53
|
+
transform: translateY(4px);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
@media (prefers-reduced-motion: reduce) {
|
|
57
|
+
.lp-cmd-split[data-v-27071a60],
|
|
58
|
+
.lp-cmd-pane-enter-active[data-v-27071a60],
|
|
59
|
+
.lp-cmd-pane-leave-active[data-v-27071a60] {
|
|
60
|
+
transition: none;
|
|
61
|
+
}
|
|
62
|
+
.lp-cmd-para[data-v-27071a60] {
|
|
63
|
+
animation: none;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
.lp-cmd-excerpt[data-v-27071a60] {
|
|
67
|
+
display: -webkit-box;
|
|
68
|
+
-webkit-box-orient: vertical;
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
}
|
|
71
|
+
/* Each result eases in; honours reduced motion. */
|
|
72
|
+
.lp-cmd-item[data-v-27071a60] {
|
|
73
|
+
animation: lp-cmd-in-27071a60 140ms ease-out both;
|
|
74
|
+
}
|
|
75
|
+
@keyframes lp-cmd-in-27071a60 {
|
|
76
|
+
from {
|
|
77
|
+
opacity: 0;
|
|
78
|
+
transform: translateY(3px);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
@media (prefers-reduced-motion: reduce) {
|
|
82
|
+
.lp-cmd-item[data-v-27071a60] {
|
|
83
|
+
animation: none;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -90,8 +90,11 @@ export interface PaletteLabels {
|
|
|
90
90
|
copyLink: string;
|
|
91
91
|
/** Toast after "copy link"; empty for none. */
|
|
92
92
|
linkCopied: string;
|
|
93
|
-
/**
|
|
94
|
-
|
|
93
|
+
/**
|
|
94
|
+
* @deprecated The pane now closes for a row with nothing to show, so this
|
|
95
|
+
* text is never displayed. Kept so existing `labels` objects still type-check.
|
|
96
|
+
*/
|
|
97
|
+
noPreview?: string;
|
|
95
98
|
}
|
|
96
99
|
type __VLS_Props = {
|
|
97
100
|
open?: boolean;
|
|
@@ -146,14 +149,14 @@ type __VLS_Props = {
|
|
|
146
149
|
* A pane beside the list showing the highlighted result in full — its
|
|
147
150
|
* `preview` text, or the `preview` slot. For palettes that search
|
|
148
151
|
* content rather than commands: the excerpt says why a row matched, the
|
|
149
|
-
* pane lets it be read without leaving the page.
|
|
150
|
-
*
|
|
152
|
+
* pane lets it be read without leaving the page. It opens only for a row
|
|
153
|
+
* with `preview` or `excerpt` (any row, with the slot): the dialog eases
|
|
154
|
+
* wider, the pane slides in, and the text arrives paragraph by paragraph.
|
|
151
155
|
* Hidden below the md breakpoint, where there is no room beside the list.
|
|
152
156
|
*
|
|
153
|
-
* The
|
|
154
|
-
* an `md:` override
|
|
155
|
-
* order where the base
|
|
156
|
-
* Both widths are 92vw on a narrow screen, so no breakpoint is needed.
|
|
157
|
+
* The width is an inline style rather than a pair of classes: a base
|
|
158
|
+
* width plus an `md:` override could be emitted by a consumer's Tailwind
|
|
159
|
+
* build in an order where the base won, and the pane opened squeezed.
|
|
157
160
|
*/
|
|
158
161
|
previewPane?: boolean;
|
|
159
162
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent, computed, ref, useId, shallowRef, watch, nextTick, openBlock, createBlock, unref, withCtx, createVNode, normalizeClass, withModifiers, createTextVNode, createElementVNode, createElementBlock, Fragment, renderList, createCommentVNode, toDisplayString, withDirectives, withKeys, vModelText, resolveDynamicComponent, normalizeStyle, renderSlot } from "vue";
|
|
1
|
+
import { defineComponent, computed, ref, useId, shallowRef, useSlots, watch, nextTick, openBlock, createBlock, unref, withCtx, createVNode, normalizeClass, withModifiers, createTextVNode, createElementVNode, createElementBlock, Fragment, renderList, createCommentVNode, toDisplayString, withDirectives, withKeys, vModelText, resolveDynamicComponent, normalizeStyle, Transition, renderSlot } from "vue";
|
|
2
2
|
import { DialogRoot, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription } from "reka-ui";
|
|
3
3
|
import { _ as _sfc_main$1 } from "../chunks/LpIcon-CCnX5_2j.js";
|
|
4
4
|
import { _ as _sfc_main$2 } from "../chunks/LpScrollArea-D8xyKXcF.js";
|
|
@@ -6,7 +6,7 @@ import { useAsyncTask } from "../composables/useAsyncTask.js";
|
|
|
6
6
|
import { useClipboard } from "../composables/useClipboard.js";
|
|
7
7
|
import { useHotkeys } from "../composables/useHotkeys.js";
|
|
8
8
|
import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
|
|
9
|
-
import '../assets/LpCommandPalette-
|
|
9
|
+
import '../assets/LpCommandPalette-DDGFwOom.css';const _hoisted_1 = { class: "flex shrink-0 items-center gap-2.5 border-b border-line px-4" };
|
|
10
10
|
const _hoisted_2 = ["title", "onClick"];
|
|
11
11
|
const _hoisted_3 = ["aria-activedescendant", "placeholder", "onKeydown"];
|
|
12
12
|
const _hoisted_4 = { class: "flex min-h-0 flex-1" };
|
|
@@ -40,41 +40,35 @@ const _hoisted_14 = {
|
|
|
40
40
|
};
|
|
41
41
|
const _hoisted_15 = {
|
|
42
42
|
key: 0,
|
|
43
|
-
class: "hidden min-h-0
|
|
43
|
+
class: "lp-cmd-pane hidden min-h-0 shrink-0 flex-col border-l border-line md:flex"
|
|
44
44
|
};
|
|
45
|
-
const _hoisted_16 = { class: "flex items-center gap-2 text-ink" };
|
|
45
|
+
const _hoisted_16 = { class: "lp-cmd-para flex items-center gap-2 text-ink" };
|
|
46
46
|
const _hoisted_17 = { class: "min-w-0 text-base font-semibold" };
|
|
47
47
|
const _hoisted_18 = {
|
|
48
48
|
key: 0,
|
|
49
|
-
class: "mt-0.5 text-xs text-muted"
|
|
49
|
+
class: "lp-cmd-para mt-0.5 text-xs text-muted"
|
|
50
50
|
};
|
|
51
51
|
const _hoisted_19 = {
|
|
52
|
-
key: 1,
|
|
53
|
-
class: "mt-3 whitespace-pre-line text-sm leading-relaxed text-ink/90"
|
|
54
|
-
};
|
|
55
|
-
const _hoisted_20 = {
|
|
56
52
|
key: 0,
|
|
57
53
|
class: "rounded-xs bg-brand/25 text-ink"
|
|
58
54
|
};
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const _hoisted_22 = { class: "flex shrink-0 items-center gap-4 border-t border-line px-4 py-2.5 text-[11px] text-muted" };
|
|
64
|
-
const _hoisted_23 = { class: "flex items-center gap-1" };
|
|
65
|
-
const _hoisted_24 = { class: "flex items-center gap-1" };
|
|
66
|
-
const _hoisted_25 = {
|
|
55
|
+
const _hoisted_20 = { class: "flex shrink-0 items-center gap-4 border-t border-line px-4 py-2.5 text-[11px] text-muted" };
|
|
56
|
+
const _hoisted_21 = { class: "flex items-center gap-1" };
|
|
57
|
+
const _hoisted_22 = { class: "flex items-center gap-1" };
|
|
58
|
+
const _hoisted_23 = {
|
|
67
59
|
key: 0,
|
|
68
60
|
class: "flex items-center gap-1"
|
|
69
61
|
};
|
|
70
|
-
const
|
|
62
|
+
const _hoisted_24 = {
|
|
71
63
|
key: 1,
|
|
72
64
|
class: "flex items-center gap-1"
|
|
73
65
|
};
|
|
74
|
-
const
|
|
66
|
+
const _hoisted_25 = { class: "ml-auto flex items-center gap-1" };
|
|
75
67
|
const LATIN = "qwertyuiop[]asdfghjkl;'zxcvbnm,.`";
|
|
76
68
|
const CYRILLIC = "йцукенгшщзхъфывапролджэячсмитьбюё";
|
|
77
69
|
const TYPO_MIN = 5;
|
|
70
|
+
const STAGGER_MS = 45;
|
|
71
|
+
const STAGGER_MAX = 10;
|
|
78
72
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
79
73
|
__name: "LpCommandPalette",
|
|
80
74
|
props: {
|
|
@@ -312,15 +306,22 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
312
306
|
return [...items.slice(0, limit), more];
|
|
313
307
|
}
|
|
314
308
|
const flat = computed(() => grouped.value.flatMap((g) => g.items));
|
|
309
|
+
const slots = useSlots();
|
|
315
310
|
const active = computed(() => {
|
|
316
311
|
const c = flat.value[selectedIndex.value];
|
|
317
312
|
return c && !moreOf.has(c) ? c : void 0;
|
|
318
313
|
});
|
|
319
314
|
const previewRef = ref(null);
|
|
315
|
+
const paneOpen = computed(
|
|
316
|
+
() => props.previewPane && !!active.value && (!!slots.preview || !!(active.value.preview || active.value.excerpt))
|
|
317
|
+
);
|
|
318
|
+
const paragraphs = computed(
|
|
319
|
+
() => (active.value?.preview ?? active.value?.excerpt ?? "").split(/\n{2,}/).map((part) => part.trim()).filter(Boolean)
|
|
320
|
+
);
|
|
320
321
|
watch(
|
|
321
322
|
() => active.value?.id,
|
|
322
323
|
async () => {
|
|
323
|
-
if (!
|
|
324
|
+
if (!paneOpen.value) return;
|
|
324
325
|
await nextTick();
|
|
325
326
|
const viewport = previewRef.value?.viewportEl;
|
|
326
327
|
if (!viewport) return;
|
|
@@ -484,10 +485,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
484
485
|
class: normalizeClass([{ "lp-scrim--light": __props.glass }, "lp-scrim fixed inset-0 z-(--z-overlay) data-[state=open]:animate-[fade-in_150ms_ease] data-[state=closed]:animate-[fade-out_130ms_ease]"])
|
|
485
486
|
}, null, 8, ["class"]),
|
|
486
487
|
createVNode(unref(DialogContent), {
|
|
487
|
-
class: normalizeClass(["fixed left-1/2 top-[12vh] z-(--z-modal) flex max-h-[76vh] -translate-x-1/2 flex-col overflow-hidden rounded-card border border-line shadow-panel outline-none data-[state=open]:animate-[pop-in_160ms_var(--ease-emphasized)] data-[state=closed]:animate-[pop-out_130ms_ease]", [
|
|
488
|
-
|
|
489
|
-
__props.previewPane ? "w-[min(92vw,64rem)] md:h-[min(76vh,36rem)]" : "w-[min(92vw,40rem)]"
|
|
490
|
-
]]),
|
|
488
|
+
class: normalizeClass(["lp-cmd-dialog fixed left-1/2 top-[12vh] z-(--z-modal) flex max-h-[76vh] -translate-x-1/2 flex-col overflow-hidden rounded-card border border-line shadow-panel outline-none data-[state=open]:animate-[pop-in_160ms_var(--ease-emphasized)] data-[state=closed]:animate-[pop-out_130ms_ease]", [__props.glass ? "lp-glass-overlay" : "bg-surface-raised", __props.previewPane && "lp-cmd-split"]]),
|
|
489
|
+
"data-pane": paneOpen.value ? "open" : void 0,
|
|
491
490
|
onOpenAutoFocus: _cache[2] || (_cache[2] = withModifiers(() => {
|
|
492
491
|
}, ["prevent"]))
|
|
493
492
|
}, {
|
|
@@ -566,7 +565,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
566
565
|
createVNode(_sfc_main$2, {
|
|
567
566
|
ref_key: "scrollRef",
|
|
568
567
|
ref: scrollRef,
|
|
569
|
-
class: "min-h-0 min-w-0 flex-1",
|
|
568
|
+
class: "lp-cmd-list min-h-0 min-w-0 flex-1",
|
|
570
569
|
"content-class": "p-2"
|
|
571
570
|
}, {
|
|
572
571
|
default: withCtx(() => [
|
|
@@ -687,71 +686,83 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
687
686
|
]),
|
|
688
687
|
_: 1
|
|
689
688
|
}, 512),
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
689
|
+
createVNode(Transition, { name: "lp-cmd-pane" }, {
|
|
690
|
+
default: withCtx(() => [
|
|
691
|
+
paneOpen.value ? (openBlock(), createElementBlock("aside", _hoisted_15, [
|
|
692
|
+
createVNode(_sfc_main$2, {
|
|
693
|
+
ref_key: "previewRef",
|
|
694
|
+
ref: previewRef,
|
|
695
|
+
class: "min-h-0 flex-1",
|
|
696
|
+
"content-class": "px-5 py-4"
|
|
697
|
+
}, {
|
|
698
|
+
default: withCtx(() => [
|
|
699
|
+
active.value ? (openBlock(), createElementBlock("div", {
|
|
700
|
+
key: active.value.id
|
|
701
|
+
}, [
|
|
702
|
+
renderSlot(_ctx.$slots, "preview", { command: active.value }, () => [
|
|
703
|
+
createElementVNode("div", _hoisted_16, [
|
|
704
|
+
active.value.icon ? (openBlock(), createBlock(_sfc_main$1, {
|
|
705
|
+
key: 0,
|
|
706
|
+
name: active.value.icon,
|
|
707
|
+
size: 16,
|
|
708
|
+
class: "shrink-0 text-muted"
|
|
709
|
+
}, null, 8, ["name"])) : createCommentVNode("", true),
|
|
710
|
+
createElementVNode("h3", _hoisted_17, toDisplayString(active.value.label), 1)
|
|
711
|
+
]),
|
|
712
|
+
active.value.description ? (openBlock(), createElementBlock("p", _hoisted_18, toDisplayString(active.value.description), 1)) : createCommentVNode("", true),
|
|
713
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(paragraphs.value, (para, i) => {
|
|
714
|
+
return openBlock(), createElementBlock("p", {
|
|
715
|
+
key: i,
|
|
716
|
+
class: "lp-cmd-para mt-3 whitespace-pre-line text-sm leading-relaxed text-ink/90",
|
|
717
|
+
style: normalizeStyle({ animationDelay: `${Math.min(i + 1, STAGGER_MAX) * STAGGER_MS}ms` })
|
|
718
|
+
}, [
|
|
719
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(highlight(para, active.value.highlight), (p, pi) => {
|
|
720
|
+
return openBlock(), createElementBlock(Fragment, {
|
|
721
|
+
key: `p${pi}`
|
|
722
|
+
}, [
|
|
723
|
+
p.hit ? (openBlock(), createElementBlock("mark", _hoisted_19, toDisplayString(p.text), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
|
|
724
|
+
createTextVNode(toDisplayString(p.text), 1)
|
|
725
|
+
], 64))
|
|
726
|
+
], 64);
|
|
727
|
+
}), 128))
|
|
728
|
+
], 4);
|
|
729
|
+
}), 128))
|
|
730
|
+
], true)
|
|
731
|
+
])) : createCommentVNode("", true)
|
|
710
732
|
]),
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
p.hit ? (openBlock(), createElementBlock("mark", _hoisted_20, toDisplayString(p.text), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
|
|
718
|
-
createTextVNode(toDisplayString(p.text), 1)
|
|
719
|
-
], 64))
|
|
720
|
-
], 64);
|
|
721
|
-
}), 128))
|
|
722
|
-
])) : (openBlock(), createElementBlock("p", _hoisted_21, toDisplayString(text.value.noPreview), 1))
|
|
723
|
-
], true) : createCommentVNode("", true)
|
|
724
|
-
]),
|
|
725
|
-
_: 3
|
|
726
|
-
}, 512)
|
|
727
|
-
])) : createCommentVNode("", true)
|
|
733
|
+
_: 3
|
|
734
|
+
}, 512)
|
|
735
|
+
])) : createCommentVNode("", true)
|
|
736
|
+
]),
|
|
737
|
+
_: 3
|
|
738
|
+
})
|
|
728
739
|
]),
|
|
729
|
-
createElementVNode("div",
|
|
730
|
-
createElementVNode("span",
|
|
740
|
+
createElementVNode("div", _hoisted_20, [
|
|
741
|
+
createElementVNode("span", _hoisted_21, [
|
|
731
742
|
_cache[7] || (_cache[7] = createElementVNode("kbd", { class: "rounded border border-line px-1 text-[10px]" }, "↑", -1)),
|
|
732
743
|
_cache[8] || (_cache[8] = createElementVNode("kbd", { class: "rounded border border-line px-1 text-[10px]" }, "↓", -1)),
|
|
733
744
|
createTextVNode(" " + toDisplayString(text.value.navigate), 1)
|
|
734
745
|
]),
|
|
735
|
-
createElementVNode("span",
|
|
746
|
+
createElementVNode("span", _hoisted_22, [
|
|
736
747
|
_cache[9] || (_cache[9] = createElementVNode("kbd", { class: "rounded border border-line px-1 text-[10px]" }, "↵", -1)),
|
|
737
748
|
createTextVNode(" " + toDisplayString(text.value.select), 1)
|
|
738
749
|
]),
|
|
739
|
-
selectedActions.value.length ? (openBlock(), createElementBlock("span",
|
|
750
|
+
selectedActions.value.length ? (openBlock(), createElementBlock("span", _hoisted_23, [
|
|
740
751
|
_cache[10] || (_cache[10] = createElementVNode("kbd", { class: "rounded border border-line px-1 text-[10px]" }, "tab", -1)),
|
|
741
752
|
createTextVNode(" " + toDisplayString(text.value.actions), 1)
|
|
742
753
|
])) : createCommentVNode("", true),
|
|
743
|
-
trail.value.length ? (openBlock(), createElementBlock("span",
|
|
754
|
+
trail.value.length ? (openBlock(), createElementBlock("span", _hoisted_24, [
|
|
744
755
|
_cache[11] || (_cache[11] = createElementVNode("kbd", { class: "rounded border border-line px-1 text-[10px]" }, "⌫", -1)),
|
|
745
756
|
createTextVNode(" " + toDisplayString(text.value.back), 1)
|
|
746
757
|
])) : createCommentVNode("", true),
|
|
747
|
-
createElementVNode("span",
|
|
758
|
+
createElementVNode("span", _hoisted_25, [
|
|
748
759
|
_cache[12] || (_cache[12] = createElementVNode("kbd", { class: "rounded border border-line px-1 text-[10px]" }, "esc", -1)),
|
|
749
760
|
createTextVNode(" " + toDisplayString(text.value.close), 1)
|
|
750
761
|
])
|
|
751
762
|
])
|
|
752
763
|
]),
|
|
753
764
|
_: 3
|
|
754
|
-
}, 8, ["class"])
|
|
765
|
+
}, 8, ["class", "data-pane"])
|
|
755
766
|
]),
|
|
756
767
|
_: 3
|
|
757
768
|
})
|
|
@@ -761,7 +772,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
761
772
|
};
|
|
762
773
|
}
|
|
763
774
|
});
|
|
764
|
-
const LpCommandPalette = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-
|
|
775
|
+
const LpCommandPalette = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-27071a60"]]);
|
|
765
776
|
export {
|
|
766
777
|
LpCommandPalette as default
|
|
767
778
|
};
|
package/package.json
CHANGED
|
@@ -136,8 +136,11 @@ export interface PaletteLabels {
|
|
|
136
136
|
copyLink: string
|
|
137
137
|
/** Toast after "copy link"; empty for none. */
|
|
138
138
|
linkCopied: string
|
|
139
|
-
/**
|
|
140
|
-
|
|
139
|
+
/**
|
|
140
|
+
* @deprecated The pane now closes for a row with nothing to show, so this
|
|
141
|
+
* text is never displayed. Kept so existing `labels` objects still type-check.
|
|
142
|
+
*/
|
|
143
|
+
noPreview?: string
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
const props = withDefaults(
|
|
@@ -194,14 +197,14 @@ const props = withDefaults(
|
|
|
194
197
|
* A pane beside the list showing the highlighted result in full — its
|
|
195
198
|
* `preview` text, or the `preview` slot. For palettes that search
|
|
196
199
|
* content rather than commands: the excerpt says why a row matched, the
|
|
197
|
-
* pane lets it be read without leaving the page.
|
|
198
|
-
*
|
|
200
|
+
* pane lets it be read without leaving the page. It opens only for a row
|
|
201
|
+
* with `preview` or `excerpt` (any row, with the slot): the dialog eases
|
|
202
|
+
* wider, the pane slides in, and the text arrives paragraph by paragraph.
|
|
199
203
|
* Hidden below the md breakpoint, where there is no room beside the list.
|
|
200
204
|
*
|
|
201
|
-
* The
|
|
202
|
-
* an `md:` override
|
|
203
|
-
* order where the base
|
|
204
|
-
* Both widths are 92vw on a narrow screen, so no breakpoint is needed.
|
|
205
|
+
* The width is an inline style rather than a pair of classes: a base
|
|
206
|
+
* width plus an `md:` override could be emitted by a consumer's Tailwind
|
|
207
|
+
* build in an order where the base won, and the pane opened squeezed.
|
|
205
208
|
*/
|
|
206
209
|
previewPane?: boolean
|
|
207
210
|
/**
|
|
@@ -605,7 +608,7 @@ function folded(name: string, items: Command[]): Command[] {
|
|
|
605
608
|
// Flat list in render order — what arrow keys walk through.
|
|
606
609
|
const flat = computed(() => grouped.value.flatMap((g) => g.items))
|
|
607
610
|
|
|
608
|
-
defineSlots<{
|
|
611
|
+
const slots = defineSlots<{
|
|
609
612
|
/** Body of the preview pane for the highlighted command. */
|
|
610
613
|
preview?: (props: { command: Command }) => unknown
|
|
611
614
|
}>()
|
|
@@ -616,6 +619,30 @@ const active = computed<Command | undefined>(() => {
|
|
|
616
619
|
})
|
|
617
620
|
const previewRef = ref<{ viewportEl: HTMLElement | null } | null>(null)
|
|
618
621
|
|
|
622
|
+
/*
|
|
623
|
+
* The pane opens only for a row that has something to show: an empty pane
|
|
624
|
+
* beside a list of commands is a column of dead space. With the slot the app
|
|
625
|
+
* decides what to show, so any highlighted row opens it.
|
|
626
|
+
*/
|
|
627
|
+
const paneOpen = computed(
|
|
628
|
+
() => props.previewPane && !!active.value && (!!slots.preview || !!(active.value.preview || active.value.excerpt)),
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
/*
|
|
632
|
+
* The text in paragraphs, so they can arrive one after another. A blank line
|
|
633
|
+
* separates them, as in the `preview` the consumer wrote.
|
|
634
|
+
*/
|
|
635
|
+
const paragraphs = computed(() =>
|
|
636
|
+
(active.value?.preview ?? active.value?.excerpt ?? "")
|
|
637
|
+
.split(/\n{2,}/)
|
|
638
|
+
.map((part) => part.trim())
|
|
639
|
+
.filter(Boolean),
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
/** Stagger between paragraphs, capped so a long section does not trickle in. */
|
|
643
|
+
const STAGGER_MS = 45
|
|
644
|
+
const STAGGER_MAX = 10
|
|
645
|
+
|
|
619
646
|
/*
|
|
620
647
|
* A preview is opened for the match, so the pane lands on it: the first
|
|
621
648
|
* highlighted word is brought into view, and a result without one starts
|
|
@@ -624,7 +651,7 @@ const previewRef = ref<{ viewportEl: HTMLElement | null } | null>(null)
|
|
|
624
651
|
watch(
|
|
625
652
|
() => active.value?.id,
|
|
626
653
|
async () => {
|
|
627
|
-
if (!
|
|
654
|
+
if (!paneOpen.value) return
|
|
628
655
|
await nextTick()
|
|
629
656
|
const viewport = previewRef.value?.viewportEl
|
|
630
657
|
if (!viewport) return
|
|
@@ -841,11 +868,9 @@ useHotkeys(() =>
|
|
|
841
868
|
class="lp-scrim fixed inset-0 z-(--z-overlay) data-[state=open]:animate-[fade-in_150ms_ease] data-[state=closed]:animate-[fade-out_130ms_ease]"
|
|
842
869
|
/>
|
|
843
870
|
<DialogContent
|
|
844
|
-
class="fixed left-1/2 top-[12vh] z-(--z-modal) flex max-h-[76vh] -translate-x-1/2 flex-col overflow-hidden rounded-card border border-line shadow-panel outline-none data-[state=open]:animate-[pop-in_160ms_var(--ease-emphasized)] data-[state=closed]:animate-[pop-out_130ms_ease]"
|
|
845
|
-
:class="[
|
|
846
|
-
|
|
847
|
-
previewPane ? 'w-[min(92vw,64rem)] md:h-[min(76vh,36rem)]' : 'w-[min(92vw,40rem)]',
|
|
848
|
-
]"
|
|
871
|
+
class="lp-cmd-dialog fixed left-1/2 top-[12vh] z-(--z-modal) flex max-h-[76vh] -translate-x-1/2 flex-col overflow-hidden rounded-card border border-line shadow-panel outline-none data-[state=open]:animate-[pop-in_160ms_var(--ease-emphasized)] data-[state=closed]:animate-[pop-out_130ms_ease]"
|
|
872
|
+
:class="[glass ? 'lp-glass-overlay' : 'bg-surface-raised', previewPane && 'lp-cmd-split']"
|
|
873
|
+
:data-pane="paneOpen ? 'open' : undefined"
|
|
849
874
|
@open-auto-focus.prevent
|
|
850
875
|
>
|
|
851
876
|
<DialogTitle class="sr-only">Command palette</DialogTitle>
|
|
@@ -899,7 +924,7 @@ useHotkeys(() =>
|
|
|
899
924
|
|
|
900
925
|
<!-- Results -->
|
|
901
926
|
<div class="flex min-h-0 flex-1">
|
|
902
|
-
<LpScrollArea ref="scrollRef" class="min-h-0 min-w-0 flex-1" content-class="p-2">
|
|
927
|
+
<LpScrollArea ref="scrollRef" class="lp-cmd-list min-h-0 min-w-0 flex-1" content-class="p-2">
|
|
903
928
|
<!-- While a request is out there is nothing to say yet: "no results"
|
|
904
929
|
under a spinner reads as an answer, and it is not one. -->
|
|
905
930
|
<p v-if="flat.length === 0 && !failed" class="px-2 py-10 text-center text-sm text-muted">
|
|
@@ -1007,35 +1032,41 @@ useHotkeys(() =>
|
|
|
1007
1032
|
</button>
|
|
1008
1033
|
</div>
|
|
1009
1034
|
</LpScrollArea>
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
<
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
<
|
|
1027
|
-
|
|
1028
|
-
|
|
1035
|
+
<!-- Slides in beside the list while the dialog widens to make room,
|
|
1036
|
+
and out again for a row with nothing to show. -->
|
|
1037
|
+
<Transition name="lp-cmd-pane">
|
|
1038
|
+
<aside
|
|
1039
|
+
v-if="paneOpen"
|
|
1040
|
+
class="lp-cmd-pane hidden min-h-0 shrink-0 flex-col border-l border-line md:flex"
|
|
1041
|
+
>
|
|
1042
|
+
<LpScrollArea ref="previewRef" class="min-h-0 flex-1" content-class="px-5 py-4">
|
|
1043
|
+
<!-- Keyed by the row, so moving the highlight replays the
|
|
1044
|
+
entrance rather than swapping text in place. -->
|
|
1045
|
+
<div v-if="active" :key="active.id">
|
|
1046
|
+
<slot name="preview" :command="active">
|
|
1047
|
+
<div class="lp-cmd-para flex items-center gap-2 text-ink">
|
|
1048
|
+
<LpIcon v-if="active.icon" :name="active.icon" :size="16" class="shrink-0 text-muted" />
|
|
1049
|
+
<h3 class="min-w-0 text-base font-semibold">{{ active.label }}</h3>
|
|
1050
|
+
</div>
|
|
1051
|
+
<p v-if="active.description" class="lp-cmd-para mt-0.5 text-xs text-muted">
|
|
1052
|
+
{{ active.description }}
|
|
1053
|
+
</p>
|
|
1054
|
+
<p
|
|
1055
|
+
v-for="(para, i) in paragraphs"
|
|
1056
|
+
:key="i"
|
|
1057
|
+
class="lp-cmd-para mt-3 whitespace-pre-line text-sm leading-relaxed text-ink/90"
|
|
1058
|
+
:style="{ animationDelay: `${Math.min(i + 1, STAGGER_MAX) * STAGGER_MS}ms` }"
|
|
1029
1059
|
>
|
|
1030
|
-
<
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1060
|
+
<template v-for="(p, pi) in highlight(para, active.highlight)" :key="`p${pi}`">
|
|
1061
|
+
<mark v-if="p.hit" class="rounded-xs bg-brand/25 text-ink">{{ p.text }}</mark>
|
|
1062
|
+
<template v-else>{{ p.text }}</template>
|
|
1063
|
+
</template>
|
|
1064
|
+
</p>
|
|
1065
|
+
</slot>
|
|
1066
|
+
</div>
|
|
1067
|
+
</LpScrollArea>
|
|
1068
|
+
</aside>
|
|
1069
|
+
</Transition>
|
|
1039
1070
|
</div>
|
|
1040
1071
|
|
|
1041
1072
|
<!-- Footer hints -->
|
|
@@ -1072,6 +1103,70 @@ useHotkeys(() =>
|
|
|
1072
1103
|
</template>
|
|
1073
1104
|
|
|
1074
1105
|
<style scoped>
|
|
1106
|
+
/*
|
|
1107
|
+
* Sizes for the split layout. Only the dialog's width animates: the list and
|
|
1108
|
+
* the pane keep fixed widths, and the dialog — overflow hidden — uncovers the
|
|
1109
|
+
* pane from its right edge as it grows. Animating the columns themselves made
|
|
1110
|
+
* every line of the list and the pane re-wrap on every frame.
|
|
1111
|
+
*/
|
|
1112
|
+
.lp-cmd-dialog {
|
|
1113
|
+
--lp-cmd-list: min(92vw, 40rem);
|
|
1114
|
+
--lp-cmd-pane: max(0px, min(26rem, 96vw - var(--lp-cmd-list)));
|
|
1115
|
+
width: var(--lp-cmd-list);
|
|
1116
|
+
}
|
|
1117
|
+
.lp-cmd-split {
|
|
1118
|
+
transition:
|
|
1119
|
+
width 300ms var(--ease-emphasized, ease),
|
|
1120
|
+
min-height 300ms var(--ease-emphasized, ease);
|
|
1121
|
+
}
|
|
1122
|
+
@media (min-width: 768px) {
|
|
1123
|
+
.lp-cmd-split .lp-cmd-list {
|
|
1124
|
+
flex: 0 0 auto;
|
|
1125
|
+
/* the dialog's 1px border on each side */
|
|
1126
|
+
width: calc(var(--lp-cmd-list) - 2px);
|
|
1127
|
+
}
|
|
1128
|
+
.lp-cmd-dialog[data-pane="open"] {
|
|
1129
|
+
width: calc(var(--lp-cmd-list) + var(--lp-cmd-pane));
|
|
1130
|
+
min-height: min(76vh, 36rem);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
.lp-cmd-pane {
|
|
1134
|
+
width: var(--lp-cmd-pane);
|
|
1135
|
+
}
|
|
1136
|
+
/*
|
|
1137
|
+
* The pane rides in from the right edge while the dialog opens up to it, and
|
|
1138
|
+
* leaves the same way, for as long as the dialog takes to close over it.
|
|
1139
|
+
*/
|
|
1140
|
+
.lp-cmd-pane-enter-active,
|
|
1141
|
+
.lp-cmd-pane-leave-active {
|
|
1142
|
+
transition:
|
|
1143
|
+
transform 300ms var(--ease-emphasized, ease),
|
|
1144
|
+
opacity 300ms ease;
|
|
1145
|
+
}
|
|
1146
|
+
.lp-cmd-pane-enter-from,
|
|
1147
|
+
.lp-cmd-pane-leave-to {
|
|
1148
|
+
opacity: 0;
|
|
1149
|
+
transform: translateX(40%);
|
|
1150
|
+
}
|
|
1151
|
+
.lp-cmd-para {
|
|
1152
|
+
animation: lp-cmd-para-in 260ms ease-out both;
|
|
1153
|
+
}
|
|
1154
|
+
@keyframes lp-cmd-para-in {
|
|
1155
|
+
from {
|
|
1156
|
+
opacity: 0;
|
|
1157
|
+
transform: translateY(4px);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1161
|
+
.lp-cmd-split,
|
|
1162
|
+
.lp-cmd-pane-enter-active,
|
|
1163
|
+
.lp-cmd-pane-leave-active {
|
|
1164
|
+
transition: none;
|
|
1165
|
+
}
|
|
1166
|
+
.lp-cmd-para {
|
|
1167
|
+
animation: none;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1075
1170
|
.lp-cmd-excerpt {
|
|
1076
1171
|
display: -webkit-box;
|
|
1077
1172
|
-webkit-box-orient: vertical;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
.lp-cmd-excerpt[data-v-fcb38dbd] {
|
|
3
|
-
display: -webkit-box;
|
|
4
|
-
-webkit-box-orient: vertical;
|
|
5
|
-
overflow: hidden;
|
|
6
|
-
}
|
|
7
|
-
/* Each result eases in; honours reduced motion. */
|
|
8
|
-
.lp-cmd-item[data-v-fcb38dbd] {
|
|
9
|
-
animation: lp-cmd-in-fcb38dbd 140ms ease-out both;
|
|
10
|
-
}
|
|
11
|
-
@keyframes lp-cmd-in-fcb38dbd {
|
|
12
|
-
from {
|
|
13
|
-
opacity: 0;
|
|
14
|
-
transform: translateY(3px);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
@media (prefers-reduced-motion: reduce) {
|
|
18
|
-
.lp-cmd-item[data-v-fcb38dbd] {
|
|
19
|
-
animation: none;
|
|
20
|
-
}
|
|
21
|
-
}
|