@tanstack/solid-hotkeys 0.5.1 → 0.7.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 (35) hide show
  1. package/dist/HotkeysProvider.cjs.map +1 -1
  2. package/dist/HotkeysProvider.d.cts +2 -1
  3. package/dist/HotkeysProvider.d.ts +2 -1
  4. package/dist/HotkeysProvider.js.map +1 -1
  5. package/dist/createHotkey.cjs +27 -8
  6. package/dist/createHotkey.cjs.map +1 -1
  7. package/dist/createHotkey.js +27 -8
  8. package/dist/createHotkey.js.map +1 -1
  9. package/dist/createHotkeySequence.cjs +27 -8
  10. package/dist/createHotkeySequence.cjs.map +1 -1
  11. package/dist/createHotkeySequence.js +28 -9
  12. package/dist/createHotkeySequence.js.map +1 -1
  13. package/dist/createHotkeySequenceRecorder.cjs +43 -0
  14. package/dist/createHotkeySequenceRecorder.cjs.map +1 -0
  15. package/dist/createHotkeySequenceRecorder.d.cts +19 -0
  16. package/dist/createHotkeySequenceRecorder.d.ts +19 -0
  17. package/dist/createHotkeySequenceRecorder.js +43 -0
  18. package/dist/createHotkeySequenceRecorder.js.map +1 -0
  19. package/dist/createHotkeySequences.cjs +110 -0
  20. package/dist/createHotkeySequences.cjs.map +1 -0
  21. package/dist/createHotkeySequences.d.cts +57 -0
  22. package/dist/createHotkeySequences.d.ts +57 -0
  23. package/dist/createHotkeySequences.js +110 -0
  24. package/dist/createHotkeySequences.js.map +1 -0
  25. package/dist/index.cjs +4 -0
  26. package/dist/index.d.cts +3 -1
  27. package/dist/index.d.ts +3 -1
  28. package/dist/index.js +3 -1
  29. package/package.json +2 -2
  30. package/src/HotkeysProvider.tsx +5 -1
  31. package/src/createHotkey.ts +32 -13
  32. package/src/createHotkeySequence.ts +33 -16
  33. package/src/createHotkeySequenceRecorder.ts +66 -0
  34. package/src/createHotkeySequences.ts +176 -0
  35. package/src/index.ts +2 -0
@@ -0,0 +1,110 @@
1
+ const require_HotkeysProvider = require('./HotkeysProvider.cjs');
2
+ let _tanstack_hotkeys = require("@tanstack/hotkeys");
3
+ let solid_js = require("solid-js");
4
+
5
+ //#region src/createHotkeySequences.ts
6
+ /**
7
+ * SolidJS primitive for registering multiple keyboard shortcut sequences at once (Vim-style).
8
+ *
9
+ * Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or accessors,
10
+ * so you can react to variable-length lists.
11
+ *
12
+ * Options are merged in this order:
13
+ * HotkeysProvider defaults < commonOptions < per-definition options
14
+ *
15
+ * Definitions with an empty `sequence` are skipped (no registration).
16
+ *
17
+ * @param sequences - Array of sequence definitions, or accessor returning them
18
+ * @param commonOptions - Shared options for all sequences, or accessor
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * function VimPalette() {
23
+ * createHotkeySequences([
24
+ * { sequence: ['G', 'G'], callback: () => scrollToTop() },
25
+ * { sequence: ['D', 'D'], callback: () => deleteLine() },
26
+ * ])
27
+ * }
28
+ * ```
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * function Dynamic(props) {
33
+ * createHotkeySequences(
34
+ * () => props.items.map((item) => ({
35
+ * sequence: item.chords,
36
+ * callback: item.action,
37
+ * options: { enabled: item.enabled },
38
+ * })),
39
+ * { preventDefault: true },
40
+ * )
41
+ * }
42
+ * ```
43
+ */
44
+ function createHotkeySequences(sequences, commonOptions = {}) {
45
+ const defaultOptions = require_HotkeysProvider.useDefaultHotkeysOptions();
46
+ const manager = (0, _tanstack_hotkeys.getSequenceManager)();
47
+ const registrations = /* @__PURE__ */ new Map();
48
+ (0, solid_js.onCleanup)(() => {
49
+ for (const { handle } of registrations.values()) if (handle.isActive) handle.unregister();
50
+ registrations.clear();
51
+ });
52
+ (0, solid_js.createEffect)(() => {
53
+ const resolved = typeof sequences === "function" ? sequences() : sequences;
54
+ const resolvedCommonOptions = typeof commonOptions === "function" ? commonOptions() : commonOptions;
55
+ const nextKeys = /* @__PURE__ */ new Set();
56
+ const prepared = [];
57
+ for (let i = 0; i < resolved.length; i++) {
58
+ const def = resolved[i];
59
+ const resolvedDefOptions = def.options ?? {};
60
+ const mergedOptions = {
61
+ ...defaultOptions.hotkeySequence,
62
+ ...resolvedCommonOptions,
63
+ ...resolvedDefOptions
64
+ };
65
+ if (def.sequence.length === 0) continue;
66
+ const sequenceString = (0, _tanstack_hotkeys.formatHotkeySequence)(def.sequence);
67
+ const resolvedTarget = "target" in mergedOptions ? mergedOptions.target ?? null : typeof document !== "undefined" ? document : null;
68
+ if (!resolvedTarget) continue;
69
+ const registrationKey = `${i}:${sequenceString}`;
70
+ nextKeys.add(registrationKey);
71
+ prepared.push({
72
+ registrationKey,
73
+ def,
74
+ mergedOptions,
75
+ sequenceString,
76
+ resolvedTarget
77
+ });
78
+ }
79
+ for (const [key, record] of [...registrations.entries()]) if (!nextKeys.has(key)) {
80
+ if (record.handle.isActive) record.handle.unregister();
81
+ registrations.delete(key);
82
+ }
83
+ for (const p of prepared) {
84
+ const existing = registrations.get(p.registrationKey);
85
+ if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
86
+ existing.handle.callback = p.def.callback;
87
+ const { target: _target, ...optionsWithoutTarget } = p.mergedOptions;
88
+ existing.handle.setOptions(optionsWithoutTarget);
89
+ continue;
90
+ }
91
+ if (existing) {
92
+ if (existing.handle.isActive) existing.handle.unregister();
93
+ registrations.delete(p.registrationKey);
94
+ }
95
+ const { target: _target, ...optionsWithoutTarget } = p.mergedOptions;
96
+ const handle = manager.register(p.def.sequence, p.def.callback, {
97
+ ...optionsWithoutTarget,
98
+ target: p.resolvedTarget
99
+ });
100
+ registrations.set(p.registrationKey, {
101
+ handle,
102
+ target: p.resolvedTarget
103
+ });
104
+ }
105
+ });
106
+ }
107
+
108
+ //#endregion
109
+ exports.createHotkeySequences = createHotkeySequences;
110
+ //# sourceMappingURL=createHotkeySequences.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createHotkeySequences.cjs","names":["useDefaultHotkeysOptions"],"sources":["../src/createHotkeySequences.ts"],"sourcesContent":["import { createEffect, onCleanup } from 'solid-js'\nimport { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProvider'\nimport type { CreateHotkeySequenceOptions } from './createHotkeySequence'\nimport type {\n HotkeyCallback,\n HotkeySequence,\n SequenceRegistrationHandle,\n} from '@tanstack/hotkeys'\n\n/**\n * A single sequence definition for use with `createHotkeySequences`.\n */\nexport interface CreateHotkeySequenceDefinition {\n /** Array of hotkey strings that form the sequence */\n sequence: HotkeySequence\n /** The function to call when the sequence is completed */\n callback: HotkeyCallback\n /** Per-sequence options (merged on top of commonOptions) */\n options?: CreateHotkeySequenceOptions\n}\n\n/**\n * SolidJS primitive for registering multiple keyboard shortcut sequences at once (Vim-style).\n *\n * Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or accessors,\n * so you can react to variable-length lists.\n *\n * Options are merged in this order:\n * HotkeysProvider defaults < commonOptions < per-definition options\n *\n * Definitions with an empty `sequence` are skipped (no registration).\n *\n * @param sequences - Array of sequence definitions, or accessor returning them\n * @param commonOptions - Shared options for all sequences, or accessor\n *\n * @example\n * ```tsx\n * function VimPalette() {\n * createHotkeySequences([\n * { sequence: ['G', 'G'], callback: () => scrollToTop() },\n * { sequence: ['D', 'D'], callback: () => deleteLine() },\n * ])\n * }\n * ```\n *\n * @example\n * ```tsx\n * function Dynamic(props) {\n * createHotkeySequences(\n * () => props.items.map((item) => ({\n * sequence: item.chords,\n * callback: item.action,\n * options: { enabled: item.enabled },\n * })),\n * { preventDefault: true },\n * )\n * }\n * ```\n */\nexport function createHotkeySequences(\n sequences:\n | Array<CreateHotkeySequenceDefinition>\n | (() => Array<CreateHotkeySequenceDefinition>),\n commonOptions:\n | CreateHotkeySequenceOptions\n | (() => CreateHotkeySequenceOptions) = {},\n): void {\n type RegistrationRecord = {\n handle: SequenceRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getSequenceManager()\n\n const registrations = new Map<string, RegistrationRecord>()\n\n onCleanup(() => {\n for (const { handle } of registrations.values()) {\n if (handle.isActive) {\n handle.unregister()\n }\n }\n registrations.clear()\n })\n\n createEffect(() => {\n const resolved = typeof sequences === 'function' ? sequences() : sequences\n const resolvedCommonOptions =\n typeof commonOptions === 'function' ? commonOptions() : commonOptions\n\n const nextKeys = new Set<string>()\n const prepared: Array<{\n registrationKey: string\n def: CreateHotkeySequenceDefinition\n mergedOptions: CreateHotkeySequenceOptions\n sequenceString: string\n resolvedTarget: Document | HTMLElement | Window\n }> = []\n\n for (let i = 0; i < resolved.length; i++) {\n const def = resolved[i]!\n const resolvedDefOptions = def.options ?? {}\n\n const mergedOptions = {\n ...defaultOptions.hotkeySequence,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as CreateHotkeySequenceOptions\n\n if (def.sequence.length === 0) {\n continue\n }\n\n const sequenceString = formatHotkeySequence(def.sequence)\n\n const resolvedTarget =\n 'target' in mergedOptions\n ? (mergedOptions.target ?? null)\n : typeof document !== 'undefined'\n ? document\n : null\n\n if (!resolvedTarget) {\n continue\n }\n\n const registrationKey = `${i}:${sequenceString}`\n nextKeys.add(registrationKey)\n prepared.push({\n registrationKey,\n def,\n mergedOptions,\n sequenceString,\n resolvedTarget,\n })\n }\n\n for (const [key, record] of [...registrations.entries()]) {\n if (!nextKeys.has(key)) {\n if (record.handle.isActive) {\n record.handle.unregister()\n }\n registrations.delete(key)\n }\n }\n\n for (const p of prepared) {\n const existing = registrations.get(p.registrationKey)\n if (existing?.handle.isActive && existing.target === p.resolvedTarget) {\n existing.handle.callback = p.def.callback\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\n existing.handle.setOptions(optionsWithoutTarget)\n continue\n }\n\n if (existing) {\n if (existing.handle.isActive) {\n existing.handle.unregister()\n }\n registrations.delete(p.registrationKey)\n }\n\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\n const handle = manager.register(p.def.sequence, p.def.callback, {\n ...optionsWithoutTarget,\n target: p.resolvedTarget,\n })\n registrations.set(p.registrationKey, {\n handle,\n target: p.resolvedTarget,\n })\n }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAgB,sBACd,WAGA,gBAE0C,EAAE,EACtC;CAMN,MAAM,iBAAiBA,kDAA0B;CACjD,MAAM,qDAA8B;CAEpC,MAAM,gCAAgB,IAAI,KAAiC;AAE3D,+BAAgB;AACd,OAAK,MAAM,EAAE,YAAY,cAAc,QAAQ,CAC7C,KAAI,OAAO,SACT,QAAO,YAAY;AAGvB,gBAAc,OAAO;GACrB;AAEF,kCAAmB;EACjB,MAAM,WAAW,OAAO,cAAc,aAAa,WAAW,GAAG;EACjE,MAAM,wBACJ,OAAO,kBAAkB,aAAa,eAAe,GAAG;EAE1D,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAMD,EAAE;AAEP,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;GACxC,MAAM,MAAM,SAAS;GACrB,MAAM,qBAAqB,IAAI,WAAW,EAAE;GAE5C,MAAM,gBAAgB;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;AAED,OAAI,IAAI,SAAS,WAAW,EAC1B;GAGF,MAAM,6DAAsC,IAAI,SAAS;GAEzD,MAAM,iBACJ,YAAY,gBACP,cAAc,UAAU,OACzB,OAAO,aAAa,cAClB,WACA;AAER,OAAI,CAAC,eACH;GAGF,MAAM,kBAAkB,GAAG,EAAE,GAAG;AAChC,YAAS,IAAI,gBAAgB;AAC7B,YAAS,KAAK;IACZ;IACA;IACA;IACA;IACA;IACD,CAAC;;AAGJ,OAAK,MAAM,CAAC,KAAK,WAAW,CAAC,GAAG,cAAc,SAAS,CAAC,CACtD,KAAI,CAAC,SAAS,IAAI,IAAI,EAAE;AACtB,OAAI,OAAO,OAAO,SAChB,QAAO,OAAO,YAAY;AAE5B,iBAAc,OAAO,IAAI;;AAI7B,OAAK,MAAM,KAAK,UAAU;GACxB,MAAM,WAAW,cAAc,IAAI,EAAE,gBAAgB;AACrD,OAAI,UAAU,OAAO,YAAY,SAAS,WAAW,EAAE,gBAAgB;AACrE,aAAS,OAAO,WAAW,EAAE,IAAI;IACjC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;AACvD,aAAS,OAAO,WAAW,qBAAqB;AAChD;;AAGF,OAAI,UAAU;AACZ,QAAI,SAAS,OAAO,SAClB,UAAS,OAAO,YAAY;AAE9B,kBAAc,OAAO,EAAE,gBAAgB;;GAGzC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;GACvD,MAAM,SAAS,QAAQ,SAAS,EAAE,IAAI,UAAU,EAAE,IAAI,UAAU;IAC9D,GAAG;IACH,QAAQ,EAAE;IACX,CAAC;AACF,iBAAc,IAAI,EAAE,iBAAiB;IACnC;IACA,QAAQ,EAAE;IACX,CAAC;;GAEJ"}
@@ -0,0 +1,57 @@
1
+ import { CreateHotkeySequenceOptions } from "./createHotkeySequence.cjs";
2
+ import { HotkeyCallback, HotkeySequence } from "@tanstack/hotkeys";
3
+
4
+ //#region src/createHotkeySequences.d.ts
5
+ /**
6
+ * A single sequence definition for use with `createHotkeySequences`.
7
+ */
8
+ interface CreateHotkeySequenceDefinition {
9
+ /** Array of hotkey strings that form the sequence */
10
+ sequence: HotkeySequence;
11
+ /** The function to call when the sequence is completed */
12
+ callback: HotkeyCallback;
13
+ /** Per-sequence options (merged on top of commonOptions) */
14
+ options?: CreateHotkeySequenceOptions;
15
+ }
16
+ /**
17
+ * SolidJS primitive for registering multiple keyboard shortcut sequences at once (Vim-style).
18
+ *
19
+ * Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or accessors,
20
+ * so you can react to variable-length lists.
21
+ *
22
+ * Options are merged in this order:
23
+ * HotkeysProvider defaults < commonOptions < per-definition options
24
+ *
25
+ * Definitions with an empty `sequence` are skipped (no registration).
26
+ *
27
+ * @param sequences - Array of sequence definitions, or accessor returning them
28
+ * @param commonOptions - Shared options for all sequences, or accessor
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * function VimPalette() {
33
+ * createHotkeySequences([
34
+ * { sequence: ['G', 'G'], callback: () => scrollToTop() },
35
+ * { sequence: ['D', 'D'], callback: () => deleteLine() },
36
+ * ])
37
+ * }
38
+ * ```
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * function Dynamic(props) {
43
+ * createHotkeySequences(
44
+ * () => props.items.map((item) => ({
45
+ * sequence: item.chords,
46
+ * callback: item.action,
47
+ * options: { enabled: item.enabled },
48
+ * })),
49
+ * { preventDefault: true },
50
+ * )
51
+ * }
52
+ * ```
53
+ */
54
+ declare function createHotkeySequences(sequences: Array<CreateHotkeySequenceDefinition> | (() => Array<CreateHotkeySequenceDefinition>), commonOptions?: CreateHotkeySequenceOptions | (() => CreateHotkeySequenceOptions)): void;
55
+ //#endregion
56
+ export { CreateHotkeySequenceDefinition, createHotkeySequences };
57
+ //# sourceMappingURL=createHotkeySequences.d.cts.map
@@ -0,0 +1,57 @@
1
+ import { CreateHotkeySequenceOptions } from "./createHotkeySequence.js";
2
+ import { HotkeyCallback, HotkeySequence } from "@tanstack/hotkeys";
3
+
4
+ //#region src/createHotkeySequences.d.ts
5
+ /**
6
+ * A single sequence definition for use with `createHotkeySequences`.
7
+ */
8
+ interface CreateHotkeySequenceDefinition {
9
+ /** Array of hotkey strings that form the sequence */
10
+ sequence: HotkeySequence;
11
+ /** The function to call when the sequence is completed */
12
+ callback: HotkeyCallback;
13
+ /** Per-sequence options (merged on top of commonOptions) */
14
+ options?: CreateHotkeySequenceOptions;
15
+ }
16
+ /**
17
+ * SolidJS primitive for registering multiple keyboard shortcut sequences at once (Vim-style).
18
+ *
19
+ * Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or accessors,
20
+ * so you can react to variable-length lists.
21
+ *
22
+ * Options are merged in this order:
23
+ * HotkeysProvider defaults < commonOptions < per-definition options
24
+ *
25
+ * Definitions with an empty `sequence` are skipped (no registration).
26
+ *
27
+ * @param sequences - Array of sequence definitions, or accessor returning them
28
+ * @param commonOptions - Shared options for all sequences, or accessor
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * function VimPalette() {
33
+ * createHotkeySequences([
34
+ * { sequence: ['G', 'G'], callback: () => scrollToTop() },
35
+ * { sequence: ['D', 'D'], callback: () => deleteLine() },
36
+ * ])
37
+ * }
38
+ * ```
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * function Dynamic(props) {
43
+ * createHotkeySequences(
44
+ * () => props.items.map((item) => ({
45
+ * sequence: item.chords,
46
+ * callback: item.action,
47
+ * options: { enabled: item.enabled },
48
+ * })),
49
+ * { preventDefault: true },
50
+ * )
51
+ * }
52
+ * ```
53
+ */
54
+ declare function createHotkeySequences(sequences: Array<CreateHotkeySequenceDefinition> | (() => Array<CreateHotkeySequenceDefinition>), commonOptions?: CreateHotkeySequenceOptions | (() => CreateHotkeySequenceOptions)): void;
55
+ //#endregion
56
+ export { CreateHotkeySequenceDefinition, createHotkeySequences };
57
+ //# sourceMappingURL=createHotkeySequences.d.ts.map
@@ -0,0 +1,110 @@
1
+ import { useDefaultHotkeysOptions } from "./HotkeysProvider.js";
2
+ import { formatHotkeySequence, getSequenceManager } from "@tanstack/hotkeys";
3
+ import { createEffect, onCleanup } from "solid-js";
4
+
5
+ //#region src/createHotkeySequences.ts
6
+ /**
7
+ * SolidJS primitive for registering multiple keyboard shortcut sequences at once (Vim-style).
8
+ *
9
+ * Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or accessors,
10
+ * so you can react to variable-length lists.
11
+ *
12
+ * Options are merged in this order:
13
+ * HotkeysProvider defaults < commonOptions < per-definition options
14
+ *
15
+ * Definitions with an empty `sequence` are skipped (no registration).
16
+ *
17
+ * @param sequences - Array of sequence definitions, or accessor returning them
18
+ * @param commonOptions - Shared options for all sequences, or accessor
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * function VimPalette() {
23
+ * createHotkeySequences([
24
+ * { sequence: ['G', 'G'], callback: () => scrollToTop() },
25
+ * { sequence: ['D', 'D'], callback: () => deleteLine() },
26
+ * ])
27
+ * }
28
+ * ```
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * function Dynamic(props) {
33
+ * createHotkeySequences(
34
+ * () => props.items.map((item) => ({
35
+ * sequence: item.chords,
36
+ * callback: item.action,
37
+ * options: { enabled: item.enabled },
38
+ * })),
39
+ * { preventDefault: true },
40
+ * )
41
+ * }
42
+ * ```
43
+ */
44
+ function createHotkeySequences(sequences, commonOptions = {}) {
45
+ const defaultOptions = useDefaultHotkeysOptions();
46
+ const manager = getSequenceManager();
47
+ const registrations = /* @__PURE__ */ new Map();
48
+ onCleanup(() => {
49
+ for (const { handle } of registrations.values()) if (handle.isActive) handle.unregister();
50
+ registrations.clear();
51
+ });
52
+ createEffect(() => {
53
+ const resolved = typeof sequences === "function" ? sequences() : sequences;
54
+ const resolvedCommonOptions = typeof commonOptions === "function" ? commonOptions() : commonOptions;
55
+ const nextKeys = /* @__PURE__ */ new Set();
56
+ const prepared = [];
57
+ for (let i = 0; i < resolved.length; i++) {
58
+ const def = resolved[i];
59
+ const resolvedDefOptions = def.options ?? {};
60
+ const mergedOptions = {
61
+ ...defaultOptions.hotkeySequence,
62
+ ...resolvedCommonOptions,
63
+ ...resolvedDefOptions
64
+ };
65
+ if (def.sequence.length === 0) continue;
66
+ const sequenceString = formatHotkeySequence(def.sequence);
67
+ const resolvedTarget = "target" in mergedOptions ? mergedOptions.target ?? null : typeof document !== "undefined" ? document : null;
68
+ if (!resolvedTarget) continue;
69
+ const registrationKey = `${i}:${sequenceString}`;
70
+ nextKeys.add(registrationKey);
71
+ prepared.push({
72
+ registrationKey,
73
+ def,
74
+ mergedOptions,
75
+ sequenceString,
76
+ resolvedTarget
77
+ });
78
+ }
79
+ for (const [key, record] of [...registrations.entries()]) if (!nextKeys.has(key)) {
80
+ if (record.handle.isActive) record.handle.unregister();
81
+ registrations.delete(key);
82
+ }
83
+ for (const p of prepared) {
84
+ const existing = registrations.get(p.registrationKey);
85
+ if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
86
+ existing.handle.callback = p.def.callback;
87
+ const { target: _target, ...optionsWithoutTarget } = p.mergedOptions;
88
+ existing.handle.setOptions(optionsWithoutTarget);
89
+ continue;
90
+ }
91
+ if (existing) {
92
+ if (existing.handle.isActive) existing.handle.unregister();
93
+ registrations.delete(p.registrationKey);
94
+ }
95
+ const { target: _target, ...optionsWithoutTarget } = p.mergedOptions;
96
+ const handle = manager.register(p.def.sequence, p.def.callback, {
97
+ ...optionsWithoutTarget,
98
+ target: p.resolvedTarget
99
+ });
100
+ registrations.set(p.registrationKey, {
101
+ handle,
102
+ target: p.resolvedTarget
103
+ });
104
+ }
105
+ });
106
+ }
107
+
108
+ //#endregion
109
+ export { createHotkeySequences };
110
+ //# sourceMappingURL=createHotkeySequences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createHotkeySequences.js","names":[],"sources":["../src/createHotkeySequences.ts"],"sourcesContent":["import { createEffect, onCleanup } from 'solid-js'\nimport { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProvider'\nimport type { CreateHotkeySequenceOptions } from './createHotkeySequence'\nimport type {\n HotkeyCallback,\n HotkeySequence,\n SequenceRegistrationHandle,\n} from '@tanstack/hotkeys'\n\n/**\n * A single sequence definition for use with `createHotkeySequences`.\n */\nexport interface CreateHotkeySequenceDefinition {\n /** Array of hotkey strings that form the sequence */\n sequence: HotkeySequence\n /** The function to call when the sequence is completed */\n callback: HotkeyCallback\n /** Per-sequence options (merged on top of commonOptions) */\n options?: CreateHotkeySequenceOptions\n}\n\n/**\n * SolidJS primitive for registering multiple keyboard shortcut sequences at once (Vim-style).\n *\n * Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or accessors,\n * so you can react to variable-length lists.\n *\n * Options are merged in this order:\n * HotkeysProvider defaults < commonOptions < per-definition options\n *\n * Definitions with an empty `sequence` are skipped (no registration).\n *\n * @param sequences - Array of sequence definitions, or accessor returning them\n * @param commonOptions - Shared options for all sequences, or accessor\n *\n * @example\n * ```tsx\n * function VimPalette() {\n * createHotkeySequences([\n * { sequence: ['G', 'G'], callback: () => scrollToTop() },\n * { sequence: ['D', 'D'], callback: () => deleteLine() },\n * ])\n * }\n * ```\n *\n * @example\n * ```tsx\n * function Dynamic(props) {\n * createHotkeySequences(\n * () => props.items.map((item) => ({\n * sequence: item.chords,\n * callback: item.action,\n * options: { enabled: item.enabled },\n * })),\n * { preventDefault: true },\n * )\n * }\n * ```\n */\nexport function createHotkeySequences(\n sequences:\n | Array<CreateHotkeySequenceDefinition>\n | (() => Array<CreateHotkeySequenceDefinition>),\n commonOptions:\n | CreateHotkeySequenceOptions\n | (() => CreateHotkeySequenceOptions) = {},\n): void {\n type RegistrationRecord = {\n handle: SequenceRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getSequenceManager()\n\n const registrations = new Map<string, RegistrationRecord>()\n\n onCleanup(() => {\n for (const { handle } of registrations.values()) {\n if (handle.isActive) {\n handle.unregister()\n }\n }\n registrations.clear()\n })\n\n createEffect(() => {\n const resolved = typeof sequences === 'function' ? sequences() : sequences\n const resolvedCommonOptions =\n typeof commonOptions === 'function' ? commonOptions() : commonOptions\n\n const nextKeys = new Set<string>()\n const prepared: Array<{\n registrationKey: string\n def: CreateHotkeySequenceDefinition\n mergedOptions: CreateHotkeySequenceOptions\n sequenceString: string\n resolvedTarget: Document | HTMLElement | Window\n }> = []\n\n for (let i = 0; i < resolved.length; i++) {\n const def = resolved[i]!\n const resolvedDefOptions = def.options ?? {}\n\n const mergedOptions = {\n ...defaultOptions.hotkeySequence,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as CreateHotkeySequenceOptions\n\n if (def.sequence.length === 0) {\n continue\n }\n\n const sequenceString = formatHotkeySequence(def.sequence)\n\n const resolvedTarget =\n 'target' in mergedOptions\n ? (mergedOptions.target ?? null)\n : typeof document !== 'undefined'\n ? document\n : null\n\n if (!resolvedTarget) {\n continue\n }\n\n const registrationKey = `${i}:${sequenceString}`\n nextKeys.add(registrationKey)\n prepared.push({\n registrationKey,\n def,\n mergedOptions,\n sequenceString,\n resolvedTarget,\n })\n }\n\n for (const [key, record] of [...registrations.entries()]) {\n if (!nextKeys.has(key)) {\n if (record.handle.isActive) {\n record.handle.unregister()\n }\n registrations.delete(key)\n }\n }\n\n for (const p of prepared) {\n const existing = registrations.get(p.registrationKey)\n if (existing?.handle.isActive && existing.target === p.resolvedTarget) {\n existing.handle.callback = p.def.callback\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\n existing.handle.setOptions(optionsWithoutTarget)\n continue\n }\n\n if (existing) {\n if (existing.handle.isActive) {\n existing.handle.unregister()\n }\n registrations.delete(p.registrationKey)\n }\n\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\n const handle = manager.register(p.def.sequence, p.def.callback, {\n ...optionsWithoutTarget,\n target: p.resolvedTarget,\n })\n registrations.set(p.registrationKey, {\n handle,\n target: p.resolvedTarget,\n })\n }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAgB,sBACd,WAGA,gBAE0C,EAAE,EACtC;CAMN,MAAM,iBAAiB,0BAA0B;CACjD,MAAM,UAAU,oBAAoB;CAEpC,MAAM,gCAAgB,IAAI,KAAiC;AAE3D,iBAAgB;AACd,OAAK,MAAM,EAAE,YAAY,cAAc,QAAQ,CAC7C,KAAI,OAAO,SACT,QAAO,YAAY;AAGvB,gBAAc,OAAO;GACrB;AAEF,oBAAmB;EACjB,MAAM,WAAW,OAAO,cAAc,aAAa,WAAW,GAAG;EACjE,MAAM,wBACJ,OAAO,kBAAkB,aAAa,eAAe,GAAG;EAE1D,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAMD,EAAE;AAEP,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;GACxC,MAAM,MAAM,SAAS;GACrB,MAAM,qBAAqB,IAAI,WAAW,EAAE;GAE5C,MAAM,gBAAgB;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;AAED,OAAI,IAAI,SAAS,WAAW,EAC1B;GAGF,MAAM,iBAAiB,qBAAqB,IAAI,SAAS;GAEzD,MAAM,iBACJ,YAAY,gBACP,cAAc,UAAU,OACzB,OAAO,aAAa,cAClB,WACA;AAER,OAAI,CAAC,eACH;GAGF,MAAM,kBAAkB,GAAG,EAAE,GAAG;AAChC,YAAS,IAAI,gBAAgB;AAC7B,YAAS,KAAK;IACZ;IACA;IACA;IACA;IACA;IACD,CAAC;;AAGJ,OAAK,MAAM,CAAC,KAAK,WAAW,CAAC,GAAG,cAAc,SAAS,CAAC,CACtD,KAAI,CAAC,SAAS,IAAI,IAAI,EAAE;AACtB,OAAI,OAAO,OAAO,SAChB,QAAO,OAAO,YAAY;AAE5B,iBAAc,OAAO,IAAI;;AAI7B,OAAK,MAAM,KAAK,UAAU;GACxB,MAAM,WAAW,cAAc,IAAI,EAAE,gBAAgB;AACrD,OAAI,UAAU,OAAO,YAAY,SAAS,WAAW,EAAE,gBAAgB;AACrE,aAAS,OAAO,WAAW,EAAE,IAAI;IACjC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;AACvD,aAAS,OAAO,WAAW,qBAAqB;AAChD;;AAGF,OAAI,UAAU;AACZ,QAAI,SAAS,OAAO,SAClB,UAAS,OAAO,YAAY;AAE9B,kBAAc,OAAO,EAAE,gBAAgB;;GAGzC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;GACvD,MAAM,SAAS,QAAQ,SAAS,EAAE,IAAI,UAAU,EAAE,IAAI,UAAU;IAC9D,GAAG;IACH,QAAQ,EAAE;IACX,CAAC;AACF,iBAAc,IAAI,EAAE,iBAAiB;IACnC;IACA,QAAQ,EAAE;IACX,CAAC;;GAEJ"}
package/dist/index.cjs CHANGED
@@ -6,7 +6,9 @@ const require_createHeldKeys = require('./createHeldKeys.cjs');
6
6
  const require_createHeldKeyCodes = require('./createHeldKeyCodes.cjs');
7
7
  const require_createKeyHold = require('./createKeyHold.cjs');
8
8
  const require_createHotkeySequence = require('./createHotkeySequence.cjs');
9
+ const require_createHotkeySequences = require('./createHotkeySequences.cjs');
9
10
  const require_createHotkeyRecorder = require('./createHotkeyRecorder.cjs');
11
+ const require_createHotkeySequenceRecorder = require('./createHotkeySequenceRecorder.cjs');
10
12
 
11
13
  exports.HotkeysProvider = require_HotkeysProvider.HotkeysProvider;
12
14
  exports.createHeldKeyCodes = require_createHeldKeyCodes.createHeldKeyCodes;
@@ -14,6 +16,8 @@ exports.createHeldKeys = require_createHeldKeys.createHeldKeys;
14
16
  exports.createHotkey = require_createHotkey.createHotkey;
15
17
  exports.createHotkeyRecorder = require_createHotkeyRecorder.createHotkeyRecorder;
16
18
  exports.createHotkeySequence = require_createHotkeySequence.createHotkeySequence;
19
+ exports.createHotkeySequenceRecorder = require_createHotkeySequenceRecorder.createHotkeySequenceRecorder;
20
+ exports.createHotkeySequences = require_createHotkeySequences.createHotkeySequences;
17
21
  exports.createHotkeys = require_createHotkeys.createHotkeys;
18
22
  exports.createKeyHold = require_createKeyHold.createKeyHold;
19
23
  exports.useDefaultHotkeysOptions = require_HotkeysProvider.useDefaultHotkeysOptions;
package/dist/index.d.cts CHANGED
@@ -5,6 +5,8 @@ import { CreateHotkeyDefinition, createHotkeys } from "./createHotkeys.cjs";
5
5
  import { createHeldKeys } from "./createHeldKeys.cjs";
6
6
  import { createHeldKeyCodes } from "./createHeldKeyCodes.cjs";
7
7
  import { createKeyHold } from "./createKeyHold.cjs";
8
+ import { CreateHotkeySequenceDefinition, createHotkeySequences } from "./createHotkeySequences.cjs";
8
9
  import { SolidHotkeyRecorder, createHotkeyRecorder } from "./createHotkeyRecorder.cjs";
10
+ import { SolidHotkeySequenceRecorder, createHotkeySequenceRecorder } from "./createHotkeySequenceRecorder.cjs";
9
11
  export * from "@tanstack/hotkeys";
10
- export { CreateHotkeyDefinition, CreateHotkeyOptions, CreateHotkeySequenceOptions, HotkeysProvider, HotkeysProviderOptions, HotkeysProviderProps, SolidHotkeyRecorder, createHeldKeyCodes, createHeldKeys, createHotkey, createHotkeyRecorder, createHotkeySequence, createHotkeys, createKeyHold, useDefaultHotkeysOptions, useHotkeysContext };
12
+ export { CreateHotkeyDefinition, CreateHotkeyOptions, CreateHotkeySequenceDefinition, CreateHotkeySequenceOptions, HotkeysProvider, HotkeysProviderOptions, HotkeysProviderProps, SolidHotkeyRecorder, SolidHotkeySequenceRecorder, createHeldKeyCodes, createHeldKeys, createHotkey, createHotkeyRecorder, createHotkeySequence, createHotkeySequenceRecorder, createHotkeySequences, createHotkeys, createKeyHold, useDefaultHotkeysOptions, useHotkeysContext };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ import { CreateHotkeyDefinition, createHotkeys } from "./createHotkeys.js";
5
5
  import { createHeldKeys } from "./createHeldKeys.js";
6
6
  import { createHeldKeyCodes } from "./createHeldKeyCodes.js";
7
7
  import { createKeyHold } from "./createKeyHold.js";
8
+ import { CreateHotkeySequenceDefinition, createHotkeySequences } from "./createHotkeySequences.js";
8
9
  import { SolidHotkeyRecorder, createHotkeyRecorder } from "./createHotkeyRecorder.js";
10
+ import { SolidHotkeySequenceRecorder, createHotkeySequenceRecorder } from "./createHotkeySequenceRecorder.js";
9
11
  export * from "@tanstack/hotkeys";
10
- export { CreateHotkeyDefinition, CreateHotkeyOptions, CreateHotkeySequenceOptions, HotkeysProvider, HotkeysProviderOptions, HotkeysProviderProps, SolidHotkeyRecorder, createHeldKeyCodes, createHeldKeys, createHotkey, createHotkeyRecorder, createHotkeySequence, createHotkeys, createKeyHold, useDefaultHotkeysOptions, useHotkeysContext };
12
+ export { CreateHotkeyDefinition, CreateHotkeyOptions, CreateHotkeySequenceDefinition, CreateHotkeySequenceOptions, HotkeysProvider, HotkeysProviderOptions, HotkeysProviderProps, SolidHotkeyRecorder, SolidHotkeySequenceRecorder, createHeldKeyCodes, createHeldKeys, createHotkey, createHotkeyRecorder, createHotkeySequence, createHotkeySequenceRecorder, createHotkeySequences, createHotkeys, createKeyHold, useDefaultHotkeysOptions, useHotkeysContext };
package/dist/index.js CHANGED
@@ -5,8 +5,10 @@ import { createHeldKeys } from "./createHeldKeys.js";
5
5
  import { createHeldKeyCodes } from "./createHeldKeyCodes.js";
6
6
  import { createKeyHold } from "./createKeyHold.js";
7
7
  import { createHotkeySequence } from "./createHotkeySequence.js";
8
+ import { createHotkeySequences } from "./createHotkeySequences.js";
8
9
  import { createHotkeyRecorder } from "./createHotkeyRecorder.js";
10
+ import { createHotkeySequenceRecorder } from "./createHotkeySequenceRecorder.js";
9
11
 
10
12
  export * from "@tanstack/hotkeys"
11
13
 
12
- export { HotkeysProvider, createHeldKeyCodes, createHeldKeys, createHotkey, createHotkeyRecorder, createHotkeySequence, createHotkeys, createKeyHold, useDefaultHotkeysOptions, useHotkeysContext };
14
+ export { HotkeysProvider, createHeldKeyCodes, createHeldKeys, createHotkey, createHotkeyRecorder, createHotkeySequence, createHotkeySequenceRecorder, createHotkeySequences, createHotkeys, createKeyHold, useDefaultHotkeysOptions, useHotkeysContext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/solid-hotkeys",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "description": "SolidJS adapter for TanStack Hotkeys",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  ],
44
44
  "dependencies": {
45
45
  "@tanstack/solid-store": "^0.9.2",
46
- "@tanstack/hotkeys": "0.4.3"
46
+ "@tanstack/hotkeys": "0.5.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@solidjs/testing-library": "^0.8.10",
@@ -1,12 +1,16 @@
1
1
  import { createContext, useContext } from 'solid-js'
2
2
  import type { JSX, ParentComponent } from 'solid-js'
3
- import type { HotkeyRecorderOptions } from '@tanstack/hotkeys'
3
+ import type {
4
+ HotkeyRecorderOptions,
5
+ HotkeySequenceRecorderOptions,
6
+ } from '@tanstack/hotkeys'
4
7
  import type { CreateHotkeyOptions } from './createHotkey'
5
8
  import type { CreateHotkeySequenceOptions } from './createHotkeySequence'
6
9
 
7
10
  export interface HotkeysProviderOptions {
8
11
  hotkey?: Partial<CreateHotkeyOptions>
9
12
  hotkeyRecorder?: Partial<HotkeyRecorderOptions>
13
+ hotkeySequenceRecorder?: Partial<HotkeySequenceRecorderOptions>
10
14
  hotkeySequence?: Partial<CreateHotkeySequenceOptions>
11
15
  }
12
16
 
@@ -89,6 +89,17 @@ export function createHotkey(
89
89
  const manager = getHotkeyManager()
90
90
 
91
91
  let registration: HotkeyRegistrationHandle | null = null
92
+ let lastHotkeyString: Hotkey | null = null
93
+ let lastTarget: HTMLElement | Document | Window | null = null
94
+
95
+ onCleanup(() => {
96
+ if (registration?.isActive) {
97
+ registration.unregister()
98
+ registration = null
99
+ }
100
+ lastHotkeyString = null
101
+ lastTarget = null
102
+ })
92
103
 
93
104
  createEffect(() => {
94
105
  // Resolve reactive values
@@ -119,36 +130,44 @@ export function createHotkey(
119
130
  : null
120
131
 
121
132
  if (!resolvedTarget) {
133
+ if (registration?.isActive) {
134
+ registration.unregister()
135
+ registration = null
136
+ }
137
+ lastHotkeyString = null
138
+ lastTarget = null
139
+ return
140
+ }
141
+
142
+ // Extract options without target (target is handled separately)
143
+ const { target: _target, ...optionsWithoutTarget } = mergedOptions
144
+
145
+ if (
146
+ registration?.isActive &&
147
+ lastHotkeyString === hotkeyString &&
148
+ lastTarget === resolvedTarget
149
+ ) {
150
+ registration.callback = callback
151
+ registration.setOptions(optionsWithoutTarget)
122
152
  return
123
153
  }
124
154
 
125
- // Unregister previous registration if it exists
126
155
  if (registration?.isActive) {
127
156
  registration.unregister()
128
157
  registration = null
129
158
  }
130
159
 
131
- // Extract options without target (target is handled separately)
132
- const { target: _target, ...optionsWithoutTarget } = mergedOptions
133
-
134
- // Register the hotkey
135
160
  registration = manager.register(hotkeyString, callback, {
136
161
  ...optionsWithoutTarget,
137
162
  target: resolvedTarget,
138
163
  })
139
164
 
140
- // Update callback and options on every effect run
141
165
  if (registration.isActive) {
142
166
  registration.callback = callback
143
167
  registration.setOptions(optionsWithoutTarget)
144
168
  }
145
169
 
146
- // Cleanup on disposal
147
- onCleanup(() => {
148
- if (registration?.isActive) {
149
- registration.unregister()
150
- registration = null
151
- }
152
- })
170
+ lastHotkeyString = hotkeyString
171
+ lastTarget = resolvedTarget
153
172
  })
154
173
  }
@@ -1,5 +1,5 @@
1
1
  import { createEffect, onCleanup } from 'solid-js'
2
- import { getSequenceManager } from '@tanstack/hotkeys'
2
+ import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'
3
3
  import { useDefaultHotkeysOptions } from './HotkeysProvider'
4
4
  import type {
5
5
  HotkeyCallback,
@@ -72,6 +72,17 @@ export function createHotkeySequence(
72
72
  const manager = getSequenceManager()
73
73
 
74
74
  let registration: SequenceRegistrationHandle | null = null
75
+ let lastSequenceKey: string | null = null
76
+ let lastTarget: HTMLElement | Document | Window | null = null
77
+
78
+ onCleanup(() => {
79
+ if (registration?.isActive) {
80
+ registration.unregister()
81
+ registration = null
82
+ }
83
+ lastSequenceKey = null
84
+ lastTarget = null
85
+ })
75
86
 
76
87
  createEffect(() => {
77
88
  // Resolve reactive values
@@ -87,10 +98,6 @@ export function createHotkeySequence(
87
98
  // Extract options without target (target is handled separately)
88
99
  const { target: _target, ...optionsWithoutTarget } = mergedOptions
89
100
 
90
- if (resolvedSequence.length === 0) {
91
- return
92
- }
93
-
94
101
  // Resolve target: when explicitly provided (even as null), use it and skip if null.
95
102
  // When not provided, default to document. Matches createHotkey.
96
103
  const resolvedTarget =
@@ -100,34 +107,44 @@ export function createHotkeySequence(
100
107
  ? document
101
108
  : null
102
109
 
103
- if (!resolvedTarget) {
110
+ if (resolvedSequence.length === 0 || !resolvedTarget) {
111
+ if (registration?.isActive) {
112
+ registration.unregister()
113
+ registration = null
114
+ }
115
+ lastSequenceKey = null
116
+ lastTarget = null
117
+ return
118
+ }
119
+
120
+ const sequenceKey = formatHotkeySequence(resolvedSequence)
121
+
122
+ if (
123
+ registration?.isActive &&
124
+ lastSequenceKey === sequenceKey &&
125
+ lastTarget === resolvedTarget
126
+ ) {
127
+ registration.callback = callback
128
+ registration.setOptions(optionsWithoutTarget)
104
129
  return
105
130
  }
106
131
 
107
- // Unregister previous registration if it exists
108
132
  if (registration?.isActive) {
109
133
  registration.unregister()
110
134
  registration = null
111
135
  }
112
136
 
113
- // Register the sequence
114
137
  registration = manager.register(resolvedSequence, callback, {
115
138
  ...mergedOptions,
116
139
  target: resolvedTarget,
117
140
  })
118
141
 
119
- // Sync callback and options on every effect run
120
142
  if (registration.isActive) {
121
143
  registration.callback = callback
122
144
  registration.setOptions(optionsWithoutTarget)
123
145
  }
124
146
 
125
- // Cleanup on disposal
126
- onCleanup(() => {
127
- if (registration?.isActive) {
128
- registration.unregister()
129
- registration = null
130
- }
131
- })
147
+ lastSequenceKey = sequenceKey
148
+ lastTarget = resolvedTarget
132
149
  })
133
150
  }