@witchcraft/spellcraft 0.0.1

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 (367) hide show
  1. package/README.md +579 -0
  2. package/dist/core/EmulatedEvent.d.ts +10 -0
  3. package/dist/core/EmulatedEvent.js +19 -0
  4. package/dist/core/Emulator.d.ts +74 -0
  5. package/dist/core/Emulator.js +153 -0
  6. package/dist/core/ShortcutManagerManager.d.ts +103 -0
  7. package/dist/core/ShortcutManagerManager.js +267 -0
  8. package/dist/core/addCommand.d.ts +7 -0
  9. package/dist/core/addCommand.js +7 -0
  10. package/dist/core/addKey.d.ts +7 -0
  11. package/dist/core/addKey.js +7 -0
  12. package/dist/core/addShortcut.d.ts +7 -0
  13. package/dist/core/addShortcut.js +7 -0
  14. package/dist/core/attach.d.ts +10 -0
  15. package/dist/core/attach.js +13 -0
  16. package/dist/core/createCommand.d.ts +4 -0
  17. package/dist/core/createCommand.js +11 -0
  18. package/dist/core/createCommands.d.ts +11 -0
  19. package/dist/core/createCommands.js +20 -0
  20. package/dist/core/createCondition.d.ts +7 -0
  21. package/dist/core/createCondition.js +11 -0
  22. package/dist/core/createContext.d.ts +2 -0
  23. package/dist/core/createContext.js +8 -0
  24. package/dist/core/createKey.d.ts +10 -0
  25. package/dist/core/createKey.js +35 -0
  26. package/dist/core/createKeys.d.ts +5 -0
  27. package/dist/core/createKeys.js +36 -0
  28. package/dist/core/createManager.d.ts +81 -0
  29. package/dist/core/createManager.js +83 -0
  30. package/dist/core/createManagerEventListeners.d.ts +4 -0
  31. package/dist/core/createManagerEventListeners.js +130 -0
  32. package/dist/core/createManagerOptions.d.ts +8 -0
  33. package/dist/core/createManagerOptions.js +17 -0
  34. package/dist/core/createShortcut.d.ts +8 -0
  35. package/dist/core/createShortcut.js +24 -0
  36. package/dist/core/createShortcuts.d.ts +13 -0
  37. package/dist/core/createShortcuts.js +20 -0
  38. package/dist/core/detach.d.ts +10 -0
  39. package/dist/core/detach.js +6 -0
  40. package/dist/core/index.d.ts +32 -0
  41. package/dist/core/index.js +32 -0
  42. package/dist/core/removeCommand.d.ts +7 -0
  43. package/dist/core/removeCommand.js +7 -0
  44. package/dist/core/removeKey.d.ts +7 -0
  45. package/dist/core/removeKey.js +7 -0
  46. package/dist/core/removeShortcut.d.ts +7 -0
  47. package/dist/core/removeShortcut.js +7 -0
  48. package/dist/core/setCommandProp.d.ts +17 -0
  49. package/dist/core/setCommandProp.js +96 -0
  50. package/dist/core/setCommandsProp.d.ts +15 -0
  51. package/dist/core/setCommandsProp.js +94 -0
  52. package/dist/core/setKeyProp.d.ts +16 -0
  53. package/dist/core/setKeyProp.js +47 -0
  54. package/dist/core/setKeysProp.d.ts +10 -0
  55. package/dist/core/setKeysProp.js +166 -0
  56. package/dist/core/setManagerProp.d.ts +34 -0
  57. package/dist/core/setManagerProp.js +54 -0
  58. package/dist/core/setShortcutProp.d.ts +9 -0
  59. package/dist/core/setShortcutProp.js +50 -0
  60. package/dist/core/setShortcutsProp.d.ts +12 -0
  61. package/dist/core/setShortcutsProp.js +93 -0
  62. package/dist/defaults/KeysSorter.d.ts +35 -0
  63. package/dist/defaults/KeysSorter.js +20 -0
  64. package/dist/defaults/Stringifier.d.ts +66 -0
  65. package/dist/defaults/Stringifier.js +119 -0
  66. package/dist/defaults/defaultConditionEquals.d.ts +19 -0
  67. package/dist/defaults/defaultConditionEquals.js +5 -0
  68. package/dist/defaults/defaultManagerCallback.d.ts +7 -0
  69. package/dist/defaults/defaultManagerCallback.js +5 -0
  70. package/dist/helpers/KnownError.d.ts +8 -0
  71. package/dist/helpers/KnownError.js +3 -0
  72. package/dist/helpers/calculateAndSetPositionAndWidth.d.ts +13 -0
  73. package/dist/helpers/calculateAndSetPositionAndWidth.js +18 -0
  74. package/dist/helpers/calculateLayoutSize.d.ts +9 -0
  75. package/dist/helpers/calculateLayoutSize.js +13 -0
  76. package/dist/helpers/doesShortcutConflict.d.ts +18 -0
  77. package/dist/helpers/doesShortcutConflict.js +42 -0
  78. package/dist/helpers/equalsCommand.d.ts +7 -0
  79. package/dist/helpers/equalsCommand.js +4 -0
  80. package/dist/helpers/equalsContext.d.ts +7 -0
  81. package/dist/helpers/equalsContext.js +19 -0
  82. package/dist/helpers/equalsShortcut.d.ts +9 -0
  83. package/dist/helpers/equalsShortcut.js +7 -0
  84. package/dist/helpers/forceClear.d.ts +12 -0
  85. package/dist/helpers/forceClear.js +16 -0
  86. package/dist/helpers/forceUpdateNativeKeysState.d.ts +5 -0
  87. package/dist/helpers/forceUpdateNativeKeysState.js +4 -0
  88. package/dist/helpers/generateKeyShortcutMap.d.ts +23 -0
  89. package/dist/helpers/generateKeyShortcutMap.js +64 -0
  90. package/dist/helpers/getKeyFromEventCode.d.ts +15 -0
  91. package/dist/helpers/getKeyFromEventCode.js +36 -0
  92. package/dist/helpers/getKeyFromIdOrVariant.d.ts +4 -0
  93. package/dist/helpers/getKeyFromIdOrVariant.js +26 -0
  94. package/dist/helpers/getKeyboardLayoutMap.d.ts +5 -0
  95. package/dist/helpers/getKeyboardLayoutMap.js +7 -0
  96. package/dist/helpers/getLabel.d.ts +9 -0
  97. package/dist/helpers/getLabel.js +6 -0
  98. package/dist/helpers/getTriggerableShortcut.d.ts +6 -0
  99. package/dist/helpers/getTriggerableShortcut.js +25 -0
  100. package/dist/helpers/index.d.ts +28 -0
  101. package/dist/helpers/index.js +28 -0
  102. package/dist/helpers/isValidManager.d.ts +3 -0
  103. package/dist/helpers/isValidManager.js +20 -0
  104. package/dist/helpers/isValidShortcut.d.ts +3 -0
  105. package/dist/helpers/isValidShortcut.js +10 -0
  106. package/dist/helpers/labelWithEvent.d.ts +13 -0
  107. package/dist/helpers/labelWithEvent.js +20 -0
  108. package/dist/helpers/labelWithKeyboardMap.d.ts +15 -0
  109. package/dist/helpers/labelWithKeyboardMap.js +17 -0
  110. package/dist/helpers/managerToStorableClone.d.ts +12 -0
  111. package/dist/helpers/managerToStorableClone.js +13 -0
  112. package/dist/helpers/onKeyboardLayoutChange.d.ts +10 -0
  113. package/dist/helpers/onKeyboardLayoutChange.js +7 -0
  114. package/dist/helpers/safeSetManagerChain.d.ts +20 -0
  115. package/dist/helpers/safeSetManagerChain.js +37 -0
  116. package/dist/helpers/shortcutCanExecuteIn.d.ts +4 -0
  117. package/dist/helpers/shortcutCanExecuteIn.js +9 -0
  118. package/dist/helpers/shortcutIsTriggerableBy.d.ts +2 -0
  119. package/dist/helpers/shortcutIsTriggerableBy.js +5 -0
  120. package/dist/helpers/shortcutSwapChords.d.ts +75 -0
  121. package/dist/helpers/shortcutSwapChords.js +118 -0
  122. package/dist/helpers/virtualPress.d.ts +13 -0
  123. package/dist/helpers/virtualPress.js +15 -0
  124. package/dist/helpers/virtualRelease.d.ts +5 -0
  125. package/dist/helpers/virtualRelease.js +15 -0
  126. package/dist/helpers/virtualToggle.d.ts +7 -0
  127. package/dist/helpers/virtualToggle.js +16 -0
  128. package/dist/internal/addToChain.d.ts +3 -0
  129. package/dist/internal/addToChain.js +27 -0
  130. package/dist/internal/areValidKeys.d.ts +7 -0
  131. package/dist/internal/areValidKeys.js +28 -0
  132. package/dist/internal/areValidVariants.d.ts +4 -0
  133. package/dist/internal/areValidVariants.js +45 -0
  134. package/dist/internal/checkTrigger.d.ts +2 -0
  135. package/dist/internal/checkTrigger.js +47 -0
  136. package/dist/internal/checkUntrigger.d.ts +2 -0
  137. package/dist/internal/checkUntrigger.js +18 -0
  138. package/dist/internal/cloneLastChord.d.ts +4 -0
  139. package/dist/internal/cloneLastChord.js +5 -0
  140. package/dist/internal/containsPossibleToggleChords.d.ts +13 -0
  141. package/dist/internal/containsPossibleToggleChords.js +65 -0
  142. package/dist/internal/errorTextAdd.d.ts +1 -0
  143. package/dist/internal/errorTextAdd.js +11 -0
  144. package/dist/internal/errorTextInUse.d.ts +1 -0
  145. package/dist/internal/errorTextInUse.js +8 -0
  146. package/dist/internal/errorTextRemove.d.ts +1 -0
  147. package/dist/internal/errorTextRemove.js +8 -0
  148. package/dist/internal/getModifierState.d.ts +2 -0
  149. package/dist/internal/getModifierState.js +7 -0
  150. package/dist/internal/getPressedKeys.d.ts +7 -0
  151. package/dist/internal/getPressedKeys.js +18 -0
  152. package/dist/internal/getPressedModifierKeys.d.ts +2 -0
  153. package/dist/internal/getPressedModifierKeys.js +10 -0
  154. package/dist/internal/getPressedNonModifierKeys.d.ts +7 -0
  155. package/dist/internal/getPressedNonModifierKeys.js +11 -0
  156. package/dist/internal/inChain.d.ts +5 -0
  157. package/dist/internal/inChain.js +14 -0
  158. package/dist/internal/isValidChain.d.ts +10 -0
  159. package/dist/internal/isValidChain.js +22 -0
  160. package/dist/internal/isValidChord.d.ts +11 -0
  161. package/dist/internal/isValidChord.js +59 -0
  162. package/dist/internal/isValidCommand.d.ts +12 -0
  163. package/dist/internal/isValidCommand.js +20 -0
  164. package/dist/internal/keyOrder.d.ts +6 -0
  165. package/dist/internal/keyOrder.js +14 -0
  166. package/dist/internal/removeFromChain.d.ts +3 -0
  167. package/dist/internal/removeFromChain.js +32 -0
  168. package/dist/internal/safeSetEmulatedToggleState.d.ts +4 -0
  169. package/dist/internal/safeSetEmulatedToggleState.js +13 -0
  170. package/dist/internal/setKeysState.d.ts +8 -0
  171. package/dist/internal/setKeysState.js +42 -0
  172. package/dist/internal/updateNativeKeysState.d.ts +14 -0
  173. package/dist/internal/updateNativeKeysState.js +58 -0
  174. package/dist/layouts/createLayout.d.ts +25 -0
  175. package/dist/layouts/createLayout.js +221 -0
  176. package/dist/module.d.mts +13 -0
  177. package/dist/module.json +9 -0
  178. package/dist/module.mjs +40 -0
  179. package/dist/runtime/composables/useLabeledByKeyboardLayoutMap.d.ts +9 -0
  180. package/dist/runtime/composables/useLabeledByKeyboardLayoutMap.js +19 -0
  181. package/dist/runtime/composables/usePointerCoords.d.ts +32 -0
  182. package/dist/runtime/composables/usePointerCoords.js +17 -0
  183. package/dist/runtime/composables/useShortcutManagerContextCount.d.ts +14 -0
  184. package/dist/runtime/composables/useShortcutManagerContextCount.js +61 -0
  185. package/dist/runtime/composables/useShortcutManagerKeysLayout.d.ts +18 -0
  186. package/dist/runtime/composables/useShortcutManagerKeysLayout.js +22 -0
  187. package/dist/runtime/composables/useShortcutManagerVirtualPress.d.ts +6 -0
  188. package/dist/runtime/composables/useShortcutManagerVirtualPress.js +24 -0
  189. package/dist/runtime/types.d.ts +10 -0
  190. package/dist/runtime/types.js +1 -0
  191. package/dist/runtime/utils/shortcutToId.d.ts +5 -0
  192. package/dist/runtime/utils/shortcutToId.js +6 -0
  193. package/dist/types/commands.d.ts +113 -0
  194. package/dist/types/commands.js +0 -0
  195. package/dist/types/condition.d.ts +29 -0
  196. package/dist/types/condition.js +0 -0
  197. package/dist/types/context.d.ts +18 -0
  198. package/dist/types/context.js +0 -0
  199. package/dist/types/enums.d.ts +186 -0
  200. package/dist/types/enums.js +70 -0
  201. package/dist/types/general.d.ts +92 -0
  202. package/dist/types/general.js +0 -0
  203. package/dist/types/index.d.ts +8 -0
  204. package/dist/types/index.js +8 -0
  205. package/dist/types/keys.d.ts +332 -0
  206. package/dist/types/keys.js +0 -0
  207. package/dist/types/manager.d.ts +249 -0
  208. package/dist/types/manager.js +0 -0
  209. package/dist/types/plugins.d.ts +1 -0
  210. package/dist/types/plugins.js +0 -0
  211. package/dist/types/shortcuts.d.ts +144 -0
  212. package/dist/types/shortcuts.js +0 -0
  213. package/dist/types/utils.d.ts +1 -0
  214. package/dist/types/utils.js +0 -0
  215. package/dist/types.d.mts +3 -0
  216. package/dist/utils/chainContainsSubset.d.ts +27 -0
  217. package/dist/utils/chainContainsSubset.js +45 -0
  218. package/dist/utils/cloneChain.d.ts +2 -0
  219. package/dist/utils/cloneChain.js +11 -0
  220. package/dist/utils/cloneKey.d.ts +3 -0
  221. package/dist/utils/cloneKey.js +26 -0
  222. package/dist/utils/containsKey.d.ts +7 -0
  223. package/dist/utils/containsKey.js +4 -0
  224. package/dist/utils/dedupeKeys.d.ts +9 -0
  225. package/dist/utils/dedupeKeys.js +9 -0
  226. package/dist/utils/equalsKey.d.ts +12 -0
  227. package/dist/utils/equalsKey.js +13 -0
  228. package/dist/utils/equalsKeys.d.ts +24 -0
  229. package/dist/utils/equalsKeys.js +15 -0
  230. package/dist/utils/index.d.ts +14 -0
  231. package/dist/utils/index.js +14 -0
  232. package/dist/utils/isAnyKey.d.ts +5 -0
  233. package/dist/utils/isAnyKey.js +5 -0
  234. package/dist/utils/isMouseKey.d.ts +5 -0
  235. package/dist/utils/isMouseKey.js +20 -0
  236. package/dist/utils/isNormalKey.d.ts +5 -0
  237. package/dist/utils/isNormalKey.js +5 -0
  238. package/dist/utils/isTriggerKey.d.ts +5 -0
  239. package/dist/utils/isTriggerKey.js +3 -0
  240. package/dist/utils/isWheelKey.d.ts +5 -0
  241. package/dist/utils/isWheelKey.js +11 -0
  242. package/dist/utils/mapKeys.d.ts +8 -0
  243. package/dist/utils/mapKeys.js +5 -0
  244. package/dist/utils/removeKeys.d.ts +7 -0
  245. package/dist/utils/removeKeys.js +4 -0
  246. package/package.json +156 -0
  247. package/src/core/EmulatedEvent.ts +29 -0
  248. package/src/core/Emulator.ts +185 -0
  249. package/src/core/ShortcutManagerManager.ts +380 -0
  250. package/src/core/addCommand.ts +24 -0
  251. package/src/core/addKey.ts +25 -0
  252. package/src/core/addShortcut.ts +24 -0
  253. package/src/core/attach.ts +27 -0
  254. package/src/core/createCommand.ts +24 -0
  255. package/src/core/createCommands.ts +45 -0
  256. package/src/core/createCondition.ts +21 -0
  257. package/src/core/createContext.ts +14 -0
  258. package/src/core/createKey.ts +59 -0
  259. package/src/core/createKeys.ts +68 -0
  260. package/src/core/createManager.ts +209 -0
  261. package/src/core/createManagerEventListeners.ts +139 -0
  262. package/src/core/createManagerOptions.ts +29 -0
  263. package/src/core/createShortcut.ts +49 -0
  264. package/src/core/createShortcuts.ts +51 -0
  265. package/src/core/detach.ts +21 -0
  266. package/src/core/index.ts +35 -0
  267. package/src/core/removeCommand.ts +25 -0
  268. package/src/core/removeKey.ts +25 -0
  269. package/src/core/removeShortcut.ts +24 -0
  270. package/src/core/setCommandProp.ts +140 -0
  271. package/src/core/setCommandsProp.ts +128 -0
  272. package/src/core/setKeyProp.ts +80 -0
  273. package/src/core/setKeysProp.ts +205 -0
  274. package/src/core/setManagerProp.ts +111 -0
  275. package/src/core/setShortcutProp.ts +89 -0
  276. package/src/core/setShortcutsProp.ts +124 -0
  277. package/src/defaults/KeysSorter.ts +55 -0
  278. package/src/defaults/Stringifier.ts +234 -0
  279. package/src/defaults/defaultConditionEquals.ts +29 -0
  280. package/src/defaults/defaultManagerCallback.ts +14 -0
  281. package/src/helpers/KnownError.ts +13 -0
  282. package/src/helpers/calculateAndSetPositionAndWidth.ts +30 -0
  283. package/src/helpers/calculateLayoutSize.ts +22 -0
  284. package/src/helpers/doesShortcutConflict.ts +72 -0
  285. package/src/helpers/equalsCommand.ts +18 -0
  286. package/src/helpers/equalsContext.ts +29 -0
  287. package/src/helpers/equalsShortcut.ts +34 -0
  288. package/src/helpers/forceClear.ts +31 -0
  289. package/src/helpers/forceUpdateNativeKeysState.ts +9 -0
  290. package/src/helpers/generateKeyShortcutMap.ts +113 -0
  291. package/src/helpers/getKeyFromEventCode.ts +67 -0
  292. package/src/helpers/getKeyFromIdOrVariant.ts +33 -0
  293. package/src/helpers/getKeyboardLayoutMap.ts +13 -0
  294. package/src/helpers/getLabel.ts +15 -0
  295. package/src/helpers/getTriggerableShortcut.ts +37 -0
  296. package/src/helpers/index.ts +30 -0
  297. package/src/helpers/isValidManager.ts +29 -0
  298. package/src/helpers/isValidShortcut.ts +20 -0
  299. package/src/helpers/labelWithEvent.ts +41 -0
  300. package/src/helpers/labelWithKeyboardMap.ts +37 -0
  301. package/src/helpers/managerToStorableClone.ts +26 -0
  302. package/src/helpers/onKeyboardLayoutChange.ts +17 -0
  303. package/src/helpers/safeSetManagerChain.ts +66 -0
  304. package/src/helpers/shortcutCanExecuteIn.ts +24 -0
  305. package/src/helpers/shortcutIsTriggerableBy.ts +15 -0
  306. package/src/helpers/shortcutSwapChords.ts +240 -0
  307. package/src/helpers/virtualPress.ts +34 -0
  308. package/src/helpers/virtualRelease.ts +25 -0
  309. package/src/helpers/virtualToggle.ts +28 -0
  310. package/src/internal/addToChain.ts +40 -0
  311. package/src/internal/areValidKeys.ts +40 -0
  312. package/src/internal/areValidVariants.ts +59 -0
  313. package/src/internal/checkTrigger.ts +55 -0
  314. package/src/internal/checkUntrigger.ts +26 -0
  315. package/src/internal/cloneLastChord.ts +10 -0
  316. package/src/internal/containsPossibleToggleChords.ts +91 -0
  317. package/src/internal/errorTextAdd.ts +18 -0
  318. package/src/internal/errorTextInUse.ts +10 -0
  319. package/src/internal/errorTextRemove.ts +10 -0
  320. package/src/internal/getModifierState.ts +15 -0
  321. package/src/internal/getPressedKeys.ts +26 -0
  322. package/src/internal/getPressedModifierKeys.ts +14 -0
  323. package/src/internal/getPressedNonModifierKeys.ts +19 -0
  324. package/src/internal/inChain.ts +25 -0
  325. package/src/internal/isValidChain.ts +42 -0
  326. package/src/internal/isValidChord.ts +87 -0
  327. package/src/internal/isValidCommand.ts +35 -0
  328. package/src/internal/keyOrder.ts +24 -0
  329. package/src/internal/removeFromChain.ts +46 -0
  330. package/src/internal/safeSetEmulatedToggleState.ts +23 -0
  331. package/src/internal/setKeysState.ts +71 -0
  332. package/src/internal/updateNativeKeysState.ts +84 -0
  333. package/src/layouts/createLayout.ts +328 -0
  334. package/src/module.ts +62 -0
  335. package/src/runtime/composables/useLabeledByKeyboardLayoutMap.ts +28 -0
  336. package/src/runtime/composables/usePointerCoords.ts +40 -0
  337. package/src/runtime/composables/useShortcutManagerContextCount.ts +81 -0
  338. package/src/runtime/composables/useShortcutManagerKeysLayout.ts +42 -0
  339. package/src/runtime/composables/useShortcutManagerVirtualPress.ts +30 -0
  340. package/src/runtime/types.ts +10 -0
  341. package/src/runtime/utils/shortcutToId.ts +14 -0
  342. package/src/types/commands.ts +148 -0
  343. package/src/types/condition.ts +35 -0
  344. package/src/types/context.ts +19 -0
  345. package/src/types/enums.ts +236 -0
  346. package/src/types/general.ts +117 -0
  347. package/src/types/index.ts +10 -0
  348. package/src/types/keys.ts +385 -0
  349. package/src/types/manager.ts +374 -0
  350. package/src/types/plugins.ts +32 -0
  351. package/src/types/shortcuts.ts +204 -0
  352. package/src/types/utils.ts +40 -0
  353. package/src/utils/chainContainsSubset.ts +97 -0
  354. package/src/utils/cloneChain.ts +13 -0
  355. package/src/utils/cloneKey.ts +33 -0
  356. package/src/utils/containsKey.ts +17 -0
  357. package/src/utils/dedupeKeys.ts +23 -0
  358. package/src/utils/equalsKey.ts +32 -0
  359. package/src/utils/equalsKeys.ts +50 -0
  360. package/src/utils/index.ts +16 -0
  361. package/src/utils/isAnyKey.ts +12 -0
  362. package/src/utils/isMouseKey.ts +27 -0
  363. package/src/utils/isNormalKey.ts +15 -0
  364. package/src/utils/isTriggerKey.ts +7 -0
  365. package/src/utils/isWheelKey.ts +18 -0
  366. package/src/utils/mapKeys.ts +21 -0
  367. package/src/utils/removeKeys.ts +16 -0
package/README.md ADDED
@@ -0,0 +1,579 @@
1
+ [![NPM Version (with latest tag)](https://img.shields.io/npm/v/%40witchcraft%2Fspellcraft/latest)](https://www.npmjs.com/package/@witchcraft/spellcraft/v/latest)
2
+ <!-- [![NPM Version (with beta tag)](https://img.shields.io/npm/v/%40witchcraft%2Fspellcraft/beta)](https://www.npmjs.com/package/@witchcraft/spellcraft/v/beta) -->
3
+ [![Docs](https://github.com/witchcraftjs/spellcraft/workflows/Docs/badge.svg)](https://github.com/witchcraftjs/spellcraft/actions/workflows/docs.yml)
4
+ [![Release](https://github.com/witchcraftjs/spellcraft/actions/workflows/release.yml/badge.svg)](https://github.com/witchcraftjs/spellcraft/actions/workflows/release.yml)
5
+
6
+ # Spellcraft 🪄
7
+
8
+ A shortcut manager library for handling ALL the shortcut needs of an application.
9
+
10
+ # [Docs](https://witchcraftjs.github.io/spellcraft)
11
+ # [Demo](https://witchcraftjs.github.io/spellcraft/demo) (please report any issues)
12
+
13
+ # Features
14
+ - Manages anything key like (mouse + keyboard).
15
+ - Modifiers, toggles, mouse buttons, and even the mouse wheel.
16
+ - Manages layouts.
17
+ - Easy to create and layout keys in one go.
18
+ - Provides mechanisms for handling left/right key variants in any way you like.
19
+ - Optional auto key labeling.
20
+ - Supports shortcuts chains (e.g. `Ctrl+A B C`).
21
+ - Has methods for swapping/moving parts of the chain with ease.
22
+ - [Framework agnostic\*](#usage-with-frameworks).
23
+ - Hooks to allow listening to state changes and controlling whether shortcuts are valid, can be added, removed, modified, etc.
24
+ - Easy error handling with type safe result monads.
25
+ - e.g. to check a shortcut can be changed: `setShortcutProp(shortcut, "chain", [...]).isOk`
26
+ - Helper and utility functions for common use cases.
27
+ - Heavily tested.
28
+
29
+ # Usage
30
+
31
+
32
+ This is a simple example of how you can quickly setup a manager.
33
+
34
+ ```ts
35
+ // While a barrel "@witchcraft/spellcraft" import is available
36
+ // for the root and all the parts of the library. It's
37
+ // recommended to import the individual functions instead,
38
+ // especially if using a bundler like vite which does not tree-shake
39
+ // in dev mode. It will be faster.
40
+
41
+ import { createManager } from "@witchcraft/spellcraft/createManager"
42
+ import { createKeys } from "@witchcraft/spellcraft/createKeys"
43
+ import { createContext } from "@witchcraft/spellcraft/createContext"
44
+ import { ERROR } from "@witchcraft/spellcraft/types"
45
+ import type { Context } from "@witchcraft/spellcraft/types"
46
+ import { createCommand } from "@witchcraft/spellcraft/createCommand"
47
+ import { createShortcut } from "@witchcraft/spellcraft/createShortcut"
48
+ import { addCommand } from "@witchcraft/spellcraft/addCommand"
49
+ import { addShortcut } from "@witchcraft/spellcraft/addShortcut"
50
+
51
+ const manager = createManager({
52
+ name: "default",
53
+ // use/modify one of the layouts provided, or create your own (more below)
54
+ keys: createLayout("ansi"),
55
+ commands: [
56
+ {name: "makeBold", execute: () => {}},
57
+ ],
58
+ shortcuts: [
59
+ {chain: [["ctrl", "b"]], command: "makeBold"},
60
+ ],
61
+ // a context to evaluate conditions on, you can use Context<YourContextType> to use anything you'd like.
62
+ context: createContext<Context<Record<string, boolean>>>({
63
+ a: false
64
+ b: false,
65
+ }),
66
+ options: {
67
+ // this is required to tell the manager how to evaluate conditions
68
+ evaluateCondition(condition, context) {
69
+ return context.value[condition.text]
70
+ },
71
+ cb(_manager, error, e) {
72
+ // the manger can throw soft errors working, these can be handled here
73
+ if (error.code === ERROR.UNKNOWN_KEY_EVENT) {
74
+ } else {
75
+ // some other error (e.g. no shortcut available to trigger, multiple shortcuts found, etc)
76
+ console.log(error, e)
77
+ }
78
+ },
79
+ },
80
+ listener: ({ event, manager, keys }) => {
81
+ // You can listen to all the events the manger listens to
82
+ // and inspect how it's interpreting the keys.
83
+ // This can also be used to label keys and
84
+ // preventDefault some events when recording, more below.
85
+ },
86
+ }).unwrap()
87
+
88
+ const command = createCommand("test", { execute: () => {} })
89
+ addCommand(command,manager).unwrap()
90
+
91
+ const shortcut = createShortcut({
92
+ chain: [["a"]],
93
+ command: "test",
94
+ }, manager).unwrap()
95
+ addShortcut(shortcut,manager).unwrap()
96
+ ```
97
+
98
+ ### Listening to Events
99
+ Then we need to attach the manager to the dom or an emulator\* so it can listen to events.
100
+
101
+ ```ts
102
+ // all listeners are created by default, but you can use the second parameter
103
+ // to make it only create some of them
104
+ const listeners = createManagerEventListeners(manager)
105
+
106
+ // then we can attach to the dom or an emulator
107
+ // we can also pass options to some listeners
108
+ // by default, the wheel listener is passive
109
+ attach(el, listeners, { wheel: { passive: true } })
110
+ // to later detach
111
+ detach(el, listeners, { wheel: { passive: true } })
112
+ // or listeners.abort()
113
+ ```
114
+ \* The emulator is for testing purposes and is not needed for virtual key presses (see `virtualPress/Toggle/Release`) which can be used to allow the user to "press" a key via some other mechanism (e.g. clicking a virtual keyboard).
115
+
116
+ ## How Does It Work?
117
+
118
+ The listeners listen to all the events, look up the keys in the manager's key entries, then add/remove the keys from the manager's chain (`state.chain`). When a user presses a trigger key (non-modifier key), if there is a shortcut with the current chain as it's base, the manager will create a new chord on the next key press (`[[a]]` => `[[a], []]`).
119
+
120
+ The manager checks if the chain should trigger a shortcut, and triggers the corresponding `command.execute` both on `keyup` and `keydown.`. On `keydown` said shortcut is saved to `state.untrigger` in case we need to untrigger it early.
121
+
122
+ You can choose in your execute function what exactly to do at that point for both the `keyup` and the `keydown`. Usually you will want your logic to only run on keydown and you should clear the manager's chain with `safeSetManagerChain`.
123
+
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
+
126
+ ## Errors and `{check}` Option
127
+
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
+
130
+ For example, you could create a key like this if needed (e.g. when loading user configurations):
131
+ ```ts
132
+ const res = createKey(key.id, key)
133
+ if (res.isError) {
134
+ // handle error
135
+ res.error // typed
136
+ res.error.code // see the ERROR enum
137
+ res.error.info // returns relevant objects
138
+ } else {
139
+ return res.value
140
+ }
141
+ ```
142
+
143
+ Many functions also offer the ability to pass `{check: true}` to check if they will succeed without actually doing anything. This is useful, for example, to check when dragging if a shortcut can be dragged to a key or not.
144
+ ```ts
145
+ const res = setShortcutProp(shortcut, "chain", newChain, manager, { check: true })
146
+ if (res.isOk) {
147
+ // ... shortcut can be changed
148
+ }
149
+ ```
150
+ # Advanced Usage - Building the Manager Piece by Piece
151
+
152
+ For more advanced use cases, you might want to build the manager piece by piece.
153
+
154
+ Above the example took in raw keys/lists and `createManager` internally converted and checked everything. But sometimes, like when loading a manager, it can be useful to do all this manually. For this there are several `create*` functions.
155
+
156
+ ## Manager Options
157
+
158
+ Many of the functions need parts of the manager, including some of it's options to verify the creation of the keys/commands/shortcuts. For example, we can't be sure a shortcut is valid, unless we know the set of commands and keys that will be connected to it.
159
+
160
+ But we can't create a manager before creating a manager..., so instead we can create it's options first and pass those to the functions that need it:
161
+
162
+ ```ts
163
+ const options = createManagerOptions({
164
+ evaluadeCondition(condition, context) {
165
+ return context.value[condition.text]
166
+ },
167
+ //...
168
+ })
169
+ ```
170
+
171
+ ## Keys and Layouts
172
+
173
+ Then we need to create a layout. This is a list of keys in their raw form (they can be missing some properties), that describes the position of the keys and their width/height. `createLayout` is provided to help generate variations of the common ansi/iso layouts. We can then create real keys from these.
174
+ ```ts
175
+ import { createLayout } from "@witchcraft/spellcraft/layouts/createLayout"
176
+
177
+ const layout = createLayout("ansi", {
178
+ numpad: false // don't add numbpad keys
179
+ })
180
+
181
+ const keysList = layout.map(key => {
182
+ // modify layout as you need
183
+ //...
184
+ return createKey(key.id, key).unwrap()
185
+ })
186
+ ```
187
+ You can also build completely custom layouts, the `calculateAndSetPositionAndSize` helper is provided to make laying things out easier. It shifts the x position of the next key based on the previous key and auto sets width/height to 1 unit if not specified.
188
+
189
+ **Note: Key ids must be valid KeyboardEvent.code values, unless they are toggles, or have a list of variants. See `Key` docs for more info.**
190
+
191
+ ```ts
192
+ import { calculateAndSetPositionAndSize } from "@witchcraft/spellcraft/helpers/calculateAndSetPositionAndWidth"
193
+
194
+ const firstRow = calculateAndSetPositionAndSize([
195
+ { id: "Escape" as const, label: "Esc" }, // {x: 0, width: 1, height: 1}
196
+ // F1 needs to be shifted to skip 1 key unit
197
+ { id: "F1" as const, x: 2 },
198
+ { id: "F2" as const },
199
+ { id: "F3" as const },
200
+ { id: "F4" as const },
201
+ // again after the 4th F* key, we shift one unit right
202
+ { id: "F5" as const, x: 6.5 },
203
+ { id: "F6" as const },
204
+ { id: "F7" as const },
205
+ { id: "F8" as const },
206
+ // and again
207
+ { id: "F9" as const, x: 11 },
208
+ { id: "F10" as const },
209
+ { id: "F11" as const },
210
+ { id: "F12" as const },
211
+ ]).map(_ => _.y = 0), // y position is not set, so we set it
212
+ ```
213
+ Rotation is not currently supported, but it's easy to add. You can extend the `BaseKey` interface yourself to add the needed properties. There's also the `NonToggleKey` and `ToggleKey` interfaces if you them.
214
+
215
+ ```ts
216
+ // global.d.ts
217
+ declare module "@witchcraft/spellcraft/types/index" {
218
+ export interface BaseKey {
219
+ yourProperty:string
220
+ }
221
+ }
222
+ export { }
223
+ ```
224
+
225
+ Next you need to create a `Keys` object which describes a group of `Key`s. When keys are added/removed with `add/removeKey`, they will take care of adding/removing keys properly from the `Keys` since keys also need to be added to additional properties of `Keys` such as `toggles`, `variants`, etc. properties. These are used to speed up lookups and can be useful for searching for keys and or applying styles (e.g. `if (toggles[id]) // id is toggle`).
226
+
227
+ ```ts
228
+ import { createKeys } from "@witchcraft/spellcraft/createKeys"
229
+
230
+ // using the keysList and options we created above
231
+ const keys = createKeys(keysList, options).unwrap()
232
+ ```
233
+
234
+ ## Commands
235
+
236
+ Command creation cannot error, so there is no unwrap.
237
+
238
+ `command.execute` is of type `CommandExecute` if you need to type your command execute function separately.
239
+
240
+ ```ts
241
+ import { createCommand } from "@witchcraft/spellcraft/createCommand"
242
+
243
+ const command = createCommand(
244
+ "test",
245
+ {
246
+ execute: ({ isKeydown, command, shortcut, event, manager }) => {
247
+ // note that event might be undefined if using virtual key presses
248
+ // the manager and shortcut might also be undefined
249
+ // this is to allow calling the command manually without the library
250
+ },
251
+ // commands can also have their own conditions that must be met
252
+ condition: createCondition("a || b")
253
+ }
254
+ )
255
+
256
+ const commandSet = createCommands([
257
+ command,
258
+ // command2
259
+ ], options).unwrap()
260
+ ```
261
+
262
+ ## Conditions
263
+
264
+ `Condition` is just an object that provides a wrapper the library understands, it does not actually implement evaluation, etc. For that you can use a seperate library, like [expressit](https://github.com/witchcraftjs/expressit) which I created for this purpose.
265
+
266
+ This is why we *must* tell the manager how to evaluate conditions.
267
+
268
+ To extend `Condition` and add properties to it, you can extend the `Condition` interface yourself.
269
+ ```ts
270
+ // global.d.ts
271
+ import type { ConditionNode, ExpressionNode, GroupNode } from "@witchcraft/expressit/types"
272
+ declare module "@witchcraft/spellcraft/types/index" {
273
+ export interface Condition {
274
+ ast?: ExpressionNode | ConditionNode | GroupNode
275
+ }
276
+ }
277
+ export { }
278
+ ```
279
+
280
+ Additionally, when you create a condition, you can pass a function to `parse` it and add these needed properties:
281
+ ```ts
282
+ const condition = createCondition("a || b ", (_) => {
283
+ _.ast = parse(_.text)
284
+ return _
285
+ })
286
+ ```
287
+
288
+ ## Contexts
289
+
290
+ Similarly with contexts, you can use any sort of object or type that you like.
291
+
292
+ You can tell the manager it's type when you create it. For example, say we wanted to use a map:
293
+
294
+ ```ts
295
+ const manager = createManager({
296
+ context: createContext<Context<Map<string, boolean>>>(new Map()),
297
+ options: {
298
+ evaluateCondition(condition, context) {
299
+ // context is now correctly typed
300
+ return context.value.has(condition.text)
301
+ },
302
+ }
303
+ })
304
+ ```
305
+
306
+ ## Shortcuts
307
+
308
+ Creating a shortcut requires a the key/commands we created and the manager options to create a valid shortcut.
309
+
310
+ ```ts
311
+ const shortcut =createShortcut({
312
+ command: "test",
313
+ chain: [["a"]],
314
+ condition: createCondition("a || b", true),
315
+ enabled: true,
316
+ }, {options, keys, commands}).unwrap()
317
+
318
+ const shortcuts = createShortcuts([
319
+ shortcut,
320
+ // shortcut2
321
+ ], {options, keys, commands}).unwrap()
322
+
323
+ ```
324
+
325
+ At this point we can create the manager. This time, because we passed full `Keys/Commands/Shortcuts` objects, the manager will not create them internal as it does when you pass it raw keys/commands/etc.
326
+
327
+ We should also set the listener at this point to prevent default events while recording, and to label keys automatically if we want (see `labelWithEvent` and `labelWithKeyboardMap` for details).
328
+ ```ts
329
+ const manager = createManager({
330
+ keys,
331
+ commands,
332
+ shortcuts,
333
+ options,
334
+ listener: ({ event, manager, keys }) => {
335
+ if (!event) return
336
+ labelWithEvent(event, keys, manager)
337
+ // this is only an example, the specifics, depend on how you implement recording
338
+ if (
339
+ manager.state.isRecording
340
+ && !(event instanceof MouseEvent)
341
+ && "preventDefault" in event
342
+ ) {
343
+ // prevent default effect of keys when recording
344
+ event.preventDefault()
345
+ }
346
+ },
347
+ })
348
+
349
+ //in either case, keys and commands are typed
350
+ const keyA = manager.keys.entries.KeyA
351
+ const testCommand = manager.commands.entries.test
352
+ ```
353
+
354
+ ## Changing/Setting Properties and Hooks
355
+
356
+ A series of `set*Prop` functions are provided to safely set properties on keys, shortcuts, etc. We pass these the manager to give the functions context so they can tell whether a given action is ok.
357
+
358
+ When we create the manager, we can also pass additional restrictions using `hooks` or just hook in to listen to events (e.g. in the demo, these are used to trigger saving).
359
+ ```ts
360
+ const manager = createManager({
361
+ // ...
362
+ hooks: {
363
+ onSetShortcutProp(...args) {
364
+ throw new CustomError("You can't change the shortcuts.")
365
+ }
366
+ }
367
+ })
368
+
369
+ const res = setShortcutProp(shortcut, "chain", newChain, manager)
370
+ if (res.isError) {
371
+ // res.error is now typed as all the errors setting this property can throw
372
+ // + CustomError
373
+ }
374
+ ```
375
+
376
+ Note that while the built in errors are property specific, custom errors are not.
377
+
378
+ ## Managing Multiple Managers
379
+
380
+ A helper class `ShortcutManagerManager` is provided to help manage multiple managers.
381
+
382
+ ```ts
383
+ import { ShortcutManagerManager } from "@witchcraft/spellcraft"
384
+
385
+ const managerManager = new ShortcutManagerManager(
386
+ raw => {
387
+ return yourCreateDefaultManager(raw)
388
+ },
389
+ {
390
+ onError: (e) => notifyUserHere(e),
391
+ onParse: (parsed: any) => {
392
+ const isValid = yourValidationFunction(parsed)
393
+ if (!isValid) return Error()
394
+ // connect the commands to the real command functions
395
+ // you should NEVER save/parse the command functions, that's a security risk
396
+
397
+ for (const commandName of objectKeys(parsed.commands.entries)) {
398
+ const command: Command = parsed.commands.entries[commandName]
399
+ const commandExec = getYourCommandFunction(commandName)
400
+ // @ts-expect-error execute is typically considered readonly
401
+ command.execute = commandExec
402
+ }
403
+ // it's recommended you use some sort of versioning system
404
+ (parsed as any).__version = VERSION
405
+ },
406
+ onSave: (clone: any) => {
407
+ // do any further cleanup here
408
+ for (const shortcut of clone.shortcuts.entries) {
409
+ delete shortcut.condition.ast
410
+ }
411
+ (clone as any).__version = VERSION
412
+ },
413
+ onExport(res: object) {
414
+ // prompt browser to save file
415
+ },
416
+ // update your state when the class does
417
+ onSetActiveManager: (managerName: string) => {
418
+ },
419
+ onSetManagerNames: (names: string[]) => {
420
+ },
421
+ onSetManager: (name: string, manager: Manager) => {
422
+ }
423
+ },
424
+ {
425
+ storageKeys // change the keys used
426
+ storage // change the storage used (localStorage by default), it just needs to be able to setItem/getItem/removeItem
427
+ }
428
+ )
429
+ // actually attempt to load the saved managers
430
+ managerManager.init()
431
+ // change/create a manager, force means it wont error if the manager doesn't exist
432
+ // and instead creates it
433
+ managerManager.changeManager("myManagerName", { force: true })
434
+ managerManager.duplicateManager("myManagerName", "myDuplicateManagerName")
435
+ managerManager.deleteManager("myManagerName")
436
+ managerManager.renameManager("myManagerName", "myNewManagerName")
437
+ ```
438
+ 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
+
440
+ ## Other Helpers and Utilities
441
+
442
+ There are many helpers provided to simplify common use cases under `/helpers`. Some notable ones are:
443
+
444
+ - `equals*` functions for checking equality.
445
+ - `calculateLayoutSize` for calculating the total size of a layout in key units
446
+ - `safeSetManagerChain` for safely setting of the state of the manager's chain.
447
+ - `shortcutCanExecuteIn` and `shortcutIsTriggerableBy`.
448
+ - `virtualPress/Toggle/Release` for virtual key presses (allowing a click on a virtual keyboard to trigger a key press).
449
+ - `getKeyboardLayoutMap` for getting the keyboard layout map needed for `labelWithKeyboardMap`.
450
+ - `shortcutSwapChords` for swapping the base chords of shortcuts.
451
+ - `generateKeyShortcutMap` This is a complex helper that generates a map, keyed by all the key id's, with info regarding what shortcuts can be pressed. This is crucial for showing a visual representation of the shortcuts on the keys depending on the current key state as is done in the demo.
452
+
453
+ There's also some smaller utility functions in `/utils`:
454
+ - `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
+ - `isAny/Trigger/Wheel/MouseKey`.
456
+
457
+
458
+ 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).
459
+
460
+ ## More Examples
461
+
462
+ I'm currently working polishing the library and making it easier to use.
463
+
464
+ Many of the methods/properties have extensive documentation with examples.
465
+
466
+ For a more advanced example, you can look at the [demo](https://alanscodelog.github.io/spellcraft/demo) and it's [code](https://github.com/AlansCodeLog/spellcraft/tree/master/demo).
467
+
468
+ There are also extensive tests you can look at, specifically the [tests/Manager.spec.ts](https://github.com/AlansCodeLog/spellcraft/tree/master/tests/Manager.spec.ts) file.
469
+
470
+ ## Usage with Frameworks
471
+
472
+ Originally this was written with classes, but that kind of grew into a tangled mess and it made it hard to override/customize functionality and save objects. It also made it hard to work with frameworks, even proxy-based ones.
473
+
474
+ Now everything is just a plain object. The library still mutates everything directly and that is unlikely to change\*, but in frameworks like vue which allow deep reactivity, this should work perfectly. In frameworks that don't, I would suggest a library like valtio to be able to use proxy based reactivity, or set listeners on all state and copy it all each time.
475
+
476
+ The reason the library mutates objects directly is that some changes can cause multiple other changes to happen (for example, safely setting the manager's chain can touch a lot of state) and making the library immutable would is expensive for frameworks that don't need it.
477
+
478
+ \* The other possibility is to add immutable versions of the `set*` functions and the listeners.
479
+
480
+ # [Development](./docs-src/DEVELOPMENT.md)
481
+
482
+ ## Notes
483
+
484
+ Under gnome at least, if a key (usually Ctrl) is set to locate the cursor, it will not send any key events. It will only be detected when pressed with another key.
485
+
486
+ ## Related
487
+
488
+ [@witchcraft/expressit (boolean parser)](https://github.com/witchcraftjs/expressit)
489
+ [Parsekey (shortcuts parser)](https://github.com/alanscodelog/parsekey)
490
+
491
+ # FAQ
492
+
493
+ ## Browser shortcuts interfere with certain shortcuts, how can this be avoided?
494
+
495
+ You can use a listener on the manager to e.preventDefault() some of these, but this doesn't work for all of them.
496
+
497
+ 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
+
499
+ ## How to label keys with their local names?
500
+
501
+ 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
+
503
+
504
+ ## How to set multiple manager properties safely (i.e. batch replace shortcuts/commands/keys)?
505
+
506
+ 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
+
508
+ To achieve this:
509
+
510
+ You can shallow clone the manager, change all the properties you want directly, then validate it's state by using isValidManager.
511
+
512
+ Once you know it's valid, detach the old manager and attach the new one.
513
+ ```ts
514
+ detach(manager, ...)
515
+ const clone = {...manager, keys: newKeys, shortcuts: newShortcuts}
516
+
517
+ if (isValidManager(manager)) {
518
+ attach(clone, ...)
519
+ }
520
+
521
+ ```
522
+ ## How to create `modifier-only` shortcuts? (e.g. a shortcut `Ctrl` that changes the some state like enabling multiple selection).
523
+
524
+ To do this, instead of clearing the manager's chain, you just set the state directly.
525
+
526
+ ```ts
527
+ const enableMultiSelect = createCommand("enableMultiSelect", {
528
+ execute: ({isKeydown}) => {
529
+ state.multiSelect = isKeydown
530
+ }
531
+ })
532
+ const shortcut = createShortcut({
533
+ chain: [["ControlLeft"]],
534
+ command: enableMultiSelect.name,
535
+ }, manager).unwrap()
536
+ addShortcut(shortcut, manager).unwrap()
537
+ ```
538
+
539
+ While the click on some item could be handled by a shortcut, usually you will want to handle it in your framework:
540
+
541
+ ```vue
542
+ // vue
543
+ <template>
544
+ <button
545
+ v-for="item in items"
546
+ :class="twMerge(
547
+ state.multiSelect && `cursor-pointer`
548
+ `)"
549
+ @click="addToSelected(item)"
550
+ ></button>
551
+ </template>
552
+ <script setup>
553
+ import { state } from "./state.js" // reactive({ multiSelect: false })
554
+ const selected = ref([])
555
+ function addToSelected(item) {
556
+ if (state.multiSelect) {
557
+ if (!selected.value.includes(item)) {
558
+ selected.value.push(item)
559
+ }
560
+ } else {
561
+ selected.value = [item]
562
+ }
563
+ }
564
+ </script>
565
+ ```
566
+ # How to type the context?
567
+
568
+ ```ts [global.ts]
569
+ declare module "@witchcraft/spellcraft/types" {
570
+ // or for nuxt
571
+ // declare module "#witchcraft/spellcraft/types.js" {
572
+ export interface Register {
573
+ ExtendedContextInfo: {
574
+ // extend types here
575
+ }
576
+ }
577
+ }
578
+ ```
579
+
@@ -0,0 +1,10 @@
1
+ export declare class EmulatedEvent<TType extends "keydown" | "keyup" | "mousedown" | "mouseup" | "wheel" | "mouseenter" = "keydown" | "keyup" | "mousedown" | "mouseup" | "wheel" | "mouseenter"> {
2
+ code: TType extends "keydown" | "keyup" ? string : never;
3
+ button: TType extends "mousedown" | "mouseup" ? number : never;
4
+ deltaY: TType extends "wheel" ? number : never;
5
+ modifiers: string[];
6
+ type: TType;
7
+ constructor(type: TType, info?: Partial<EmulatedEvent<TType>>, modifiers?: string[]);
8
+ getModifierState(code: string): boolean;
9
+ preventDefault(): void;
10
+ }
@@ -0,0 +1,19 @@
1
+ export class EmulatedEvent {
2
+ code;
3
+ button;
4
+ deltaY;
5
+ modifiers;
6
+ type;
7
+ constructor(type, info = {}, modifiers = []) {
8
+ this.modifiers = modifiers;
9
+ this.type = type;
10
+ for (const property of Object.keys(info)) {
11
+ this[property] = info[property];
12
+ }
13
+ }
14
+ getModifierState(code) {
15
+ return this.modifiers.includes(code);
16
+ }
17
+ preventDefault() {
18
+ }
19
+ }
@@ -0,0 +1,74 @@
1
+ import type { AnyFunction } from "@alanscodelog/utils/types";
2
+ import type { Keys } from "../types/index.js";
3
+ /**
4
+ * A simple key emulator for testing purposes. Should be hooked up to a manager class as if it was an element:
5
+ * ```ts
6
+ * const emulator = new Emulator()
7
+ * manager.attach(emulator)
8
+ * // press and release a
9
+ * emulator.fire("KeyA")
10
+ * // in case the pressed keys need to be cleared you can use the manager to do so:
11
+ * manager.clear()
12
+ * ```
13
+ */
14
+ export declare class Emulator {
15
+ validKeys?: Keys;
16
+ constructor(keys?: Keys);
17
+ listeners: {
18
+ keydown: AnyFunction[];
19
+ keyup: AnyFunction[];
20
+ wheel: AnyFunction[];
21
+ mousedown: AnyFunction[];
22
+ mouseup: AnyFunction[];
23
+ mouseenter: AnyFunction[];
24
+ };
25
+ initiated: boolean;
26
+ addEventListener(type: string, func: AnyFunction): void;
27
+ removeEventListener(type: string, func: AnyFunction): void;
28
+ mouseenter(modifiers?: string[]): void;
29
+ /**
30
+ *
31
+ * Emulate pressing/releasing keys.
32
+ *
33
+ * ```ts
34
+ * // press and release a
35
+ * emulator.fire("KeyA")
36
+ * // hold A down
37
+ * emulator.fire("KeyA+")
38
+ * // release A
39
+ * emulator.fire("KeyA-")
40
+ * // press Ctrl+A
41
+ * emulator.fire("Ctrl+ KeyA Ctrl-")
42
+ *
43
+ * // to truly simulate pressing native modifiers, pass all modifiers pressed as an array
44
+ * emulator.fire("Ctrl+ KeyA Ctrl-", ["ControlLeft"])
45
+ *
46
+ * // emulate a modifier state change out of focus
47
+ * // e.g. user focuses out, holds control+A, and focuses back
48
+ * // only KeyA would fire
49
+ * emulator.fire("KeyA+", ["ControlLeft"])
50
+ * // they release both in focus
51
+ * emulater.fire("KeyA- Ctrl-")
52
+ *
53
+ * // to simulate them releasing out of focus again you will have to add a delay since no events would fire
54
+ * delay(1000)
55
+ *
56
+ * ```
57
+ *
58
+ * Keys should be a {@link KeyboardEvent.code} (though this is not validated) seperated by one or more whitespace characters*. For buttons `0-5` can be used. For wheel events, you can pass `wheelUp/Down` to set the deltaY respectively which is how the manager gets the direction.
59
+ *
60
+ * `+` and `-` are used to indicate keydown and keyup respectively (except for wheel events*). This can seem confusing but think of the signs as adding/removing from the set of currently held keys. If no `+/-` is given, both are fired.
61
+ *
62
+ * There is no need to handle toggle keys in any special way. You should fire the root code normally (e.g. `emulator.fire("Capslock")` or `emulator.fire("Capslock+ Capslock-")`)
63
+ *
64
+ * \* Multiple whitespace characters have no real meaning, but in tests usually I use them to more easily delimite chords.
65
+ * \*\* Wheel events do not have keyup/keydown so passing `wheelUp+/-` will incorrectly create a keyboard event.
66
+ *
67
+ * Note: While the emulator is aware of correct mouse/wheel names, it does not check key names are valid.
68
+ */
69
+ fire(str: string, modifiers?: string[], validKeys?: Keys): void;
70
+ press(type: "mouse" | "wheel" | "key", key: string, modifiers?: string[], validKeys?: Keys): void;
71
+ release(type: "mouse" | "key", key: string, modifiers?: string[], validKeys?: Keys): void;
72
+ private _checkIsValidKey;
73
+ private _dispatch;
74
+ }