@leavepulse/ui 0.20.9 → 0.22.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-C3LdDNHk.css → LpCommandPalette-CEY-pajO.css} +4 -4
- package/dist/assets/LpShortcutsDialog-B7H-RZLQ.css +55 -0
- package/dist/chunks/{LpAddressInput-B411MOJa.js → LpAddressInput-Dah7asXy.js} +1 -1
- package/dist/chunks/{LpAppShell-CfM7fdr3.js → LpAppShell-DGZv907S.js} +2 -2
- package/dist/chunks/{LpConfirmDialog-D8pBGPOI.js → LpConfirmDialog-CkMzeKdp.js} +1 -1
- package/dist/chunks/{LpDrawer-B4cNj39a.js → LpDrawer-DAJN5rGK.js} +1 -1
- package/dist/chunks/{LpFileTree-d9nXtSVo.js → LpFileTree-DWx77B1F.js} +1 -1
- package/dist/chunks/{LpLogViewer-BXPBUV21.js → LpLogViewer-B1aLUVdz.js} +1 -1
- package/dist/chunks/{LpModal-DKFdzKwV.js → LpModal-CNAFRgCU.js} +36 -3
- package/dist/chunks/{LpNotificationBell-Nges4kJk.js → LpNotificationBell-HSGo1pcf.js} +1 -1
- package/dist/chunks/{LpScrollArea-Du8tW8c5.js → LpScrollArea-D8xyKXcF.js} +10 -2
- package/dist/chunks/{LpSidebar-Bd0148dd.js → LpSidebar-JvMaaYsy.js} +2 -2
- package/dist/chunks/{LpSidebarNav-BqGpNr4S.js → LpSidebarNav-BIYzk7e_.js} +1 -1
- package/dist/chunks/{LpTable-BBKlNR9j.js → LpTable-DTj52qlg.js} +45 -9
- package/dist/chunks/{LpTooltip-CIwoRCag.js → LpTooltip-D1hbjQWG.js} +23 -9
- package/dist/chunks/_plugin-vue_export-helper-1tPrXgE0.js +10 -0
- package/dist/component-names.d.ts +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +17 -21
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpModal.vue.d.ts +16 -5
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpScrollArea.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.d.ts +25 -0
- package/dist/components/LpShortcutsDialog.vue.js +63 -0
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpTable.vue.js +1 -1
- package/dist/components/LpTooltip.vue.d.ts +19 -1
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/composables/useHotkeys.d.ts +45 -0
- package/dist/composables/useHotkeys.js +53 -0
- package/dist/composables/useRovingFocus.d.ts +49 -0
- package/dist/composables/useRovingFocus.js +160 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +20 -12
- package/package.json +1 -1
- package/src/component-names.ts +1 -0
- package/src/components/LpCommandPalette.vue +18 -11
- package/src/components/LpModal.vue +67 -1
- package/src/components/LpScrollArea.vue +26 -6
- package/src/components/LpShortcutsDialog.vue +127 -0
- package/src/components/LpTable.vue +57 -2
- package/src/components/LpTooltip.vue +57 -5
- package/src/composables/useHotkeys.ts +131 -0
- package/src/composables/useRovingFocus.ts +275 -0
- package/src/index.ts +6 -0
- package/src/tokens/utilities.css +15 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { defineComponent, openBlock, createBlock, withCtx, createElementVNode, createElementBlock, Fragment, renderList, toDisplayString, unref } from "vue";
|
|
2
|
+
import { _ as _sfc_main$1 } from "../chunks/LpModal-CNAFRgCU.js";
|
|
3
|
+
import { appleModifierLabel } from "../composables/useHotkeys.js";
|
|
4
|
+
import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
|
|
5
|
+
import '../assets/LpShortcutsDialog-B7H-RZLQ.css';const _hoisted_1 = { class: "shortcut-groups" };
|
|
6
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
7
|
+
__name: "LpShortcutsDialog",
|
|
8
|
+
props: {
|
|
9
|
+
open: { type: Boolean, default: false },
|
|
10
|
+
groups: {},
|
|
11
|
+
title: { default: "Keyboard shortcuts" },
|
|
12
|
+
description: {}
|
|
13
|
+
},
|
|
14
|
+
emits: ["update:open"],
|
|
15
|
+
setup(__props, { emit: __emit }) {
|
|
16
|
+
const props = __props;
|
|
17
|
+
const emit = __emit;
|
|
18
|
+
return (_ctx, _cache) => {
|
|
19
|
+
return openBlock(), createBlock(_sfc_main$1, {
|
|
20
|
+
open: props.open,
|
|
21
|
+
size: "lg",
|
|
22
|
+
title: props.title,
|
|
23
|
+
description: props.description,
|
|
24
|
+
"onUpdate:open": _cache[0] || (_cache[0] = (value) => emit("update:open", value))
|
|
25
|
+
}, {
|
|
26
|
+
default: withCtx(() => [
|
|
27
|
+
createElementVNode("div", _hoisted_1, [
|
|
28
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(props.groups, (group) => {
|
|
29
|
+
return openBlock(), createElementBlock("section", {
|
|
30
|
+
key: group.title,
|
|
31
|
+
class: "shortcut-group"
|
|
32
|
+
}, [
|
|
33
|
+
createElementVNode("h3", null, toDisplayString(group.title), 1),
|
|
34
|
+
createElementVNode("dl", null, [
|
|
35
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(group.entries, (entry) => {
|
|
36
|
+
return openBlock(), createElementBlock("div", {
|
|
37
|
+
key: entry.combo,
|
|
38
|
+
class: "shortcut-row"
|
|
39
|
+
}, [
|
|
40
|
+
createElementVNode("dt", null, toDisplayString(entry.label), 1),
|
|
41
|
+
createElementVNode("dd", null, [
|
|
42
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(entry.combo.split("+"), (key, index) => {
|
|
43
|
+
return openBlock(), createElementBlock("kbd", {
|
|
44
|
+
key: `${key}-${index}`
|
|
45
|
+
}, toDisplayString(unref(appleModifierLabel)(key.trim())), 1);
|
|
46
|
+
}), 128))
|
|
47
|
+
])
|
|
48
|
+
]);
|
|
49
|
+
}), 128))
|
|
50
|
+
])
|
|
51
|
+
]);
|
|
52
|
+
}), 128))
|
|
53
|
+
])
|
|
54
|
+
]),
|
|
55
|
+
_: 1
|
|
56
|
+
}, 8, ["open", "title", "description"]);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
const LpShortcutsDialog = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-d86b724b"]]);
|
|
61
|
+
export {
|
|
62
|
+
LpShortcutsDialog as default
|
|
63
|
+
};
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
2
|
content?: string;
|
|
3
3
|
sideOffset?: number;
|
|
4
|
+
/** Which edge to prefer; flips automatically when there is no room. */
|
|
5
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
6
|
+
/**
|
|
7
|
+
* The tip names the control rather than describing it — for a trigger with
|
|
8
|
+
* no visible text of its own. Sets the accessible name instead of only
|
|
9
|
+
* adding a description.
|
|
10
|
+
*/
|
|
11
|
+
label?: boolean;
|
|
12
|
+
/** Milliseconds before the tip appears on hover. */
|
|
13
|
+
delay?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Show on any focus, not just keyboard focus. Off by default: a tip that
|
|
16
|
+
* pops up after every mouse click on the trigger is noise.
|
|
17
|
+
*/
|
|
18
|
+
showOnPointerFocus?: boolean;
|
|
4
19
|
};
|
|
5
20
|
declare var __VLS_20: {}, __VLS_34: {};
|
|
6
21
|
type __VLS_Slots = {} & {
|
|
@@ -8,7 +23,10 @@ type __VLS_Slots = {} & {
|
|
|
8
23
|
} & {
|
|
9
24
|
content?: (props: typeof __VLS_34) => any;
|
|
10
25
|
};
|
|
11
|
-
declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
26
|
+
declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
27
|
+
delay: number;
|
|
28
|
+
side: "top" | "right" | "bottom" | "left";
|
|
29
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
12
30
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
13
31
|
declare const _default: typeof __VLS_export;
|
|
14
32
|
export default _default;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { MaybeRefOrGetter } from 'vue';
|
|
2
|
+
export interface HotkeyBinding {
|
|
3
|
+
/** Layout-produced key, compared case-insensitively ("l", "f2", "escape"). */
|
|
4
|
+
key: string;
|
|
5
|
+
/**
|
|
6
|
+
* Physical key position ("KeyL", "F2", "Digit1"). Matches on its own, which
|
|
7
|
+
* is what makes non-Latin layouts work.
|
|
8
|
+
*/
|
|
9
|
+
code?: string;
|
|
10
|
+
/** Ctrl on Windows/Linux, ⌘ on macOS — the two are treated as one. */
|
|
11
|
+
ctrl?: boolean;
|
|
12
|
+
shift?: boolean;
|
|
13
|
+
alt?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Runs even while a text field has focus. Only for shortcuts that mean
|
|
16
|
+
* something there — Escape, and the palette the user reaches from anywhere.
|
|
17
|
+
*/
|
|
18
|
+
allowInInput?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Returning `false` declines the key: the event keeps its default and the
|
|
21
|
+
* search moves on. This is what lets a binding defer to a layer that owns the
|
|
22
|
+
* key better than it does — Escape belongs to whichever overlay is on top,
|
|
23
|
+
* and swallowing it here would close all of them.
|
|
24
|
+
*/
|
|
25
|
+
handler: () => void | boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether this machine prints ⌘ where the rest print Ctrl. Read once: the
|
|
29
|
+
* keyboard does not change under a running window, and the check is only ever
|
|
30
|
+
* used to pick a label.
|
|
31
|
+
*/
|
|
32
|
+
export declare const isAppleKeyboard: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The symbol to print for a modifier on this platform. Bindings treat Ctrl and
|
|
35
|
+
* Meta as one modifier, so only the label may differ — never the binding.
|
|
36
|
+
*/
|
|
37
|
+
export declare function appleModifierLabel(key: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Bind a list of hotkeys for as long as the calling component is mounted.
|
|
40
|
+
*
|
|
41
|
+
* The list is read on every keystroke rather than captured once, so bindings
|
|
42
|
+
* may come and go with the state that owns them — a getter is the point, not a
|
|
43
|
+
* formality.
|
|
44
|
+
*/
|
|
45
|
+
export declare function useHotkeys(bindings: MaybeRefOrGetter<HotkeyBinding[]>): void;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { onMounted, onBeforeUnmount, toValue } from "vue";
|
|
2
|
+
const isAppleKeyboard = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.platform || "");
|
|
3
|
+
function appleModifierLabel(key) {
|
|
4
|
+
if (!isAppleKeyboard) return key;
|
|
5
|
+
if (key === "Ctrl") return "⌘";
|
|
6
|
+
if (key === "Alt") return "⌥";
|
|
7
|
+
return key;
|
|
8
|
+
}
|
|
9
|
+
const TEXT_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
10
|
+
"text",
|
|
11
|
+
"search",
|
|
12
|
+
"email",
|
|
13
|
+
"number",
|
|
14
|
+
"password",
|
|
15
|
+
"tel",
|
|
16
|
+
"url"
|
|
17
|
+
]);
|
|
18
|
+
function isTextEntry(target) {
|
|
19
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
20
|
+
if (target.isContentEditable) return true;
|
|
21
|
+
if (target instanceof HTMLTextAreaElement) return true;
|
|
22
|
+
if (target instanceof HTMLInputElement) {
|
|
23
|
+
return TEXT_INPUT_TYPES.has(target.type);
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
function matches(event, binding) {
|
|
28
|
+
const ctrlHeld = event.ctrlKey || event.metaKey;
|
|
29
|
+
if (ctrlHeld !== Boolean(binding.ctrl)) return false;
|
|
30
|
+
if (event.shiftKey !== Boolean(binding.shift)) return false;
|
|
31
|
+
if (event.altKey !== Boolean(binding.alt)) return false;
|
|
32
|
+
return event.key.toLowerCase() === binding.key.toLowerCase() || binding.code !== void 0 && event.code === binding.code;
|
|
33
|
+
}
|
|
34
|
+
function useHotkeys(bindings) {
|
|
35
|
+
function onKeydown(event) {
|
|
36
|
+
if (event.isComposing) return;
|
|
37
|
+
const inTextEntry = isTextEntry(event.target);
|
|
38
|
+
for (const binding of toValue(bindings)) {
|
|
39
|
+
if (inTextEntry && !binding.allowInInput) continue;
|
|
40
|
+
if (!matches(event, binding)) continue;
|
|
41
|
+
if (binding.handler() === false) continue;
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
onMounted(() => window.addEventListener("keydown", onKeydown));
|
|
47
|
+
onBeforeUnmount(() => window.removeEventListener("keydown", onKeydown));
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
appleModifierLabel,
|
|
51
|
+
isAppleKeyboard,
|
|
52
|
+
useHotkeys
|
|
53
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { MaybeRefOrGetter } from 'vue';
|
|
2
|
+
export interface RovingItem {
|
|
3
|
+
/** Stable identity of the row. */
|
|
4
|
+
id: string;
|
|
5
|
+
/**
|
|
6
|
+
* What typeahead matches against. Omit to opt this row out of typeahead —
|
|
7
|
+
* a separator, say.
|
|
8
|
+
*/
|
|
9
|
+
label?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface UseRovingFocusOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Which axis the arrows follow. `vertical` (default) uses ↑↓, `horizontal`
|
|
15
|
+
* uses ←→, `both` accepts either — for a grid whose wrapping the caller
|
|
16
|
+
* cannot describe here.
|
|
17
|
+
*/
|
|
18
|
+
orientation?: "vertical" | "horizontal" | "both";
|
|
19
|
+
/** Wrap around at the ends instead of stopping. Default true. */
|
|
20
|
+
loop?: boolean;
|
|
21
|
+
/** Jump to an item by typing its first letters. Default true. */
|
|
22
|
+
typeahead?: boolean;
|
|
23
|
+
/** Called when the user activates the current item (Enter or Space). */
|
|
24
|
+
onActivate?: (id: string) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Called when focus should leave the list without activating anything —
|
|
27
|
+
* Escape. Lets a dialog close, or a filter field take focus back.
|
|
28
|
+
*/
|
|
29
|
+
onEscape?: () => void;
|
|
30
|
+
}
|
|
31
|
+
export declare function useRovingFocus(items: MaybeRefOrGetter<RovingItem[]>, options?: UseRovingFocusOptions): {
|
|
32
|
+
activeId: import('vue').Ref<string | undefined, string | undefined>;
|
|
33
|
+
focus: (id: string | undefined) => void;
|
|
34
|
+
move: (delta: number) => void;
|
|
35
|
+
first: () => void;
|
|
36
|
+
last: () => void;
|
|
37
|
+
onKeydown: (event: KeyboardEvent) => void;
|
|
38
|
+
setContainer: (el: HTMLElement | null) => void;
|
|
39
|
+
containerProps: import('vue').ComputedRef<{
|
|
40
|
+
ref: (el: unknown) => void;
|
|
41
|
+
onKeydown: (event: KeyboardEvent) => void;
|
|
42
|
+
}>;
|
|
43
|
+
itemProps: (id: string) => {
|
|
44
|
+
tabindex: number;
|
|
45
|
+
"data-active": string | undefined;
|
|
46
|
+
"data-roving-id": string;
|
|
47
|
+
onFocus: () => void;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { ref, computed, toValue, watch } from "vue";
|
|
2
|
+
const TYPEAHEAD_RESET_MS = 700;
|
|
3
|
+
function useRovingFocus(items, options = {}) {
|
|
4
|
+
const {
|
|
5
|
+
orientation = "vertical",
|
|
6
|
+
loop = true,
|
|
7
|
+
typeahead = true,
|
|
8
|
+
onActivate,
|
|
9
|
+
onEscape
|
|
10
|
+
} = options;
|
|
11
|
+
const activeId = ref();
|
|
12
|
+
const containerEl = ref(null);
|
|
13
|
+
const enabled = computed(() => toValue(items).filter((item) => !item.disabled));
|
|
14
|
+
watch(
|
|
15
|
+
enabled,
|
|
16
|
+
(list) => {
|
|
17
|
+
if (list.length === 0) {
|
|
18
|
+
activeId.value = void 0;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (!activeId.value || !list.some((item) => item.id === activeId.value)) {
|
|
22
|
+
activeId.value = list[0].id;
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{ immediate: true }
|
|
26
|
+
);
|
|
27
|
+
function indexOfActive() {
|
|
28
|
+
return enabled.value.findIndex((item) => item.id === activeId.value);
|
|
29
|
+
}
|
|
30
|
+
function focus(id) {
|
|
31
|
+
if (id) activeId.value = id;
|
|
32
|
+
}
|
|
33
|
+
function move(delta) {
|
|
34
|
+
const list = enabled.value;
|
|
35
|
+
if (list.length === 0) return;
|
|
36
|
+
const from = indexOfActive();
|
|
37
|
+
if (from === -1) {
|
|
38
|
+
focus(list[0].id);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let next = from + delta;
|
|
42
|
+
if (next < 0) next = loop ? list.length - 1 : 0;
|
|
43
|
+
if (next >= list.length) next = loop ? 0 : list.length - 1;
|
|
44
|
+
focus(list[next].id);
|
|
45
|
+
}
|
|
46
|
+
function first() {
|
|
47
|
+
focus(enabled.value[0]?.id);
|
|
48
|
+
}
|
|
49
|
+
function last() {
|
|
50
|
+
focus(enabled.value.at(-1)?.id);
|
|
51
|
+
}
|
|
52
|
+
let typed = "";
|
|
53
|
+
let typedAt = 0;
|
|
54
|
+
function typeaheadTo(character) {
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
typed = now - typedAt > TYPEAHEAD_RESET_MS ? character : typed + character;
|
|
57
|
+
typedAt = now;
|
|
58
|
+
const list = enabled.value;
|
|
59
|
+
const needle = typed.toLowerCase();
|
|
60
|
+
const repeated = typed.length > 1 && [...typed].every((c) => c === typed[0]);
|
|
61
|
+
const query = repeated ? typed[0].toLowerCase() : needle;
|
|
62
|
+
const from = indexOfActive();
|
|
63
|
+
const offset = repeated || typed.length === 1 ? 1 : 0;
|
|
64
|
+
for (let step = 0; step < list.length; step += 1) {
|
|
65
|
+
const item = list[(from + offset + step) % list.length];
|
|
66
|
+
if (item.label?.toLowerCase().startsWith(query)) {
|
|
67
|
+
focus(item.id);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function onKeydown(event) {
|
|
73
|
+
const vertical = orientation === "vertical" || orientation === "both";
|
|
74
|
+
const horizontal = orientation === "horizontal" || orientation === "both";
|
|
75
|
+
switch (event.key) {
|
|
76
|
+
case "ArrowDown":
|
|
77
|
+
if (!vertical) return;
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
move(1);
|
|
80
|
+
return;
|
|
81
|
+
case "ArrowUp":
|
|
82
|
+
if (!vertical) return;
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
move(-1);
|
|
85
|
+
return;
|
|
86
|
+
case "ArrowRight":
|
|
87
|
+
if (!horizontal) return;
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
move(1);
|
|
90
|
+
return;
|
|
91
|
+
case "ArrowLeft":
|
|
92
|
+
if (!horizontal) return;
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
move(-1);
|
|
95
|
+
return;
|
|
96
|
+
case "Home":
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
first();
|
|
99
|
+
return;
|
|
100
|
+
case "End":
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
last();
|
|
103
|
+
return;
|
|
104
|
+
case "Enter":
|
|
105
|
+
case " ":
|
|
106
|
+
if (!activeId.value) return;
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
onActivate?.(activeId.value);
|
|
109
|
+
return;
|
|
110
|
+
case "Escape":
|
|
111
|
+
if (!onEscape) return;
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
onEscape();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (typeahead && event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey && event.key !== " ") {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
typeaheadTo(event.key);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
watch(activeId, (id) => {
|
|
122
|
+
if (!id || typeof document === "undefined") return;
|
|
123
|
+
const container = containerEl.value;
|
|
124
|
+
if (!container || !container.contains(document.activeElement)) return;
|
|
125
|
+
const target = container.querySelector(
|
|
126
|
+
`[data-roving-id="${CSS.escape(id)}"]`
|
|
127
|
+
);
|
|
128
|
+
target?.focus();
|
|
129
|
+
});
|
|
130
|
+
function setContainer(el) {
|
|
131
|
+
containerEl.value = el;
|
|
132
|
+
}
|
|
133
|
+
const containerProps = computed(() => ({
|
|
134
|
+
ref: (el) => setContainer(el instanceof HTMLElement ? el : null),
|
|
135
|
+
onKeydown
|
|
136
|
+
}));
|
|
137
|
+
function itemProps(id) {
|
|
138
|
+
const active = activeId.value === id;
|
|
139
|
+
return {
|
|
140
|
+
tabindex: active ? 0 : -1,
|
|
141
|
+
"data-active": active ? "" : void 0,
|
|
142
|
+
"data-roving-id": id,
|
|
143
|
+
onFocus: () => focus(id)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
activeId,
|
|
148
|
+
focus,
|
|
149
|
+
move,
|
|
150
|
+
first,
|
|
151
|
+
last,
|
|
152
|
+
onKeydown,
|
|
153
|
+
setContainer,
|
|
154
|
+
containerProps,
|
|
155
|
+
itemProps
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export {
|
|
159
|
+
useRovingFocus
|
|
160
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -67,6 +67,8 @@ export { default as LpSelect } from './components/LpSelect.vue';
|
|
|
67
67
|
export type { SelectOption, SelectValue } from './components/LpSelect.vue';
|
|
68
68
|
export { default as LpServiceNode } from './components/LpServiceNode.vue';
|
|
69
69
|
export type { ServiceNodeData } from './components/LpServiceNode.vue';
|
|
70
|
+
export { default as LpShortcutsDialog } from './components/LpShortcutsDialog.vue';
|
|
71
|
+
export type { ShortcutEntry, ShortcutGroup } from './components/LpShortcutsDialog.vue';
|
|
70
72
|
export { default as LpSidebar } from './components/LpSidebar.vue';
|
|
71
73
|
export type { SidebarItem, SidebarSection } from './components/LpSidebar.vue';
|
|
72
74
|
export { default as LpSkeleton } from './components/LpSkeleton.vue';
|
|
@@ -107,8 +109,12 @@ export { amber, dark, leavepulse, light, lime, nord, presets, rose, violet } fro
|
|
|
107
109
|
export type { PresetName } from './theme/presets';
|
|
108
110
|
export { parseConfig, serializeConfig, UI_CONFIG_VERSION } from './theme/config';
|
|
109
111
|
export type { UiConfig } from './theme/config';
|
|
112
|
+
export { useHotkeys, isAppleKeyboard, appleModifierLabel } from './composables/useHotkeys';
|
|
113
|
+
export type { HotkeyBinding } from './composables/useHotkeys';
|
|
110
114
|
export { useInputFilter } from './composables/useInputFilter';
|
|
111
115
|
export type { UseInputFilterOptions } from './composables/useInputFilter';
|
|
116
|
+
export { useRovingFocus } from './composables/useRovingFocus';
|
|
117
|
+
export type { RovingItem, UseRovingFocusOptions } from './composables/useRovingFocus';
|
|
112
118
|
export { useSettled } from './composables/useSettled';
|
|
113
119
|
export type { UseSettledOptions } from './composables/useSettled';
|
|
114
120
|
export { useTilt } from './composables/useTilt';
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { _ } from "./chunks/LayoutCanvas-Dd20boUG.js";
|
|
2
|
-
import { _ as _2 } from "./chunks/LpAddressInput-
|
|
2
|
+
import { _ as _2 } from "./chunks/LpAddressInput-Dah7asXy.js";
|
|
3
3
|
import { _ as _3 } from "./chunks/LpMapPicker-DFKrd1Ne.js";
|
|
4
4
|
import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
|
|
5
5
|
import { _ as _4 } from "./chunks/LpAlert-BAYHDSS1.js";
|
|
6
|
-
import { _ as _5 } from "./chunks/LpAppShell-
|
|
6
|
+
import { _ as _5 } from "./chunks/LpAppShell-DGZv907S.js";
|
|
7
7
|
import { _ as _6 } from "./chunks/LpAutocomplete-DIgXmtCA.js";
|
|
8
8
|
import { _ as _7 } from "./chunks/LpAvatar-B1Ez0ZAx.js";
|
|
9
9
|
import { _ as _8 } from "./chunks/LpBadge-CXzBPnwO.js";
|
|
@@ -14,15 +14,15 @@ import { _ as _12 } from "./chunks/LpCard-BmrTvWOe.js";
|
|
|
14
14
|
import { _ as _13 } from "./chunks/LpCheckbox-BO0zuuPh.js";
|
|
15
15
|
import { _ as _14 } from "./chunks/LpCodeBlock-CSiOSPs8.js";
|
|
16
16
|
import { default as default2 } from "./components/LpCommandPalette.vue.js";
|
|
17
|
-
import { _ as _15 } from "./chunks/LpConfirmDialog-
|
|
17
|
+
import { _ as _15 } from "./chunks/LpConfirmDialog-CkMzeKdp.js";
|
|
18
18
|
import { _ as _16 } from "./chunks/LpContextMenu-D0mzXqA7.js";
|
|
19
19
|
import { _ as _17 } from "./chunks/LpDatePicker-dY2q4dr4.js";
|
|
20
20
|
import { _ as _18 } from "./chunks/LpDisclosure-DP6yfgfM.js";
|
|
21
21
|
import { _ as _19 } from "./chunks/LpDivider-CUX46KR0.js";
|
|
22
|
-
import { _ as _20 } from "./chunks/LpDrawer-
|
|
22
|
+
import { _ as _20 } from "./chunks/LpDrawer-DAJN5rGK.js";
|
|
23
23
|
import { _ as _21 } from "./chunks/LpDropdownMenu-DJpoqRCb.js";
|
|
24
24
|
import { _ as _22 } from "./chunks/LpEmptyState-CrfyRJUG.js";
|
|
25
|
-
import { _ as _23 } from "./chunks/LpFileTree-
|
|
25
|
+
import { _ as _23 } from "./chunks/LpFileTree-DWx77B1F.js";
|
|
26
26
|
import { ancestorIds, checkStateOf, checkedCount, computeStats, fileIcon, formatModified, formatSize, sortNodes, subtreeIds, subtreeSize } from "./components/fileTree.js";
|
|
27
27
|
import { _ as _24 } from "./chunks/LpFormField-Bn-ZwC6z.js";
|
|
28
28
|
import { _ as _25 } from "./chunks/LpIcon-CCnX5_2j.js";
|
|
@@ -31,9 +31,9 @@ import { _ as _27 } from "./chunks/LpInput-BauZRyzm.js";
|
|
|
31
31
|
import { _ as _28 } from "./chunks/LpLightbox-Ci35Y_xP.js";
|
|
32
32
|
import { fileNameOf } from "./components/lightbox.js";
|
|
33
33
|
import { _ as _29 } from "./chunks/LpLink-DHwOEp4e.js";
|
|
34
|
-
import { _ as _30 } from "./chunks/LpLogViewer-
|
|
35
|
-
import { _ as _31 } from "./chunks/LpModal-
|
|
36
|
-
import { _ as _32 } from "./chunks/LpNotificationBell-
|
|
34
|
+
import { _ as _30 } from "./chunks/LpLogViewer-B1aLUVdz.js";
|
|
35
|
+
import { _ as _31 } from "./chunks/LpModal-CNAFRgCU.js";
|
|
36
|
+
import { _ as _32 } from "./chunks/LpNotificationBell-HSGo1pcf.js";
|
|
37
37
|
import { _ as _33 } from "./chunks/LpNumberField-C5ar-OwE.js";
|
|
38
38
|
import { _ as _34 } from "./chunks/LpOtpInput-DqaT0Z75.js";
|
|
39
39
|
import { _ as _35 } from "./chunks/LpPagination-CeGuNe_q.js";
|
|
@@ -44,17 +44,18 @@ import { _ as _38 } from "./chunks/LpPopover-8268UmlC.js";
|
|
|
44
44
|
import { _ as _39 } from "./chunks/LpProgress-BhI0IbjC.js";
|
|
45
45
|
import { _ as _40 } from "./chunks/LpRadio-CL-pI0_O.js";
|
|
46
46
|
import { _ as _41 } from "./chunks/LpRadioGroup-B4yDTmi6.js";
|
|
47
|
-
import { _ as _42 } from "./chunks/LpScrollArea-
|
|
47
|
+
import { _ as _42 } from "./chunks/LpScrollArea-D8xyKXcF.js";
|
|
48
48
|
import { _ as _43 } from "./chunks/LpSegmented-BmpanyAo.js";
|
|
49
49
|
import { _ as _44 } from "./chunks/LpSelect-D5mU4cZO.js";
|
|
50
50
|
import { _ as _45 } from "./chunks/LpServiceNode-Dc-Ut5RS.js";
|
|
51
|
-
import {
|
|
51
|
+
import { default as default3 } from "./components/LpShortcutsDialog.vue.js";
|
|
52
|
+
import { _ as _46 } from "./chunks/LpSidebar-JvMaaYsy.js";
|
|
52
53
|
import { _ as _47 } from "./chunks/LpSkeleton-eSFIo4kD.js";
|
|
53
54
|
import { _ as _48 } from "./chunks/LpSlider-B1SF3pRV.js";
|
|
54
55
|
import { _ as _49 } from "./chunks/LpStat-CdbEFOvn.js";
|
|
55
56
|
import { _ as _50 } from "./chunks/LpStepper-5dFTrW21.js";
|
|
56
57
|
import { _ as _51 } from "./chunks/LpSwitch-v6fnccSM.js";
|
|
57
|
-
import { _ as _52 } from "./chunks/LpTable-
|
|
58
|
+
import { _ as _52 } from "./chunks/LpTable-DTj52qlg.js";
|
|
58
59
|
import { _ as _53 } from "./chunks/LpTableOfContents-CjvxPMpU.js";
|
|
59
60
|
import { _ as _54 } from "./chunks/LpTabs-Cefe_lys.js";
|
|
60
61
|
import { _ as _55 } from "./chunks/LpTextarea-CRAkhemZ.js";
|
|
@@ -62,14 +63,16 @@ import { p as presets, a as parseTheme, s as serializeTheme } from "./chunks/LpT
|
|
|
62
63
|
import { D, _ as _56, N, T, b, c, d, e, f, g, i, l, h, j, n, r, t, k, m, u, v } from "./chunks/LpThemeSwitcher-B_kj0T2b.js";
|
|
63
64
|
import { _ as _57 } from "./chunks/LpTilt-DuyKbpkA.js";
|
|
64
65
|
import { _ as _58 } from "./chunks/LpToaster-gDB1EaU3.js";
|
|
65
|
-
import { _ as _59 } from "./chunks/LpTooltip-
|
|
66
|
+
import { _ as _59 } from "./chunks/LpTooltip-D1hbjQWG.js";
|
|
66
67
|
import { _ as _60 } from "./chunks/LpTopologyCanvas-DVwyQfKa.js";
|
|
67
68
|
import { _ as _61 } from "./chunks/LpUptimeBar-CB366k0O.js";
|
|
68
69
|
import { deserializeLayout, serializeLayout } from "./layout/tree.js";
|
|
69
70
|
import { addLeaf, addTab, countBlocks, countLeaves, makeLayout, moveLeaf, removeBlock, removeLeaf, reorderTab, resizeAt, setActiveTab } from "./layout/tree.js";
|
|
70
71
|
import { blockTitle, defineBlocks } from "./layout/registry.js";
|
|
71
72
|
import { useLayout } from "./layout/useLayout.js";
|
|
73
|
+
import { appleModifierLabel, isAppleKeyboard, useHotkeys } from "./composables/useHotkeys.js";
|
|
72
74
|
import { useInputFilter } from "./composables/useInputFilter.js";
|
|
75
|
+
import { useRovingFocus } from "./composables/useRovingFocus.js";
|
|
73
76
|
import { useSettled } from "./composables/useSettled.js";
|
|
74
77
|
import { useTilt } from "./composables/useTilt.js";
|
|
75
78
|
import { useZoomPan } from "./composables/useZoomPan.js";
|
|
@@ -152,6 +155,7 @@ export {
|
|
|
152
155
|
_43 as LpSegmented,
|
|
153
156
|
_44 as LpSelect,
|
|
154
157
|
_45 as LpServiceNode,
|
|
158
|
+
default3 as LpShortcutsDialog,
|
|
155
159
|
_46 as LpSidebar,
|
|
156
160
|
_47 as LpSkeleton,
|
|
157
161
|
_48 as LpSlider,
|
|
@@ -176,6 +180,7 @@ export {
|
|
|
176
180
|
addTab,
|
|
177
181
|
b as amber,
|
|
178
182
|
ancestorIds,
|
|
183
|
+
appleModifierLabel,
|
|
179
184
|
c as applyTheme,
|
|
180
185
|
d as applyThemeWithTransition,
|
|
181
186
|
blockTitle,
|
|
@@ -198,6 +203,7 @@ export {
|
|
|
198
203
|
formatAddress,
|
|
199
204
|
formatModified,
|
|
200
205
|
formatSize,
|
|
206
|
+
isAppleKeyboard,
|
|
201
207
|
i as isBrokenBackdropEngine,
|
|
202
208
|
l as leavepulse,
|
|
203
209
|
h as light,
|
|
@@ -228,8 +234,10 @@ export {
|
|
|
228
234
|
m as themeToCssVars,
|
|
229
235
|
tileUrl,
|
|
230
236
|
unproject,
|
|
237
|
+
useHotkeys,
|
|
231
238
|
useInputFilter,
|
|
232
239
|
useLayout,
|
|
240
|
+
useRovingFocus,
|
|
233
241
|
useSettled,
|
|
234
242
|
u as useTheme,
|
|
235
243
|
useTilt,
|
package/package.json
CHANGED
package/src/component-names.ts
CHANGED
|
@@ -18,9 +18,10 @@ import {
|
|
|
18
18
|
DialogRoot,
|
|
19
19
|
DialogTitle,
|
|
20
20
|
} from "reka-ui"
|
|
21
|
-
import { computed, nextTick,
|
|
21
|
+
import { computed, nextTick, ref, watch } from "vue"
|
|
22
22
|
import LpIcon from "./LpIcon.vue"
|
|
23
23
|
import LpScrollArea from "./LpScrollArea.vue"
|
|
24
|
+
import { useHotkeys } from "../composables/useHotkeys"
|
|
24
25
|
|
|
25
26
|
export interface Command {
|
|
26
27
|
id: string
|
|
@@ -196,16 +197,22 @@ watch(
|
|
|
196
197
|
},
|
|
197
198
|
)
|
|
198
199
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
200
|
+
// Through useHotkeys rather than a listener of its own: the palette is the one
|
|
201
|
+
// shortcut that has to work from inside a text field, and `code` is what makes
|
|
202
|
+
// it fire on a layout where Ctrl-K arrives as "л".
|
|
203
|
+
useHotkeys(() =>
|
|
204
|
+
props.hotkey
|
|
205
|
+
? [
|
|
206
|
+
{
|
|
207
|
+
key: "k",
|
|
208
|
+
code: "KeyK",
|
|
209
|
+
ctrl: true,
|
|
210
|
+
allowInInput: true,
|
|
211
|
+
handler: () => emit("update:open", !props.open),
|
|
212
|
+
},
|
|
213
|
+
]
|
|
214
|
+
: [],
|
|
215
|
+
)
|
|
209
216
|
</script>
|
|
210
217
|
|
|
211
218
|
<template>
|