@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,89 @@
1
+ import { castType } from "@alanscodelog/utils/castType"
2
+ import { Err, Ok, type Result } from "@alanscodelog/utils/Result"
3
+
4
+ import { addShortcut } from "./addShortcut.js"
5
+ import { removeShortcut } from "./removeShortcut.js"
6
+
7
+ import { isValidChain } from "../internal/isValidChain.js"
8
+ import { isValidCommand } from "../internal/isValidCommand.js"
9
+ import { type CanHookErrors, type CanHookShortcutProps, type Hooks, type Manager, type MultipleErrors, type Shortcut, SHORTCUT_ERROR, type Shortcuts, type ShortcutSetEntries } from "../types/index.js"
10
+
11
+
12
+ const canHookable: CanHookShortcutProps[] = ["chain", "command", "condition", "enabled"]
13
+
14
+ /* Sets a settable shortcut property.
15
+ *
16
+ * You should not use this to set properties the manager manages (those tagged with @Managed in the docs) unless you've forgone using the manager.
17
+ */
18
+
19
+
20
+ export function setShortcutProp<
21
+ TEntries extends ShortcutSetEntries,
22
+ TProp extends keyof ShortcutSetEntries,
23
+ TEntry extends TEntries[TProp],
24
+ THooks extends Manager["hooks"],
25
+ TCheck extends boolean | "only" = true
26
+ >(
27
+ /** Shortcut is mutated if check is not "only". */
28
+ shortcut: Shortcut,
29
+ prop: TProp,
30
+ val: TEntry["val"],
31
+ manager: (TEntry["manager"] extends never ? unknown : TEntry["manager"]) & { hooks?: THooks },
32
+ { check = true as TCheck }: { check?: TCheck } = {}
33
+ ):
34
+ Result<
35
+ TCheck extends "only" ? true : Shortcut,
36
+ MultipleErrors<
37
+ TEntry["error"]
38
+ >
39
+ | CanHookErrors<Manager["hooks"] extends never ? never : THooks, "canSetShortcutProp">
40
+ > {
41
+ if (check) {
42
+ switch (prop) {
43
+ case "chain": {
44
+ castType<TEntries["chain"]["val"]>(val)
45
+ castType<TEntries["chain"]["manager"]>(manager)
46
+
47
+ const res = isValidChain(val, manager)
48
+ if (res.isError) return res
49
+
50
+ const shortcutsShallowClone: Shortcuts = { ...manager.shortcuts, entries: [...manager.shortcuts.entries] }
51
+ const managerClone = { ...manager, shortcuts: shortcutsShallowClone } as any as Manager
52
+ // todo better way
53
+ const resRemove = removeShortcut(shortcut, managerClone)
54
+ // we could be setting a shortcut not in the set
55
+ if (resRemove.isError && "code" in resRemove.error && resRemove.error.code !== SHORTCUT_ERROR.MISSING) return resRemove as any
56
+ const resAdd = addShortcut({ ...shortcut, chain: val }, managerClone)
57
+ if (resAdd.isError) return resAdd as any
58
+ break
59
+ }
60
+ case "command": {
61
+ castType<TEntries["command"]["val"]>(val)
62
+ castType<TEntries["command"]["manager"]>(manager)
63
+
64
+ const res = isValidCommand(val, manager, shortcut)
65
+ if (res.isError) return res
66
+ break
67
+ }
68
+ default: {
69
+ break
70
+ }
71
+ }
72
+ if (manager?.hooks && "canSetShortcutProp" in manager.hooks && canHookable.includes(prop as any)) {
73
+ const canHook = (manager.hooks as Hooks).canSetShortcutProp?.(shortcut, prop as any, val)
74
+ if (canHook instanceof Error) {
75
+ return Err(canHook) as any
76
+ }
77
+ }
78
+ }
79
+ if (check === "only") {
80
+ return Ok(true) satisfies Result<true, never> as any
81
+ }
82
+ shortcut[prop] = val as any
83
+
84
+ (manager?.hooks as Hooks)?.onSetShortcutProp?.(shortcut, prop, val)
85
+ manager?.hooks?.onSetShortcutProp?.(shortcut, prop, val)
86
+
87
+ return Ok(shortcut) satisfies Result<Shortcut, never> as any
88
+ }
89
+
@@ -0,0 +1,124 @@
1
+ import { castType } from "@alanscodelog/utils/castType"
2
+ import { Err, Ok, type Result } from "@alanscodelog/utils/Result"
3
+
4
+ import { doesShortcutConflict } from "../helpers/doesShortcutConflict.js"
5
+ import { equalsShortcut } from "../helpers/equalsShortcut.js"
6
+ import { isValidShortcut } from "../helpers/isValidShortcut.js"
7
+ import { KnownError } from "../helpers/KnownError.js"
8
+ import { errorTextAdd } from "../internal/errorTextAdd.js"
9
+ import { errorTextRemove } from "../internal/errorTextRemove.js"
10
+ import { type CanHookErrors, type CanHookShortcutsProps, type Manager, type MultipleErrors, type Shortcut, SHORTCUT_ERROR, type Shortcuts, type ShortcutsSetEntries } from "../types/index.js"
11
+
12
+
13
+ const canHookable: CanHookShortcutsProps[] = ["entries@add", "entries@remove"]
14
+ /**
15
+ * Sets a settable {@link Shortcuts} property.
16
+ */
17
+ export function setShortcutsProp<
18
+ TEntries extends ShortcutsSetEntries,
19
+ TProp extends keyof ShortcutsSetEntries,
20
+ TEntry extends TEntries[TProp],
21
+ THooks extends Manager["hooks"],
22
+ TCheck extends boolean | "only" = true
23
+ >(
24
+ prop: TProp,
25
+ val: TEntry["val"],
26
+ /** Shortcuts is mutated if check is not "only". */
27
+ manager: TEntry["manager"] & { hooks?: THooks },
28
+ {
29
+ check = true as TCheck
30
+ }: { check?: TCheck } = {}
31
+ ): Result<
32
+ TCheck extends "only" ? true : Shortcut,
33
+ MultipleErrors<TEntry["error"]>
34
+ | CanHookErrors<Manager["hooks"] extends never ? never : THooks, "canSetShortcutsProp">
35
+ > {
36
+ const s = manager.options.stringifier
37
+ const shortcuts = manager.shortcuts
38
+ if (check) {
39
+ switch (prop) {
40
+ case "entries@add": {
41
+ castType<TEntries["entries@add"]["val"]>(val)
42
+ castType<TEntries["entries@add"]["manager"]>(manager)
43
+
44
+ const shortcut = val as any as Shortcut
45
+ const existing = (shortcuts.entries).find(_ =>
46
+ equalsShortcut(shortcut, _, manager, { ignoreCommand: true })
47
+ || doesShortcutConflict(_, shortcut, manager)
48
+ )
49
+
50
+ if (existing) {
51
+ return Err(new KnownError(
52
+ SHORTCUT_ERROR.DUPLICATE_SHORTCUT,
53
+ errorTextAdd(
54
+ "Shortcut",
55
+ s.stringify(existing.chain, manager),
56
+ s.stringify(existing, manager),
57
+ s.stringify(shortcut, manager)
58
+ ),
59
+ { existing: (existing as any), self: shortcuts }
60
+ ))
61
+ }
62
+ const isValid = isValidShortcut(shortcut, manager)
63
+ if (isValid.isError) return isValid
64
+ break
65
+ }
66
+ case "entries@remove": {
67
+ const shortcut = val as any as Shortcut
68
+ // note we don't ignore the command here, we want to find an exact match
69
+ const existing = (shortcuts.entries).find(_ =>
70
+ _ === shortcut || equalsShortcut(shortcut, _, manager, { ignoreCommand: false })
71
+ )
72
+ if (existing === undefined) {
73
+ return Err(new KnownError(
74
+ SHORTCUT_ERROR.MISSING,
75
+ errorTextRemove(
76
+ "Shortcut",
77
+ s.stringify(shortcut, manager),
78
+ s.stringifyList("shortcuts", shortcuts.entries, manager)
79
+ ),
80
+ { entry: shortcut, self: shortcuts }
81
+ ))
82
+ }
83
+ break
84
+ }
85
+
86
+ default: break
87
+ }
88
+ if (manager?.hooks && "canSetShortcutsProp" in manager.hooks && canHookable.includes(prop as any)) {
89
+ const canHook = manager.hooks.canSetShortcutsProp?.(shortcuts, prop as any, val as any)
90
+ if (canHook instanceof Error) {
91
+ return Err(canHook) as any
92
+ }
93
+ }
94
+ }
95
+
96
+ if (check === "only") {
97
+ return Ok(true) satisfies Result<true, never> as any
98
+ }
99
+
100
+ switch (prop) {
101
+ case "entries@add": {
102
+ const shortcut = val
103
+ shortcuts.entries.push(shortcut)
104
+ break
105
+ }
106
+ case "entries@remove": {
107
+ const shortcut = val
108
+ const i = shortcuts.entries.findIndex(_ => _ === shortcut || equalsShortcut(shortcut, _, manager, { ignoreCommand: false }))
109
+ if (i < 0) {
110
+ throw new Error("If used correctly, shortcut should exist at this point, but it does not.")
111
+ }
112
+ shortcuts.entries.splice(i, 1)
113
+ break
114
+ }
115
+ default:
116
+ (shortcuts as any)[prop] = val
117
+ break
118
+ }
119
+
120
+ manager.hooks?.onSetShortcutsProp?.(shortcuts, prop as any, val as any)
121
+
122
+ return Ok(shortcuts) satisfies Result<Shortcuts, never> as any
123
+ }
124
+
@@ -0,0 +1,55 @@
1
+ import { getKeyFromIdOrVariant } from "../helpers/getKeyFromIdOrVariant.js"
2
+ import { keyOrder } from "../internal/keyOrder.js"
3
+ import { type IKeysSorter, KEY_SORT_POS, type Keys, type KeySortPos } from "../types/index.js"
4
+
5
+
6
+ /**
7
+ * The default class based implementation of the {@link IKeysSorter} interface.
8
+ *
9
+ * Creates a keys sorter for shortcut chains.
10
+ *
11
+ * Can either be passed some (object) enum of KeySortPos or it's keys with the values changed, for example:
12
+ * ```ts
13
+ * const MyKeySortPos = {
14
+ * [KEY_SORT_POS.modmouse]: 0,
15
+ * [KEY_SORT_POS.mod]: 1,
16
+ * // or
17
+ * modmouse: 0,
18
+ * mod: 1,
19
+ * //...
20
+ * }
21
+ *
22
+ * const mySorter = new KeysSorter(MyKeySortPos)
23
+ * new Shortcut([...keys], {sorter: mySorter})
24
+ * ```
25
+ * They way this works is the enum should contain every possible combination of key "types". All sort does is determine the type and use it's position in the enum to know where to sort it. If two keys are of the same type, they are sorted alphabetically by their id.
26
+ *
27
+ * Or you can extend from the class and implement a custom sort function.
28
+ *
29
+ * Ideally a single sorter should be created and shared amongst all instances. This is already taken care of if you do not pass a custom sorter, a default sorter instance is re-used throughout.
30
+ *
31
+ * The order of the default sorter can be changed without creating a new class by importing it early and changing it's `order` property.
32
+ */
33
+ export class KeysSorter implements IKeysSorter {
34
+ private _sort(aId: string, bId: string, keys: Keys, order: KeySortPos): number {
35
+ const a = getKeyFromIdOrVariant(aId, keys).unwrap()[0]
36
+ const b = getKeyFromIdOrVariant(bId, keys).unwrap()[0]
37
+ // -1 = a b
38
+ if (keyOrder(a, order) < keyOrder(b, order)) return -1
39
+ // 1 = b a
40
+ if (keyOrder(b, order) < keyOrder(a, order)) return 1
41
+ return aId.localeCompare(bId) // => alphabetical
42
+ }
43
+
44
+ order: KeySortPos
45
+
46
+ constructor(order: KeysSorter["order"] = KEY_SORT_POS) {
47
+ this.order = order
48
+ }
49
+
50
+ sort(keyList: string[], keys: Keys): string[] {
51
+ return keyList.sort((a, b) => this._sort!(a, b, keys, this.order))
52
+ }
53
+ }
54
+
55
+ export const defaultSorter = new KeysSorter()
@@ -0,0 +1,234 @@
1
+ import { castType } from "@alanscodelog/utils/castType"
2
+ import { crop } from "@alanscodelog/utils/crop"
3
+ import { dedupe } from "@alanscodelog/utils/dedupe"
4
+ import { isArray } from "@alanscodelog/utils/isArray"
5
+ import { isObject } from "@alanscodelog/utils/isObject"
6
+ import { pretty } from "@alanscodelog/utils/pretty"
7
+ import { unreachable } from "@alanscodelog/utils/unreachable"
8
+
9
+ import { getKeyFromIdOrVariant } from "../helpers/getKeyFromIdOrVariant.js"
10
+ import { getLabel } from "../helpers/getLabel.js"
11
+ import type { Command, Condition, DefaultStringifierOptions, IStringifier, Key, Manager, Shortcut } from "../types/index.js"
12
+
13
+
14
+ /**
15
+ * The default class based implementation of the {@link IStringifier} interface.
16
+ *
17
+ * It can be passed (and a default instance is passed by default) to most functions to specify how to stringify items in errors.
18
+ *
19
+ * The default method `stringify` can be called with any key, chord, chain, shortcut, etc. and calls the respective {@link DefaultStringifierOptions} method depending on the type property or in the case of chains, chords, and keys, the array depth.
20
+ *
21
+ * That method called then calls any others it needs (e.g. if you pass a shortcut, it will call `stringifyShortcut` which will call `stringifyCommand`, `stringifyCondition`, and `stringifyChain`, which will call `stringifyChord` and so on)
22
+ *
23
+ * These can be customized by changing the options of the default instance (see Customizing below). These options are methods that describe in simpler term how items should be joined, without handling all the logic (the class pieces it all together)
24
+ *
25
+ * For chains the default method uses a key's label and combines keys inside chords with `+` and the chords of shortcut chains with a space ` `.
26
+ * ```
27
+ * Key+Key Key+Key+Key
28
+ * ^Chord^ ^Chord ^
29
+ * ^Chain ^
30
+ * ```
31
+ *
32
+ * For shortcuts, the default is:
33
+ * ```
34
+ * Shortcut Key+Key Key+Key+Key (command: command_name, condition: condition_text)
35
+ * ```
36
+ * If the condition or command are undefined:
37
+ * - For command it will still say it's undefined.
38
+ * - The condition is removed entirely if it's undefined.
39
+ * ```
40
+ * Shortcut Key+Key Key+Key+Key (command: undefined)
41
+ * ```
42
+ *
43
+ * `stringifyLists` returns a lists joined by a comma and new line:
44
+ *
45
+ * ```
46
+ * item,
47
+ * item
48
+ * ```
49
+ *
50
+ * ## Customizing
51
+ * Ideally a single stringifier should be created and shared amongst all instances. This is already taken care of if you do not pass a custom stringifier, a default stringifier instance is re-used throughout. Unless you're implementing your own {@link IStringifier}, you should not need to pass the default one around.
52
+ *
53
+ * You can just import it early and change it's options.
54
+ */
55
+
56
+ export class Stringifier implements IStringifier {
57
+ opts: DefaultStringifierOptions
58
+
59
+ constructor(opts: DefaultStringifierOptions = {}) {
60
+ this.opts = opts
61
+ }
62
+
63
+ stringify(
64
+ entry: string | string[] | string[][],
65
+ manager: Pick<Manager, "keys">
66
+ ): string
67
+
68
+ stringify(
69
+ entry: Shortcut,
70
+ manager: Pick<Manager, "keys" | "commands">
71
+ ): string
72
+
73
+ stringify(
74
+ entry: Key | Key[] | Command | Condition,
75
+ ): string
76
+
77
+ stringify(
78
+ entry: string | string[] | string[][] | Key | Key[] | Shortcut | Command | Condition,
79
+ manager?: Pick<Manager, "keys" | "commands"> | Pick<Manager, "keys">
80
+
81
+ ): string {
82
+ if (isObject(entry) && "type" in entry) {
83
+ switch (entry.type) {
84
+ case "key": return this.stringifyKey(entry)
85
+ case "shortcut": return this.stringifyShortcut(entry, manager as any)
86
+ case "command": return this.stringifyCommand(entry as any, manager as any)
87
+ case "condition": return this.stringifyCondition(entry)
88
+ }
89
+ } else {
90
+ if (isArray(entry)) {
91
+ if (entry.length === 0) return this.stringifyChord([] as any)
92
+ // not getting correctly narrow :/
93
+ if (!isArray(entry[0])) {
94
+ castType<string[] | Key[]>(entry)
95
+ return this.stringifyChord(entry as any, manager!)
96
+ }
97
+ castType<string[][] | Key[][]>(entry)
98
+ return this.stringifyChain(entry as any, manager!)
99
+ } else {
100
+ return this.stringifyKey(entry, manager!)
101
+ }
102
+ }
103
+
104
+ unreachable()
105
+ }
106
+
107
+ stringifyPropertyValue(entry: any): string {
108
+ let res = ""
109
+ try {
110
+ res = this.stringify(entry)
111
+ return res
112
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
113
+ } catch (e) {
114
+ // ignore
115
+ }
116
+ const type = typeof entry
117
+
118
+ switch (type) {
119
+ case "string": return entry
120
+ case "number":
121
+ case "boolean":
122
+ return `${entry}`
123
+ case "function":
124
+ return `function "${entry.constructor.name}"`
125
+ case "object":
126
+ return pretty(entry)
127
+ default:
128
+ return entry.toString()
129
+ }
130
+ }
131
+
132
+
133
+ protected stringifyShortcut(
134
+ shortcut: Shortcut,
135
+ manager: Pick<Manager, "keys" | "commands">
136
+ ): string {
137
+ if (this.opts.shortcut) return this.opts.shortcut(shortcut)
138
+ const command = `command: ${this.stringifyCommand(shortcut.command ? manager.commands.entries[shortcut.command] : undefined)}`
139
+ const chain = this.stringifyChain(shortcut.chain.map(chord => chord.map(id => manager.keys.entries[id] ?? manager.keys.toggles[id] ?? id)) as any, manager)
140
+ const condition = this.stringifyCondition(shortcut.condition)
141
+ return crop`Shortcut ${chain} (${command}${shortcut.condition ? `, ${condition}` : ""})`
142
+ }
143
+
144
+ protected stringifyCondition(condition?: Condition): string {
145
+ if (this.opts.condition) return this.opts.condition(condition)
146
+ return condition ? `condition: "${condition?.text}"` : `condition: undefined`
147
+ }
148
+
149
+ stringifyCommand(name: string, manager: Pick<Manager, "commands">): string
150
+
151
+ stringifyCommand(command?: Command): string
152
+
153
+ stringifyCommand(nameOrCommand?: string | Command | undefined, manager?: Pick<Manager, "commands">): string {
154
+ const command = typeof nameOrCommand === "string"
155
+ ? manager!.commands.entries[nameOrCommand]
156
+ : nameOrCommand
157
+
158
+ if (this.opts.command) return this.opts.command(command)
159
+ return command ? command.name : "(None)"
160
+ }
161
+
162
+ protected stringifyKey(key: Key): string
163
+
164
+ protected stringifyKey(key: string, manager: Pick<Manager, "keys">): string
165
+
166
+ protected stringifyKey(keyOrId: Key | string, manager?: Pick<Manager, "keys">): string {
167
+ if (typeof keyOrId === "string") {
168
+ const res = getKeyFromIdOrVariant(keyOrId, manager!.keys)
169
+ const key = res.isOk ? res.value[0] : undefined
170
+ if (key) {
171
+ if (this.opts.key) return this.opts.key(keyOrId, key)
172
+ return getLabel(keyOrId, key)
173
+ } else {
174
+ if (this.opts.key) return this.opts.key(keyOrId)
175
+ return keyOrId
176
+ }
177
+ } else {
178
+ if (this.opts.key) return this.opts.key(keyOrId.id, keyOrId)
179
+ return getLabel(keyOrId.id, keyOrId)
180
+ }
181
+ }
182
+
183
+ protected stringifyChord(chord: Key[]): string
184
+
185
+ protected stringifyChord(chord: string[], manager: Pick<Manager, "keys">): string
186
+
187
+ protected stringifyChord(chord: Key[] | string[], manager?: Pick<Manager, "keys">): string {
188
+ const stringified = dedupe(chord.map(key => this.stringifyKey(key satisfies string | Key as any, manager!)), { mutate: true })
189
+ if (this.opts.chord) return this.opts.chord(stringified)
190
+ return stringified.join("+")
191
+ }
192
+
193
+ protected stringifyChain(chain: Key[][]): string
194
+
195
+ protected stringifyChain(chain: string[][], manager: Pick<Manager, "keys">): string
196
+
197
+ protected stringifyChain(chain: Key[][] | string[][], manager?: Pick<Manager, "keys">): string {
198
+ const stringified = chain.map(chord => this.stringifyChord(chord satisfies string[] | Key[] as any, manager!))
199
+
200
+ if (this.opts.chain) return this.opts.chain(stringified)
201
+ return stringified.join(" ")
202
+ }
203
+
204
+ stringifyList(type: "keys", entries: Key[]): string
205
+
206
+ stringifyList(type: "commands", entries: Command[]): string
207
+
208
+ stringifyList(type: "keys", entries: string[], manager: Pick<Manager, "keys">): string
209
+
210
+ stringifyList(type: "commands", entries: string[], manager: Pick<Manager, "commands">): string
211
+
212
+ stringifyList(type: "shortcuts", entries: Shortcut[], manager: Pick<Manager, "keys" | "commands">): string
213
+
214
+ stringifyList(
215
+ type: "keys" | "commands" | "shortcuts",
216
+ entries: Key[] | Command[] | Shortcut[] | string[],
217
+ manager?: Pick<Manager, "commands"> | Pick<Manager, "keys"> | Pick<Manager, "keys">
218
+ ): string {
219
+ let stringified: string[]
220
+ if (typeof entries[0] === "string") {
221
+ stringified = (entries as string[]).map(entry => type === "keys"
222
+ ? this.stringify(entry, manager as any)
223
+ : this.stringifyCommand(entry, manager as any)
224
+ )
225
+ } else {
226
+ stringified = (entries as any[]).map(entry => this.stringify(entry, manager as any))
227
+ }
228
+ if (this.opts.list) return this.opts.list(stringified, type)
229
+ return stringified.join(",\n")
230
+ }
231
+ }
232
+
233
+ export const defaultStringifier = new Stringifier()
234
+
@@ -0,0 +1,29 @@
1
+ import type { Condition } from "../types/index.js"
2
+
3
+ /**
4
+ * Returns whether the condition passed is equal to this one.
5
+ *
6
+ * The default method does a simplistic object check, and otherwise a check of the `text` property for equality. If both conditions are undefined, note this will return true.
7
+ *
8
+ * If you override the default conditionEquals option you will likely want it to just always return false.
9
+ *
10
+ * Why? Because unless you're using simple single variable conditions that you can presort to make them uniquely identifiable (i.e. not boolean expressions, e.g. `!a b !c`), this will return A LOT of false negatives.
11
+ *
12
+ * Why the false negatives? Because two conditions might be functionally equal but have differing representations (e.g: `a && b`, `b && a`). You might think, lets normalize them all, but normalizing boolean expressions (converting them to CNF) can be dangerous with very long expressions because it can take exponential time.
13
+ *
14
+ * Now the main reason for checking the equality of two conditions is to check if two shortcuts might conflict. If we're using boolean expressions it just can't be done safely.
15
+ *
16
+ * This is a personal preference, but if we have a method that gives false negatives it can be confusing that some shortcuts immediately error when added because their conditions are simple, while others don't until triggered. The simpler, more consistent alternative is to only have them error on triggering. Aditionally conflicting conditions can be shown on the keyboard layout when then user picks contexts to check against.
17
+ *
18
+ * Why use the default implementation at all then? Well, shortcuts aren't the only ones that have conditions, commands can too, but unlike shortcuts, usually it's developers who are in charge of assigning a command's condition, and since they are usually simple, it's more possible to make sure the conditions are unique (e.g. tests could enforce they're unique by converting them all to CNF and pre-checking them for equality).
19
+ */
20
+ export function defaultConditionEquals<TCondition extends Condition>(
21
+ conditionA?: TCondition,
22
+ conditionB?: Condition
23
+ ): conditionB is TCondition {
24
+ // both are the same object or both undefined
25
+ if (conditionA === conditionB) return true
26
+ // one if undefined
27
+ if (!conditionA || !conditionB) return false
28
+ return conditionA.text === conditionB.text
29
+ }
@@ -0,0 +1,14 @@
1
+ import { setManagerProp } from "../core/setManagerProp.js"
2
+ import type { Manager } from "../types/index.js"
3
+
4
+
5
+ /**
6
+ * The default callback for the manager which logs the error, the event that caused it, and clears the chain.
7
+ *
8
+ * @internal
9
+ */
10
+ export const defaultManagerCallback: Manager["options"]["cb"] = (manager: Manager, error, e): void => {
11
+ // eslint-disable-next-line no-console
12
+ console.warn(error, e)
13
+ setManagerProp(manager, "state.chain", [])
14
+ }
@@ -0,0 +1,13 @@
1
+ import { TypedError } from "@alanscodelog/utils"
2
+
3
+ import type { ErrorInfo, ShortcutError, TypeError } from "../types/index.js"
4
+
5
+
6
+ /**
7
+ * Creates a known error that extends the base Error with some extra information.
8
+ * All the variables used to create the error message are stored in it's info property so you can easily craft your own error messages to show to users.
9
+ */
10
+ export class KnownError<
11
+ T extends ShortcutError | TypeError = ShortcutError | TypeError
12
+ // TInfo extends ErrorInfo<T> = ErrorInfo<T>,
13
+ > extends TypedError<T, ErrorInfo<T>> {}
@@ -0,0 +1,30 @@
1
+ import { setReadOnly } from "@alanscodelog/utils/setReadOnly"
2
+
3
+ import type { RawKey } from "../types/keys.js"
4
+
5
+ /**
6
+ * Auto calculate and set the x positions of a row of raw keys based on their width if set (otherwise width is set to 1). If the key already has an x position it takes priority and "shifts" the rest of the keys.
7
+ *
8
+ * Useful for creating layouts.
9
+ *
10
+ * This is for *RAW* keys, so it mutates the key directly without going through {@link setKeyProp}
11
+ */
12
+ export function calculateAndSetPositionAndSize<
13
+ T extends RawKey
14
+ >(row: T[]): (T & { x: number, width: number, height: 1 })[] {
15
+ let x = 0
16
+ for (const key of row) {
17
+ if (key.x) x = key.x
18
+ setReadOnly(key, "x", x)
19
+ if (key.width) {
20
+ x += key.width
21
+ } else {
22
+ setReadOnly(key, "width", 1)
23
+ x++
24
+ }
25
+ if (key.height === undefined) {
26
+ setReadOnly(key, "height", 1)
27
+ }
28
+ }
29
+ return row as any
30
+ }
@@ -0,0 +1,22 @@
1
+ import type { Key, Keys } from "../types/index.js"
2
+
3
+ /**
4
+ * Returns the layout size in key units for a set of {@link Keys}. This will only look at keys which are set to ({@link Key.render}).
5
+ *
6
+ * See {@link Keys.autoManageLayout} and {@link Keys.layout}.
7
+ *
8
+ * This should be done after creating {@link Keys} then on any user changes to the size/pos properties of keys. You can hook into these changed with the manager's {@link Manager.hooks.onSetKeyProp}.
9
+ */
10
+ export function calculateLayoutSize(keys: Keys): Keys["layout"] {
11
+ let x = 0
12
+ let y = 0
13
+ for (const key of Object.values<Key>(keys.entries)) {
14
+ if (key.render) {
15
+ const xLimit = key.x + key.width
16
+ x = xLimit > x ? xLimit : x
17
+ const yLimit = key.y + key.height
18
+ y = yLimit > y ? yLimit : y
19
+ }
20
+ }
21
+ return { x, y }
22
+ }