@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.
- package/dist/assets/HighlightText-BwC5xbqn.css +6 -0
- package/dist/assets/{LpCommandPalette-HzAA4gri.css → LpCommandPalette-DmQUsJqA.css} +21 -21
- package/dist/assets/{LpDropzone-i4BdMi1H.css → LpDropzone-Ev6X03zj.css} +12 -12
- package/dist/assets/LpKeys-BxRhfQda.css +36 -0
- package/dist/assets/LpPaletteFacts-B9FdVmIO.css +93 -0
- package/dist/assets/LpPaletteHeader-BVOu1UX5.css +70 -0
- package/dist/assets/LpPalettePreview-CJmAKgYW.css +43 -0
- package/dist/assets/LpPaletteStats-ClE-37MK.css +33 -0
- package/dist/assets/LpPaletteSwatches-CA0B8y2E.css +21 -0
- package/dist/assets/{LpShortcutsDialog-CqYX3yy6.css → LpShortcutsDialog-BUp_BX4s.css} +7 -28
- package/dist/assets/{LpTable-CPwWxTJS.css → LpTable-xWwI4KPZ.css} +17 -17
- package/dist/chunks/HighlightText-BvnYZkhi.js +27 -0
- package/dist/chunks/{LpAddressInput-CJOYGZlI.js → LpAddressInput-iOJhmRzy.js} +1 -1
- package/dist/chunks/{LpAppShell-B07Lf48e.js → LpAppShell-BmMYcEVx.js} +2 -2
- package/dist/chunks/{LpAvatar-C1XStdOo.js → LpAvatar-Cb1jtOVv.js} +22 -4
- package/dist/chunks/{LpCard-C2IvucmX.js → LpCard-BdxkMXjo.js} +1 -1
- package/dist/chunks/{LpColorPicker-zz4VGkG4.js → LpColorPicker-BLqPX5Vw.js} +1 -1
- package/dist/chunks/{LpConfirmDialog-BTVAR4tF.js → LpConfirmDialog-Ca-CTlxT.js} +1 -1
- package/dist/chunks/{LpConfirmHost-Ci_uCHgQ.js → LpConfirmHost-BAaSaT2b.js} +1 -1
- package/dist/chunks/{LpContextMenu-BqkAYa9X.js → LpContextMenu-1cKew_Dc.js} +17 -4
- package/dist/chunks/{LpDrawer-CfRhHwmE.js → LpDrawer-CBFVD624.js} +1 -1
- package/dist/chunks/{LpDropdownMenu-DdJGQzPn.js → LpDropdownMenu-DrpJJBp0.js} +22 -8
- package/dist/chunks/{LpFileTree-CNurOvkV.js → LpFileTree-bk5VjBey.js} +2 -2
- package/dist/chunks/{LpFileTreeNode-D_qRRdsH.js → LpFileTreeNode-BvMSBM7X.js} +1 -1
- package/dist/chunks/{LpLightbox-DSDahF23.js → LpLightbox-EVXzAq62.js} +3 -3
- package/dist/chunks/{LpLogViewer-KypbgOyo.js → LpLogViewer-BDFXzvcl.js} +2 -2
- package/dist/chunks/{LpMediaGallery-YKr-cQPN.js → LpMediaGallery-BnlkN_1f.js} +1 -1
- package/dist/chunks/{LpModal-BzTVeNqe.js → LpModal-JNa3B_Lr.js} +12 -5
- package/dist/chunks/{LpModalNav-CjABSGCu.js → LpModalNav-BAxC9et7.js} +10 -3
- package/dist/chunks/{LpNotificationBell-CCwyJP5C.js → LpNotificationBell-DsqSyMwS.js} +2 -2
- package/dist/chunks/LpPalettePreview-CFav_7co.js +19 -0
- package/dist/chunks/{LpPie-BEJ2SfI6.js → LpPie-BdSnta11.js} +1 -1
- package/dist/chunks/{LpScrollArea-Bry1fdOM.js → LpScrollArea-Hz3-JWUG.js} +23 -5
- package/dist/chunks/{LpSidebar-Bwws_X29.js → LpSidebar-DpEiGi8L.js} +2 -2
- package/dist/chunks/{LpSidebarNav-kx-LdqNU.js → LpSidebarNav-BUuxipX-.js} +2 -2
- package/dist/chunks/{LpThemeSwitcher-B4aVPbNC.js → LpThemeSwitcher-BbYFFT5w.js} +1 -1
- package/dist/chunks/MenuTitle-J3ZDYjpV.js +56 -0
- package/dist/component-names.d.ts +1 -1
- package/dist/components/HighlightText.vue.d.ts +7 -0
- package/dist/components/HighlightText.vue.js +4 -0
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAvatar.vue.d.ts +12 -0
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +152 -15
- package/dist/components/LpCommandPalette.vue.js +181 -92
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.js +1 -1
- package/dist/components/LpContextMenu.vue.d.ts +11 -0
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.d.ts +6 -0
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.d.ts +14 -0
- package/dist/components/LpDropzone.vue.js +9 -7
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpKeys.vue.d.ts +22 -0
- package/dist/components/LpKeys.vue.js +52 -0
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.d.ts +7 -5
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.d.ts +5 -3
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpPaletteFacts.vue.d.ts +25 -0
- package/dist/components/LpPaletteFacts.vue.js +85 -0
- package/dist/components/LpPaletteHeader.vue.d.ts +32 -0
- package/dist/components/LpPaletteHeader.vue.js +103 -0
- package/dist/components/LpPalettePreview.vue.d.ts +19 -0
- package/dist/components/LpPalettePreview.vue.js +4 -0
- package/dist/components/LpPaletteStats.vue.d.ts +11 -0
- package/dist/components/LpPaletteStats.vue.js +47 -0
- package/dist/components/LpPaletteSwatches.vue.d.ts +10 -0
- package/dist/components/LpPaletteSwatches.vue.js +34 -0
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpScrollArea.vue.d.ts +2 -2
- package/dist/components/LpScrollArea.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +9 -29
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpTable.vue.js +8 -6
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/MenuTitle.vue.d.ts +9 -0
- package/dist/components/MenuTitle.vue.js +4 -0
- package/dist/components/commandPalette.d.ts +157 -0
- package/dist/components/commandPalette.js +103 -0
- package/dist/components/commandSearch.d.ts +32 -0
- package/dist/components/commandSearch.js +48 -0
- package/dist/components/keys.d.ts +2 -0
- package/dist/components/keys.js +11 -0
- package/dist/composables/useClipboard.js +35 -2
- package/dist/index.d.ts +12 -3
- package/dist/index.js +161 -138
- package/package.json +1 -1
- package/src/component-names.ts +6 -0
- package/src/components/HighlightText.vue +28 -0
- package/src/components/LpAvatar.vue +21 -2
- package/src/components/LpCommandPalette.vue +392 -86
- package/src/components/LpContextMenu.vue +19 -1
- package/src/components/LpDropdownMenu.vue +14 -2
- package/src/components/LpDropzone.vue +13 -5
- package/src/components/LpKeys.vue +79 -0
- package/src/components/LpLightbox.vue +4 -1
- package/src/components/LpModal.vue +7 -1
- package/src/components/LpModalNav.vue +4 -0
- package/src/components/LpPaletteFacts.vue +165 -0
- package/src/components/LpPaletteHeader.vue +155 -0
- package/src/components/LpPalettePreview.vue +77 -0
- package/src/components/LpPaletteStats.vue +81 -0
- package/src/components/LpPaletteSwatches.vue +56 -0
- package/src/components/LpScrollArea.vue +30 -2
- package/src/components/LpShortcutsDialog.vue +2 -45
- package/src/components/LpTable.vue +7 -2
- package/src/components/MenuTitle.vue +34 -0
- package/src/components/commandPalette.ts +264 -0
- package/src/components/commandSearch.ts +79 -0
- package/src/components/keys.ts +13 -0
- package/src/composables/useClipboard.ts +49 -7
- 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.
|
|
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
|
-
|
|
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
|
|
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"
|