@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
@@ -0,0 +1,26 @@
1
+ import type { DeepPartial } from "@alanscodelog/utils/types"
2
+ import { walk } from "@alanscodelog/utils/walk"
3
+
4
+ import type { Manager } from "../types/index.js"
5
+
6
+ /**
7
+ * Strips the manager of properties that should not be saved such as `hooks`, `listener`, `state`, and the following function options:
8
+ *
9
+ * - `evaluateCondition`
10
+ * - `cb`
11
+ * - `sorter`
12
+ * - `stringifier`
13
+ * - `conditionEquals`
14
+ */
15
+ export function managerToStorableClone(m: Manager): DeepPartial<Manager> {
16
+ const clone = walk(m, undefined, { save: true })
17
+ delete clone.hooks
18
+ delete clone.listener
19
+ delete clone.state
20
+ delete clone.options.evaluateCondition
21
+ delete clone.options.cb
22
+ delete clone.options.sorter
23
+ delete clone.options.stringifier
24
+ delete clone.options.conditionEquals
25
+ return clone
26
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The spec describes a ["layoutchange" event](https://wicg.github.io/keyboard-map/#layoutchange-event) that can be added to the navigator.keyboard.
3
+ *
4
+ * I have not found a browser that implements it.
5
+ *
6
+ * But this should safely attach to the event if it exists and call the given callback if it does.
7
+ *
8
+ * It will return true if it managed to attach.
9
+ */
10
+ export function onKeyboardLayoutChange(cb: () => void | Promise<void>): boolean {
11
+ // castType<Navigator>(navigator) // not working during build
12
+ if (typeof navigator !== "undefined" && "keyboard" in navigator && "addEventListener" in (navigator.keyboard as any)) {
13
+ (navigator.keyboard as any).addEventListener(cb)
14
+ return true
15
+ }
16
+ return false
17
+ }
@@ -0,0 +1,66 @@
1
+ import { pushIfNotIn } from "@alanscodelog/utils"
2
+ import { last } from "@alanscodelog/utils/last"
3
+ import { Ok, type Result } from "@alanscodelog/utils/Result"
4
+
5
+ import { setManagerProp } from "../core/setManagerProp.js"
6
+ import { checkTrigger } from "../internal/checkTrigger.js"
7
+ import { checkUntrigger } from "../internal/checkUntrigger.js"
8
+ import { getPressedModifierKeys } from "../internal/getPressedModifierKeys.js"
9
+ import { getPressedNonModifierKeys } from "../internal/getPressedNonModifierKeys.js"
10
+ import { inChain } from "../internal/inChain.js"
11
+ import type { Manager, ManagerSetEntries, MultipleErrors } from "../types/index.js"
12
+ import { cloneChain } from "../utils/cloneChain.js"
13
+
14
+ /**
15
+ * Safely sets the manager's chain.
16
+ *
17
+ * Note that it might seem to do odd things to keep the state consistant and prevent us from getting into invalid states or triggering shorcuts when we shouldn't. Some of these include:
18
+ * - If the new chain would trigger a shortcut, the shortcut will be synthetically triggered with a keyup.
19
+ * - If the manager is {@link Manager.state.isAwaitingKeyup} to trigger anything ({@link Manager.state.untrigger}), the shortcut will be synthetically triggered with a keyup.
20
+ * - If there are still non-modifier keys being pressed, the manager will wait until they are all released before allowing keys to be added to the chained again.
21
+ * - Modifiers will stay in the first chord if they are still being pressed according to {@link Key.press}. This makes it possible for the user to trigger multiple shortcuts without releasing a modifier. This can result in potentially strange behavior for shortcuts longer than one chord, as further chords can be started without releasing the modifiers. If you don't like this, you can pass `{preserveModifiers: false}` to force the user to release the modifiers.
22
+ * - If there are still modifier keys being pressed, the manager will add them to the last chord, unless you pass `preserverModifiers: false`.
23
+ * - **If keys are currently being held ({@link Key.pressed} is true), note that the manager's state and the key state state may not match. This is usually not a problem with normal key presses, but... **
24
+ * - If using virtualPress/virtualToggle, the keys might remain pressed, potentially causing issues. In those cases you can use the {@link Manager.listener} to keep track of whether keys are virtually pressed or not (virtually pressed keys have no events), and unpress the ones you should.
25
+ * - Setting a `[]` chain is slightly different than setting a `[[]]` chain to keep the behavior of the manager consistent on the next key press.
26
+ * - When you set `[]`, the function will also set {@link Manager.state.nextIsChord} to `true`, so it knows to insert a chord on the next key press.
27
+ * - When you set `[[]]`, it will set it to `false`, since the chord already exists.
28
+ */
29
+ export function safeSetManagerChain(
30
+ manager: Manager,
31
+ chain: string[][],
32
+ { preserveModifiers = true }: { preserveModifiers?: boolean } = {}
33
+ ): Result<string[][], MultipleErrors<
34
+ ManagerSetEntries["state.chain"]["error"]
35
+ >> {
36
+ const newChain = cloneChain(chain)
37
+ if (getPressedNonModifierKeys(manager).length > 0) {
38
+ setManagerProp(manager, "state.isAwaitingKeyup", true, { check: false })
39
+ }
40
+ const pressedModifiers = getPressedModifierKeys(manager)
41
+ if (preserveModifiers && pressedModifiers.length > 0) {
42
+ if (newChain.length > 0) {
43
+ pushIfNotIn(last(newChain), pressedModifiers)
44
+ } else {
45
+ newChain.push(pressedModifiers)
46
+ }
47
+ }
48
+
49
+
50
+ const can = setManagerProp(manager, "state.chain", newChain, { check: "only" })
51
+ if (can.isError) return can as any
52
+
53
+ checkUntrigger(manager)
54
+
55
+ setManagerProp(manager, "state.chain", newChain).unwrap()
56
+ const isEmpty = newChain.length === 0 || (newChain.length === 1 && newChain?.[0].length === 0)
57
+ if (!isEmpty) {
58
+ setManagerProp(manager, "state.nextIsChord", inChain(manager), { check: false })
59
+ checkTrigger(manager)
60
+ checkUntrigger(manager)
61
+ } else {
62
+ setManagerProp(manager, "state.nextIsChord", newChain.length === 0, { check: false })
63
+ }
64
+ return Ok(manager.state.chain)
65
+ }
66
+
@@ -0,0 +1,24 @@
1
+ import type { Manager, PickManager, Shortcut } from "../types/index.js"
2
+
3
+
4
+ export function shortcutCanExecuteIn(
5
+ shortcut: Shortcut,
6
+ manager: Pick<Manager, "context" | "commands"> & PickManager<"options", "evaluateCondition">,
7
+ {
8
+ allowEmptyCommand = false
9
+ }: {
10
+ allowEmptyCommand?: boolean
11
+ } = {}
12
+ ): boolean {
13
+ const context = manager.context
14
+ const commands = manager.commands
15
+ const evaluateCondition = manager.options.evaluateCondition
16
+
17
+ const fullCommand = shortcut.command ? commands.entries[shortcut.command] : undefined
18
+ return shortcut.enabled && (
19
+ (
20
+ allowEmptyCommand && (fullCommand?.execute === undefined))
21
+ || (fullCommand !== undefined && evaluateCondition(fullCommand?.condition, context)
22
+ )
23
+ ) && shortcut.condition !== undefined && evaluateCondition(shortcut.condition, context)
24
+ }
@@ -0,0 +1,15 @@
1
+ import { shortcutCanExecuteIn } from "./shortcutCanExecuteIn.js"
2
+
3
+ import type { Manager, PickManager, Shortcut } from "../types/index.js"
4
+ import { equalsKeys } from "../utils/equalsKeys.js"
5
+
6
+
7
+ export function shortcutIsTriggerableBy(
8
+ chain: string[][],
9
+ shortcut: Shortcut,
10
+ manager: Pick<Manager, "context" | "commands" | "keys"> & PickManager<"options", "evaluateCondition">
11
+ ): boolean {
12
+ return shortcutCanExecuteIn(shortcut, manager, { allowEmptyCommand: true })
13
+ && equalsKeys(shortcut.chain, chain, manager.keys, undefined, { allowVariants: true })
14
+ }
15
+
@@ -0,0 +1,240 @@
1
+ import { crop } from "@alanscodelog/utils/crop"
2
+ import { Err, Ok, type Result } from "@alanscodelog/utils/Result"
3
+
4
+ import { KnownError } from "./KnownError.js"
5
+
6
+ import { setShortcutProp } from "../core/setShortcutProp.js"
7
+ import { defaultStringifier } from "../defaults/Stringifier.js"
8
+ import type { IStringifier, Keys, Manager, PickManager, Shortcut, Shortcuts } from "../types/index.js"
9
+ import { SHORTCUT_ERROR } from "../types/index.js"
10
+ import { equalsKeys } from "../utils/equalsKeys.js"
11
+
12
+
13
+ function setForceUnequalOnAll<TSave extends boolean = false>(shortcuts: Shortcut[], val: boolean | boolean[], save: TSave = false as TSave): TSave extends true ? boolean[] : undefined {
14
+ const was: boolean[] | undefined = save ? [] : undefined
15
+ for (let i = 0; i < shortcuts.length; i++) {
16
+ const shortcut = shortcuts[i]
17
+ if (save) was!.push(shortcut.forceUnequal)
18
+ // it's safe to not handle the result, forceUnequal is not canHookable
19
+ setShortcutProp(shortcut, "forceUnequal", typeof val === "boolean" ? val : val[i], {})
20
+ }
21
+ return was as any
22
+ }
23
+
24
+ function canSwapChords(
25
+ shortcuts: Shortcuts,
26
+ manager: Pick<Manager, "keys" | "shortcuts" | "commands">
27
+ & PickManager<"options", | "evaluateCondition" | "conditionEquals" | "stringifier" | "sorter">,
28
+ chainA: string[][],
29
+ chainB: string[][],
30
+ filter?: (shortcut: Shortcut) => boolean
31
+ ): Result<true, Error | KnownError<typeof SHORTCUT_ERROR.INVALID_SWAP_CHORDS | typeof SHORTCUT_ERROR.DUPLICATE_SHORTCUT>> {
32
+ let can: Result<true, any> = Ok(true)
33
+ const shortcutsClone = { ...shortcuts, entries: [...shortcuts.entries.map(_ => ({ ..._ }))] }
34
+ const managerClone = { ...manager, shortcuts: shortcutsClone }
35
+
36
+ const { shortcutsA, shortcutsB } = getToSwap(shortcutsClone, manager, chainA, chainB, filter)
37
+
38
+ const wasA = setForceUnequalOnAll(shortcutsA, true, true)
39
+
40
+ for (const shortcutB of shortcutsB) {
41
+ const newChain = [...chainA, ...shortcutB.chain.slice(chainA.length, shortcutB.chain.length)]
42
+ const res = setShortcutProp(shortcutB, "chain", newChain, managerClone)
43
+ if (res.isError) {
44
+ can = res as any
45
+ break
46
+ }
47
+ }
48
+ setForceUnequalOnAll(shortcutsA, wasA)
49
+
50
+ if (can.isOk) {
51
+ const wasB = setForceUnequalOnAll(shortcutsB, true, true)
52
+ for (const shortcutA of shortcutsA) {
53
+ const newChain = [...chainB, ...shortcutA.chain.slice(chainB.length, shortcutA.chain.length)]
54
+ const res = setShortcutProp(shortcutA, "chain", newChain, managerClone)
55
+ if (res.isError) {
56
+ can = res as any
57
+ break
58
+ }
59
+ }
60
+ setForceUnequalOnAll(shortcutsB, wasB)
61
+ }
62
+
63
+ return can
64
+ }
65
+
66
+
67
+ function assertChordsNotEmpty(chord: string[][],
68
+ keys: Keys,
69
+ { stringifier: s = defaultStringifier }: { stringifier?: IStringifier } = {}
70
+ ): Result<true, KnownError<typeof SHORTCUT_ERROR.INVALID_SWAP_CHORDS>> {
71
+ let found: undefined | string[][]
72
+ if (chord.length === 0 || chord.find(ks => ks.length === 0)) {
73
+ found = chord
74
+ }
75
+ if (found) {
76
+ return Err(new KnownError(
77
+ SHORTCUT_ERROR.INVALID_SWAP_CHORDS,
78
+ `Cannot swap with empty chord, but ${s.stringify(chord, { keys })} contains an empty chord.`,
79
+ { chord }
80
+ ))
81
+ }
82
+ return Ok(true)
83
+ }
84
+
85
+
86
+ function assertCorrectSwapParameters(
87
+ keys: Keys,
88
+ chordsA: string[][], chordsB: string[][],
89
+ {
90
+ stringifier: s = defaultStringifier
91
+ }: {
92
+ stringifier?: IStringifier
93
+ } = {}
94
+ ): Result<true, KnownError<typeof SHORTCUT_ERROR.INVALID_SWAP_CHORDS>> {
95
+ const canA = assertChordsNotEmpty(chordsA, keys, { stringifier: s })
96
+ if (canA.isError) { return canA }
97
+ const canB = assertChordsNotEmpty(chordsB, keys, { stringifier: s })
98
+ if (canB.isError) { return canB }
99
+
100
+ if (equalsKeys(chordsA, chordsB, keys, chordsB.length)
101
+ || equalsKeys(chordsB, chordsA, keys, chordsA.length)
102
+ ) {
103
+ return Err(new KnownError(SHORTCUT_ERROR.INVALID_SWAP_CHORDS, crop`
104
+ The chords to swap cannot share starting chords.
105
+ Chords:
106
+ ${s.stringify(chordsA, { keys })}
107
+ ${s.stringify(chordsB, { keys })}
108
+ `, { chordsA, chordsB }))
109
+ }
110
+ return Ok(true)
111
+ }
112
+ function getToSwap(
113
+ shortcuts: Shortcuts,
114
+ manager: Pick<Manager, "keys" | "shortcuts" | "commands">
115
+ & PickManager<"options", | "evaluateCondition" | "conditionEquals" | "stringifier" | "sorter">,
116
+ chainA: string[][],
117
+ chainB: string[][],
118
+ filter?: (shortcut: Shortcut) => boolean
119
+ ): { shortcutsA: Shortcut[], shortcutsB: Shortcut[] } {
120
+ let shortcutsA = shortcuts.entries.filter(shortcut => equalsKeys(shortcut.chain, chainA, manager.keys, chainA.length))
121
+ let shortcutsB = shortcuts.entries.filter(shortcut => equalsKeys(shortcut.chain, chainB, manager.keys, chainB.length))
122
+
123
+ if (filter) {
124
+ shortcutsA = shortcutsA.filter(filter)
125
+ shortcutsB = shortcutsB.filter(filter)
126
+ }
127
+ return { shortcutsA, shortcutsB }
128
+ }
129
+
130
+ /**
131
+ * Swaps the given chords for all matching shortcuts.
132
+ *
133
+ * This is done by using forceUnequal for each set of matching shortcuts in turn.
134
+ *
135
+ * EXAMPLES:
136
+ *
137
+ * Given the following shortcuts:
138
+ *
139
+ * ```
140
+ * 1 A
141
+ * 1 B
142
+ * 2 C
143
+ * 2 D
144
+ * ```
145
+ *
146
+ * `swapChords([[1]], [[2]])` would result in:
147
+ *
148
+ * ```
149
+ * 2 A
150
+ * 2 B
151
+ * 1 C
152
+ * 1 D
153
+ * ```
154
+ *
155
+ * Multiple chords, and chords of unequal lengths can be safely swapped.
156
+ *
157
+ * ```
158
+ * 1 2 A
159
+ * 1 2 B
160
+ * 3 C
161
+ * 3 D
162
+ * ```
163
+ *
164
+ * `swapChords([[1], [2]], [[3]])`:
165
+ * ```
166
+ * 3 A
167
+ * 3 B
168
+ * 1 2 C
169
+ * 1 2 D
170
+ * ```
171
+ *
172
+ * A filter function is provided, to, for example, filter out disabled entries from the swap. Note that it might be unsafe to swap entries with a filter if the new entries can be equal to the ignored ones, hence why the `check` exists.
173
+ *
174
+ * Example of how it might be a problem:
175
+ * ```
176
+ * A
177
+ * B
178
+ * ```
179
+ * `shortcutSwapChords([[A]], [[B]], () => { filter that ignores A })` would result in two A shortcuts.
180
+ *
181
+ * But if, for example, you use the filter to ignore disabled shortcuts, this wouldn't be a problem because you'd get two unequal shortcuts (A and A(disabled)), though re/dis-abling one of them would trigger a conflict.
182
+ *
183
+ * Note: Certain types of chords cannot be swapped, like empty chords, or chords which share a base.
184
+ *
185
+ * If using the experimental {@link ignoreModifierConflicts Shortcuts["ignoreModifierConflicts"]}, note that you cannot use this to swap the base modifiers.
186
+ *
187
+ * For example, say you had:
188
+ * ```
189
+ * Ctrl+A
190
+ * Ctrl
191
+ * ```
192
+ * If you do `swapChords([Ctrl],[Shift])`, `Ctrl+A` is not considered to match the `[Ctrl]` chord and you will get:
193
+ * ```
194
+ * Ctrl+A
195
+ * Shift
196
+ * ```
197
+ */
198
+ export function shortcutSwapChords(
199
+ shortcuts: Shortcuts,
200
+ chainA: string[][],
201
+ chainB: string[][],
202
+ manager: Pick<Manager, "keys" | "shortcuts" | "commands">
203
+ & PickManager<"options", | "evaluateCondition" | "conditionEquals" | "stringifier" | "sorter">,
204
+ {
205
+ check = true
206
+ }: {
207
+ check?: boolean | "only"
208
+ } = {},
209
+ filter?: (shortcut: Shortcut) => boolean
210
+ ): Result<true, KnownError<typeof SHORTCUT_ERROR.INVALID_SWAP_CHORDS | typeof SHORTCUT_ERROR.INVALID_SWAP_CHORDS | typeof SHORTCUT_ERROR.DUPLICATE_SHORTCUT> | Error> {
211
+ const res = assertCorrectSwapParameters(manager.keys, chainA, chainB)
212
+ if (res.isError) { return res }
213
+
214
+ if (check) {
215
+ const res = canSwapChords(shortcuts, manager, chainA, chainB, filter)
216
+ if (res.isError) { return res }
217
+ }
218
+
219
+ if (check === "only") {
220
+ return Ok(true)
221
+ }
222
+
223
+ const { shortcutsA, shortcutsB } = getToSwap(shortcuts, manager, chainA, chainB, filter)
224
+
225
+ const wasA = setForceUnequalOnAll(shortcutsA, true, true)
226
+ for (const shortcutB of shortcutsB) {
227
+ setShortcutProp(shortcutB, "chain", [...chainA, ...shortcutB.chain.slice(chainA.length, shortcutB.chain.length)], manager, { check: false })
228
+ }
229
+ setForceUnequalOnAll(shortcutsA, wasA)
230
+
231
+ const wasB = setForceUnequalOnAll(shortcutsA, true, true)
232
+ for (const shortcutA of shortcutsA) {
233
+ setShortcutProp(shortcutA, "chain", [...chainB, ...shortcutA.chain.slice(chainB.length, shortcutA.chain.length)], manager, { check: false })
234
+ }
235
+ setForceUnequalOnAll(shortcutsA, wasB, true)
236
+
237
+
238
+ return Ok(true)
239
+ }
240
+
@@ -0,0 +1,34 @@
1
+ import { getKeyFromIdOrVariant } from "./getKeyFromIdOrVariant.js"
2
+
3
+ import { addToChain } from "../internal/addToChain.js"
4
+ import { setKeysState } from "../internal/setKeysState.js"
5
+ import type { Manager } from "../types/index.js"
6
+
7
+ /**
8
+ * The manager is not designed to react to setting a key's pressed state directly only to events to then set the key state.
9
+ *
10
+ * So this method is provided to allow a "virtual" press (i.e. by some method that is not a real key press such as directly clicking on a visual representation of the key), without having to use the Emulator.
11
+ *
12
+ * It takes care of changing the key press state, and adding the key to the chain.
13
+ *
14
+ * Note that it is more simplistic and less "precise" than the emulator. It will ignore the type of toggles (native toggles will be treated as emulated). If using {@link Manager.options.updateStateOnAllEvents}, you will probably want to temporarily disable it or the state of the key press might get immediately reset.
15
+ *
16
+ * There is also {@link virtualRelease} and {@link virtualToggle}.
17
+ */
18
+ export function virtualPress(
19
+ manager: Manager,
20
+ keyIdOrVariant: string
21
+ ): void {
22
+ const res = getKeyFromIdOrVariant(keyIdOrVariant, manager.keys)
23
+ if (res.isError) {
24
+ manager.options.cb(manager, res.error)
25
+ return
26
+ }
27
+ const key = res.value[0]
28
+ const keys = [key.id]
29
+
30
+ manager.listener?.({ isKeydown: true, keys, manager })
31
+ setKeysState(keys, manager, true, { ignoreToggleType: true })
32
+ addToChain(manager, keys, undefined)
33
+ }
34
+
@@ -0,0 +1,25 @@
1
+ import { getKeyFromIdOrVariant } from "./getKeyFromIdOrVariant.js"
2
+
3
+ import { removeFromChain } from "../internal/removeFromChain.js"
4
+ import { setKeysState } from "../internal/setKeysState.js"
5
+ import type { Manager } from "../types/index.js"
6
+
7
+ /**
8
+ * See {@link virtualPress}
9
+ */
10
+ export function virtualRelease(
11
+ manager: Manager,
12
+ keyIdOrVariant: string
13
+ ): void {
14
+ const res = getKeyFromIdOrVariant(keyIdOrVariant, manager.keys)
15
+ if (res.isError) {
16
+ manager.options.cb(manager, res.error)
17
+ return
18
+ }
19
+ const key = res.value[0]
20
+ const keys = [key.id]
21
+ manager.listener?.({ isKeydown: false, keys, manager })
22
+ setKeysState(keys, manager, false)
23
+ removeFromChain(manager, keys, undefined)
24
+ }
25
+
@@ -0,0 +1,28 @@
1
+ import { getKeyFromIdOrVariant } from "./getKeyFromIdOrVariant.js"
2
+ import { virtualPress } from "./virtualPress.js"
3
+ import { virtualRelease } from "./virtualRelease.js"
4
+
5
+ import type { Manager } from "../types/index.js"
6
+
7
+ /**
8
+ * Calls {@link virtualRelease} if the key is pressed.
9
+ *
10
+ * Calls {@link virtualPress} if the key is released.
11
+ */
12
+ export function virtualToggle(
13
+ manager: Manager,
14
+ keyIdOrVariant: string
15
+ ): void {
16
+ const res = getKeyFromIdOrVariant(keyIdOrVariant, manager.keys)
17
+ if (res.isError) {
18
+ manager.options.cb(manager, res.error)
19
+ return
20
+ }
21
+ const key = res.value[0]
22
+ if (key.pressed) {
23
+ virtualRelease(manager, keyIdOrVariant)
24
+ } else {
25
+ virtualPress(manager, keyIdOrVariant)
26
+ }
27
+ }
28
+
@@ -0,0 +1,40 @@
1
+ import { Ok, type Result } from "@alanscodelog/utils/Result"
2
+
3
+ import { checkTrigger } from "./checkTrigger.js"
4
+
5
+ import { setManagerProp } from "../core/setManagerProp.js"
6
+ import type { AnyInputEvent, Manager, ManagerSetEntries, MultipleErrors } from "../types/index.js"
7
+ import { cloneChain } from "../utils/cloneChain.js"
8
+
9
+
10
+ export function addToChain(
11
+ manager: Manager,
12
+ keysList: string[],
13
+ e?: AnyInputEvent
14
+ ): Result<true, MultipleErrors<ManagerSetEntries["state.chain"]["error"]>> {
15
+ const sorter = manager.options.sorter
16
+ if (manager.state.isAwaitingKeyup) return Ok(true)
17
+ if (keysList.length === 0) return Ok(true)
18
+
19
+ if (manager.state.nextIsChord) {
20
+ // we unwrap when setting the chain because the manager should not be creating invalid states
21
+ // if it does, it's a bug
22
+ setManagerProp(manager, "state.chain", cloneChain([...manager.state.chain, []])).unwrap()
23
+ setManagerProp(manager, "state.nextIsChord", false)
24
+ }
25
+ const length = manager.state.chain.length - 1
26
+ const lastChord = [...(manager.state.chain[length] ?? [])]
27
+ for (const id of keysList) {
28
+ if (!lastChord.includes(id)) {
29
+ lastChord.push(id)
30
+ const res = setManagerProp(manager, "state.chain", cloneChain([
31
+ ...manager.state.chain.slice(0, length),
32
+ sorter.sort(lastChord, manager.keys)
33
+ ]))
34
+ if (res.isError) return res as any
35
+ checkTrigger(manager, e)
36
+ }
37
+ }
38
+ return Ok(true)
39
+ }
40
+
@@ -0,0 +1,40 @@
1
+ import { crop } from "@alanscodelog/utils/crop"
2
+ import { Err, Ok, type Result } from "@alanscodelog/utils/Result"
3
+
4
+ import { getKeyFromIdOrVariant } from "../helpers/getKeyFromIdOrVariant.js"
5
+ import { KnownError } from "../helpers/KnownError.js"
6
+ import type { Manager, MultipleErrors, PickManager, Shortcut } from "../types/index.js"
7
+ import { SHORTCUT_ERROR } from "../types/index.js"
8
+
9
+ /**
10
+ * @internal
11
+ */
12
+ export function areValidKeys(
13
+ chain: string[][] | Shortcut,
14
+ manager: Pick<Manager, "keys"> & PickManager<"options", "stringifier">
15
+
16
+ ): Result<true, MultipleErrors<typeof SHORTCUT_ERROR.UNKNOWN_KEY>> {
17
+ const s = manager.options.stringifier
18
+ const keys = manager.keys
19
+
20
+ const shortcut = "type" in chain ? chain : undefined
21
+ chain = "type" in chain ? chain.chain : chain
22
+ const unknownKeys = []
23
+ for (const key of chain.flat()) {
24
+ const res = getKeyFromIdOrVariant(key, keys)
25
+ if (res.isError) {
26
+ unknownKeys.push(key)
27
+ }
28
+ }
29
+
30
+ if (unknownKeys.length > 0) {
31
+ const stringified = s.stringifyList("keys", unknownKeys, { keys })
32
+ return Err(new KnownError(SHORTCUT_ERROR.UNKNOWN_KEY, crop`
33
+ ${s.stringify(shortcut ?? chain, manager)} contains unknown keys: ${stringified}
34
+ `, {
35
+ shortcut: shortcut ?? { chain },
36
+ keys: unknownKeys
37
+ }))
38
+ }
39
+ return Ok(true)
40
+ }
@@ -0,0 +1,59 @@
1
+ import { crop } from "@alanscodelog/utils/crop"
2
+ import { Err, Ok, type Result } from "@alanscodelog/utils/Result"
3
+
4
+ import { getKeyFromIdOrVariant } from "../helpers/getKeyFromIdOrVariant.js"
5
+ import { KnownError } from "../helpers/KnownError.js"
6
+ import { type Key, type KeysSetEntries, SHORTCUT_ERROR } from "../types/index.js"
7
+
8
+
9
+ export function areValidVariants(
10
+ key: Key,
11
+ manager: KeysSetEntries["entries@add"]["manager"]
12
+ ): Result<true, KnownError<typeof SHORTCUT_ERROR.INVALID_VARIANT_PAIR>> {
13
+ const keys = manager.keys
14
+ const s = manager.options.stringifier
15
+ const existingVariants = (keys.variants?.[key.id]?.map(id => keys.entries[id]) ?? [])
16
+
17
+ const keysVariants = key.variants
18
+ ? key.variants
19
+ .flatMap(id => {
20
+ const res = getKeyFromIdOrVariant(id, keys)
21
+ if (res.isOk) return res.value
22
+ // it's fine for a key's variants to not exist / have been added yet
23
+ return []
24
+ })
25
+ : []
26
+
27
+ const variants = [...existingVariants, ...keysVariants]
28
+ for (const k of variants) {
29
+ if (!!key.isToggle !== !!k.isToggle) {
30
+ const stringKey = s.stringify(key, manager)
31
+ const stringOtherKey = s.stringify(k, manager)
32
+ return Err(new KnownError(SHORTCUT_ERROR.INVALID_VARIANT_PAIR, crop`
33
+ Key ${stringKey} specifies a variant or matches an existing key's variant (${stringOtherKey}), but their functionality is not the same.
34
+
35
+ Key ${stringKey} "isToggle" is "${key.isToggle}" while key ${stringOtherKey} "isToggle" is "${k.isToggle}".
36
+ `, {
37
+ variants,
38
+ key,
39
+ otherKey: k
40
+
41
+ }))
42
+ }
43
+
44
+ if (!!key.isModifier !== !!k.isModifier) {
45
+ const stringKey = s.stringify(key, manager)
46
+ const stringOtherKey = s.stringify(k, manager)
47
+ return Err(new KnownError(SHORTCUT_ERROR.INVALID_VARIANT_PAIR, crop`
48
+ Key ${stringKey} specifies a variant or matches an existing key's variant (${stringOtherKey}), but their functionality is not the same.
49
+
50
+ Key ${stringKey} "isModifier" is "${key.isModifier}" while key ${stringOtherKey} "isModifier" is "${k.isModifier}".
51
+ `, {
52
+ variants,
53
+ key,
54
+ otherKey: k
55
+ }))
56
+ }
57
+ }
58
+ return Ok(true)
59
+ }
@@ -0,0 +1,55 @@
1
+ import { checkUntrigger } from "./checkUntrigger.js"
2
+ import { cloneLastChord } from "./cloneLastChord.js"
3
+ import { inChain } from "./inChain.js"
4
+
5
+ import { setManagerProp } from "../core/setManagerProp.js"
6
+ import { getTriggerableShortcut } from "../helpers/getTriggerableShortcut.js"
7
+ import { KnownError } from "../helpers/KnownError.js"
8
+ import { type AnyInputEvent, type Manager, SHORTCUT_ERROR } from "../types/index.js"
9
+ import { isTriggerKey } from "../utils/isTriggerKey.js"
10
+
11
+
12
+ export function checkTrigger(
13
+ manager: Manager,
14
+ e?: AnyInputEvent
15
+ ): void {
16
+ checkUntrigger(manager, e)
17
+ const hadUntrigger = manager.state.untrigger
18
+ const cb = manager.options.cb
19
+ if (!manager.options.enableShortcuts) return
20
+ const res = getTriggerableShortcut(manager)
21
+ if (res.isError) {
22
+ cb(manager, res.error as KnownError<typeof SHORTCUT_ERROR.MULTIPLE_MATCHING_SHORTCUTS>, e)
23
+ } else if (res.value && res.value !== hadUntrigger) {
24
+ setManagerProp(manager, "state.untrigger", res.value)
25
+ setManagerProp(manager, "state.nextIsChord", false)
26
+ const command = manager.commands.entries[res.value.command]
27
+ command?.execute?.({
28
+ isKeydown: true,
29
+ command,
30
+ shortcut: res.value,
31
+ event: e,
32
+ manager,
33
+ context: manager.context
34
+ })
35
+ }
36
+ const triggerKey = cloneLastChord(manager.state.chain)?.find(id => isTriggerKey(manager.keys.entries[id]))
37
+ const nonTriggerKey = cloneLastChord(manager.state.chain)?.find(id => !isTriggerKey(manager.keys.entries[id]))
38
+ if (triggerKey) {
39
+ if (inChain(manager) || manager.state.isRecording) {
40
+ setManagerProp(manager, "state.nextIsChord", true)
41
+ if (nonTriggerKey) {
42
+ setManagerProp(manager, "state.isAwaitingKeyup", true)
43
+ }
44
+ } else if (!manager.state.isRecording && !inChain(manager) && (res.isOk && !res.value) && manager.state.chain.length > 1) {
45
+ const error = new KnownError(
46
+ SHORTCUT_ERROR.NO_MATCHING_SHORTCUT,
47
+ "A chord containing a non-modifier key was pressed while in a chord chain, but no shortcut found to trigger.",
48
+ { chain: manager.state.chain }
49
+ )
50
+
51
+ cb(manager, error, e)
52
+ }
53
+ }
54
+ }
55
+