@witchcraft/spellcraft 0.0.3 → 0.1.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/README.md CHANGED
@@ -123,7 +123,7 @@ You can choose in your execute function what exactly to do at that point for bot
123
123
 
124
124
  If non-modifier keys are still being held at this point, the manager will not allow triggering a shortcut until they are released (see `state.isAwaitingKeyup`). Modifiers are not affect by this. We usually want the user to be able to keep the modifier pressed and do, for example, `Ctrl+B` then `Ctrl+I` to bold and italicize text, without having to release `Ctrl`, only `B` and `I`.
125
125
 
126
- ## Errors and `{check}` Option
126
+ ## Errors and Checking if Actions can be Performed
127
127
 
128
128
  Note the use of `unwrap()`. Because many actions can throw "soft" errors, to better help deal with all the errors the library uses a Result monad in most of the return types. `unwrap` is like rust's unwrap and will throw the error if there was one, otherwise "unwrap" and return the value within.
129
129
 
@@ -278,12 +278,58 @@ export { }
278
278
  ```
279
279
 
280
280
  Additionally, when you create a condition, you can pass a function to `parse` it and add these needed properties:
281
+
282
+ You'll probably want to create a wrapping function to do this. Here's an example with the expressit library:
283
+
284
+
285
+ <details>
286
+
287
+ <summary>Click to Expand</summary>
288
+
281
289
  ```ts
282
- const condition = createCondition("a || b ", (_) => {
283
- _.ast = parse(_.text)
290
+ import { ShortcutContextParser } from "@witchcraft/expressit/examples/ShortcutContextParser"
291
+ import { createCondition as _createCondition } from "@witchcraft/spellcraft"
292
+
293
+ const dummyContext = { a: { }, b: { c: {} } }
294
+ const shortcutParser = new ShortcutContextParser(dummyContext) // see docs for details
295
+ function parser(text: string, _: Condition) {
296
+ const res = shortcutsParser.parse(text)
297
+ // === undefined is to narrow out the ErrorToken type
298
+ if (!res.valid || res.type === undefined) {
299
+ throw new Error(`Shortcut context parser failed to parse condition ${_.text}`)
300
+ }
301
+
302
+ _.ast = res
284
303
  return _
304
+ }
305
+
306
+ /** Creates a condition with the ast saved in it. */
307
+ function createCondition(text: string) {
308
+ return _createCondition(text, parser)
309
+ }
310
+ const condition = createCondition("a || b")
311
+ condition.ast // should exist
312
+
313
+ // tell the manager how to evaluare the condition:
314
+ const manager = createmanager({
315
+ context: createcontext<context<map<string, boolean>>>(new map()),
316
+ options: {
317
+ evaluatecondition(condition, context) {
318
+ if (condition.ast === undefined) throw new Error("condition ast is undefined")
319
+
320
+ const res = condition.ast.valid
321
+ ? conditionParser.evaluate(
322
+ conditionParser.normalize(condition.ast),
323
+ context.value.isActive
324
+ )
325
+ : false
326
+ return res
327
+ },
328
+ }
285
329
  })
330
+
286
331
  ```
332
+ </details>
287
333
 
288
334
  ## Contexts
289
335
 
@@ -292,10 +338,10 @@ Similarly with contexts, you can use any sort of object or type that you like.
292
338
  You can tell the manager it's type when you create it. For example, say we wanted to use a map:
293
339
 
294
340
  ```ts
295
- const manager = createManager({
296
- context: createContext<Context<Map<string, boolean>>>(new Map()),
341
+ const manager = createmanager({
342
+ context: createcontext<context<map<string, boolean>>>(new map()),
297
343
  options: {
298
- evaluateCondition(condition, context) {
344
+ evaluatecondition(condition, context) {
299
345
  // context is now correctly typed
300
346
  return context.value.has(condition.text)
301
347
  },
@@ -308,10 +354,10 @@ const manager = createManager({
308
354
  Creating a shortcut requires a the key/commands we created and the manager options to create a valid shortcut.
309
355
 
310
356
  ```ts
311
- const shortcut =createShortcut({
357
+ const shortcut = createShortcut({
312
358
  command: "test",
313
359
  chain: [["a"]],
314
- condition: createCondition("a || b", true),
360
+ condition: createCondition("a || b"),
315
361
  enabled: true,
316
362
  }, {options, keys, commands}).unwrap()
317
363
 
@@ -379,6 +425,10 @@ Note that while the built in errors are property specific, custom errors are not
379
425
 
380
426
  A helper class `ShortcutManagerManager` is provided to help manage multiple managers.
381
427
 
428
+ <details>
429
+
430
+ <summary>Click to Expand</summary>
431
+
382
432
  ```ts
383
433
  import { ShortcutManagerManager } from "@witchcraft/spellcraft"
384
434
 
@@ -435,6 +485,9 @@ managerManager.duplicateManager("myManagerName", "myDuplicateManagerName")
435
485
  managerManager.deleteManager("myManagerName")
436
486
  managerManager.renameManager("myManagerName", "myNewManagerName")
437
487
  ```
488
+
489
+ </details>
490
+
438
491
  You can then use the active manager's `onSet*Prop` hooks to call debouncedSave. You can wrap only the active manager to intercept all it's `onSet*Prop` calls. See the `useMultipleManagers` composable in the demo.
439
492
 
440
493
  ## Other Helpers and Utilities
@@ -453,6 +506,7 @@ There are many helpers provided to simplify common use cases under `/helpers`. S
453
506
  There's also some smaller utility functions in `/utils`:
454
507
  - `equals/dedupe/clone/*Key` These are particularly important for manipulating chords. This is because keys which are variants of eachother (see `Key.variants`) do not have matching ids and we usually want to be able to dedupe by the variants as well.
455
508
  - `isAny/Trigger/Wheel/MouseKey`.
509
+ - `getKeyCodesFromKeys` for getting the raw key codes from a list of keys for use with third-party libraries.
456
510
 
457
511
 
458
512
  There's also a few other functions that in the future might be moved from the demo were I created them and into the library. See [demo/src/common](https://github.com/AlansCodeLog/spellcraft/tree/master/demo/src/common).
@@ -490,18 +544,28 @@ Under gnome at least, if a key (usually Ctrl) is set to locate the cursor, it wi
490
544
 
491
545
  # FAQ
492
546
 
493
- ## Browser shortcuts interfere with certain shortcuts, how can this be avoided?
547
+
548
+ <details>
549
+
550
+ <summary>Browser shortcuts interfere with certain shortcuts, how can this be avoided?</summary>
494
551
 
495
552
  You can use a listener on the manager to e.preventDefault() some of these, but this doesn't work for all of them.
496
553
 
497
554
  If available you can also try using the [Keyboard API's](https://developer.mozilla.org/en-US/docs/Web/API/Keyboard_API) lock method (see [Keyboard Locking](https://developer.mozilla.org/en-US/docs/Web/API/Keyboard_API#keyboard_locking) ).
498
555
 
499
- ## How to label keys with their local names?
556
+ </details>
557
+
558
+ <details>
559
+
560
+ <summary>How to label keys with their local names?</summary>
500
561
 
501
562
  If the [Keyboard API](https://developer.mozilla.org/en-US/docs/Web/API/Keyboard_API) is available, you can use it's [navigator.keyboard.getLayoutMap method.](https://developer.mozilla.org/en-US/docs/Web/API/Keyboard/getLayoutMap). Helpers (getKeyboardLayoutMap and labelWithNavigator) are provided for this purpose, see them for details.
502
563
 
564
+ </details>
503
565
 
504
- ## How to set multiple manager properties safely (i.e. batch replace shortcuts/commands/keys)?
566
+ <details>
567
+
568
+ <summary>How to set multiple manager properties safely (i.e. batch replace shortcuts/commands/keys)?</summary>
505
569
 
506
570
  This can be an issue because there isn't a way to tell the manager you want to replace *multiple* properties and it might be impossible to, for example, replace commands with a smaller subset but not have it error even if you're planning to replace the shortcuts so they don't contain missing commands.
507
571
 
@@ -519,7 +583,11 @@ if (isValidManager(manager)) {
519
583
  }
520
584
 
521
585
  ```
522
- ## How to create `modifier-only` shortcuts? (e.g. a shortcut `Ctrl` that changes the some state like enabling multiple selection).
586
+ </details>
587
+
588
+ <details>
589
+
590
+ <summary>How to create `modifier-only` shortcuts? (e.g. a shortcut `Ctrl` that changes the some state like enabling multiple selection).</summary>
523
591
 
524
592
  To do this, instead of clearing the manager's chain, you just set the state directly.
525
593
 
@@ -563,17 +631,48 @@ function addToSelected(item) {
563
631
  }
564
632
  </script>
565
633
  ```
566
- # How to type the context?
634
+ </details>
635
+
636
+ <details>
637
+
638
+ <summary>How to show pretty shortcut strings to the user?</summary>
639
+
640
+ To show a pretty stringified version of a shortcut, use `manager.options.stringifier.stringify(shortcut.chain, manager)`.
641
+
642
+ Here's an advanced example in vue that shows a hint only when needed:
643
+
644
+ ```ts
645
+ const usageInstructions = computed(() => {
646
+ const manager = shortcuts.activeManager!.value!
647
+ const s = manager.options.stringifier
648
+ const context = shortcuts.context!
649
+ const isDragging = context.value.layout.drag.isDragging
650
+ const splitChain = manager.shortcuts.entries.find(_ => _.command === LAYOUT_COMMANDS.dragModifierSplit)?.chain
651
+
652
+ return isDragging && splitChain
653
+ `Hold ${s.stringify(splitChain, manager)} to Split`
654
+ : undefined,
655
+ })
656
+ ```
657
+
658
+ </details>
659
+
660
+ <details>
661
+
662
+ <summary>How to type the context?</summary>
663
+
664
+ Types can be extended multiple times from different files so long as the `NAME` part below is unique for each extension.
567
665
 
568
666
  ```ts [global.ts]
569
667
  declare module "@witchcraft/spellcraft/types" {
570
668
  // or for nuxt
571
669
  // declare module "#witchcraft/spellcraft/types.js" {
572
670
  export interface Register {
573
- ExtendedContextInfo: {
671
+ ShortcutManagerContextNAME: { //replace NAME with something
574
672
  // extend types here
575
673
  }
576
674
  }
577
675
  }
578
676
  ```
677
+ </details>
579
678
 
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Given an list of keys, returns an array of deduped key codes.
3
+ *
4
+ * Useful for when the codes must be passed to a third-party library (for example, a dragging library that takes a list modifiers).
5
+ *
6
+ * This properly takes a look at the key and all it's variants.
7
+ *
8
+ * `skipIdIfHasVariants` is true by default and will skip adding the key's id if it has variants as it's assumed the id is not a valid name.
9
+ */
10
+ import type { Key } from "../types/keys.js";
11
+ export declare function getKeyCodesFromKeys(keys: Key[], { skipIdIfHasVariants }?: {
12
+ skipIdIfHasVariants?: boolean;
13
+ }): string[];
@@ -0,0 +1,16 @@
1
+ export function getKeyCodesFromKeys(keys, { skipIdIfHasVariants = true } = {}) {
2
+ const res = /* @__PURE__ */ new Set();
3
+ for (const key of keys) {
4
+ if (key.variants) {
5
+ if (!skipIdIfHasVariants) {
6
+ res.add(key.id);
7
+ }
8
+ for (const variant of key.variants) {
9
+ res.add(variant);
10
+ }
11
+ } else {
12
+ res.add(key.id);
13
+ }
14
+ }
15
+ return Array.from(res);
16
+ }
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "witchcraftSpellcraft",
3
3
  "configKey": "witchcraftSpellcraft",
4
- "version": "0.0.3",
4
+ "version": "0.1.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,12 +1,7 @@
1
1
  import { createResolver, defineNuxtModule, addTypeTemplate, addImportsDir } from '@nuxt/kit';
2
2
 
3
- const dependencies = {
4
- "@witchcraft/ui": "^0.3.2"};
5
- const pkg = {
6
- dependencies: dependencies};
7
-
8
3
  const { resolve } = createResolver(import.meta.url);
9
- const module = defineNuxtModule({
4
+ const module$1 = defineNuxtModule({
10
5
  meta: {
11
6
  name: "witchcraftSpellcraft",
12
7
  configKey: "witchcraftSpellcraft"
@@ -14,7 +9,7 @@ const module = defineNuxtModule({
14
9
  defaults: {},
15
10
  moduleDependencies: {
16
11
  "@witchcraft/ui/nuxt": {
17
- version: pkg.dependencies["@witchcraft/ui"]
12
+ version: "^0.3.6"
18
13
  }
19
14
  },
20
15
  async setup(_options, nuxt) {
@@ -37,4 +32,4 @@ const module = defineNuxtModule({
37
32
  }
38
33
  });
39
34
 
40
- export { module as default };
35
+ export { module$1 as default };
@@ -1,9 +1,7 @@
1
1
  export interface Register {
2
2
  }
3
- type ExtendedContextInfo = Register extends {
4
- ExtendedContextInfo: infer T;
5
- } ? T : unknown;
6
- export type ContextInfo = ExtendedContextInfo & {
3
+ export type ExtendedShortcutManagerInfo = keyof Register extends `ShortcutManagerContext${infer T}` ? Register extends Record<`ShortcutManagerContext${T}`, infer U> ? U : {} : {};
4
+ export type ContextInfo = ExtendedShortcutManagerInfo & {
7
5
  count: Record<string, number>;
8
6
  isActive: Record<string, boolean>;
9
7
  };
package/package.json CHANGED
@@ -1,156 +1,157 @@
1
1
  {
2
- "name": "@witchcraft/spellcraft",
3
- "description": "A shortcut manager library for handling ALL the shortcut needs of an application.",
4
- "version": "0.0.3",
5
- "type": "module",
6
- "main": "./dist/core/index.js",
7
- "sideEffects": false,
8
- "build": {
9
- "failOnWarn": false
10
- },
11
- "exports": {
12
- ".": {
13
- "types": "./dist/core/index.d.ts",
14
- "import": "./dist/core/index.js"
15
- },
16
- "./utils": {
17
- "types": "./dist/utils/index.d.ts",
18
- "import": "./dist/utils/index.js"
19
- },
20
- "./helpers": {
21
- "types": "./dist/helpers/index.d.ts",
22
- "import": "./dist/helpers/index.js"
23
- },
24
- "./types": {
25
- "types": "./dist/types/index.d.ts",
26
- "import": "./dist/types/index.js"
27
- },
28
- "./core": {
29
- "types": "./dist/core/index.d.ts",
30
- "import": "./dist/core/index.js"
31
- },
32
- "./nuxt": {
33
- "types": "./dist/types.d.mts",
34
- "import": "./dist/module.mjs"
35
- },
36
- "./*": {
37
- "types": "./dist/*.js",
38
- "import": "./dist/*.js"
39
- }
40
- },
41
- "scripts": {
42
- "prepare": "husky && pnpm nuxt-module-build prepare && pnpm build:only && cd playground && pnpm i --ignore-scripts --ignore-workspace",
43
- "build": "nuxt-module-build prepare && nuxt-module-build build && nuxi generate playground",
44
- "build:only": "nuxt-module-build build",
45
- "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
46
- "dev:playground": "nuxi dev playground",
47
- "dev": "nuxi dev playground",
48
- "test": "pnpm lint:types && vitest run --exclude '.direnv/**/*'",
49
- "test:watch": "vitest --watch --exclude '.direnv/**/*'",
50
- "test:inspect-errors": "cross-env INSPECT_ERRORS=true npm run test",
51
- "coverage": "vitest --exclude '.direnv/**/*' --coverage",
52
- "coverage:dev": "vitest --exclude '.direnv/**/*' --watch --coverage",
53
- "doc": "typedoc",
54
- "doc:watch": "onchange -i \"src/**/*.ts\" \"typedoc.config.js\" -- pnpm doc",
55
- "doc:serve": "http-server docs --port=5001",
56
- "doc:dev": "concurrently \"pnpm doc:watch\" \"pnpm doc:serve\"",
57
- "doc:check-invalid": "typedoc --listInvalidSymbolLinks",
58
- "lint:eslint": "eslint \"{src,test,playground/app}/**/*.{js,ts,vue,cjs}\" \"*.{js,ts}\" --max-warnings=0 --report-unused-disable-directives",
59
- "lint:types": "vue-tsc --noEmit && echo 'todo cd playground && vue-tsc --noEmit'",
60
- "lint:commits": "commitlint --from-last-tag --to HEAD --verbose",
61
- "lint:imports": "madge --circular --extensions ts ./src",
62
- "lint": "pnpm lint:types && pnpm lint:eslint && pnpm lint:commits && pnpm lint:imports",
63
- "actions:debug": "act -r -v",
64
- "gen:exports": "indexit update -o '${path}.js'"
65
- },
66
- "peerDependencies": {
67
- "@witchcraft/expressit": "^0.4.0",
68
- "@witchcraft/ui": "^0.3.2"
69
- },
70
- "peerDependenciesMeta": {
71
- "@witchcraft/ui": {
72
- "optional": true
73
- },
74
- "@witchcraft/expressit": {
75
- "optional": true
76
- }
77
- },
78
- "dependencies": {
79
- "@alanscodelog/utils": "^6.0.2",
80
- "@witchcraft/expressit": "^0.4.1",
81
- "@witchcraft/ui": "^0.3.2",
82
- "vue": "^3.5.21"
83
- },
84
- "devDependencies": {
85
- "@alanscodelog/commitlint-config": "^3.1.2",
86
- "@alanscodelog/eslint-config": "^6.3.1",
87
- "@alanscodelog/semantic-release-config": "^5.0.4",
88
- "@alanscodelog/tsconfigs": "^6.2.0",
89
- "@alanscodelog/vite-config": "^0.0.6",
90
- "@commitlint/cli": "^19.8.1",
91
- "@iconify/json": "^2.2.385",
92
- "@nuxt/eslint-config": "^1.9.0",
93
- "@nuxt/kit": "^4.1.2",
94
- "@nuxt/module-builder": "^1.0.2",
95
- "@nuxt/schema": "^4.1.2",
96
- "@nuxt/types": "^2.18.1",
97
- "@types/node": "^24.5.2",
98
- "@vitejs/plugin-vue": "^6.0.1",
99
- "@vitest/coverage-c8": "^0.33.0",
100
- "concurrently": "^9.2.1",
101
- "cross-env": "^10.0.0",
102
- "defu": "^6.1.4",
103
- "fast-glob": "^3.3.3",
104
- "http-server": "^14.1.1",
105
- "husky": "^9.1.7",
106
- "indexit": "2.1.0-beta.3",
107
- "madge": "^8.0.0",
108
- "nuxt": "^4.1.2",
109
- "onchange": "^7.1.0",
110
- "semantic-release": "^24.2.8",
111
- "tailwindcss": "^4.1.13",
112
- "typedoc": "0.28.13",
113
- "typescript": "^5.9.2",
114
- "unbuild": "^3.6.1",
115
- "unplugin-icons": "^22.3.0",
116
- "vite-tsconfig-paths": "^5.1.4",
117
- "vitest": "^3.2.4",
118
- "vue-tsc": "^3.0.7"
119
- },
120
- "author": "Alan <alanscodelog@gmail.com>",
121
- "repository": "https://github.com/witchcraftjs/spellcraft",
122
- "license": "MIT",
123
- "files": [
124
- "src",
125
- "dist"
126
- ],
127
- "release": {
128
- "extends": [
129
- "@alanscodelog/semantic-release-config"
130
- ]
131
- },
132
- "commitlint": {
133
- "extends": [
134
- "@alanscodelog"
135
- ]
136
- },
137
- "babel": {
138
- "presets": [
139
- "@alanscodelog"
140
- ]
141
- },
142
- "madge": {
143
- "detectiveOptions": {
144
- "ts": {
145
- "skipTypeImports": true
146
- }
147
- }
148
- },
149
- "browserslist": "> 0.5%, last 2 versions, not dead, not < 0.25%, not IE > 0, maintained node versions",
150
- "engines": {
151
- "node": ">=20.0.0"
152
- },
153
- "publishConfig": {
154
- "access": "public"
155
- }
156
- }
2
+ "name": "@witchcraft/spellcraft",
3
+ "description": "A shortcut manager library for handling ALL the shortcut needs of an application.",
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "main": "./dist/core/index.js",
7
+ "sideEffects": false,
8
+ "build": {
9
+ "failOnWarn": false
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/core/index.d.ts",
14
+ "import": "./dist/core/index.js"
15
+ },
16
+ "./utils": {
17
+ "types": "./dist/utils/index.d.ts",
18
+ "import": "./dist/utils/index.js"
19
+ },
20
+ "./helpers": {
21
+ "types": "./dist/helpers/index.d.ts",
22
+ "import": "./dist/helpers/index.js"
23
+ },
24
+ "./types": {
25
+ "types": "./dist/types/index.d.ts",
26
+ "import": "./dist/types/index.js"
27
+ },
28
+ "./core": {
29
+ "types": "./dist/core/index.d.ts",
30
+ "import": "./dist/core/index.js"
31
+ },
32
+ "./nuxt": {
33
+ "types": "./dist/types.d.mts",
34
+ "import": "./dist/module.mjs"
35
+ },
36
+ "./*": {
37
+ "types": "./dist/*.js",
38
+ "import": "./dist/*.js"
39
+ }
40
+ },
41
+ "peerDependencies": {
42
+ "@witchcraft/expressit": "^0.4.1",
43
+ "@witchcraft/ui": "^0.3.20"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "@witchcraft/ui": {
47
+ "optional": true
48
+ },
49
+ "@witchcraft/expressit": {
50
+ "optional": true
51
+ }
52
+ },
53
+ "dependencies": {
54
+ "@alanscodelog/utils": "^6.0.2",
55
+ "@witchcraft/expressit": "^0.4.1",
56
+ "@witchcraft/ui": "^0.3.20",
57
+ "reka-ui": "^2.8.0",
58
+ "vue": "^3.5.27"
59
+ },
60
+ "devDependencies": {
61
+ "@alanscodelog/commitlint-config": "^3.1.2",
62
+ "@alanscodelog/eslint-config": "^6.3.1",
63
+ "@alanscodelog/semantic-release-config": "^6.0.0",
64
+ "@alanscodelog/tsconfigs": "^6.2.0",
65
+ "@alanscodelog/vite-config": "^0.0.7",
66
+ "@commitlint/cli": "^20.4.1",
67
+ "@iconify/json": "^2.2.436",
68
+ "@nuxt/eslint-config": "^1.13.0",
69
+ "@nuxt/kit": "^4.3.0",
70
+ "@nuxt/module-builder": "^1.0.2",
71
+ "@nuxt/schema": "^4.3.0",
72
+ "@nuxt/types": "^2.18.1",
73
+ "@types/node": "^25.2.1",
74
+ "@vitejs/plugin-vue": "^6.0.4",
75
+ "@vitest/coverage-c8": "^0.33.0",
76
+ "concurrently": "^9.2.1",
77
+ "cross-env": "^10.1.0",
78
+ "defu": "^6.1.4",
79
+ "fast-glob": "^3.3.3",
80
+ "http-server": "^14.1.1",
81
+ "husky": "^9.1.7",
82
+ "indexit": "2.1.0-beta.3",
83
+ "madge": "^8.0.0",
84
+ "nuxt": "^4.3.0",
85
+ "onchange": "^7.1.0",
86
+ "semantic-release": "^25.0.3",
87
+ "tailwindcss": "^4.1.18",
88
+ "typedoc": "0.28.16",
89
+ "typescript": "^5.9.3",
90
+ "unbuild": "^3.6.1",
91
+ "unplugin-icons": "^23.0.1",
92
+ "vite-tsconfig-paths": "^6.0.5",
93
+ "vitest": "^4.0.18",
94
+ "vue-tsc": "^3.2.4"
95
+ },
96
+ "author": "Alan <alanscodelog@gmail.com>",
97
+ "repository": "https://github.com/witchcraftjs/spellcraft",
98
+ "license": "MIT",
99
+ "files": [
100
+ "src",
101
+ "dist"
102
+ ],
103
+ "release": {
104
+ "extends": [
105
+ "@alanscodelog/semantic-release-config"
106
+ ]
107
+ },
108
+ "commitlint": {
109
+ "extends": [
110
+ "@alanscodelog"
111
+ ]
112
+ },
113
+ "babel": {
114
+ "presets": [
115
+ "@alanscodelog"
116
+ ]
117
+ },
118
+ "madge": {
119
+ "detectiveOptions": {
120
+ "ts": {
121
+ "skipTypeImports": true
122
+ }
123
+ }
124
+ },
125
+ "browserslist": "> 0.5%, last 2 versions, not dead, not < 0.25%, not IE > 0, maintained node versions",
126
+ "engines": {
127
+ "node": ">=20.0.0"
128
+ },
129
+ "publishConfig": {
130
+ "access": "public",
131
+ "provenance": true
132
+ },
133
+ "scripts": {
134
+ "build": "nuxt-module-build prepare && nuxt-module-build build && nuxi generate playground",
135
+ "build:only": "nuxt-module-build build",
136
+ "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
137
+ "dev:playground": "nuxi dev playground",
138
+ "dev": "nuxi dev playground",
139
+ "test": "pnpm lint:types && vitest run --exclude '.direnv/**/*'",
140
+ "test:watch": "vitest --watch --exclude '.direnv/**/*'",
141
+ "test:inspect-errors": "cross-env INSPECT_ERRORS=true npm run test",
142
+ "coverage": "vitest --exclude '.direnv/**/*' --coverage",
143
+ "coverage:dev": "vitest --exclude '.direnv/**/*' --watch --coverage",
144
+ "doc": "typedoc",
145
+ "doc:watch": "onchange -i \"src/**/*.ts\" \"typedoc.config.js\" -- pnpm doc",
146
+ "doc:serve": "http-server docs --port=5001",
147
+ "doc:dev": "concurrently \"pnpm doc:watch\" \"pnpm doc:serve\"",
148
+ "doc:check-invalid": "typedoc --listInvalidSymbolLinks",
149
+ "lint:eslint": "eslint \"{src,test,playground/app}/**/*.{js,ts,vue,cjs}\" \"*.{js,ts}\" --max-warnings=0 --report-unused-disable-directives",
150
+ "lint:types": "vue-tsc --noEmit && echo 'todo cd playground && vue-tsc --noEmit'",
151
+ "lint:commits": "commitlint --from-last-tag --to HEAD --verbose",
152
+ "lint:imports": "madge --circular --extensions ts ./src",
153
+ "lint": "pnpm lint:types && pnpm lint:eslint && pnpm lint:commits && pnpm lint:imports",
154
+ "actions:debug": "act -r -v",
155
+ "gen:exports": "indexit update -o '${path}.js'"
156
+ }
157
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Given an list of keys, returns an array of deduped key codes.
3
+ *
4
+ * Useful for when the codes must be passed to a third-party library (for example, a dragging library that takes a list modifiers).
5
+ *
6
+ * This properly takes a look at the key and all it's variants.
7
+ *
8
+ * `skipIdIfHasVariants` is true by default and will skip adding the key's id if it has variants as it's assumed the id is not a valid name.
9
+ */
10
+
11
+ import type { Key } from "../types/keys.js"
12
+
13
+ export function getKeyCodesFromKeys(keys: Key[], { skipIdIfHasVariants = true}: { skipIdIfHasVariants?: boolean } = {}): string[] {
14
+ const res = new Set<string>()
15
+ for (const key of keys) {
16
+ if (key.variants) {
17
+ if (!skipIdIfHasVariants) {
18
+ res.add(key.id)
19
+ }
20
+ for (const variant of key.variants) {
21
+ res.add(variant)
22
+ }
23
+ } else {
24
+ res.add(key.id)
25
+ }
26
+ }
27
+ return Array.from(res)
28
+ }
package/src/module.ts CHANGED
@@ -5,8 +5,6 @@ import {
5
5
  defineNuxtModule
6
6
  } from "@nuxt/kit"
7
7
 
8
- import pkg from "../package.json" with { type: "json" }
9
-
10
8
 
11
9
  const { resolve } = createResolver(import.meta.url)
12
10
 
@@ -30,7 +28,7 @@ export default defineNuxtModule<ModuleOptions>({
30
28
  },
31
29
  moduleDependencies: {
32
30
  "@witchcraft/ui/nuxt": {
33
- version: pkg.dependencies["@witchcraft/ui"]
31
+ version: "^0.3.6"
34
32
  }
35
33
  },
36
34
  async setup(_options, nuxt) {
@@ -1,9 +1,22 @@
1
- export interface Register { }
1
+ // allows users to register multiple contexts
2
+ // @eslint-disable-next-line @typescript-eslint/no-empty-object-type
3
+ export interface Register {}
2
4
 
3
- // eslint-disable-next-line @typescript-eslint/naming-convention
4
- type ExtendedContextInfo = Register extends { ExtendedContextInfo: infer T } ? T : unknown
5
+ export type ExtendedShortcutManagerInfo = keyof Register extends `ShortcutManagerContext${infer T}`
6
+ ? Register extends Record<`ShortcutManagerContext${T}`, infer U>
7
+ ? U
8
+ // we can further constrain the type users can register if needed
9
+ // ? U extends { }
10
+ // ? U
11
+ // : {}
12
+ // : {}
13
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
14
+ : {}
15
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
16
+ : {}
5
17
 
6
- export type ContextInfo = ExtendedContextInfo & {
18
+
19
+ export type ContextInfo = ExtendedShortcutManagerInfo & {
7
20
  count: Record<string, number>
8
21
  isActive: Record<string, boolean>
9
22
  }