@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.
- package/README.md +579 -0
- package/dist/core/EmulatedEvent.d.ts +10 -0
- package/dist/core/EmulatedEvent.js +19 -0
- package/dist/core/Emulator.d.ts +74 -0
- package/dist/core/Emulator.js +153 -0
- package/dist/core/ShortcutManagerManager.d.ts +103 -0
- package/dist/core/ShortcutManagerManager.js +267 -0
- package/dist/core/addCommand.d.ts +7 -0
- package/dist/core/addCommand.js +7 -0
- package/dist/core/addKey.d.ts +7 -0
- package/dist/core/addKey.js +7 -0
- package/dist/core/addShortcut.d.ts +7 -0
- package/dist/core/addShortcut.js +7 -0
- package/dist/core/attach.d.ts +10 -0
- package/dist/core/attach.js +13 -0
- package/dist/core/createCommand.d.ts +4 -0
- package/dist/core/createCommand.js +11 -0
- package/dist/core/createCommands.d.ts +11 -0
- package/dist/core/createCommands.js +20 -0
- package/dist/core/createCondition.d.ts +7 -0
- package/dist/core/createCondition.js +11 -0
- package/dist/core/createContext.d.ts +2 -0
- package/dist/core/createContext.js +8 -0
- package/dist/core/createKey.d.ts +10 -0
- package/dist/core/createKey.js +35 -0
- package/dist/core/createKeys.d.ts +5 -0
- package/dist/core/createKeys.js +36 -0
- package/dist/core/createManager.d.ts +81 -0
- package/dist/core/createManager.js +83 -0
- package/dist/core/createManagerEventListeners.d.ts +4 -0
- package/dist/core/createManagerEventListeners.js +130 -0
- package/dist/core/createManagerOptions.d.ts +8 -0
- package/dist/core/createManagerOptions.js +17 -0
- package/dist/core/createShortcut.d.ts +8 -0
- package/dist/core/createShortcut.js +24 -0
- package/dist/core/createShortcuts.d.ts +13 -0
- package/dist/core/createShortcuts.js +20 -0
- package/dist/core/detach.d.ts +10 -0
- package/dist/core/detach.js +6 -0
- package/dist/core/index.d.ts +32 -0
- package/dist/core/index.js +32 -0
- package/dist/core/removeCommand.d.ts +7 -0
- package/dist/core/removeCommand.js +7 -0
- package/dist/core/removeKey.d.ts +7 -0
- package/dist/core/removeKey.js +7 -0
- package/dist/core/removeShortcut.d.ts +7 -0
- package/dist/core/removeShortcut.js +7 -0
- package/dist/core/setCommandProp.d.ts +17 -0
- package/dist/core/setCommandProp.js +96 -0
- package/dist/core/setCommandsProp.d.ts +15 -0
- package/dist/core/setCommandsProp.js +94 -0
- package/dist/core/setKeyProp.d.ts +16 -0
- package/dist/core/setKeyProp.js +47 -0
- package/dist/core/setKeysProp.d.ts +10 -0
- package/dist/core/setKeysProp.js +166 -0
- package/dist/core/setManagerProp.d.ts +34 -0
- package/dist/core/setManagerProp.js +54 -0
- package/dist/core/setShortcutProp.d.ts +9 -0
- package/dist/core/setShortcutProp.js +50 -0
- package/dist/core/setShortcutsProp.d.ts +12 -0
- package/dist/core/setShortcutsProp.js +93 -0
- package/dist/defaults/KeysSorter.d.ts +35 -0
- package/dist/defaults/KeysSorter.js +20 -0
- package/dist/defaults/Stringifier.d.ts +66 -0
- package/dist/defaults/Stringifier.js +119 -0
- package/dist/defaults/defaultConditionEquals.d.ts +19 -0
- package/dist/defaults/defaultConditionEquals.js +5 -0
- package/dist/defaults/defaultManagerCallback.d.ts +7 -0
- package/dist/defaults/defaultManagerCallback.js +5 -0
- package/dist/helpers/KnownError.d.ts +8 -0
- package/dist/helpers/KnownError.js +3 -0
- package/dist/helpers/calculateAndSetPositionAndWidth.d.ts +13 -0
- package/dist/helpers/calculateAndSetPositionAndWidth.js +18 -0
- package/dist/helpers/calculateLayoutSize.d.ts +9 -0
- package/dist/helpers/calculateLayoutSize.js +13 -0
- package/dist/helpers/doesShortcutConflict.d.ts +18 -0
- package/dist/helpers/doesShortcutConflict.js +42 -0
- package/dist/helpers/equalsCommand.d.ts +7 -0
- package/dist/helpers/equalsCommand.js +4 -0
- package/dist/helpers/equalsContext.d.ts +7 -0
- package/dist/helpers/equalsContext.js +19 -0
- package/dist/helpers/equalsShortcut.d.ts +9 -0
- package/dist/helpers/equalsShortcut.js +7 -0
- package/dist/helpers/forceClear.d.ts +12 -0
- package/dist/helpers/forceClear.js +16 -0
- package/dist/helpers/forceUpdateNativeKeysState.d.ts +5 -0
- package/dist/helpers/forceUpdateNativeKeysState.js +4 -0
- package/dist/helpers/generateKeyShortcutMap.d.ts +23 -0
- package/dist/helpers/generateKeyShortcutMap.js +64 -0
- package/dist/helpers/getKeyFromEventCode.d.ts +15 -0
- package/dist/helpers/getKeyFromEventCode.js +36 -0
- package/dist/helpers/getKeyFromIdOrVariant.d.ts +4 -0
- package/dist/helpers/getKeyFromIdOrVariant.js +26 -0
- package/dist/helpers/getKeyboardLayoutMap.d.ts +5 -0
- package/dist/helpers/getKeyboardLayoutMap.js +7 -0
- package/dist/helpers/getLabel.d.ts +9 -0
- package/dist/helpers/getLabel.js +6 -0
- package/dist/helpers/getTriggerableShortcut.d.ts +6 -0
- package/dist/helpers/getTriggerableShortcut.js +25 -0
- package/dist/helpers/index.d.ts +28 -0
- package/dist/helpers/index.js +28 -0
- package/dist/helpers/isValidManager.d.ts +3 -0
- package/dist/helpers/isValidManager.js +20 -0
- package/dist/helpers/isValidShortcut.d.ts +3 -0
- package/dist/helpers/isValidShortcut.js +10 -0
- package/dist/helpers/labelWithEvent.d.ts +13 -0
- package/dist/helpers/labelWithEvent.js +20 -0
- package/dist/helpers/labelWithKeyboardMap.d.ts +15 -0
- package/dist/helpers/labelWithKeyboardMap.js +17 -0
- package/dist/helpers/managerToStorableClone.d.ts +12 -0
- package/dist/helpers/managerToStorableClone.js +13 -0
- package/dist/helpers/onKeyboardLayoutChange.d.ts +10 -0
- package/dist/helpers/onKeyboardLayoutChange.js +7 -0
- package/dist/helpers/safeSetManagerChain.d.ts +20 -0
- package/dist/helpers/safeSetManagerChain.js +37 -0
- package/dist/helpers/shortcutCanExecuteIn.d.ts +4 -0
- package/dist/helpers/shortcutCanExecuteIn.js +9 -0
- package/dist/helpers/shortcutIsTriggerableBy.d.ts +2 -0
- package/dist/helpers/shortcutIsTriggerableBy.js +5 -0
- package/dist/helpers/shortcutSwapChords.d.ts +75 -0
- package/dist/helpers/shortcutSwapChords.js +118 -0
- package/dist/helpers/virtualPress.d.ts +13 -0
- package/dist/helpers/virtualPress.js +15 -0
- package/dist/helpers/virtualRelease.d.ts +5 -0
- package/dist/helpers/virtualRelease.js +15 -0
- package/dist/helpers/virtualToggle.d.ts +7 -0
- package/dist/helpers/virtualToggle.js +16 -0
- package/dist/internal/addToChain.d.ts +3 -0
- package/dist/internal/addToChain.js +27 -0
- package/dist/internal/areValidKeys.d.ts +7 -0
- package/dist/internal/areValidKeys.js +28 -0
- package/dist/internal/areValidVariants.d.ts +4 -0
- package/dist/internal/areValidVariants.js +45 -0
- package/dist/internal/checkTrigger.d.ts +2 -0
- package/dist/internal/checkTrigger.js +47 -0
- package/dist/internal/checkUntrigger.d.ts +2 -0
- package/dist/internal/checkUntrigger.js +18 -0
- package/dist/internal/cloneLastChord.d.ts +4 -0
- package/dist/internal/cloneLastChord.js +5 -0
- package/dist/internal/containsPossibleToggleChords.d.ts +13 -0
- package/dist/internal/containsPossibleToggleChords.js +65 -0
- package/dist/internal/errorTextAdd.d.ts +1 -0
- package/dist/internal/errorTextAdd.js +11 -0
- package/dist/internal/errorTextInUse.d.ts +1 -0
- package/dist/internal/errorTextInUse.js +8 -0
- package/dist/internal/errorTextRemove.d.ts +1 -0
- package/dist/internal/errorTextRemove.js +8 -0
- package/dist/internal/getModifierState.d.ts +2 -0
- package/dist/internal/getModifierState.js +7 -0
- package/dist/internal/getPressedKeys.d.ts +7 -0
- package/dist/internal/getPressedKeys.js +18 -0
- package/dist/internal/getPressedModifierKeys.d.ts +2 -0
- package/dist/internal/getPressedModifierKeys.js +10 -0
- package/dist/internal/getPressedNonModifierKeys.d.ts +7 -0
- package/dist/internal/getPressedNonModifierKeys.js +11 -0
- package/dist/internal/inChain.d.ts +5 -0
- package/dist/internal/inChain.js +14 -0
- package/dist/internal/isValidChain.d.ts +10 -0
- package/dist/internal/isValidChain.js +22 -0
- package/dist/internal/isValidChord.d.ts +11 -0
- package/dist/internal/isValidChord.js +59 -0
- package/dist/internal/isValidCommand.d.ts +12 -0
- package/dist/internal/isValidCommand.js +20 -0
- package/dist/internal/keyOrder.d.ts +6 -0
- package/dist/internal/keyOrder.js +14 -0
- package/dist/internal/removeFromChain.d.ts +3 -0
- package/dist/internal/removeFromChain.js +32 -0
- package/dist/internal/safeSetEmulatedToggleState.d.ts +4 -0
- package/dist/internal/safeSetEmulatedToggleState.js +13 -0
- package/dist/internal/setKeysState.d.ts +8 -0
- package/dist/internal/setKeysState.js +42 -0
- package/dist/internal/updateNativeKeysState.d.ts +14 -0
- package/dist/internal/updateNativeKeysState.js +58 -0
- package/dist/layouts/createLayout.d.ts +25 -0
- package/dist/layouts/createLayout.js +221 -0
- package/dist/module.d.mts +13 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +40 -0
- package/dist/runtime/composables/useLabeledByKeyboardLayoutMap.d.ts +9 -0
- package/dist/runtime/composables/useLabeledByKeyboardLayoutMap.js +19 -0
- package/dist/runtime/composables/usePointerCoords.d.ts +32 -0
- package/dist/runtime/composables/usePointerCoords.js +17 -0
- package/dist/runtime/composables/useShortcutManagerContextCount.d.ts +14 -0
- package/dist/runtime/composables/useShortcutManagerContextCount.js +61 -0
- package/dist/runtime/composables/useShortcutManagerKeysLayout.d.ts +18 -0
- package/dist/runtime/composables/useShortcutManagerKeysLayout.js +22 -0
- package/dist/runtime/composables/useShortcutManagerVirtualPress.d.ts +6 -0
- package/dist/runtime/composables/useShortcutManagerVirtualPress.js +24 -0
- package/dist/runtime/types.d.ts +10 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/utils/shortcutToId.d.ts +5 -0
- package/dist/runtime/utils/shortcutToId.js +6 -0
- package/dist/types/commands.d.ts +113 -0
- package/dist/types/commands.js +0 -0
- package/dist/types/condition.d.ts +29 -0
- package/dist/types/condition.js +0 -0
- package/dist/types/context.d.ts +18 -0
- package/dist/types/context.js +0 -0
- package/dist/types/enums.d.ts +186 -0
- package/dist/types/enums.js +70 -0
- package/dist/types/general.d.ts +92 -0
- package/dist/types/general.js +0 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.js +8 -0
- package/dist/types/keys.d.ts +332 -0
- package/dist/types/keys.js +0 -0
- package/dist/types/manager.d.ts +249 -0
- package/dist/types/manager.js +0 -0
- package/dist/types/plugins.d.ts +1 -0
- package/dist/types/plugins.js +0 -0
- package/dist/types/shortcuts.d.ts +144 -0
- package/dist/types/shortcuts.js +0 -0
- package/dist/types/utils.d.ts +1 -0
- package/dist/types/utils.js +0 -0
- package/dist/types.d.mts +3 -0
- package/dist/utils/chainContainsSubset.d.ts +27 -0
- package/dist/utils/chainContainsSubset.js +45 -0
- package/dist/utils/cloneChain.d.ts +2 -0
- package/dist/utils/cloneChain.js +11 -0
- package/dist/utils/cloneKey.d.ts +3 -0
- package/dist/utils/cloneKey.js +26 -0
- package/dist/utils/containsKey.d.ts +7 -0
- package/dist/utils/containsKey.js +4 -0
- package/dist/utils/dedupeKeys.d.ts +9 -0
- package/dist/utils/dedupeKeys.js +9 -0
- package/dist/utils/equalsKey.d.ts +12 -0
- package/dist/utils/equalsKey.js +13 -0
- package/dist/utils/equalsKeys.d.ts +24 -0
- package/dist/utils/equalsKeys.js +15 -0
- package/dist/utils/index.d.ts +14 -0
- package/dist/utils/index.js +14 -0
- package/dist/utils/isAnyKey.d.ts +5 -0
- package/dist/utils/isAnyKey.js +5 -0
- package/dist/utils/isMouseKey.d.ts +5 -0
- package/dist/utils/isMouseKey.js +20 -0
- package/dist/utils/isNormalKey.d.ts +5 -0
- package/dist/utils/isNormalKey.js +5 -0
- package/dist/utils/isTriggerKey.d.ts +5 -0
- package/dist/utils/isTriggerKey.js +3 -0
- package/dist/utils/isWheelKey.d.ts +5 -0
- package/dist/utils/isWheelKey.js +11 -0
- package/dist/utils/mapKeys.d.ts +8 -0
- package/dist/utils/mapKeys.js +5 -0
- package/dist/utils/removeKeys.d.ts +7 -0
- package/dist/utils/removeKeys.js +4 -0
- package/package.json +156 -0
- package/src/core/EmulatedEvent.ts +29 -0
- package/src/core/Emulator.ts +185 -0
- package/src/core/ShortcutManagerManager.ts +380 -0
- package/src/core/addCommand.ts +24 -0
- package/src/core/addKey.ts +25 -0
- package/src/core/addShortcut.ts +24 -0
- package/src/core/attach.ts +27 -0
- package/src/core/createCommand.ts +24 -0
- package/src/core/createCommands.ts +45 -0
- package/src/core/createCondition.ts +21 -0
- package/src/core/createContext.ts +14 -0
- package/src/core/createKey.ts +59 -0
- package/src/core/createKeys.ts +68 -0
- package/src/core/createManager.ts +209 -0
- package/src/core/createManagerEventListeners.ts +139 -0
- package/src/core/createManagerOptions.ts +29 -0
- package/src/core/createShortcut.ts +49 -0
- package/src/core/createShortcuts.ts +51 -0
- package/src/core/detach.ts +21 -0
- package/src/core/index.ts +35 -0
- package/src/core/removeCommand.ts +25 -0
- package/src/core/removeKey.ts +25 -0
- package/src/core/removeShortcut.ts +24 -0
- package/src/core/setCommandProp.ts +140 -0
- package/src/core/setCommandsProp.ts +128 -0
- package/src/core/setKeyProp.ts +80 -0
- package/src/core/setKeysProp.ts +205 -0
- package/src/core/setManagerProp.ts +111 -0
- package/src/core/setShortcutProp.ts +89 -0
- package/src/core/setShortcutsProp.ts +124 -0
- package/src/defaults/KeysSorter.ts +55 -0
- package/src/defaults/Stringifier.ts +234 -0
- package/src/defaults/defaultConditionEquals.ts +29 -0
- package/src/defaults/defaultManagerCallback.ts +14 -0
- package/src/helpers/KnownError.ts +13 -0
- package/src/helpers/calculateAndSetPositionAndWidth.ts +30 -0
- package/src/helpers/calculateLayoutSize.ts +22 -0
- package/src/helpers/doesShortcutConflict.ts +72 -0
- package/src/helpers/equalsCommand.ts +18 -0
- package/src/helpers/equalsContext.ts +29 -0
- package/src/helpers/equalsShortcut.ts +34 -0
- package/src/helpers/forceClear.ts +31 -0
- package/src/helpers/forceUpdateNativeKeysState.ts +9 -0
- package/src/helpers/generateKeyShortcutMap.ts +113 -0
- package/src/helpers/getKeyFromEventCode.ts +67 -0
- package/src/helpers/getKeyFromIdOrVariant.ts +33 -0
- package/src/helpers/getKeyboardLayoutMap.ts +13 -0
- package/src/helpers/getLabel.ts +15 -0
- package/src/helpers/getTriggerableShortcut.ts +37 -0
- package/src/helpers/index.ts +30 -0
- package/src/helpers/isValidManager.ts +29 -0
- package/src/helpers/isValidShortcut.ts +20 -0
- package/src/helpers/labelWithEvent.ts +41 -0
- package/src/helpers/labelWithKeyboardMap.ts +37 -0
- package/src/helpers/managerToStorableClone.ts +26 -0
- package/src/helpers/onKeyboardLayoutChange.ts +17 -0
- package/src/helpers/safeSetManagerChain.ts +66 -0
- package/src/helpers/shortcutCanExecuteIn.ts +24 -0
- package/src/helpers/shortcutIsTriggerableBy.ts +15 -0
- package/src/helpers/shortcutSwapChords.ts +240 -0
- package/src/helpers/virtualPress.ts +34 -0
- package/src/helpers/virtualRelease.ts +25 -0
- package/src/helpers/virtualToggle.ts +28 -0
- package/src/internal/addToChain.ts +40 -0
- package/src/internal/areValidKeys.ts +40 -0
- package/src/internal/areValidVariants.ts +59 -0
- package/src/internal/checkTrigger.ts +55 -0
- package/src/internal/checkUntrigger.ts +26 -0
- package/src/internal/cloneLastChord.ts +10 -0
- package/src/internal/containsPossibleToggleChords.ts +91 -0
- package/src/internal/errorTextAdd.ts +18 -0
- package/src/internal/errorTextInUse.ts +10 -0
- package/src/internal/errorTextRemove.ts +10 -0
- package/src/internal/getModifierState.ts +15 -0
- package/src/internal/getPressedKeys.ts +26 -0
- package/src/internal/getPressedModifierKeys.ts +14 -0
- package/src/internal/getPressedNonModifierKeys.ts +19 -0
- package/src/internal/inChain.ts +25 -0
- package/src/internal/isValidChain.ts +42 -0
- package/src/internal/isValidChord.ts +87 -0
- package/src/internal/isValidCommand.ts +35 -0
- package/src/internal/keyOrder.ts +24 -0
- package/src/internal/removeFromChain.ts +46 -0
- package/src/internal/safeSetEmulatedToggleState.ts +23 -0
- package/src/internal/setKeysState.ts +71 -0
- package/src/internal/updateNativeKeysState.ts +84 -0
- package/src/layouts/createLayout.ts +328 -0
- package/src/module.ts +62 -0
- package/src/runtime/composables/useLabeledByKeyboardLayoutMap.ts +28 -0
- package/src/runtime/composables/usePointerCoords.ts +40 -0
- package/src/runtime/composables/useShortcutManagerContextCount.ts +81 -0
- package/src/runtime/composables/useShortcutManagerKeysLayout.ts +42 -0
- package/src/runtime/composables/useShortcutManagerVirtualPress.ts +30 -0
- package/src/runtime/types.ts +10 -0
- package/src/runtime/utils/shortcutToId.ts +14 -0
- package/src/types/commands.ts +148 -0
- package/src/types/condition.ts +35 -0
- package/src/types/context.ts +19 -0
- package/src/types/enums.ts +236 -0
- package/src/types/general.ts +117 -0
- package/src/types/index.ts +10 -0
- package/src/types/keys.ts +385 -0
- package/src/types/manager.ts +374 -0
- package/src/types/plugins.ts +32 -0
- package/src/types/shortcuts.ts +204 -0
- package/src/types/utils.ts +40 -0
- package/src/utils/chainContainsSubset.ts +97 -0
- package/src/utils/cloneChain.ts +13 -0
- package/src/utils/cloneKey.ts +33 -0
- package/src/utils/containsKey.ts +17 -0
- package/src/utils/dedupeKeys.ts +23 -0
- package/src/utils/equalsKey.ts +32 -0
- package/src/utils/equalsKeys.ts +50 -0
- package/src/utils/index.ts +16 -0
- package/src/utils/isAnyKey.ts +12 -0
- package/src/utils/isMouseKey.ts +27 -0
- package/src/utils/isNormalKey.ts +15 -0
- package/src/utils/isTriggerKey.ts +7 -0
- package/src/utils/isWheelKey.ts +18 -0
- package/src/utils/mapKeys.ts +21 -0
- 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
|
+
|