@leavepulse/ui 0.40.1 → 0.41.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/{LpCommandPalette-DDGFwOom.css → LpCommandPalette-DyT6aGdW.css} +21 -21
- package/dist/assets/{LpShortcutsDialog-Cap_ALQM.css → LpShortcutsDialog-C6YoQ8_M.css} +7 -7
- package/dist/chunks/{LpAddressInput-H5NdhK6c.js → LpAddressInput-CMjaPQVw.js} +1 -1
- package/dist/chunks/{LpAppShell-BfqSA1ba.js → LpAppShell-Bcv6EhqU.js} +1 -1
- package/dist/chunks/{LpConfirmDialog-BBRnMkV4.js → LpConfirmDialog-BNAvOR24.js} +2 -2
- package/dist/chunks/{LpDrawer-B8vBnCWF.js → LpDrawer-DgaS2s2i.js} +6 -4
- package/dist/chunks/{LpModal-EyEdRuqF.js → LpModal-8bja5bWS.js} +5 -3
- package/dist/chunks/{LpModalNav-BrDtP7b_.js → LpModalNav-w9kfwHqO.js} +3 -3
- package/dist/chunks/{LpSidebar-DZ20iBxY.js → LpSidebar-DlTn5nXc.js} +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +2 -1
- package/dist/components/LpCommandPalette.vue.js +7 -5
- package/dist/components/LpConfirmDialog.vue.d.ts +2 -1
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpDrawer.vue.d.ts +4 -2
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpModal.vue.d.ts +2 -1
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.d.ts +2 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.d.ts +8 -11
- package/dist/components/LpShortcutsDialog.vue.js +14 -9
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/composables/useGlass.d.ts +9 -0
- package/dist/composables/useGlass.js +13 -0
- package/dist/composables/useHotkeys.d.ts +41 -2
- package/dist/composables/useHotkeys.js +57 -4
- package/dist/index.d.ts +3 -3
- package/dist/index.js +13 -8
- package/package.json +1 -1
- package/src/components/LpCommandPalette.vue +8 -4
- package/src/components/LpConfirmDialog.vue +2 -2
- package/src/components/LpDrawer.vue +9 -5
- package/src/components/LpModal.vue +8 -4
- package/src/components/LpModalNav.vue +2 -1
- package/src/components/LpShortcutsDialog.vue +32 -23
- package/src/composables/useGlass.ts +25 -0
- package/src/composables/useHotkeys.ts +107 -6
- package/src/index.ts +9 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { onBeforeUnmount, onMounted, type MaybeRefOrGetter, toValue } from "vue"
|
|
1
|
+
import { computed, onBeforeUnmount, onMounted, shallowReactive, type MaybeRefOrGetter, toValue } from "vue"
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
* One keydown listener over a registry of bindings, so a shortcut is declared
|
|
@@ -39,13 +39,43 @@ export interface HotkeyBinding {
|
|
|
39
39
|
* something there — Escape, and the palette the user reaches from anywhere.
|
|
40
40
|
*/
|
|
41
41
|
allowInInput?: boolean
|
|
42
|
+
/**
|
|
43
|
+
* How the key reference lists this binding while it is mounted. Bindings
|
|
44
|
+
* without it still work, they just stay out of `activeShortcuts`.
|
|
45
|
+
*/
|
|
46
|
+
help?: HotkeyHelp
|
|
42
47
|
/**
|
|
43
48
|
* Returning `false` declines the key: the event keeps its default and the
|
|
44
49
|
* search moves on. This is what lets a binding defer to a layer that owns the
|
|
45
50
|
* key better than it does — Escape belongs to whichever overlay is on top,
|
|
46
|
-
* and swallowing it here would close all of them.
|
|
51
|
+
* and swallowing it here would close all of them. The event is passed for
|
|
52
|
+
* the checks only it can answer: where the focus is, whether an inner
|
|
53
|
+
* control already took the key.
|
|
47
54
|
*/
|
|
48
|
-
handler: () => void | boolean
|
|
55
|
+
handler: (event: KeyboardEvent) => void | boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface HotkeyHelp {
|
|
59
|
+
/** Section of the reference the entry goes under. */
|
|
60
|
+
group: string
|
|
61
|
+
label: string
|
|
62
|
+
/**
|
|
63
|
+
* Written the way it reads, "+"-separated. Defaults to one built from the
|
|
64
|
+
* binding; set it where the binding is not the whole story, such as the
|
|
65
|
+
* second key of a chord ("G+D") or a key that needs Shift to type ("?").
|
|
66
|
+
*/
|
|
67
|
+
combo?: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ShortcutEntry {
|
|
71
|
+
/** Rendered one <kbd> per "+"-separated key, so write it the way it reads. */
|
|
72
|
+
combo: string
|
|
73
|
+
label: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ShortcutGroup {
|
|
77
|
+
title: string
|
|
78
|
+
entries: ShortcutEntry[]
|
|
49
79
|
}
|
|
50
80
|
|
|
51
81
|
/**
|
|
@@ -149,12 +179,83 @@ export function useHotkeys(
|
|
|
149
179
|
if (!matches(event, binding)) continue
|
|
150
180
|
// Claimed only once the handler has run and not declined, so a binding
|
|
151
181
|
// that stands down leaves the key untouched for whoever handles it next.
|
|
152
|
-
if (binding.handler() === false) continue
|
|
182
|
+
if (binding.handler(event) === false) continue
|
|
153
183
|
event.preventDefault()
|
|
154
184
|
return
|
|
155
185
|
}
|
|
156
186
|
}
|
|
157
187
|
|
|
158
|
-
|
|
159
|
-
|
|
188
|
+
const read = () => toValue(bindings)
|
|
189
|
+
onMounted(() => {
|
|
190
|
+
window.addEventListener("keydown", onKeydown)
|
|
191
|
+
mounted.add(read)
|
|
192
|
+
})
|
|
193
|
+
onBeforeUnmount(() => {
|
|
194
|
+
window.removeEventListener("keydown", onKeydown)
|
|
195
|
+
mounted.delete(read)
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/*
|
|
200
|
+
* Every mounted binding list, in mount order. The lists are getters, so the
|
|
201
|
+
* reference below follows the state behind them too: a binding that only
|
|
202
|
+
* exists while a mode is on shows up and goes away with that mode.
|
|
203
|
+
*/
|
|
204
|
+
const mounted = shallowReactive(new Set<() => HotkeyBinding[]>())
|
|
205
|
+
|
|
206
|
+
const KEY_NAMES: Record<string, string> = {
|
|
207
|
+
escape: "Esc",
|
|
208
|
+
enter: "Enter",
|
|
209
|
+
backspace: "Backspace",
|
|
210
|
+
delete: "Delete",
|
|
211
|
+
tab: "Tab",
|
|
212
|
+
" ": "Space",
|
|
213
|
+
arrowup: "↑",
|
|
214
|
+
arrowdown: "↓",
|
|
215
|
+
arrowleft: "←",
|
|
216
|
+
arrowright: "→",
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function comboOf(binding: HotkeyBinding): string {
|
|
220
|
+
const key = binding.key.toLowerCase()
|
|
221
|
+
const name = KEY_NAMES[key] ?? (key.length === 1 || /^f\d+$/.test(key) ? key.toUpperCase() : binding.key)
|
|
222
|
+
return [binding.ctrl && "Ctrl", binding.alt && "Alt", binding.shift && "Shift", name].filter(Boolean).join("+")
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* The shortcuts that work right now, grouped for `LpShortcutsDialog`: every
|
|
227
|
+
* binding with `help` from every mounted `useHotkeys`. A screen that binds its
|
|
228
|
+
* own keys adds its section while it is open, so the reference never lists a
|
|
229
|
+
* key that does nothing here or misses one that does.
|
|
230
|
+
*/
|
|
231
|
+
export const activeShortcuts = computed<ShortcutGroup[]>(() => {
|
|
232
|
+
const groups = new Map<string, ShortcutEntry[]>()
|
|
233
|
+
for (const read of mounted) {
|
|
234
|
+
for (const binding of read()) {
|
|
235
|
+
if (!binding.help) continue
|
|
236
|
+
const combo = binding.help.combo ?? comboOf(binding)
|
|
237
|
+
const entries = groups.get(binding.help.group) ?? []
|
|
238
|
+
if (!entries.some((e) => e.combo === combo)) entries.push({ combo, label: binding.help.label })
|
|
239
|
+
groups.set(binding.help.group, entries)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return [...groups].map(([title, entries]) => ({ title, entries }))
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Groups merged by title, entries kept once per combo: what a reference shows
|
|
247
|
+
* when it lists both its own entries and the live ones.
|
|
248
|
+
*/
|
|
249
|
+
export function mergeShortcutGroups(...lists: ShortcutGroup[][]): ShortcutGroup[] {
|
|
250
|
+
const groups = new Map<string, ShortcutEntry[]>()
|
|
251
|
+
for (const list of lists) {
|
|
252
|
+
for (const group of list) {
|
|
253
|
+
const entries = groups.get(group.title) ?? []
|
|
254
|
+
for (const entry of group.entries) {
|
|
255
|
+
if (!entries.some((e) => e.combo === entry.combo)) entries.push(entry)
|
|
256
|
+
}
|
|
257
|
+
groups.set(group.title, entries)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return [...groups].map(([title, entries]) => ({ title, entries }))
|
|
160
261
|
}
|
package/src/index.ts
CHANGED
|
@@ -141,7 +141,6 @@ export { default as LpServiceNode } from "./components/LpServiceNode.vue"
|
|
|
141
141
|
export type { ServiceNodeData } from "./components/LpServiceNode.vue"
|
|
142
142
|
export { default as LpShift } from "./components/LpShift.vue"
|
|
143
143
|
export { default as LpShortcutsDialog } from "./components/LpShortcutsDialog.vue"
|
|
144
|
-
export type { ShortcutEntry, ShortcutGroup } from "./components/LpShortcutsDialog.vue"
|
|
145
144
|
export { default as LpSidebar } from "./components/LpSidebar.vue"
|
|
146
145
|
export type { SidebarItem, SidebarSection } from "./components/LpSidebar.vue"
|
|
147
146
|
export { default as LpSkeleton } from "./components/LpSkeleton.vue"
|
|
@@ -228,8 +227,15 @@ export type { UiConfig } from "./theme/config"
|
|
|
228
227
|
|
|
229
228
|
export { useClipboard } from "./composables/useClipboard"
|
|
230
229
|
export type { CopyOptions, UseClipboard } from "./composables/useClipboard"
|
|
231
|
-
export {
|
|
232
|
-
|
|
230
|
+
export {
|
|
231
|
+
useHotkeys,
|
|
232
|
+
isAppleKeyboard,
|
|
233
|
+
appleModifierLabel,
|
|
234
|
+
activeShortcuts,
|
|
235
|
+
mergeShortcutGroups,
|
|
236
|
+
} from "./composables/useHotkeys"
|
|
237
|
+
export type { HotkeyBinding, HotkeyHelp, ShortcutEntry, ShortcutGroup } from "./composables/useHotkeys"
|
|
238
|
+
export { provideGlass, useGlass } from "./composables/useGlass"
|
|
233
239
|
export { useMergedClass, useMergedAttrs, mergeClass } from "./composables/useMergedClass"
|
|
234
240
|
export { useInputFilter } from "./composables/useInputFilter"
|
|
235
241
|
export type { UseInputFilterOptions } from "./composables/useInputFilter"
|