@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
@@ -79,3 +79,82 @@ export function rankCommandItems<T extends SearchableCommand>(items: T[], terms:
79
79
  .sort((a, b) => b.score - a.score || a.command.label.localeCompare(b.command.label))
80
80
  .map((entry) => entry.command)
81
81
  }
82
+
83
+ /**
84
+ * A query as a fetcher receives it. `text` is what a remote search should be
85
+ * sent: the positive words and phrases, in the case they were typed, without
86
+ * the operators a server would not understand. The operators come alongside
87
+ * as data, so a provider that can scope or exclude on the server may.
88
+ */
89
+ export interface PaletteQuery extends CommandSearchTerms {
90
+ /** The box as typed, operators and all. */
91
+ raw: string
92
+ /** Positive terms joined by spaces, case kept, quotes dropped. */
93
+ text: string
94
+ }
95
+
96
+ export function parsePaletteQuery(raw: string): PaletteQuery {
97
+ const terms = parseCommandQuery(raw)
98
+ const words: string[] = []
99
+ QUERY_TOKEN.lastIndex = 0
100
+ for (let match; (match = QUERY_TOKEN.exec(raw)); ) {
101
+ const text = (match[3] ?? match[4] ?? "").trim()
102
+ if (text && !match[1] && !match[2]) words.push(text)
103
+ }
104
+ return { ...terms, raw, text: words.join(" ") }
105
+ }
106
+
107
+ /**
108
+ * The operators applied to results someone else matched: a server answered
109
+ * the positive words, and `-word` and `in:group` still have to hold for what
110
+ * it sent back. Positive terms are not re-checked here; the server may match
111
+ * word forms the text does not contain.
112
+ */
113
+ export function applyQueryFilters<T extends SearchableCommand>(items: T[], terms: CommandSearchTerms): T[] {
114
+ if (!terms.exclude.length && !terms.groups.length) return items
115
+ return items.filter((command) => {
116
+ const group = command.group?.toLowerCase() ?? ""
117
+ return !terms.exclude.some((term) => matches(command, term)) && terms.groups.every((term) => group.includes(term))
118
+ })
119
+ }
120
+
121
+ /** A run of text, and whether it matched the query. */
122
+ export interface HighlightPart {
123
+ text: string
124
+ hit: boolean
125
+ }
126
+
127
+ /**
128
+ * Text split around the terms that matched, for <mark> without v-html. Terms
129
+ * shorter than two characters are skipped (one letter would mark half the
130
+ * text), longer ones win where two start at the same place, and case is
131
+ * ignored. Pass positive terms only: an excluded word is never a match.
132
+ */
133
+ export function highlightParts(value: string, terms: readonly string[]): HighlightPart[] {
134
+ const sorted = [...new Set(terms.map((t) => t.toLowerCase()).filter((t) => t.length >= 2))].sort(
135
+ (a, b) => b.length - a.length,
136
+ )
137
+ if (!value || sorted.length === 0) return value ? [{ text: value, hit: false }] : []
138
+ const lower = value.toLowerCase()
139
+ const parts: HighlightPart[] = []
140
+ let i = 0
141
+ while (i < value.length) {
142
+ let at = -1
143
+ let term = ""
144
+ for (const t of sorted) {
145
+ const found = lower.indexOf(t, i)
146
+ if (found !== -1 && (at === -1 || found < at || (found === at && t.length > term.length))) {
147
+ at = found
148
+ term = t
149
+ }
150
+ }
151
+ if (at === -1) {
152
+ parts.push({ text: value.slice(i), hit: false })
153
+ break
154
+ }
155
+ if (at > i) parts.push({ text: value.slice(i, at), hit: false })
156
+ parts.push({ text: value.slice(at, at + term.length), hit: true })
157
+ i = at + term.length
158
+ }
159
+ return parts
160
+ }
@@ -0,0 +1,13 @@
1
+ /*
2
+ * How a written combo splits into caps. One key per "+", except a "+" that is
3
+ * the key itself: "Ctrl++" is Ctrl and plus, not Ctrl and two blanks. Spaces
4
+ * separate the steps of a sequence: "G I" reads "G then I".
5
+ */
6
+ export function comboKeys(chord: string): string[] {
7
+ return chord.split(/\+(?!$)/)
8
+ }
9
+
10
+ export function comboSteps(combo: string): string[][] {
11
+ const trimmed = combo.trim()
12
+ return trimmed ? trimmed.split(/\s+/).map(comboKeys) : []
13
+ }
@@ -13,7 +13,9 @@ import { useToast } from "./useToast"
13
13
  *
14
14
  * Why every call is allowed to fail: the Clipboard API needs a secure context
15
15
  * and, in some browsers, a permission. It is absent over plain http, rejects
16
- * outside a user gesture, and is missing entirely in a non-DOM test runner. A
16
+ * outside a user gesture, and is missing entirely in a non-DOM test runner.
17
+ * Where it is missing or refuses, the old copy command is tried before
18
+ * giving up (see `writeLegacy`). A
17
19
  * copy that cannot happen is a disappointment, not an exception, and a rejected
18
20
  * promise reaching a component means an unhandled rejection in the console of
19
21
  * an app that did nothing wrong.
@@ -23,6 +25,51 @@ import { useToast } from "./useToast"
23
25
  * than any message, and the two are complementary rather than alternatives.
24
26
  */
25
27
 
28
+ /** The Clipboard API, where there is one and it agrees. */
29
+ async function writeAsync(text: string): Promise<boolean> {
30
+ if (typeof navigator === "undefined" || !navigator.clipboard) return false
31
+ try {
32
+ await navigator.clipboard.writeText(text)
33
+ return true
34
+ } catch {
35
+ return false
36
+ }
37
+ }
38
+
39
+ /*
40
+ * The old copy command, for where the Clipboard API is missing or refuses.
41
+ * Embedded Chromium (CEF in a Tauri or Electron-like shell) rejects
42
+ * `writeText` when the host answers no permission requests, and plain http
43
+ * has no API at all, while a copy command run inside the click still works
44
+ * there. It copies the selection, so a throwaway text field holds the text
45
+ * and the page's own selection and focus are put back afterwards.
46
+ */
47
+ function writeLegacy(text: string): boolean {
48
+ if (typeof document === "undefined") return false
49
+ const active = document.activeElement as HTMLElement | null
50
+ const selection = document.getSelection()
51
+ const ranges = selection ? Array.from({ length: selection.rangeCount }, (_, i) => selection.getRangeAt(i)) : []
52
+ const field = document.createElement("textarea")
53
+ field.value = text
54
+ field.setAttribute("readonly", "")
55
+ field.style.cssText = "position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;pointer-events:none"
56
+ document.body.appendChild(field)
57
+ field.select()
58
+ let ok = false
59
+ try {
60
+ ok = document.execCommand("copy")
61
+ } catch {
62
+ ok = false
63
+ }
64
+ field.remove()
65
+ if (selection) {
66
+ selection.removeAllRanges()
67
+ for (const range of ranges) selection.addRange(range)
68
+ }
69
+ active?.focus?.({ preventScroll: true })
70
+ return ok
71
+ }
72
+
26
73
  /** How long `copied` stays true — long enough to read, short enough to not linger. */
27
74
  const COPIED_MS = 1400
28
75
 
@@ -81,12 +128,7 @@ export function useClipboard(): UseClipboard {
81
128
  async function copy(text: string, options: CopyOptions = {}): Promise<boolean> {
82
129
  const { toast = false, errorToast = false } = options
83
130
  try {
84
- // Optional-chained: `navigator.clipboard` is undefined, not throwing, on
85
- // an insecure origin, and that should take the same path as a rejection.
86
- await navigator.clipboard?.writeText(text)
87
- // A missing API resolves `undefined` rather than rejecting, so confirm
88
- // the write was actually possible before claiming it happened.
89
- if (!navigator.clipboard) throw new Error("clipboard unavailable")
131
+ if (!(await writeAsync(text)) && !writeLegacy(text)) throw new Error("clipboard unavailable")
90
132
 
91
133
  copied.value = true
92
134
  clearTimeout(timer)
package/src/index.ts CHANGED
@@ -71,9 +71,18 @@ export {
71
71
  } from "./components/color"
72
72
  export type { ColorFormat, Hsv, Rgb } from "./components/color"
73
73
  export { default as LpCommandPalette } from "./components/LpCommandPalette.vue"
74
- export type { Command, PaletteLabels } from "./components/LpCommandPalette.vue"
75
- export { parseCommandQuery, rankCommandItems } from "./components/commandSearch"
76
- export type { CommandSearchTerms, SearchableCommand } from "./components/commandSearch"
74
+ export type { Command, CommandFetcher, CommandImage, PaletteLabels } from "./components/LpCommandPalette.vue"
75
+ export { applyQueryFilters, highlightParts, parseCommandQuery, parsePaletteQuery, rankCommandItems } from "./components/commandSearch"
76
+ export { commandHasPreview, pushRecent, readRecent, toRecentItem } from "./components/commandPalette"
77
+ export type { PaletteFact, PaletteStat, PreviewableCommand, RecentEntry, RecentItem } from "./components/commandPalette"
78
+ export { default as LpPalettePreview } from "./components/LpPalettePreview.vue"
79
+ export { default as LpPaletteHeader } from "./components/LpPaletteHeader.vue"
80
+ export { default as LpPaletteFacts } from "./components/LpPaletteFacts.vue"
81
+ export { default as LpPaletteStats } from "./components/LpPaletteStats.vue"
82
+ export { default as LpPaletteSwatches } from "./components/LpPaletteSwatches.vue"
83
+ export { default as LpKeys } from "./components/LpKeys.vue"
84
+ export { comboKeys, comboSteps } from "./components/keys"
85
+ export type { CommandSearchTerms, HighlightPart, PaletteQuery, SearchableCommand } from "./components/commandSearch"
77
86
  export { default as LpConfirmDialog } from "./components/LpConfirmDialog.vue"
78
87
  export type { ConfirmLabels } from "./components/LpConfirmDialog.vue"
79
88
  export { default as LpConfirmHost } from "./components/LpConfirmHost.vue"