@leavepulse/ui 0.90.0 → 0.91.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.
@@ -5,28 +5,28 @@
5
5
  * pane from its right edge as it grows. Animating the columns themselves made
6
6
  * every line of the list and the pane re-wrap on every frame.
7
7
  */
8
- .lp-cmd-dialog[data-v-1801065c] {
8
+ .lp-cmd-dialog[data-v-d51dc98f] {
9
9
  --lp-cmd-list: min(92vw, 40rem);
10
10
  --lp-cmd-pane: max(0px, min(26rem, 96vw - var(--lp-cmd-list)));
11
11
  width: var(--lp-cmd-list);
12
12
  }
13
- .lp-cmd-split[data-v-1801065c] {
13
+ .lp-cmd-split[data-v-d51dc98f] {
14
14
  transition:
15
15
  width 300ms var(--ease-emphasized, ease),
16
16
  min-height 300ms var(--ease-emphasized, ease);
17
17
  }
18
18
  @media (min-width: 768px) {
19
- .lp-cmd-split .lp-cmd-list[data-v-1801065c] {
19
+ .lp-cmd-split .lp-cmd-list[data-v-d51dc98f] {
20
20
  flex: 0 0 auto;
21
21
  /* the dialog's 1px border on each side */
22
22
  width: calc(var(--lp-cmd-list) - 2px);
23
23
  }
24
- .lp-cmd-dialog[data-pane="open"][data-v-1801065c] {
24
+ .lp-cmd-dialog[data-pane="open"][data-v-d51dc98f] {
25
25
  width: calc(var(--lp-cmd-list) + var(--lp-cmd-pane));
26
26
  min-height: min(76vh, 36rem);
27
27
  }
28
28
  }
29
- .lp-cmd-pane[data-v-1801065c] {
29
+ .lp-cmd-pane[data-v-d51dc98f] {
30
30
  width: var(--lp-cmd-pane);
31
31
  }
32
32
  /*
@@ -36,7 +36,7 @@
36
36
  * desktop window keeps the card.
37
37
  */
38
38
  @media (hover: none) and (pointer: coarse) and (max-width: 639px) {
39
- .lp-cmd-dialog[data-v-1801065c] {
39
+ .lp-cmd-dialog[data-v-d51dc98f] {
40
40
  inset: var(--lp-cmd-vy, 0) auto auto var(--lp-cmd-vx, 0);
41
41
  translate: none;
42
42
  width: var(--lp-cmd-vw, 100%);
@@ -48,10 +48,10 @@
48
48
  padding-bottom: env(safe-area-inset-bottom);
49
49
  }
50
50
  }
51
- .lp-cmd-dialog[data-editing] .lp-cmd-pane[data-v-1801065c] {
51
+ .lp-cmd-dialog[data-editing] .lp-cmd-pane[data-v-d51dc98f] {
52
52
  width: 100%;
53
53
  }
54
- .lp-cmd-dialog[data-editing][data-v-1801065c] {
54
+ .lp-cmd-dialog[data-editing][data-v-d51dc98f] {
55
55
  width: var(--lp-cmd-list);
56
56
  min-height: 0;
57
57
  }
@@ -59,43 +59,43 @@
59
59
  * The pane rides in from the right edge while the dialog opens up to it, and
60
60
  * leaves the same way, for as long as the dialog takes to close over it.
61
61
  */
62
- .lp-cmd-pane-enter-active[data-v-1801065c],
63
- .lp-cmd-pane-leave-active[data-v-1801065c] {
62
+ .lp-cmd-pane-enter-active[data-v-d51dc98f],
63
+ .lp-cmd-pane-leave-active[data-v-d51dc98f] {
64
64
  transition:
65
65
  transform 300ms var(--ease-emphasized, ease),
66
66
  opacity 300ms ease;
67
67
  }
68
- .lp-cmd-pane-enter-from[data-v-1801065c],
69
- .lp-cmd-pane-leave-to[data-v-1801065c] {
68
+ .lp-cmd-pane-enter-from[data-v-d51dc98f],
69
+ .lp-cmd-pane-leave-to[data-v-d51dc98f] {
70
70
  opacity: 0;
71
71
  transform: translateX(40%);
72
72
  }
73
- .lp-cmd-para[data-v-1801065c] {
73
+ .lp-cmd-para[data-v-d51dc98f] {
74
74
  --rise: 4px;
75
75
  animation: rise-in 260ms ease-out both;
76
76
  }
77
77
  @media (prefers-reduced-motion: reduce) {
78
- .lp-cmd-split[data-v-1801065c],
79
- .lp-cmd-pane-enter-active[data-v-1801065c],
80
- .lp-cmd-pane-leave-active[data-v-1801065c] {
78
+ .lp-cmd-split[data-v-d51dc98f],
79
+ .lp-cmd-pane-enter-active[data-v-d51dc98f],
80
+ .lp-cmd-pane-leave-active[data-v-d51dc98f] {
81
81
  transition: none;
82
82
  }
83
- .lp-cmd-para[data-v-1801065c] {
83
+ .lp-cmd-para[data-v-d51dc98f] {
84
84
  animation: none;
85
85
  }
86
86
  }
87
- .lp-cmd-excerpt[data-v-1801065c] {
87
+ .lp-cmd-excerpt[data-v-d51dc98f] {
88
88
  display: -webkit-box;
89
89
  -webkit-box-orient: vertical;
90
90
  overflow: hidden;
91
91
  }
92
92
  /* Each result eases in; honours reduced motion. */
93
- .lp-cmd-item[data-v-1801065c] {
93
+ .lp-cmd-item[data-v-d51dc98f] {
94
94
  --rise: 3px;
95
95
  animation: rise-in 140ms ease-out both;
96
96
  }
97
97
  @media (prefers-reduced-motion: reduce) {
98
- .lp-cmd-item[data-v-1801065c] {
98
+ .lp-cmd-item[data-v-d51dc98f] {
99
99
  animation: none;
100
100
  }
101
101
  }
@@ -53,11 +53,19 @@ export interface Command {
53
53
  group?: string;
54
54
  disabled?: boolean;
55
55
  /**
56
- * Hidden from the default (empty-query) list — only surfaces once the user
57
- * types something it matches. For rarely-used or destructive actions (prune,
58
- * decommission, …) that shouldn't clutter the palette but stay reachable.
56
+ * Hidden from the default (empty-query) list and surfaced only when the query
57
+ * names it: every typed word starts a word of its label or keywords. A word
58
+ * inside another word, the description or a near miss does not reveal it.
59
+ * For rarely-used or destructive actions (prune, decommission, …) that
60
+ * shouldn't clutter the palette but stay reachable.
59
61
  */
60
62
  hidden?: boolean;
63
+ /**
64
+ * For a hidden command: the words that reveal it, instead of its label and
65
+ * keywords. A variant whose label repeats its parent's word ("Vacation: sick
66
+ * leave…") then stays out of a search for the parent and shows up for "sick".
67
+ */
68
+ revealOn?: string[];
61
69
  /**
62
70
  * Picking this opens a mode instead of closing: a nested list, either given
63
71
  * outright or fetched from the query typed inside it. This is what keeps a
@@ -26,7 +26,7 @@ import { parsePaletteQuery, parseCommandQuery, searchCommandItems, applyQueryFil
26
26
  import { PALETTE_MODES, modeChipLabel, settleFetched, fallbackRows, readRecent, resolveRecentItems, recentVisible, commandHasPreview, matchModePrefix, isSafeHref, absoluteHref, commandKey, recentKey, pushRecent, moreRowId, toRecentItem } from "./commandPalette.js";
27
27
  import { useView, usePortalTarget, useOutsideGuard, viewWindow } from "../composables/useView.js";
28
28
  import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
29
- import '../assets/LpCommandPalette-CO_5TFOQ.css';const _hoisted_1 = { class: "relative flex shrink-0 items-center gap-2.5 border-b border-line px-4" };
29
+ import '../assets/LpCommandPalette-D9ZuNQmc.css';const _hoisted_1 = { class: "relative flex shrink-0 items-center gap-2.5 border-b border-line px-4" };
30
30
  const _hoisted_2 = ["title", "onClick"];
31
31
  const _hoisted_3 = ["readonly", "aria-label", "aria-expanded", "aria-controls", "aria-activedescendant", "placeholder", "onKeydown"];
32
32
  const _hoisted_4 = { class: "shrink-0 rounded-small border border-line px-1.5 py-0.5 text-[10px] text-muted [@media(hover:none)_and_(pointer:coarse)]:hidden" };
@@ -1400,7 +1400,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1400
1400
  };
1401
1401
  }
1402
1402
  });
1403
- const LpCommandPalette = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-1801065c"]]);
1403
+ const LpCommandPalette = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-d51dc98f"]]);
1404
1404
  export {
1405
1405
  LpCommandPalette as default
1406
1406
  };
@@ -3,6 +3,8 @@ export interface SearchableCommand {
3
3
  description?: string;
4
4
  keywords?: string[];
5
5
  group?: string;
6
+ hidden?: boolean;
7
+ revealOn?: string[];
6
8
  }
7
9
  export interface CommandSearchTerms {
8
10
  include: string[];
@@ -26,9 +26,17 @@ function nearWord(term, label) {
26
26
  if (term.length < 5) return false;
27
27
  return label.split(/[^\p{L}\p{N}]+/u).some((word) => withinOne(term, word) || word.length > term.length && withinOne(term, word.slice(0, term.length)));
28
28
  }
29
+ function wordStarts(text, term) {
30
+ return text.split(/[^\p{L}\p{N}]+/u).some((word) => word.startsWith(term));
31
+ }
32
+ function named(command, terms) {
33
+ const words = command.revealOn ? command.revealOn.map((word) => word.toLowerCase()) : [command.label.toLowerCase(), ...(command.keywords ?? []).map((keyword) => keyword.toLowerCase())];
34
+ return terms.include.length > 0 && terms.include.every((term) => words.some((text) => wordStarts(text, term)));
35
+ }
29
36
  function score(command, terms) {
37
+ if (command.hidden && !named(command, terms)) return 0;
30
38
  const label = command.label.toLowerCase();
31
- const keywords = (command.keywords ?? []).map((keyword) => keyword.toLowerCase());
39
+ const keywords = [...command.keywords ?? [], ...command.revealOn ?? []].map((keyword) => keyword.toLowerCase());
32
40
  const description = command.description?.toLowerCase() ?? "";
33
41
  const group = command.group?.toLowerCase() ?? "";
34
42
  if (terms.exclude.some((term) => matches(command, term)) || terms.groups.some((term) => !group.includes(term))) return 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.90.0",
3
+ "version": "0.91.0",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -143,11 +143,19 @@ export interface Command {
143
143
  group?: string
144
144
  disabled?: boolean
145
145
  /**
146
- * Hidden from the default (empty-query) list — only surfaces once the user
147
- * types something it matches. For rarely-used or destructive actions (prune,
148
- * decommission, …) that shouldn't clutter the palette but stay reachable.
146
+ * Hidden from the default (empty-query) list and surfaced only when the query
147
+ * names it: every typed word starts a word of its label or keywords. A word
148
+ * inside another word, the description or a near miss does not reveal it.
149
+ * For rarely-used or destructive actions (prune, decommission, …) that
150
+ * shouldn't clutter the palette but stay reachable.
149
151
  */
150
152
  hidden?: boolean
153
+ /**
154
+ * For a hidden command: the words that reveal it, instead of its label and
155
+ * keywords. A variant whose label repeats its parent's word ("Vacation: sick
156
+ * leave…") then stays out of a search for the parent and shows up for "sick".
157
+ */
158
+ revealOn?: string[]
151
159
  /**
152
160
  * Picking this opens a mode instead of closing: a nested list, either given
153
161
  * outright or fetched from the query typed inside it. This is what keeps a
@@ -902,8 +910,8 @@ const local = computed<{ items: Command[]; terms: string[] }>(() => {
902
910
  const filtered = query.value.trim()
903
911
  ? source.value.filter((c) => !c.fallback)
904
912
  : source.value.filter((c) => !c.hidden && !c.fallback)
905
- // Empty query: show everything EXCEPT hidden commands (they only surface once
906
- // typed). With a query, hidden commands rank like any other.
913
+ // Empty query: show everything EXCEPT hidden commands. With a query, a hidden
914
+ // command ranks only when the query names it (see commandSearch).
907
915
  if (!terms.include.length && !terms.exclude.length && !terms.groups.length) return { items: filtered, terms: [] }
908
916
  return searchCommandItems(filtered, terms)
909
917
  })
@@ -3,6 +3,8 @@ export interface SearchableCommand {
3
3
  description?: string
4
4
  keywords?: string[]
5
5
  group?: string
6
+ hidden?: boolean
7
+ revealOn?: string[]
6
8
  }
7
9
 
8
10
  export interface CommandSearchTerms {
@@ -50,9 +52,29 @@ function nearWord(term: string, label: string): boolean {
50
52
  .some((word) => withinOne(term, word) || (word.length > term.length && withinOne(term, word.slice(0, term.length))))
51
53
  }
52
54
 
55
+ function wordStarts(text: string, term: string): boolean {
56
+ return text.split(/[^\p{L}\p{N}]+/u).some((word) => word.startsWith(term))
57
+ }
58
+
59
+ /*
60
+ * A hidden command is there for whoever names it, not for whoever types a word
61
+ * it happens to contain. Every term has to start a word of its label or of a
62
+ * keyword (or of `revealOn`, which then replaces both): "пт" no longer digs a
63
+ * settings key out of "Транскрипты", and "vacation" no longer spills every
64
+ * hidden variant of it. Descriptions, mid-word hits and near misses do not
65
+ * reveal; the visible list keeps all of those.
66
+ */
67
+ function named(command: SearchableCommand, terms: CommandSearchTerms): boolean {
68
+ const words = command.revealOn
69
+ ? command.revealOn.map((word) => word.toLowerCase())
70
+ : [command.label.toLowerCase(), ...(command.keywords ?? []).map((keyword) => keyword.toLowerCase())]
71
+ return terms.include.length > 0 && terms.include.every((term) => words.some((text) => wordStarts(text, term)))
72
+ }
73
+
53
74
  function score(command: SearchableCommand, terms: CommandSearchTerms): number {
75
+ if (command.hidden && !named(command, terms)) return 0
54
76
  const label = command.label.toLowerCase()
55
- const keywords = (command.keywords ?? []).map((keyword) => keyword.toLowerCase())
77
+ const keywords = [...(command.keywords ?? []), ...(command.revealOn ?? [])].map((keyword) => keyword.toLowerCase())
56
78
  const description = command.description?.toLowerCase() ?? ""
57
79
  const group = command.group?.toLowerCase() ?? ""
58
80
  if (terms.exclude.some((term) => matches(command, term)) || terms.groups.some((term) => !group.includes(term))) return 0