@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.
Files changed (53) hide show
  1. package/dist/assets/{LpCommandPalette-C3LdDNHk.css → LpCommandPalette-CEY-pajO.css} +4 -4
  2. package/dist/assets/LpShortcutsDialog-B7H-RZLQ.css +55 -0
  3. package/dist/chunks/{LpAddressInput-B411MOJa.js → LpAddressInput-Dah7asXy.js} +1 -1
  4. package/dist/chunks/{LpAppShell-CfM7fdr3.js → LpAppShell-DGZv907S.js} +2 -2
  5. package/dist/chunks/{LpConfirmDialog-D8pBGPOI.js → LpConfirmDialog-CkMzeKdp.js} +1 -1
  6. package/dist/chunks/{LpDrawer-B4cNj39a.js → LpDrawer-DAJN5rGK.js} +1 -1
  7. package/dist/chunks/{LpFileTree-d9nXtSVo.js → LpFileTree-DWx77B1F.js} +1 -1
  8. package/dist/chunks/{LpLogViewer-BXPBUV21.js → LpLogViewer-B1aLUVdz.js} +1 -1
  9. package/dist/chunks/{LpModal-DKFdzKwV.js → LpModal-CNAFRgCU.js} +36 -3
  10. package/dist/chunks/{LpNotificationBell-Nges4kJk.js → LpNotificationBell-HSGo1pcf.js} +1 -1
  11. package/dist/chunks/{LpScrollArea-Du8tW8c5.js → LpScrollArea-D8xyKXcF.js} +10 -2
  12. package/dist/chunks/{LpSidebar-Bd0148dd.js → LpSidebar-JvMaaYsy.js} +2 -2
  13. package/dist/chunks/{LpSidebarNav-BqGpNr4S.js → LpSidebarNav-BIYzk7e_.js} +1 -1
  14. package/dist/chunks/{LpTable-BBKlNR9j.js → LpTable-DTj52qlg.js} +45 -9
  15. package/dist/chunks/{LpTooltip-CIwoRCag.js → LpTooltip-D1hbjQWG.js} +23 -9
  16. package/dist/chunks/_plugin-vue_export-helper-1tPrXgE0.js +10 -0
  17. package/dist/component-names.d.ts +1 -1
  18. package/dist/components/LpAddressInput.vue.js +1 -1
  19. package/dist/components/LpAppShell.vue.js +1 -1
  20. package/dist/components/LpCommandPalette.vue.js +17 -21
  21. package/dist/components/LpConfirmDialog.vue.js +1 -1
  22. package/dist/components/LpDrawer.vue.js +1 -1
  23. package/dist/components/LpFileTree.vue.js +1 -1
  24. package/dist/components/LpLogViewer.vue.js +1 -1
  25. package/dist/components/LpModal.vue.d.ts +16 -5
  26. package/dist/components/LpModal.vue.js +1 -1
  27. package/dist/components/LpNotificationBell.vue.js +1 -1
  28. package/dist/components/LpScrollArea.vue.js +1 -1
  29. package/dist/components/LpShortcutsDialog.vue.d.ts +25 -0
  30. package/dist/components/LpShortcutsDialog.vue.js +63 -0
  31. package/dist/components/LpSidebar.vue.js +1 -1
  32. package/dist/components/LpSidebarNav.vue.js +1 -1
  33. package/dist/components/LpTable.vue.js +1 -1
  34. package/dist/components/LpTooltip.vue.d.ts +19 -1
  35. package/dist/components/LpTooltip.vue.js +1 -1
  36. package/dist/composables/useHotkeys.d.ts +45 -0
  37. package/dist/composables/useHotkeys.js +53 -0
  38. package/dist/composables/useRovingFocus.d.ts +49 -0
  39. package/dist/composables/useRovingFocus.js +160 -0
  40. package/dist/index.d.ts +6 -0
  41. package/dist/index.js +20 -12
  42. package/package.json +1 -1
  43. package/src/component-names.ts +1 -0
  44. package/src/components/LpCommandPalette.vue +18 -11
  45. package/src/components/LpModal.vue +67 -1
  46. package/src/components/LpScrollArea.vue +26 -6
  47. package/src/components/LpShortcutsDialog.vue +127 -0
  48. package/src/components/LpTable.vue +57 -2
  49. package/src/components/LpTooltip.vue +57 -5
  50. package/src/composables/useHotkeys.ts +131 -0
  51. package/src/composables/useRovingFocus.ts +275 -0
  52. package/src/index.ts +6 -0
  53. 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,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpSidebar-Bd0148dd.js";
1
+ import { _ as _sfc_main } from "../chunks/LpSidebar-JvMaaYsy.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpSidebarNav-BqGpNr4S.js";
1
+ import { _ as _sfc_main } from "../chunks/LpSidebarNav-BIYzk7e_.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpTable-BBKlNR9j.js";
1
+ import { _ as _sfc_main } from "../chunks/LpTable-DTj52qlg.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -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<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
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;
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpTooltip-CIwoRCag.js";
1
+ import { _ as _sfc_main } from "../chunks/LpTooltip-D1hbjQWG.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -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-B411MOJa.js";
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-CfM7fdr3.js";
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-D8pBGPOI.js";
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-B4cNj39a.js";
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-d9nXtSVo.js";
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-BXPBUV21.js";
35
- import { _ as _31 } from "./chunks/LpModal-DKFdzKwV.js";
36
- import { _ as _32 } from "./chunks/LpNotificationBell-Nges4kJk.js";
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-Du8tW8c5.js";
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 { _ as _46 } from "./chunks/LpSidebar-Bd0148dd.js";
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-BBKlNR9j.js";
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-CIwoRCag.js";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.20.9",
3
+ "version": "0.22.0",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -51,6 +51,7 @@ export const COMPONENT_NAMES = [
51
51
  "LpSegmented",
52
52
  "LpSelect",
53
53
  "LpServiceNode",
54
+ "LpShortcutsDialog",
54
55
  "LpSidebar",
55
56
  "LpSidebarNav",
56
57
  "LpSkeleton",
@@ -18,9 +18,10 @@ import {
18
18
  DialogRoot,
19
19
  DialogTitle,
20
20
  } from "reka-ui"
21
- import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue"
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
- function onHotkey(e: KeyboardEvent) {
200
- if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
201
- e.preventDefault()
202
- emit("update:open", !props.open)
203
- }
204
- }
205
- onMounted(() => {
206
- if (props.hotkey) window.addEventListener("keydown", onHotkey)
207
- })
208
- onBeforeUnmount(() => window.removeEventListener("keydown", onHotkey))
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>