diff --git a/common/web/types/src/consts/modifier-key-constants.ts b/common/web/types/src/consts/modifier-key-constants.ts index 1f09c183f1..cfa8f03b01 100644 --- a/common/web/types/src/consts/modifier-key-constants.ts +++ b/common/web/types/src/consts/modifier-key-constants.ts @@ -189,3 +189,42 @@ export function visualKeyboardShiftToLayerName(shift: VisualKeyboardShiftState): } return result.substring(0, result.length - 1); } + +/** + * Get modifier key state from a hyphen-separated modifier string, such as + * found in layer ids or key "layer" properties. Ignores unrecognized + * components. Order of components is not significant. + * + * @param modifierString a modifier string such as 'ctrl-shift' + * @return Keyman modifier state bitmask + */ +export function modifierStringToState(modifierString: string): ModifierKeyConstant { + const map = new Map([ + ['shift', ModifierKeyConstant.K_SHIFTFLAG], + ['leftctrl', ModifierKeyConstant.LCTRLFLAG], + ['rightctrl', ModifierKeyConstant.RCTRLFLAG], + ['ctrl', ModifierKeyConstant.K_CTRLFLAG], + ['leftalt', ModifierKeyConstant.LALTFLAG], + ['rightalt', ModifierKeyConstant.RALTFLAG], + ['alt', ModifierKeyConstant.K_ALTFLAG], + ['caps', ModifierKeyConstant.CAPITALFLAG], + // legacy mappings: these were deprecated when we introduced chiral + // modifiers in KeymanWeb in ~v14.0; typically these would have been used + // without other components, but for the purposes of this function, we can + // treat them as components without harm + ['altshift', ModifierKeyConstant.K_ALTFLAG | ModifierKeyConstant.K_SHIFTFLAG], + ['ctrlalt', ModifierKeyConstant.K_CTRLFLAG | ModifierKeyConstant.K_ALTFLAG], + ['ctrlaltshift', ModifierKeyConstant.K_CTRLFLAG | ModifierKeyConstant.K_ALTFLAG | ModifierKeyConstant.K_SHIFTFLAG], + ['ctrlshift', ModifierKeyConstant.K_CTRLFLAG | ModifierKeyConstant.K_SHIFTFLAG], + ]); + + let modifier = 0; + const components = modifierString.split('-'); + for(const component of components) { + if(map.has(component)) { + modifier |= map.get(component); + } + } + + return modifier; +} diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 3b5664279d..06cff791c4 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -20,6 +20,7 @@ export { ModifierKeyConstant, ModifierKeyConstants, LDML_MODIFIER_TO_KVK_MODIFIER, KVK_MODIFIER_TO_LDML_MODIFIER, translateLdmlModifiersToVisualKeyboardShift, translateVisualKeyboardShiftToLdmlModifiers, visualKeyboardShiftToLayerName, + modifierStringToState, } from './consts/modifier-key-constants.js'; export * as TouchLayout from './keyman-touch-layout/keyman-touch-layout-file.js'; diff --git a/common/web/types/tests/consts/modifier-key-constants.tests.ts b/common/web/types/tests/consts/modifier-key-constants.tests.ts new file mode 100644 index 0000000000..87ce5c062d --- /dev/null +++ b/common/web/types/tests/consts/modifier-key-constants.tests.ts @@ -0,0 +1,31 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ + +import 'mocha'; +import { assert } from 'chai'; +import { ModifierKeyConstant, modifierStringToState } from '../../src/consts/modifier-key-constants.js'; + +describe('modifierStringToState', function() { + it('should map modifiers correctly', function() { + assert.equal(modifierStringToState(''), 0); + assert.equal(modifierStringToState('shift'), ModifierKeyConstant.K_SHIFTFLAG); + assert.equal(modifierStringToState('ctrl'), ModifierKeyConstant.K_CTRLFLAG); + assert.equal(modifierStringToState('rightctrl'), ModifierKeyConstant.RCTRLFLAG); + assert.equal(modifierStringToState('ctrl-shift'), ModifierKeyConstant.K_CTRLFLAG | ModifierKeyConstant.K_SHIFTFLAG); + assert.equal(modifierStringToState('ctrl-shift-alt'), ModifierKeyConstant.K_CTRLFLAG | ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_ALTFLAG); + assert.equal(modifierStringToState('ctrl-shift-alt-caps'), ModifierKeyConstant.K_CTRLFLAG | ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_ALTFLAG | ModifierKeyConstant.CAPITALFLAG); + assert.equal(modifierStringToState('caps-alt-ctrl-shift'), ModifierKeyConstant.K_CTRLFLAG | ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_ALTFLAG | ModifierKeyConstant.CAPITALFLAG); + }); + + it('should map legacy modifiers correctly', function() { + assert.equal(modifierStringToState('altshift'), ModifierKeyConstant.K_ALTFLAG | ModifierKeyConstant.K_SHIFTFLAG); + assert.equal(modifierStringToState('ctrlaltshift'), ModifierKeyConstant.K_CTRLFLAG | ModifierKeyConstant.K_SHIFTFLAG | ModifierKeyConstant.K_ALTFLAG); + }); + + it('should ignore unknown modifiers', function() { + assert.equal(modifierStringToState('ctrl-shift-none'), ModifierKeyConstant.K_CTRLFLAG | ModifierKeyConstant.K_SHIFTFLAG); + assert.equal(modifierStringToState('ctrlraltshift'), 0); + assert.equal(modifierStringToState('ncaps'), 0); + }); +}); diff --git a/web/src/engine/keyboard/src/codes.ts b/web/src/engine/keyboard/src/codes.ts index c94d5b7152..4a8c9f2622 100644 --- a/web/src/engine/keyboard/src/codes.ts +++ b/web/src/engine/keyboard/src/codes.ts @@ -86,6 +86,7 @@ const Codes = { /** * Get modifier key state from layer id * + * @deprecated Use `modifierStringToState` from `@keymanapp/common-types`; note that 'caps' is also matched * @param {string} layerId layer id (e.g. ctrlshift) * @return {number} modifier key state (desktop keyboards) */ @@ -128,6 +129,7 @@ const Codes = { /** * Get state key state from layer id * + * @deprecated Use `modifierStringToState` from `@keymanapp/common-types`; note that modifiers are also matched * @param {string} layerId layer id (e.g. caps) * @return {number} modifier key state (desktop keyboards) */