@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,14 +1,11 @@
1
- import { defineComponent, computed, openBlock, createBlock, unref, withCtx, createElementVNode, createElementBlock, Fragment, renderList, toDisplayString, createTextVNode, createCommentVNode } from "vue";
2
- import { _ as _sfc_main$1 } from "../chunks/LpModal-BzTVeNqe.js";
3
- import { useAppleKeyboard, mergeShortcutGroups, activeShortcuts, useHotkeys, appleModifierLabel } from "../composables/useHotkeys.js";
1
+ import { defineComponent, computed, openBlock, createBlock, unref, withCtx, createElementVNode, createElementBlock, Fragment, renderList, toDisplayString, createTextVNode, createCommentVNode, createVNode } from "vue";
2
+ import LpKeys from "./LpKeys.vue.js";
3
+ import { _ as _sfc_main$1 } from "../chunks/LpModal-JNa3B_Lr.js";
4
+ import { mergeShortcutGroups, activeShortcuts, useHotkeys } from "../composables/useHotkeys.js";
4
5
  import { useLabels } from "../composables/useLpConfig.js";
5
6
  import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
6
- import '../assets/LpShortcutsDialog-CqYX3yy6.css';const _hoisted_1 = { class: "shortcut-groups" };
7
+ import '../assets/LpShortcutsDialog-BUp_BX4s.css';const _hoisted_1 = { class: "shortcut-groups" };
7
8
  const _hoisted_2 = ["aria-label", "title"];
8
- const _hoisted_3 = {
9
- key: 0,
10
- class: "shortcut-then"
11
- };
12
9
  const _sfc_main = /* @__PURE__ */ defineComponent({
13
10
  __name: "LpShortcutsDialog",
14
11
  props: {
@@ -25,17 +22,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
25
22
  const props = __props;
26
23
  const text = useLabels("shortcuts", {
27
24
  title: "Keyboard shortcuts",
28
- then: "then",
29
25
  conflict: "Also bound elsewhere: only one of them runs",
30
26
  group: "General"
31
27
  });
32
- const apple = useAppleKeyboard();
33
- function keysOf(chord) {
34
- return chord.split(/\+(?!$)/);
35
- }
36
- function stepsOf(combo) {
37
- return combo.trim().split(/\s+/);
38
- }
39
28
  const shown = computed(() => props.live ? mergeShortcutGroups(props.groups, activeShortcuts.value) : props.groups);
40
29
  const emit = __emit;
41
30
  useHotkeys(
@@ -84,18 +73,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
84
73
  }, "!", 8, _hoisted_2)) : createCommentVNode("", true)
85
74
  ]),
86
75
  createElementVNode("dd", null, [
87
- (openBlock(true), createElementBlock(Fragment, null, renderList(stepsOf(entry.combo), (step, stepIndex) => {
88
- return openBlock(), createElementBlock(Fragment, {
89
- key: `${step}-${stepIndex}`
90
- }, [
91
- stepIndex ? (openBlock(), createElementBlock("span", _hoisted_3, toDisplayString(unref(text).then), 1)) : createCommentVNode("", true),
92
- (openBlock(true), createElementBlock(Fragment, null, renderList(keysOf(step), (key, index) => {
93
- return openBlock(), createElementBlock("kbd", {
94
- key: `${key}-${index}`
95
- }, toDisplayString(unref(appleModifierLabel)(key.trim(), unref(apple))), 1);
96
- }), 128))
97
- ], 64);
98
- }), 128))
76
+ createVNode(LpKeys, {
77
+ combo: entry.combo
78
+ }, null, 8, ["combo"])
99
79
  ])
100
80
  ]);
101
81
  }), 128))
@@ -109,7 +89,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
109
89
  };
110
90
  }
111
91
  });
112
- const LpShortcutsDialog = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-1c4cf3fa"]]);
92
+ const LpShortcutsDialog = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-611c415f"]]);
113
93
  export {
114
94
  LpShortcutsDialog as default
115
95
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpSidebar-Bwws_X29.js";
1
+ import { _ as _sfc_main } from "../chunks/LpSidebar-DpEiGi8L.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpSidebarNav-kx-LdqNU.js";
1
+ import { _ as _sfc_main } from "../chunks/LpSidebarNav-BUuxipX-.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,10 +1,10 @@
1
1
  import { defineComponent, computed, useAttrs, ref, shallowRef, watch, onMounted, onBeforeUnmount, openBlock, createElementBlock, normalizeClass, createVNode, withCtx, createTextVNode, toDisplayString, createCommentVNode, Fragment, renderList, createElementVNode, Transition, renderSlot, mergeProps, normalizeStyle, createBlock, TransitionGroup, unref, withModifiers, nextTick } from "vue";
2
2
  import { _ as _sfc_main$7 } from "../chunks/LpCheckbox-Bkj13CbW.js";
3
- import { _ as _sfc_main$6 } from "../chunks/LpContextMenu-BqkAYa9X.js";
3
+ import { _ as _sfc_main$6 } from "../chunks/LpContextMenu-1cKew_Dc.js";
4
4
  import { _ as _sfc_main$2 } from "../chunks/LpIcon-DYtaid0K.js";
5
5
  import { _ as _sfc_main$a } from "../chunks/LpPagination-BcDlo3r_.js";
6
6
  import { _ as _sfc_main$8 } from "../chunks/LpPopover-DCKeqiVn.js";
7
- import { _ as _sfc_main$5 } from "../chunks/LpScrollArea-Bry1fdOM.js";
7
+ import { _ as _sfc_main$5 } from "../chunks/LpScrollArea-Hz3-JWUG.js";
8
8
  import { _ as _sfc_main$4 } from "../chunks/LpRollingText-DB7upf-5.js";
9
9
  import { _ as _sfc_main$3 } from "../chunks/LpShift-DXNsauQZ.js";
10
10
  import { _ as _sfc_main$9 } from "../chunks/LpTableFilter-BV8PpLM9.js";
@@ -18,7 +18,7 @@ import { _ as _sfc_main$1 } from "../chunks/LpButton-CKSwC6Mu.js";
18
18
  import { toCsv, downloadFile, fileStem } from "./exportData.js";
19
19
  import { summarizeShown, parseTableState, serializeTableState, countValues } from "./tableFilter.js";
20
20
  import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
21
- import '../assets/LpTable-CPwWxTJS.css';const _hoisted_1 = {
21
+ import '../assets/LpTable-xWwI4KPZ.css';const _hoisted_1 = {
22
22
  key: 0,
23
23
  class: "flex justify-end"
24
24
  };
@@ -544,7 +544,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
544
544
  }
545
545
  return t.rowsTotal.replace("{total}", String(props.rows.length));
546
546
  });
547
- const showBar = computed(() => props.summary || !!props.selectable || activeFilters.value.length > 0);
547
+ const showBar = computed(
548
+ () => props.summary || !!props.selectable && props.selected.length > 0 || activeFilters.value.length > 0
549
+ );
548
550
  const filteredOut = computed(() => {
549
551
  if (loading.value || failed.value || displayRows.value.length) return false;
550
552
  if (props.load) return Object.values(hiddenValues.value).some((set) => set.size);
@@ -726,7 +728,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
726
728
  }),
727
729
  createVNode(_sfc_main$5, mergeProps(scrollAttrs.value, {
728
730
  fade: false,
729
- class: "min-h-0 flex-1 rounded-card border border-line",
731
+ class: "min-h-0 flex-1 rounded-card border border-line bg-surface",
730
732
  style: __props.load && floor.value ? { minHeight: `${floor.value + 2}px` } : void 0,
731
733
  "bar-inset-top": barInsetTop.value
732
734
  }), {
@@ -924,7 +926,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
924
926
  };
925
927
  }
926
928
  });
927
- const LpTable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-fff7ed10"]]);
929
+ const LpTable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-4fa70e89"]]);
928
930
  export {
929
931
  LpTable as default
930
932
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpThemeSwitcher-B4aVPbNC.js";
1
+ import { _ as _sfc_main } from "../chunks/LpThemeSwitcher-BbYFFT5w.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -0,0 +1,9 @@
1
+ type __VLS_Props = {
2
+ title: string;
3
+ subtitle?: string;
4
+ icon?: string;
5
+ image?: string;
6
+ };
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
8
+ declare const _default: typeof __VLS_export;
9
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { _ as _sfc_main } from "../chunks/MenuTitle-J3ZDYjpV.js";
2
+ export {
3
+ _sfc_main as default
4
+ };
@@ -0,0 +1,157 @@
1
+ import { type CommandSearchTerms, type HighlightPart, type SearchableCommand } from "./commandSearch";
2
+ /** The fields of a command that decide whether the preview pane opens for it. */
3
+ export interface PreviewableCommand {
4
+ preview?: string;
5
+ excerpt?: string;
6
+ noPreview?: boolean;
7
+ }
8
+ /**
9
+ * Whether the pane has something to show for a row. `noPreview` on the row
10
+ * wins, then the app's `hasPreview`; without either, a row qualifies when the
11
+ * app supplied a `#preview` slot or the row carries text of its own.
12
+ */
13
+ export declare function commandHasPreview<T extends PreviewableCommand>(command: T | undefined, options: {
14
+ slot: boolean;
15
+ hasPreview?: (command: T) => boolean;
16
+ }): boolean;
17
+ /** One line of LpPaletteFacts: what it is, and what it is set to now. */
18
+ export interface PaletteFact {
19
+ /** Text, or the preview slot's `highlight(text)` to mark the query. */
20
+ label: string | HighlightPart[];
21
+ /** Right-aligned value; a change rolls in rather than swapping. */
22
+ value?: string | number;
23
+ /** A second, quieter line under the label. */
24
+ hint?: string | HighlightPart[];
25
+ icon?: string;
26
+ /** A colour dot in place of the icon: a role colour, a status. */
27
+ color?: string;
28
+ /** Marks the row that is in effect now: the current theme, the open tab. */
29
+ current?: boolean;
30
+ /** Draw the value in the monospace face: ids, paths, versions. */
31
+ mono?: boolean;
32
+ }
33
+ /** One tile of LpPaletteStats. */
34
+ export interface PaletteStat {
35
+ label: string;
36
+ /** A number counts to its new value; text rolls. */
37
+ value: number | string;
38
+ /** Abbreviate a number: 1.2k, 3.4M. */
39
+ compact?: boolean;
40
+ suffix?: string;
41
+ format?: (value: number) => string;
42
+ }
43
+ /** The fields of a command that let typing enter its mode. */
44
+ export interface PrefixedCommand {
45
+ prefix?: string;
46
+ children?: unknown;
47
+ disabled?: boolean;
48
+ }
49
+ /**
50
+ * The mode a query opens by its prefix, and the query left after it. Only a
51
+ * command with `children` and a non-empty `prefix` qualifies; letters match
52
+ * in any case, and the longest prefix wins, so "mr " and "m" can live side by
53
+ * side. Leading spaces are ignored: they are never what someone meant.
54
+ */
55
+ export declare function matchModePrefix<T extends PrefixedCommand>(query: string, commands: readonly T[]): {
56
+ command: T;
57
+ rest: string;
58
+ } | null;
59
+ /** The fields of a command that make it a "search elsewhere" row. */
60
+ export interface FallbackCommand extends SearchableCommand {
61
+ fallback?: boolean;
62
+ hidden?: boolean;
63
+ }
64
+ /**
65
+ * The fallback rows to show for a query, with their labels filled in. They
66
+ * appear only while positive words are typed, whatever those words are: the
67
+ * row offers to search somewhere else for them, so matching it against them
68
+ * would hide it exactly when it is needed. Exclusions and `in:` scopes still
69
+ * apply, to the row's own label, keywords and group. `{query}` in the label
70
+ * becomes the positive words, without the operators.
71
+ */
72
+ export declare function fallbackRows<T extends FallbackCommand>(items: readonly T[], raw: string): {
73
+ command: T;
74
+ label: string;
75
+ }[];
76
+ /**
77
+ * A remembered pick that is not a root command: one chosen inside a mode, or
78
+ * a result a search fetched. It keeps what the row needs to be drawn again
79
+ * and where it was found, so the palette can find the live command next time
80
+ * (`path` through static children, or the app's `resolveRecent`), and fall
81
+ * back to its `href` when neither can.
82
+ */
83
+ export interface RecentEntry {
84
+ id: string;
85
+ label: string;
86
+ description?: string;
87
+ icon?: string;
88
+ image?: string | {
89
+ src?: string | null;
90
+ alt?: string;
91
+ fallback?: string;
92
+ icon?: string;
93
+ shape?: "circle" | "soft" | "square";
94
+ };
95
+ href?: string;
96
+ /** Ids of the modes it was picked in, outermost first. */
97
+ path: string[];
98
+ /** The command's `recentData`, kept as given for `resolveRecent`. */
99
+ data?: unknown;
100
+ }
101
+ /** A stored history item: a root command id (the old format), or an entry. */
102
+ export type RecentItem = string | RecentEntry;
103
+ /** The fields of a command a history entry is built from and resolved to. */
104
+ export interface RecentSource {
105
+ id: string;
106
+ label: string;
107
+ description?: string;
108
+ icon?: string;
109
+ image?: RecentEntry["image"];
110
+ href?: string;
111
+ recentData?: unknown;
112
+ disabled?: boolean;
113
+ children?: unknown;
114
+ }
115
+ /** Stored history, with anything malformed dropped rather than trusted. */
116
+ export declare function readRecent(stored: unknown): RecentItem[];
117
+ /**
118
+ * The history item for a pick. A root pick stays a bare id, the format older
119
+ * versions wrote and still read; anything deeper becomes an entry with only
120
+ * the fields that survive JSON.
121
+ */
122
+ export declare function toRecentItem(command: RecentSource, path: readonly string[]): RecentItem;
123
+ /** The history with `item` first and any earlier copy of it dropped. */
124
+ export declare function pushRecent(list: readonly RecentItem[], item: RecentItem, keep: number): RecentItem[];
125
+ /**
126
+ * The live commands a history stands for, newest first. A bare id is looked
127
+ * up among the root commands; an entry is followed down its path through
128
+ * static children, then offered to `resolve` (for results a fetcher found),
129
+ * and last rebuilt from its own fields when it has an `href` to follow.
130
+ * Whatever none of these can find is skipped, so a stored history never
131
+ * resurrects something that cannot run.
132
+ */
133
+ export declare function resolveRecentItems<T extends RecentSource>(items: readonly RecentItem[], commands: readonly T[], options: {
134
+ limit: number;
135
+ resolve?: (entry: RecentEntry) => T | null | undefined;
136
+ fromEntry: (entry: RecentEntry) => T;
137
+ }): {
138
+ command: T;
139
+ path: string[];
140
+ }[];
141
+ /**
142
+ * Whether a recent row survives the operators typed so far. Recent rows show
143
+ * while no positive words are typed, so "-theme" or "in:recent" filters them
144
+ * like any other row; an `in:` scope matches the row's own group or the
145
+ * Recent heading it sits under.
146
+ */
147
+ export declare function recentVisible(command: SearchableCommand, terms: CommandSearchTerms, recentLabel: string): boolean;
148
+ /**
149
+ * The name a mode's chip shows. `modeLabel` when given; otherwise the label,
150
+ * with a `{query}` placeholder and the quotes around it taken out, since the
151
+ * words it stood for are in the box now ("Search mods for "{query}"" reads
152
+ * "Search mods").
153
+ */
154
+ export declare function modeChipLabel(command: {
155
+ label: string;
156
+ modeLabel?: string;
157
+ }): string;
@@ -0,0 +1,103 @@
1
+ import { parsePaletteQuery, applyQueryFilters } from "./commandSearch.js";
2
+ function commandHasPreview(command, options) {
3
+ if (!command || command.noPreview) return false;
4
+ if (options.hasPreview) return options.hasPreview(command);
5
+ return options.slot || !!(command.preview || command.excerpt);
6
+ }
7
+ function matchModePrefix(query, commands) {
8
+ const typed = query.trimStart().toLowerCase();
9
+ let best = null;
10
+ for (const command of commands) {
11
+ const prefix = command.prefix?.toLowerCase();
12
+ if (!prefix || !command.children || command.disabled || !typed.startsWith(prefix)) continue;
13
+ if (!best || prefix.length > (best.prefix?.length ?? 0)) best = command;
14
+ }
15
+ if (!best) return null;
16
+ return { command: best, rest: query.trimStart().slice(best.prefix.length) };
17
+ }
18
+ function fallbackRows(items, raw) {
19
+ const parsed = parsePaletteQuery(raw.trim());
20
+ if (!parsed.text) return [];
21
+ return applyQueryFilters(
22
+ items.filter((c) => c.fallback),
23
+ parsed
24
+ ).map((command) => ({ command, label: command.label.split("{query}").join(parsed.text) }));
25
+ }
26
+ function isEntry(value) {
27
+ if (!value || typeof value !== "object") return false;
28
+ const v = value;
29
+ return typeof v.id === "string" && typeof v.label === "string" && Array.isArray(v.path) && v.path.every((step) => typeof step === "string");
30
+ }
31
+ function readRecent(stored) {
32
+ if (!Array.isArray(stored)) return [];
33
+ return stored.filter((v) => typeof v === "string" || isEntry(v));
34
+ }
35
+ function recentKey(item) {
36
+ return typeof item === "string" ? item : [...item.path, item.id].join("\0");
37
+ }
38
+ function toRecentItem(command, path) {
39
+ if (!path.length) return command.id;
40
+ const entry = { id: command.id, label: command.label, path: [...path] };
41
+ if (command.description) entry.description = command.description;
42
+ if (command.icon) entry.icon = command.icon;
43
+ if (typeof command.image === "string") entry.image = command.image;
44
+ else if (command.image) {
45
+ const { src, alt, fallback, icon, shape } = command.image;
46
+ entry.image = { src, alt, fallback, icon, shape };
47
+ }
48
+ if (command.href) entry.href = command.href;
49
+ if (command.recentData !== void 0) entry.data = command.recentData;
50
+ return entry;
51
+ }
52
+ function pushRecent(list, item, keep) {
53
+ const key = recentKey(item);
54
+ return [item, ...list.filter((v) => recentKey(v) !== key)].slice(0, keep);
55
+ }
56
+ function resolveRecentItems(items, commands, options) {
57
+ const out = [];
58
+ const seen = /* @__PURE__ */ new Set();
59
+ for (const item of items) {
60
+ if (out.length >= options.limit) break;
61
+ const key = recentKey(item);
62
+ if (seen.has(key)) continue;
63
+ const found = typeof item === "string" ? commands.find((c) => c.id === item) : findEntry(item, commands, options);
64
+ if (!found || found.disabled || found.children) continue;
65
+ seen.add(key);
66
+ out.push({ command: found, path: typeof item === "string" ? [] : [...item.path] });
67
+ }
68
+ return out;
69
+ }
70
+ function findEntry(entry, commands, options) {
71
+ let level = commands;
72
+ for (const step of entry.path) {
73
+ const mode = level?.find((c) => c.id === step);
74
+ level = mode && Array.isArray(mode.children) ? mode.children : null;
75
+ }
76
+ const live = level?.find((c) => c.id === entry.id);
77
+ if (live) return live;
78
+ const resolved = options.resolve?.(entry);
79
+ if (resolved) return resolved;
80
+ return entry.href ? options.fromEntry(entry) : void 0;
81
+ }
82
+ function recentVisible(command, terms, recentLabel) {
83
+ if (!applyQueryFilters([{ ...command, group: void 0 }], { ...terms, groups: [] }).length) return false;
84
+ return terms.groups.every(
85
+ (term) => [command.group, recentLabel].some((group) => group?.toLowerCase().includes(term))
86
+ );
87
+ }
88
+ function modeChipLabel(command) {
89
+ if (command.modeLabel) return command.modeLabel;
90
+ if (!command.label.includes("{query}")) return command.label;
91
+ return command.label.replace(/\s*["'“”«»]?\{query\}["'“”«»]?/g, "").replace(/\s+(for|in|on|:)$/i, "").trim();
92
+ }
93
+ export {
94
+ commandHasPreview,
95
+ fallbackRows,
96
+ matchModePrefix,
97
+ modeChipLabel,
98
+ pushRecent,
99
+ readRecent,
100
+ recentVisible,
101
+ resolveRecentItems,
102
+ toRecentItem
103
+ };
@@ -11,3 +11,35 @@ export interface CommandSearchTerms {
11
11
  }
12
12
  export declare function parseCommandQuery(value: string): CommandSearchTerms;
13
13
  export declare function rankCommandItems<T extends SearchableCommand>(items: T[], terms: CommandSearchTerms): T[];
14
+ /**
15
+ * A query as a fetcher receives it. `text` is what a remote search should be
16
+ * sent: the positive words and phrases, in the case they were typed, without
17
+ * the operators a server would not understand. The operators come alongside
18
+ * as data, so a provider that can scope or exclude on the server may.
19
+ */
20
+ export interface PaletteQuery extends CommandSearchTerms {
21
+ /** The box as typed, operators and all. */
22
+ raw: string;
23
+ /** Positive terms joined by spaces, case kept, quotes dropped. */
24
+ text: string;
25
+ }
26
+ export declare function parsePaletteQuery(raw: string): PaletteQuery;
27
+ /**
28
+ * The operators applied to results someone else matched: a server answered
29
+ * the positive words, and `-word` and `in:group` still have to hold for what
30
+ * it sent back. Positive terms are not re-checked here; the server may match
31
+ * word forms the text does not contain.
32
+ */
33
+ export declare function applyQueryFilters<T extends SearchableCommand>(items: T[], terms: CommandSearchTerms): T[];
34
+ /** A run of text, and whether it matched the query. */
35
+ export interface HighlightPart {
36
+ text: string;
37
+ hit: boolean;
38
+ }
39
+ /**
40
+ * Text split around the terms that matched, for <mark> without v-html. Terms
41
+ * shorter than two characters are skipped (one letter would mark half the
42
+ * text), longer ones win where two start at the same place, and case is
43
+ * ignored. Pass positive terms only: an excluded word is never a match.
44
+ */
45
+ export declare function highlightParts(value: string, terms: readonly string[]): HighlightPart[];
@@ -47,7 +47,55 @@ function score(command, terms) {
47
47
  function rankCommandItems(items, terms) {
48
48
  return items.map((command) => ({ command, score: score(command, terms) })).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.command.label.localeCompare(b.command.label)).map((entry) => entry.command);
49
49
  }
50
+ function parsePaletteQuery(raw) {
51
+ const terms = parseCommandQuery(raw);
52
+ const words = [];
53
+ QUERY_TOKEN.lastIndex = 0;
54
+ for (let match; match = QUERY_TOKEN.exec(raw); ) {
55
+ const text = (match[3] ?? match[4] ?? "").trim();
56
+ if (text && !match[1] && !match[2]) words.push(text);
57
+ }
58
+ return { ...terms, raw, text: words.join(" ") };
59
+ }
60
+ function applyQueryFilters(items, terms) {
61
+ if (!terms.exclude.length && !terms.groups.length) return items;
62
+ return items.filter((command) => {
63
+ const group = command.group?.toLowerCase() ?? "";
64
+ return !terms.exclude.some((term) => matches(command, term)) && terms.groups.every((term) => group.includes(term));
65
+ });
66
+ }
67
+ function highlightParts(value, terms) {
68
+ const sorted = [...new Set(terms.map((t) => t.toLowerCase()).filter((t) => t.length >= 2))].sort(
69
+ (a, b) => b.length - a.length
70
+ );
71
+ if (!value || sorted.length === 0) return value ? [{ text: value, hit: false }] : [];
72
+ const lower = value.toLowerCase();
73
+ const parts = [];
74
+ let i = 0;
75
+ while (i < value.length) {
76
+ let at = -1;
77
+ let term = "";
78
+ for (const t of sorted) {
79
+ const found = lower.indexOf(t, i);
80
+ if (found !== -1 && (at === -1 || found < at || found === at && t.length > term.length)) {
81
+ at = found;
82
+ term = t;
83
+ }
84
+ }
85
+ if (at === -1) {
86
+ parts.push({ text: value.slice(i), hit: false });
87
+ break;
88
+ }
89
+ if (at > i) parts.push({ text: value.slice(i, at), hit: false });
90
+ parts.push({ text: value.slice(at, at + term.length), hit: true });
91
+ i = at + term.length;
92
+ }
93
+ return parts;
94
+ }
50
95
  export {
96
+ applyQueryFilters,
97
+ highlightParts,
51
98
  parseCommandQuery,
99
+ parsePaletteQuery,
52
100
  rankCommandItems
53
101
  };
@@ -0,0 +1,2 @@
1
+ export declare function comboKeys(chord: string): string[];
2
+ export declare function comboSteps(combo: string): string[][];
@@ -0,0 +1,11 @@
1
+ function comboKeys(chord) {
2
+ return chord.split(/\+(?!$)/);
3
+ }
4
+ function comboSteps(combo) {
5
+ const trimmed = combo.trim();
6
+ return trimmed ? trimmed.split(/\s+/).map(comboKeys) : [];
7
+ }
8
+ export {
9
+ comboKeys,
10
+ comboSteps
11
+ };
@@ -1,5 +1,39 @@
1
1
  import { ref, onScopeDispose } from "vue";
2
2
  import { useToast } from "./useToast.js";
3
+ async function writeAsync(text) {
4
+ if (typeof navigator === "undefined" || !navigator.clipboard) return false;
5
+ try {
6
+ await navigator.clipboard.writeText(text);
7
+ return true;
8
+ } catch {
9
+ return false;
10
+ }
11
+ }
12
+ function writeLegacy(text) {
13
+ if (typeof document === "undefined") return false;
14
+ const active = document.activeElement;
15
+ const selection = document.getSelection();
16
+ const ranges = selection ? Array.from({ length: selection.rangeCount }, (_, i) => selection.getRangeAt(i)) : [];
17
+ const field = document.createElement("textarea");
18
+ field.value = text;
19
+ field.setAttribute("readonly", "");
20
+ field.style.cssText = "position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;pointer-events:none";
21
+ document.body.appendChild(field);
22
+ field.select();
23
+ let ok = false;
24
+ try {
25
+ ok = document.execCommand("copy");
26
+ } catch {
27
+ ok = false;
28
+ }
29
+ field.remove();
30
+ if (selection) {
31
+ selection.removeAllRanges();
32
+ for (const range of ranges) selection.addRange(range);
33
+ }
34
+ active?.focus?.({ preventScroll: true });
35
+ return ok;
36
+ }
3
37
  const COPIED_MS = 1400;
4
38
  function useClipboard() {
5
39
  const copied = ref(false);
@@ -8,8 +42,7 @@ function useClipboard() {
8
42
  async function copy(text, options = {}) {
9
43
  const { toast = false, errorToast = false } = options;
10
44
  try {
11
- await navigator.clipboard?.writeText(text);
12
- if (!navigator.clipboard) throw new Error("clipboard unavailable");
45
+ if (!await writeAsync(text) && !writeLegacy(text)) throw new Error("clipboard unavailable");
13
46
  copied.value = true;
14
47
  clearTimeout(timer);
15
48
  timer = setTimeout(() => copied.value = false, COPIED_MS);
package/dist/index.d.ts CHANGED
@@ -35,9 +35,18 @@ export type { ColorPickerLabels } from "./components/LpColorPicker.vue";
35
35
  export { clampHsv, DEFAULT_SWATCHES, formatColor, hsvToHsl, hsvToRgb, isLight, normalizeHue, opaqueCss, parseColor, parseHex, rgbToHex, rgbToHsv, } from "./components/color";
36
36
  export type { ColorFormat, Hsv, Rgb } from "./components/color";
37
37
  export { default as LpCommandPalette } from "./components/LpCommandPalette.vue";
38
- export type { Command, PaletteLabels } from "./components/LpCommandPalette.vue";
39
- export { parseCommandQuery, rankCommandItems } from "./components/commandSearch";
40
- export type { CommandSearchTerms, SearchableCommand } from "./components/commandSearch";
38
+ export type { Command, CommandFetcher, CommandImage, PaletteLabels } from "./components/LpCommandPalette.vue";
39
+ export { applyQueryFilters, highlightParts, parseCommandQuery, parsePaletteQuery, rankCommandItems } from "./components/commandSearch";
40
+ export { commandHasPreview, pushRecent, readRecent, toRecentItem } from "./components/commandPalette";
41
+ export type { PaletteFact, PaletteStat, PreviewableCommand, RecentEntry, RecentItem } from "./components/commandPalette";
42
+ export { default as LpPalettePreview } from "./components/LpPalettePreview.vue";
43
+ export { default as LpPaletteHeader } from "./components/LpPaletteHeader.vue";
44
+ export { default as LpPaletteFacts } from "./components/LpPaletteFacts.vue";
45
+ export { default as LpPaletteStats } from "./components/LpPaletteStats.vue";
46
+ export { default as LpPaletteSwatches } from "./components/LpPaletteSwatches.vue";
47
+ export { default as LpKeys } from "./components/LpKeys.vue";
48
+ export { comboKeys, comboSteps } from "./components/keys";
49
+ export type { CommandSearchTerms, HighlightPart, PaletteQuery, SearchableCommand } from "./components/commandSearch";
41
50
  export { default as LpConfirmDialog } from "./components/LpConfirmDialog.vue";
42
51
  export type { ConfirmLabels } from "./components/LpConfirmDialog.vue";
43
52
  export { default as LpConfirmHost } from "./components/LpConfirmHost.vue";