@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,380 @@
1
+ import type { DeepPartial } from "@alanscodelog/utils"
2
+ import { debounce } from "@alanscodelog/utils/debounce"
3
+ import { isArray } from "@alanscodelog/utils/isArray"
4
+ import { keys } from "@alanscodelog/utils/keys"
5
+ import { Err, Ok, type Result } from "@alanscodelog/utils/Result"
6
+ import { setReadOnly } from "@alanscodelog/utils/setReadOnly"
7
+
8
+ import { managerToStorableClone } from "../helpers/managerToStorableClone.js"
9
+ import type { Manager, PickManager } from "../types/index.js"
10
+
11
+ export class ShortcutManagerManager {
12
+ storageKeys: {
13
+ /** Prefix for all keys. */
14
+ prefix: string
15
+ /** @default "shortcut-manager:names" */
16
+ managerNames: string
17
+ /** @default "shortcut-manager:manager." */
18
+ managerPrefix: string
19
+ /** @default "shortcut-manager:active" */
20
+ activeManager: string
21
+ }
22
+
23
+ readonly managerNames: string[] = []
24
+
25
+ readonly managers: Readonly<Record<string, Manager>> = {}
26
+
27
+ readonly activeManagerName: string = "default"
28
+
29
+ /** A debounced version of the save function. Is already bound to the instance. */
30
+ debouncedSave: (name: string) => void
31
+
32
+ storage: {
33
+ setItem: (key: string, value: string) => void
34
+ getItem: (key: string) => string | null
35
+ removeItem: (key: string) => void
36
+ }
37
+
38
+ /**
39
+ * All new managers will be created using this function. It should be capable of taking as little information as `{name}` (when a new manager is created) and returining a full manger.
40
+ *
41
+ * It's suggested you not do validation here, use the `onParse` hook instead.
42
+ *
43
+ * If you return an error, `onError` will be called, and no manager will be created.
44
+ */
45
+ createManager: (
46
+ raw: Partial<Omit<Manager, "options" | "hooks" | "listener" | "state">> & {
47
+ options: PickManager<"options", "enableShortcuts" | "enableListeners" | "updateStateOnAllEvents">
48
+ },
49
+ isNew: boolean
50
+ ) => Manager | Error
51
+
52
+ hooks: {
53
+ /**
54
+ * Called on any errors loading/saving/etc, should be used to notify the user.
55
+ */
56
+ onError: (err: Error) => void
57
+ /**
58
+ * Called when a manager is parsed from storage after being sucessfully JSON.parsed.
59
+ *
60
+ * You can validate the parsed object here so it's in the correct shape to pass to `createManager`.
61
+ *
62
+ * If you return an error, `onError` will be called and `createManager` will be skipped.
63
+ */
64
+ onParse?: (parsed: object) => undefined | Error
65
+ /**
66
+ * Called when a manager is saved or exported. It's called with the cloned version of the manager that has been stripped of properties that should not be saved (see {@link managerToStorableClone}).
67
+ *
68
+ * You can delete/add properties here (it will not modify the original manager).
69
+ */
70
+ onSave?: (clone: DeepPartial<Manager>) => void
71
+ /**
72
+ * Called when a manager is exported. Can be used to actually do the export.
73
+ */
74
+ onExport?: (res: object) => void
75
+ /**
76
+ * Called when a manager is set. Can be used to update your state.
77
+ */
78
+ onSetManager?: (name: string, clone: Manager) => void
79
+ /**
80
+ * Called when the manager names are set. Can be used to update your state.
81
+ */
82
+ onSetManagerNames?: (names: string[]) => void
83
+ /**
84
+ * Called when the active manager is set. Can be used to update your state.
85
+ */
86
+ onSetActiveManager?: (name: string) => void
87
+ }
88
+
89
+ constructor(
90
+ createManager: ShortcutManagerManager["createManager"],
91
+ hooks: ShortcutManagerManager["hooks"],
92
+ {
93
+ storageKeys = {} as any,
94
+ storage = localStorage
95
+ }:
96
+ {
97
+ storageKeys?: Partial<ShortcutManagerManager["storageKeys"]>
98
+ storage?: ShortcutManagerManager["storage"]
99
+ } = {}
100
+ ) {
101
+ this.createManager = createManager
102
+ this.hooks = hooks
103
+ storageKeys.prefix ??= ""
104
+ storageKeys.managerNames ??= "shortcut-manager:names"
105
+ storageKeys.managerPrefix ??= "shortcut-manager:manager."
106
+ storageKeys.activeManager ??= "shortcut-manager:active"
107
+ if (storageKeys.prefix) {
108
+ for (const key of keys(storageKeys)) {
109
+ if (key === "prefix") continue
110
+ storageKeys[key] = `${storageKeys.prefix}${storageKeys[key]}`
111
+ }
112
+ }
113
+ this.storageKeys = storageKeys as ShortcutManagerManager["storageKeys"]
114
+ this.storage = storage
115
+
116
+ this.debouncedSave = debounce(this.save.bind(this), 500)
117
+ }
118
+
119
+ init(): void {
120
+ const list = this.storage.getItem(this.storageKeys.managerNames)
121
+ const active = this.storage.getItem(this.storageKeys.activeManager)
122
+ this.setManagerNames(list ? JSON.parse(list) : [])
123
+
124
+ if (this.managerNames.length > 0) {
125
+ const res = this.managerNames
126
+ .map(m => {
127
+ const r = this.storageReadManager(m, { force: true })
128
+ this.notifyIfError(r)
129
+ if (r.isOk) {
130
+ this.load(r.value)
131
+ }
132
+ return r
133
+ })
134
+ const ok = res.filter(r => r.isOk)
135
+ if (ok.length > 0) {
136
+ this.loadOrCreateDefault()
137
+ }
138
+ if (active && this.managerNames.includes(active)) {
139
+ this.setActiveManager(active)
140
+ } else {
141
+ this.setActiveManager(this.managerNames[0]!)
142
+ }
143
+ } else {
144
+ this.loadOrCreateDefault()
145
+ }
146
+ }
147
+
148
+ setManager(name: string, clone?: any): void {
149
+ if (clone === undefined) {
150
+ // @ts-expect-error setting readonly
151
+ delete this.managers[name]
152
+ } else {
153
+ setReadOnly(this.managers, name, clone)
154
+ }
155
+ this.hooks.onSetManager?.(name, clone)
156
+ }
157
+
158
+ setManagerNames(names: string[]): void {
159
+ setReadOnly(this, "managerNames", names)
160
+ this.hooks.onSetManagerNames?.(names)
161
+ }
162
+
163
+ setActiveManager(name: string): void {
164
+ setReadOnly(this, "activeManagerName", name)
165
+ this.storage.setItem(this.storageKeys.activeManager, name)
166
+ this.hooks.onSetActiveManager?.(name)
167
+ }
168
+
169
+ protected addManager(name: string, clone: any): void {
170
+ this.setManager(name, clone)
171
+ if (!this.managerNames.includes(name)) {
172
+ this.setManagerNames([...this.managerNames, name])
173
+ }
174
+ }
175
+
176
+ protected removeManager(name: string): void {
177
+ this.setManager(name, undefined)
178
+ const index = this.managerNames.indexOf(name)
179
+ if (index === -1) return
180
+ this.setManagerNames(this.managerNames.toSpliced(index, 1))
181
+ }
182
+
183
+
184
+ protected storageSaveManager(name: string, clone: any): void {
185
+ this.storage.setItem(`${this.storageKeys.managerPrefix}${name}`, JSON.stringify(clone))
186
+ if (!this.managerNames.includes(name)) {
187
+ this.managerNames.push(name)
188
+ this.storage.setItem(
189
+ this.storageKeys.managerNames,
190
+ JSON.stringify(this.managerNames)
191
+ )
192
+ }
193
+ }
194
+
195
+ protected storageRemoveManager(name: string): void {
196
+ this.storage.removeItem(`${this.storageKeys.managerPrefix}${name}`)
197
+ const index = this.managerNames.indexOf(name)
198
+ if (index > -1) {
199
+ this.managerNames.splice(index, 1)
200
+ this.storage.setItem(
201
+ this.storageKeys.managerNames,
202
+ JSON.stringify(this.managerNames)
203
+ )
204
+ }
205
+ }
206
+
207
+ protected storageReadManager(
208
+ name: string,
209
+ { force = false }: { force?: boolean } = {}
210
+ ): Result<Manager, Error> {
211
+ const raw = this.storage.getItem(`${this.storageKeys.managerPrefix}${name}`)
212
+ if (!raw && !force) {
213
+ const res = Err(new Error(`No manager found by the name of ${name}.`))
214
+ return this.notifyIfError(res)
215
+ }
216
+ const r = this.createManager(
217
+ !raw && force ? { name } : this.parseJsonManager(raw!).unwrap(),
218
+ force
219
+ )
220
+ if (r instanceof Error) return Err(r)
221
+ return Ok(r)
222
+ }
223
+
224
+ parseJsonManager(
225
+ raw: string
226
+ ): Result<any, Error> {
227
+ if (!raw) {
228
+ return Err(new Error(`Nothing to parse.`))
229
+ }
230
+ try {
231
+ const parsed = JSON.parse(raw)
232
+ const res = this.hooks.onParse?.(parsed)
233
+ if (res instanceof Error) {
234
+ return this.notifyIfError(Err(res))
235
+ }
236
+ if (res === undefined) throw new Error(`onParse must return a value.`)
237
+ return Ok(res ?? parsed)
238
+ } catch (e) {
239
+ return Err(e as Error)
240
+ }
241
+ }
242
+
243
+ load(
244
+ manager: Manager,
245
+ {
246
+ doActivate = true
247
+ }: {
248
+ doActivate?: boolean
249
+ } = {}
250
+ ): void {
251
+ this.setManager(manager.name, manager)
252
+ if (doActivate) {
253
+ this.setActiveManager(manager.name)
254
+ }
255
+ this.save(manager.name)
256
+ }
257
+
258
+ protected loadOrCreateDefault(): void {
259
+ const r = this.storageReadManager("default", { force: true })
260
+ this.notifyIfError(r)
261
+ if (r.isOk) {
262
+ this.load(r.value)
263
+ }
264
+ }
265
+
266
+ save(name: string): void {
267
+ if (!this.managers[name]) {
268
+ this.notifyIfError(Err(new Error(`Manager ${name} not found`)))
269
+ }
270
+ const clone = managerToStorableClone(this.managers[name])
271
+ this.hooks.onSave?.(clone)
272
+ this.storageSaveManager(name, clone)
273
+ }
274
+
275
+ protected notifyIfError<T extends Result<any, Error>>(res: T): T {
276
+ if (!res.isOk) {
277
+ this.hooks.onError?.(res.error)
278
+ }
279
+ return res
280
+ }
281
+
282
+ changeManager(name: string, opts: { force?: boolean } = {}): Result<void, Error> {
283
+ if (this.managerNames.includes(name)) {
284
+ this.setActiveManager(name)
285
+ return Ok()
286
+ } else {
287
+ const m = this.storageReadManager(name, opts)
288
+ if (m.isOk) {
289
+ this.load(m.value)
290
+ this.setActiveManager(name)
291
+ }
292
+ return Ok()
293
+ }
294
+ }
295
+
296
+ deleteManager(name: string): Result<void, Error> {
297
+ const index = this.managerNames.indexOf(name)
298
+ let nextName = this.managerNames[index - 1] ?? this.managerNames[index + 1] ?? "default"
299
+ this.storageRemoveManager(name)
300
+ this.setManager(name, undefined)
301
+
302
+ if (nextName === name) nextName = "default"
303
+ const res = this.changeManager(nextName, { force: true })
304
+ this.notifyIfError(res)
305
+ return res
306
+ }
307
+
308
+ renameManager(name: string): void {
309
+ this.storageRemoveManager(this.activeManagerName)
310
+ this.setManager(name, this.managers[this.activeManagerName])
311
+ this.setManager(this.activeManagerName, undefined)
312
+ this.setActiveManager(name)
313
+ this.save(name)
314
+ }
315
+
316
+ duplicateManager(oldName: string, newName: string): Result<Manager, Error> {
317
+ const m = this.managers[oldName]
318
+ if (!m) {
319
+ const res = Err(new Error(`No manager found by the name of ${oldName}.`))
320
+ return this.notifyIfError(res)
321
+ }
322
+ const clone = managerToStorableClone(m)
323
+ clone.name = newName
324
+ const instance = this.createManager(clone as any, false)
325
+ if (instance instanceof Error) return this.notifyIfError(Err(instance))
326
+ else if (instance) this.load(instance)
327
+
328
+ return Ok(instance)
329
+ }
330
+
331
+ exportManagers(names: string[]): Result<object, Error> {
332
+ const obj = { managers: [] as any[] }
333
+ for (const name of names) {
334
+ const m = this.managers[name]
335
+ if (!m) {
336
+ const res = Err(new Error(`No manager found by the name of ${name}.`))
337
+ return this.notifyIfError(res)
338
+ }
339
+ const clone = managerToStorableClone(m)
340
+ this.hooks.onSave?.(clone)
341
+ obj.managers.push(clone)
342
+ }
343
+ this.hooks.onExport?.(obj)
344
+ return Ok(obj)
345
+ }
346
+
347
+ importManagers(content: string): Result<void, Error> {
348
+ const p = JSON.parse(content)
349
+ if (!p.managers || !isArray(p.manager)) {
350
+ return Err(new Error(`Not a valid manager file.`))
351
+ }
352
+ const ok: Manager[] = []
353
+ for (const parsedM of p.managers) {
354
+ const m = this.parseJsonManager(JSON.stringify(parsedM))
355
+ this.notifyIfError(m)
356
+
357
+ if (m.isOk) {
358
+ const instance = this.createManager(m.value as any, false)
359
+ if (instance instanceof Error) return this.notifyIfError(Err(instance))
360
+ ok.push(instance)
361
+ }
362
+ }
363
+ for (const instance of ok) {
364
+ this.load(instance)
365
+ }
366
+ return Ok()
367
+ }
368
+
369
+ /** Clears all managers and resets the state. `init` must be called again if you want to use the class instance again. */
370
+ clearAll(): void {
371
+ // todo, check we can init again after this
372
+ this.storage.removeItem(this.storageKeys.managerNames)
373
+ this.storage.removeItem(this.storageKeys.activeManager)
374
+ for (const name of this.managerNames) {
375
+ this.storageRemoveManager(name)
376
+ }
377
+ this.setManagerNames([])
378
+ this.setActiveManager("default")
379
+ }
380
+ }
@@ -0,0 +1,24 @@
1
+ import { Ok, type Result } from "@alanscodelog/utils/Result"
2
+
3
+ import { setCommandsProp } from "./setCommandsProp.js"
4
+
5
+ import type { CanHookErrors, Command, CommandsSetEntries, Manager, MultipleErrors } from "../types/index.js"
6
+
7
+
8
+ export function addCommand<
9
+ THooks extends Manager["hooks"],
10
+ TCheck extends boolean | "only" = true
11
+ >(
12
+ command: Command,
13
+ manager: CommandsSetEntries["entries@add"]["manager"] & { hooks?: THooks },
14
+ opts: { check?: TCheck } = {}
15
+ ): Result<
16
+ TCheck extends "only" ? true : Command,
17
+ MultipleErrors<
18
+ CommandsSetEntries["entries@add"]["error"]
19
+ > | CanHookErrors<THooks extends never ? never : THooks, "canSetCommandsProp">
20
+ > {
21
+ const res = setCommandsProp("entries@add", command, manager, opts)
22
+ if (res.isError) return res
23
+ return Ok(command) satisfies Result<Command, never> as any
24
+ }
@@ -0,0 +1,25 @@
1
+ import { Ok, type Result } from "@alanscodelog/utils/Result"
2
+
3
+ import { setKeysProp } from "./setKeysProp.js"
4
+
5
+ import type { CanHookErrors, Key, KeysSetEntries, Manager, MultipleErrors } from "../types/index.js"
6
+
7
+
8
+ export function addKey<
9
+ THooks extends Manager["hooks"],
10
+ TCheck extends boolean | "only" = true
11
+ >(
12
+ key: Key,
13
+ manager: KeysSetEntries["entries@add"]["manager"] & { hooks?: THooks },
14
+ opts: { check?: TCheck } = {}
15
+ ): Result<
16
+ TCheck extends "only" ? true : Key,
17
+ MultipleErrors<
18
+ KeysSetEntries["entries@add"]["error"]
19
+ > | CanHookErrors<THooks extends never ? never : THooks, "canSetKeysProp">
20
+ > {
21
+ const res = setKeysProp("entries@add", key, manager, opts)
22
+ if (res.isError) return res
23
+ return Ok(key) satisfies Result<Key, never> as any
24
+ }
25
+
@@ -0,0 +1,24 @@
1
+ import { Ok, type Result } from "@alanscodelog/utils/Result"
2
+
3
+ import { setShortcutsProp } from "./setShortcutsProp.js"
4
+
5
+ import type { CanHookErrors, Manager, MultipleErrors, Shortcut, ShortcutsSetEntries } from "../types/index.js"
6
+
7
+
8
+ export function addShortcut<
9
+ THooks extends Manager["hooks"],
10
+ TCheck extends boolean | "only" = true
11
+ >(
12
+ shortcut: Shortcut,
13
+ manager: ShortcutsSetEntries["entries@add"]["manager"] & { hooks?: THooks },
14
+ opts: { check?: TCheck } = {}
15
+ ): Result<
16
+ TCheck extends "only" ? true : Shortcut,
17
+ MultipleErrors<
18
+ ShortcutsSetEntries["entries@add"]["error"]
19
+ > | CanHookErrors<THooks extends never ? never : THooks, "canSetShortcutsProp">
20
+ > {
21
+ const res = setShortcutsProp("entries@add", shortcut, manager, opts)
22
+ if (res.isError) return res
23
+ return Ok(shortcut) satisfies Result<Shortcut, never> as any
24
+ }
@@ -0,0 +1,27 @@
1
+ import { keys } from "@alanscodelog/utils/keys"
2
+ import type { AnyFunction } from "@alanscodelog/utils/types"
3
+
4
+ import type { AttachTarget, EventTypes } from "../types/index.js"
5
+ /**
6
+ * Attach the event listeners created by {@link createManagerEventListeners} to an element or {@link Emulator} so the manager can listen to the needed event hooks.
7
+ *
8
+ * They can be removed with {@link detach} or by aborting the controller returned by the function.
9
+ */
10
+ export function attach(
11
+ el: AttachTarget,
12
+ listeners: Record<EventTypes, AnyFunction>,
13
+ /** Add options to some listeners. An abort controller is added by default. You can use the `*` key to set a default for all. */
14
+ opts: Partial<Record<EventTypes | "*", AddEventListenerOptions>> = { wheel: { passive: true } }
15
+ ): AbortController {
16
+ const controller = new AbortController()
17
+ const defaultOpts = opts["*"] ?? {}
18
+ for (const listenerName of keys(listeners)) {
19
+ el.addEventListener(listenerName, listeners[listenerName], {
20
+ signal: controller.signal,
21
+ ...defaultOpts,
22
+ ...opts[listenerName]
23
+ })
24
+ }
25
+ return controller
26
+ }
27
+
@@ -0,0 +1,24 @@
1
+ import type { Command, CommandExecute, RawCommand } from "../types/index.js"
2
+
3
+
4
+ export function createCommand<
5
+ TName extends string = string,
6
+ TCommand extends RawCommand<TName> = RawCommand<TName>
7
+ >(
8
+ name: TName,
9
+ // we only type the execute here so the user gets type safety but doesn't
10
+ // pay the price of Command being too strictly typed
11
+ rawCommand: Omit<TCommand, "name" | "execute"> & { execute: CommandExecute<TName> } = {} as any
12
+ // TName is not passed on purpose! making the command so strictly types is annoying
13
+ // since typescript will complain the command returned does not satisfy the base command (due to execute)
14
+ ): Command {
15
+ const command: Command = {
16
+ ...rawCommand as any,
17
+ type: "command",
18
+ name,
19
+ execute: rawCommand.execute as any,
20
+ description: rawCommand.description ?? "",
21
+ condition: rawCommand.condition ?? { type: "condition", text: "" }
22
+ }
23
+ return command as any
24
+ }
@@ -0,0 +1,45 @@
1
+ import { Ok, type Result } from "@alanscodelog/utils/Result"
2
+
3
+ import { addCommand } from "./addCommand.js"
4
+
5
+ import { defaultStringifier } from "../defaults/Stringifier.js"
6
+ import type { CanHookErrors, Command, Commands, CommandsSetEntries, Manager, MultipleErrors, PickManager } from "../types/index.js"
7
+
8
+ /**
9
+ * # Commands
10
+ * Creates a set of commands.
11
+ */
12
+ export function createCommands<
13
+ THooks extends Manager["hooks"],
14
+ TEntries extends Record<string, Command>,
15
+ TCheck extends boolean | "only" = true
16
+
17
+ >(
18
+ commandsList: Command[],
19
+ manager: PickManager<"options", "stringifier"> & { hooks?: THooks } = {
20
+ options: { stringifier: defaultStringifier }
21
+ },
22
+ { check = true as TCheck }: { check?: TCheck } = {}
23
+ ): Result<
24
+ TCheck extends "only" ? true : Commands<TEntries>,
25
+ MultipleErrors<
26
+ CommandsSetEntries["entries@add"]["error"]
27
+ > | CanHookErrors<THooks extends never ? never : THooks, "canSetCommandsProp">
28
+ > {
29
+ const commands: Commands = {
30
+ type: "commands",
31
+ entries: {}
32
+ }
33
+ const managerClone = { ...manager, commands }
34
+ if (check) {
35
+ for (const command of commandsList) {
36
+ // we check all first to avoid erroring mid-way through
37
+ const res = addCommand(command, managerClone)
38
+ if (res.isError) return res
39
+ }
40
+ }
41
+ if (check === "only") return Ok(true) satisfies Result<true, never> as any
42
+
43
+ return Ok(commands) satisfies Result<Commands, never> as any
44
+ }
45
+
@@ -0,0 +1,21 @@
1
+ import type { Condition } from "../types/index.js"
2
+
3
+
4
+ export function createCondition<TTextCondition extends string, TReturn extends Condition>(
5
+ rawCondition: TTextCondition,
6
+ /**
7
+ * Optionally modify the shape of the condition and add your own properties.
8
+ * For example, you can append a parsed AST like representation of the condition.
9
+ */
10
+ parse?: (raw: TTextCondition, condition: Condition) => TReturn
11
+ ): TReturn {
12
+ let condition: Condition = {
13
+ ...rawCondition as any,
14
+ type: "condition",
15
+ text: rawCondition ?? ""
16
+ }
17
+ if (parse) {
18
+ condition = parse(rawCondition, condition)
19
+ }
20
+ return condition as any
21
+ }
@@ -0,0 +1,14 @@
1
+ import type { Context, RawContext } from "../types/index.js"
2
+
3
+
4
+ export function createContext<TContext extends RawContext>(
5
+ value: TContext["value"],
6
+ rawContext: Omit<TContext, "value"> = {} as any
7
+ ): Context<TContext["value"]> {
8
+ const context: Context = {
9
+ type: "context",
10
+ ...rawContext,
11
+ value
12
+ }
13
+ return context
14
+ }
@@ -0,0 +1,59 @@
1
+ import { Err, Ok, type Result } from "@alanscodelog/utils/Result"
2
+
3
+ import { KnownError } from "../helpers/KnownError.js"
4
+ import { type Key, type MultipleErrors, type RawKey, SHORTCUT_ERROR } from "../types/index.js"
5
+
6
+
7
+ /**
8
+ * Creates a key.
9
+ *
10
+ * @template TId **@internal** See {@link ./README.md Collection Entries}
11
+ * @param id See {@link Key.id}
12
+ * @param opts Set {@link Key}.
13
+ */
14
+
15
+ export function createKey<
16
+ TId extends string = string,
17
+ TKey extends RawKey<TId> = RawKey<TId>
18
+ >(
19
+ id: TId,
20
+ rawKey: Omit<TKey, "id"> = {} as any
21
+ ): Result<
22
+ Key<TId>,
23
+ MultipleErrors<
24
+ | typeof SHORTCUT_ERROR.INVALID_VARIANT
25
+ >
26
+ > {
27
+ const k = rawKey
28
+ const key: Key<TId> = {
29
+ type: "key",
30
+ id,
31
+ label: k.label ?? id,
32
+ classes: [...(k.classes as any ?? [])],
33
+ x: k.x ?? 0,
34
+ y: k.y ?? 0,
35
+ width: k.width ?? 0,
36
+ height: k.height ?? 0,
37
+ enabled: k.enabled ?? true,
38
+ pressed: false,
39
+ isModifier: k.isModifier ?? false,
40
+ isToggle: (k.isToggle ?? false) satisfies Key["isToggle"] as false,
41
+ ...(k.isToggle
42
+ ? {
43
+ toggleOnId: `${id}On`,
44
+ toggleOffId: `${id}Off`,
45
+ toggleOnPressed: false,
46
+ toggleOffPressed: false
47
+ } satisfies Partial<Key<string>>
48
+ : {}) as any,
49
+ variants: k.variants ?? [],
50
+ render: k.render ?? true,
51
+ updateStateOnAllEvents: k.updateStateOnAllEvents ?? true
52
+ }
53
+ if (key.variants?.includes(key.id)) {
54
+ return Err(
55
+ new KnownError(SHORTCUT_ERROR.INVALID_VARIANT, `A key variant cannot be the key id itself. Attempted to use "${key.id}" in variants:[ ${key.variants.join(",")} ]`, { variants: key.variants as any /* todo */, id: key.id })
56
+ )
57
+ }
58
+ return Ok(key)
59
+ }