diff --git a/common/web/keyboard-processor/src/text/keyboardProcessor.ts b/common/web/keyboard-processor/src/text/keyboardProcessor.ts index ac49021d20..3726c67a17 100644 --- a/common/web/keyboard-processor/src/text/keyboardProcessor.ts +++ b/common/web/keyboard-processor/src/text/keyboardProcessor.ts @@ -37,7 +37,7 @@ export interface ProcessorInitOptions { } interface EventMap { - statekeyChange: (stateKeys: typeof KeyboardProcessor.prototype.stateKeys) => void; + statekeychange: (stateKeys: typeof KeyboardProcessor.prototype.stateKeys) => void; } export default class KeyboardProcessor extends EventEmitter { @@ -287,7 +287,7 @@ export default class KeyboardProcessor extends EventEmitter { } if(stateMutation) { - this.emit('statekeyChange', this.stateKeys); + this.emit('statekeychange', this.stateKeys); } } diff --git a/web/src/app/browser/src/hardwareEventKeyboard.ts b/web/src/app/browser/src/hardwareEventKeyboard.ts index 8a38abcd23..df03783d79 100644 --- a/web/src/app/browser/src/hardwareEventKeyboard.ts +++ b/web/src/app/browser/src/hardwareEventKeyboard.ts @@ -389,7 +389,7 @@ export default class HardwareEventKeyboard extends HardKeyboard { } // Is synchronous. - this.emit('keyEvent', Levent, (ruleBehavior, error) => { + this.emit('keyevent', Levent, (ruleBehavior, error) => { resultCapture.LeventMatched = ruleBehavior && !ruleBehavior.triggerKeyDefault; if(resultCapture.LeventMatched) { @@ -454,7 +454,7 @@ export default class HardwareEventKeyboard extends HardKeyboard { // interpretation as well by _not_ evaluating it during this pass. if(!this.swallowKeypress) { // is synchronous - this.emit('keyEvent', Levent, (result, error) => { + this.emit('keyevent', Levent, (result, error) => { resultCapture.preventDefaultKeystroke = !!result; }) } diff --git a/web/src/app/browser/src/keyboardInterface.ts b/web/src/app/browser/src/keyboardInterface.ts index bffc75e3de..4c3032d337 100644 --- a/web/src/app/browser/src/keyboardInterface.ts +++ b/web/src/app/browser/src/keyboardInterface.ts @@ -6,12 +6,6 @@ import ContextManager from './contextManager.js'; import KeymanEngine from './keymanEngine.js'; export default class KeyboardInterface extends KeyboardInterfaceBase { - // TBD: allowing it to be set and/or the retrieval mechanism. - // Note that the OSK is constructed notably later, after page load + during full engine init. - // So, the actual instance will not be available at construction-time. - private osk: OSKView; // Or some way to retrieve it. - - constructor( _jsGlobal: any, engine: KeymanEngine, @@ -20,6 +14,7 @@ export default class KeyboardInterface extends KeyboardInterfaceBase { touchLanguageMenu?: LanguageMenu; @@ -47,7 +48,15 @@ export default class KeymanEngine extends KeymanEngineBase this.legacyAPIEvents)); + super(worker, config, new ContextManager(config, () => this.legacyAPIEvents), (engine: KeymanEngine) => { + return { + // The `engine` parameter cannot be supplied with the constructing instance before calling + // `super`, hence the 'fun' rigging to supply it _from_ `super` via this closure. + keyboardInterface: new KeyboardInterface(window, engine), + defaultOutputRules: new DefaultBrowserRules(engine.contextManager) + }; + }); + this._util = new UtilApiEndpoint(config); this.beepHandler = new BeepHandler(this.core.keyboardInterface); this.core.keyboardProcessor.beepHandler = () => this.beepHandler.beep(this.contextManager.activeTarget); @@ -110,14 +119,6 @@ export default class KeymanEngine extends KeymanEngineBase) { let deviceDetector = new DeviceDetector(); let device = deviceDetector.detect(); diff --git a/web/src/app/browser/src/languageMenu.ts b/web/src/app/browser/src/languageMenu.ts index 5493752280..5a0953ec47 100644 --- a/web/src/app/browser/src/languageMenu.ts +++ b/web/src/app/browser/src/languageMenu.ts @@ -43,7 +43,7 @@ export class LanguageMenu { if(sX > spaceBar.offsetLeft && sX < spaceBar.offsetLeft+spaceBar.offsetWidth && sY > spaceBar.offsetTop && sY < spaceBar.offsetTop+spaceBar.offsetHeight ) { - this.keyman.osk.emit('showBuild'); + this.keyman.osk.emit('showbuild'); } } },false); diff --git a/web/src/app/browser/src/oskConfiguration.ts b/web/src/app/browser/src/oskConfiguration.ts index 8f78d917a2..8d7b804a47 100644 --- a/web/src/app/browser/src/oskConfiguration.ts +++ b/web/src/app/browser/src/oskConfiguration.ts @@ -7,7 +7,7 @@ import { LanguageMenu } from "./languageMenu.js"; export function setupOskListeners(engine: KeymanEngine, osk: OSKView, contextManager: ContextManager) { const focusAssistant = contextManager.focusAssistant; - osk.on('globeKey', (key, on) => { // K_LOPT + osk.on('globekey', (key, on) => { // K_LOPT if(on) { if(osk.hostDevice.touchable) { engine.touchLanguageMenu = new LanguageMenu(engine); @@ -20,26 +20,26 @@ export function setupOskListeners(engine: KeymanEngine, osk: OSKView, contextMan } }); - osk.on('hideRequested', (key) => { // K_ROPT + osk.on('hiderequested', (key) => { // K_ROPT if(osk) { osk.startHide(true); contextManager.forgetActiveTarget(); } }); - osk.on('onhide', (hiddenByUser) => { + osk.addEventListener('hide', (params) => { // If hidden by the UI, be sure to restore the focus - if(hiddenByUser) { + if(params?.HiddenByUser) { contextManager.activeTarget?.focus(); } }); - osk.on('showBuild', () => { + osk.on('showbuild', () => { engine.config.alertHost?.alert('KeymanWeb Version ' + KEYMAN_VERSION.VERSION + '

' +'Copyright © 2007-2023 SIL International'); }); - osk.on('dragMove', async (promise) => { + osk.on('dragmove', async (promise) => { focusAssistant.restoringFocus = true; await promise; @@ -50,7 +50,7 @@ export function setupOskListeners(engine: KeymanEngine, osk: OSKView, contextMan focusAssistant.setMaintainingFocus(false); }); - osk.on('resizeMove', async (promise) => { + osk.on('resizemove', async (promise) => { focusAssistant.restoringFocus = true; await promise; @@ -60,7 +60,7 @@ export function setupOskListeners(engine: KeymanEngine, osk: OSKView, contextMan focusAssistant.setMaintainingFocus(false); }); - osk.on('pointerInteraction', async (promise) => { + osk.on('pointerinteraction', async (promise) => { // On event start focusAssistant.setMaintainingFocus(true); diff --git a/web/src/app/webview/src/keymanEngine.ts b/web/src/app/webview/src/keymanEngine.ts index 93120af86c..1cc7f08248 100644 --- a/web/src/app/webview/src/keymanEngine.ts +++ b/web/src/app/webview/src/keymanEngine.ts @@ -1,5 +1,5 @@ -import { DeviceSpec } from '@keymanapp/keyboard-processor' -import { KeymanEngine as KeymanEngineBase } from 'keyman/engine/main'; +import { DefaultRules, DeviceSpec } from '@keymanapp/keyboard-processor' +import { KeymanEngine as KeymanEngineBase, KeyboardInterface } from 'keyman/engine/main'; import { AnchoredOSKView, ViewConfiguration, StaticActivator } from 'keyman/engine/osk'; import { getAbsoluteX, getAbsoluteY } from 'keyman/engine/dom-utils'; import { type KeyboardStub, toPrefixedKeyboardId, toUnprefixedKeyboardId } from 'keyman/engine/package-cache'; @@ -25,7 +25,14 @@ export default class KeymanEngine extends KeymanEngineBase { + return { + // The `engine` parameter cannot be supplied with the constructing instance before calling + // `super`, hence the 'fun' rigging to supply it _from_ `super` via this closure. + keyboardInterface: new KeyboardInterface(window, engine, config.stubNamespacer), + defaultOutputRules: new DefaultRules() + }; + }); this.hardKeyboard = new PassthroughKeyboard(config.hardDevice); } diff --git a/web/src/app/webview/src/oskConfiguration.ts b/web/src/app/webview/src/oskConfiguration.ts index d8ca35bd1c..80a8cdb698 100644 --- a/web/src/app/webview/src/oskConfiguration.ts +++ b/web/src/app/webview/src/oskConfiguration.ts @@ -9,7 +9,7 @@ import { PendingLongpress } from './osk/pendingLongpress.js'; import type KeymanEngine from "./keymanEngine.js"; export function setupEmbeddedListeners(engine: KeymanEngine, osk: OSKView) { - osk.on('globeKey', (key, on) => { + osk.on('globekey', (key, on) => { if(on) { if(typeof engine.showKeyboardList == 'function') { // OSKView event: shouldShowLanguageMenu engine.showKeyboardList(); // Is connected to VisualKeyboard event: globeKey @@ -29,7 +29,7 @@ export function setupEmbeddedListeners(engine: KeymanEngine, osk: OSKView) { } }); - osk.on('hideRequested', (key) => { + osk.on('hiderequested', (key) => { if(osk.vkbd) { osk.vkbd.highlightKey(key, false); } diff --git a/web/src/app/webview/src/passthroughKeyboard.ts b/web/src/app/webview/src/passthroughKeyboard.ts index 17a1bd6acb..87b5e123bf 100644 --- a/web/src/app/webview/src/passthroughKeyboard.ts +++ b/web/src/app/webview/src/passthroughKeyboard.ts @@ -40,7 +40,7 @@ export default class PassthroughKeyboard extends HardKeyboard { const promise = new ManagedPromise(); try { - this.emit('keyEvent', Lkc, (result, error) => { + this.emit('keyevent', Lkc, (result, error) => { if(error) { promise.reject(error); } else { diff --git a/web/src/engine/events/src/emitterListenerSpy.ts b/web/src/engine/events/src/emitterListenerSpy.ts index d0c65199b3..8557dc5c85 100644 --- a/web/src/engine/events/src/emitterListenerSpy.ts +++ b/web/src/engine/events/src/emitterListenerSpy.ts @@ -9,14 +9,14 @@ interface EventMap { * EventEmitter being spied upon. * @param eventName */ - listenerAdded(eventName: EventNames); + listeneradded(eventName: EventNames); /** * Indicates that a listener for the named event has been unregistered from the * EventEmitter being spied upon. * @param eventName */ - listenerRemoved(eventName: EventNames); + listenerremoved(eventName: EventNames); } type Emitter = EventEmitter | LegacyEventEmitter; @@ -30,19 +30,19 @@ export class EmitterListenerSpy extends EventEmitte super(); if(emitter instanceof EventEmitter) { - emitter.on = this.listenerRegistrationSpy('listenerAdded', emitter, emitter.on); - emitter.addListener = this.listenerRegistrationSpy('listenerAdded', emitter, emitter.addListener); - emitter.off = this.listenerRegistrationSpy('listenerRemoved', emitter, emitter.off); - emitter.removeListener = this.listenerRegistrationSpy('listenerRemoved', emitter, emitter.off); + emitter.on = this.listenerRegistrationSpy('listeneradded', emitter, emitter.on); + emitter.addListener = this.listenerRegistrationSpy('listeneradded', emitter, emitter.addListener); + emitter.off = this.listenerRegistrationSpy('listenerremoved', emitter, emitter.off); + emitter.removeListener = this.listenerRegistrationSpy('listenerremoved', emitter, emitter.off); } else { // TS gets really fussy about how the legacy event typing is a bit more // restrictive (due to less-restricted event name types in EventEmitter) // It's not worth the effort to make this 100% perfect at the moment. // // @ts-ignore - emitter.addEventListener = this.listenerRegistrationSpy('listenerAdded', emitter, emitter.addEventListener); + emitter.addEventListener = this.listenerRegistrationSpy('listeneradded', emitter, emitter.addEventListener); // @ts-ignore - emitter.removeEventListener = this.listenerRegistrationSpy('listenerRemoved', emitter, emitter.removeEventListener); + emitter.removeEventListener = this.listenerRegistrationSpy('listenerremoved', emitter, emitter.removeEventListener); } } @@ -82,7 +82,7 @@ export class EmitterListenerSpy extends EventEmitte // const emitter = new LegacyEventEmitter; // or `new EventEmitter`. // const emitterSpy = new EmitterListenerSpy(emitter); -// emitterSpy.on('listenerAdded', (eventName) => { +// emitterSpy.on('listeneradded', (eventName) => { // // eventName = 'c'; // will error; there is no event 'c' in the event map. // if(eventName == 'a') { // // stuff diff --git a/web/src/engine/events/src/legacyEventEmitter.ts b/web/src/engine/events/src/legacyEventEmitter.ts index a22d2a7e37..bd0b6023a7 100644 --- a/web/src/engine/events/src/legacyEventEmitter.ts +++ b/web/src/engine/events/src/legacyEventEmitter.ts @@ -95,7 +95,7 @@ export class LegacyEventEmitter { return this._removeEventListener(event, func); } - // Separate, in order to prevent `addEventListener` from sending 'listenerRemoved' events with + // Separate, in order to prevent `addEventListener` from sending 'listenerremoved' events with // EmitterListenerSpy. private _removeEventListener> ( event: T, diff --git a/web/src/engine/main/src/hardKeyboard.ts b/web/src/engine/main/src/hardKeyboard.ts index da074b62c2..71504a0187 100644 --- a/web/src/engine/main/src/hardKeyboard.ts +++ b/web/src/engine/main/src/hardKeyboard.ts @@ -6,7 +6,7 @@ interface EventMap { /** * Designed to pass key events off to any consuming modules/libraries. */ - 'keyEvent': (event: KeyEvent, callback?: (result: RuleBehavior, error?: Error) => void) => void + 'keyevent': (event: KeyEvent, callback?: (result: RuleBehavior, error?: Error) => void) => void } export default class HardKeyboard extends EventEmitter implements KeyEventSourceInterface { } diff --git a/web/src/engine/main/src/keyEventSource.interface.ts b/web/src/engine/main/src/keyEventSource.interface.ts index 75a32b3dcc..e2698345af 100644 --- a/web/src/engine/main/src/keyEventSource.interface.ts +++ b/web/src/engine/main/src/keyEventSource.interface.ts @@ -8,7 +8,7 @@ interface EventMap { /** * Designed to pass key events off to any consuming modules/libraries. */ - 'keyEvent': KeyEventHandler; + 'keyevent': KeyEventHandler; } export default interface KeyEventSourceInterface extends EventEmitter { } \ No newline at end of file diff --git a/web/src/engine/main/src/keymanEngine.ts b/web/src/engine/main/src/keymanEngine.ts index f197e23b67..6eb342803a 100644 --- a/web/src/engine/main/src/keymanEngine.ts +++ b/web/src/engine/main/src/keymanEngine.ts @@ -1,8 +1,8 @@ -import { DefaultRules, type Keyboard, KeyboardKeymanGlobal, ProcessorInitOptions, OutputTarget } from "@keymanapp/keyboard-processor"; +import { type Keyboard, KeyboardKeymanGlobal, ProcessorInitOptions } from "@keymanapp/keyboard-processor"; import { DOMKeyboardLoader as KeyboardLoader } from "@keymanapp/keyboard-processor/dom-keyboard-loader"; import { InputProcessor, PredictionContext } from "@keymanapp/input-processor"; import { OSKView } from "keyman/engine/osk"; -import { KeyboardRequisitioner, type KeyboardStub, ModelCache, ModelSpec } from "keyman/engine/package-cache"; +import { KeyboardRequisitioner, ModelCache, ModelSpec } from "keyman/engine/package-cache"; import { EngineConfiguration, InitOptionSpec } from "./engineConfiguration.js"; import KeyboardInterface from "./keyboardInterface.js"; @@ -14,6 +14,19 @@ import { EventNames, EventListener, LegacyEventEmitter } from "keyman/engine/eve import DOMCloudRequester from "keyman/engine/package-cache/dom-requester"; import KEYMAN_VERSION from "@keymanapp/keyman-version"; +// From https://stackoverflow.com/a/69328045 +type WithRequired = T & { [P in K]-?: T[P] }; +// Sets two parts non-optional at this level, while they were at lower levels. +type ProcessorConfiguration = WithRequired, 'defaultOutputRules'>; + +function determineBaseLayout(): string { + if(typeof(window['KeymanWeb_BaseLayout']) !== 'undefined') { + return window['KeymanWeb_BaseLayout']; + } else { + return 'us'; + } +} + export default class KeymanEngine< Configuration extends EngineConfiguration, ContextManager extends ContextManagerBase, @@ -73,25 +86,6 @@ export default class KeymanEngine< // processing - silent failures are far harder to diagnose. }; - // Should be overwritten as needed by engine subclasses; `browser` should set its DefaultOutput subclass in place. - protected processorConfiguration(): ProcessorInitOptions { - // I732 START - Support for European underlying keyboards #1 - let baseLayout: string; - if(typeof(window['KeymanWeb_BaseLayout']) !== 'undefined') { - baseLayout = window['KeymanWeb_BaseLayout']; - } else { - baseLayout = 'us'; - } - - return { - keyboardInterface: this.interface, - baseLayout: baseLayout, - defaultOutputRules: new DefaultRules() - }; - }; - - // - /** * @param worker A configured WebWorker to serve as the predictive-text engine's main thread. * Available in the following variants: @@ -99,13 +93,23 @@ export default class KeymanEngine< * - non-sourcemapped + minified (release) * @param config * @param contextManager + * @param processorConfigInitializer A one-time use closure used to initialize certain critical components reliant + * upon the class instance, configured by the derived class, but needed during + * the superclass constructor. */ - constructor(worker: Worker, config: Configuration, contextManager: ContextManager) { + constructor( + worker: Worker, + config: Configuration, + contextManager: ContextManager, + processorConfigInitializer: (engine: KeymanEngine) => ProcessorConfiguration + ) { this.config = config; this.contextManager = contextManager; - this.interface = new KeyboardInterface(window, this, config.stubNamespacer); - this.core = new InputProcessor(config.hostDevice, worker, this.processorConfiguration()); + const processorConfiguration = processorConfigInitializer(this); + processorConfiguration.baseLayout = determineBaseLayout(); + this.interface = processorConfiguration.keyboardInterface as KeyboardInterface; + this.core = new InputProcessor(config.hostDevice, worker, processorConfiguration); this.core.languageProcessor.on('statechange', (state) => { // The banner controller cannot directly trigger a layout-refresh at this time, @@ -117,7 +121,7 @@ export default class KeymanEngine< // The OSK does not possess a direct connection to the KeyboardProcessor's state-key // management object; this event + handler allow us to keep the OSK's related states // in sync. - this.core.keyboardProcessor.on('statekeyChange', (stateKeys) => { + this.core.keyboardProcessor.on('statekeychange', (stateKeys) => { this.osk?.vkbd?.updateStateKeys(stateKeys); }) @@ -211,7 +215,7 @@ export default class KeymanEngine< this.osk?.refreshLayout(); }); - kbdCache.on('stubAdded', (stub) => { + kbdCache.on('stubadded', (stub) => { let eventRaiser = () => { // The corresponding event is needed in order to update UI modules as new keyboard stubs "come online". this.legacyAPIEvents.callEvent('keyboardregistered', { @@ -236,7 +240,7 @@ export default class KeymanEngine< } }); - kbdCache.on('keyboardAdded', (keyboard) => { + kbdCache.on('keyboardadded', (keyboard) => { let eventRaiser = () => { // Execute any external (UI) code needed after loading keyboard this.legacyAPIEvents.callEvent('keyboardloaded', { @@ -251,7 +255,7 @@ export default class KeymanEngine< } }); - this.keyboardRequisitioner.cache.on('keyboardAdded', (keyboard) => { + this.keyboardRequisitioner.cache.on('keyboardadded', (keyboard) => { this.legacyAPIEvents.callEvent('keyboardloaded', { keyboardName: keyboard.id }); }); // @@ -272,10 +276,10 @@ export default class KeymanEngine< protected set hardKeyboard(keyboard: HardKeyboard) { if(this._hardKeyboard) { - this._hardKeyboard.off('keyEvent', this.keyEventListener); + this._hardKeyboard.off('keyevent', this.keyEventListener); } this._hardKeyboard = keyboard; - keyboard.on('keyEvent', this.keyEventListener); + keyboard.on('keyevent', this.keyEventListener); } public get osk(): OSKView { @@ -284,13 +288,13 @@ export default class KeymanEngine< public set osk(value: OSKView) { if(this._osk) { - this._osk.off('keyEvent', this.keyEventListener); + this._osk.off('keyevent', this.keyEventListener); this.core.keyboardProcessor.layerStore.handler = this.osk.layerChangeHandler; } this._osk = value; if(value) { value.activeKeyboard = this.contextManager.activeKeyboard; - value.on('keyEvent', this.keyEventListener); + value.on('keyevent', this.keyEventListener); this.core.keyboardProcessor.layerStore.handler = value.layerChangeHandler; } } diff --git a/web/src/engine/osk/src/components/resizeBar.ts b/web/src/engine/osk/src/components/resizeBar.ts index 80c806e69c..35f6ec8998 100644 --- a/web/src/engine/osk/src/components/resizeBar.ts +++ b/web/src/engine/osk/src/components/resizeBar.ts @@ -10,7 +10,7 @@ interface EventMap { /** * Triggered when the user inputs a special command to show the engine's current version number. */ - showBuild: () => void; + showbuild: () => void; } export default class ResizeBar extends EventEmitter implements OSKViewComponent { @@ -66,7 +66,7 @@ export default class ResizeBar extends EventEmitter impleme // Display build number on shift+double click Ltitle.addEventListener('dblclick', (e) => { - this.emit('showBuild'); + this.emit('showbuild'); return false; }, false); diff --git a/web/src/engine/osk/src/views/anchoredOskView.ts b/web/src/engine/osk/src/views/anchoredOskView.ts index 71e9eefeef..be9ab95789 100644 --- a/web/src/engine/osk/src/views/anchoredOskView.ts +++ b/web/src/engine/osk/src/views/anchoredOskView.ts @@ -241,4 +241,9 @@ export default class AnchoredOSKView extends OSKView { Ls.borderTop='1px solid gray'; } } + + public present() { + super.present(); + this.legacyEvents.callEvent('show', {}); + } } diff --git a/web/src/engine/osk/src/views/floatingOskView.ts b/web/src/engine/osk/src/views/floatingOskView.ts index 9e277390b0..078e7db856 100644 --- a/web/src/engine/osk/src/views/floatingOskView.ts +++ b/web/src/engine/osk/src/views/floatingOskView.ts @@ -45,25 +45,23 @@ export default class FloatingOSKView extends OSKView { super(config); - this.typedActivationModel.on('triggerChange', () => this.setDisplayPositioning()); + this.typedActivationModel.on('triggerchange', () => this.setDisplayPositioning()); document.body.appendChild(this._Box); // Add header element to OSK only for desktop browsers this.titleBar = new TitleBar(this.titleDragHandler); this.titleBar.on('help', () => { - this.emit('showHelp'); this.legacyEvents.callEvent('helpclick', {}); }); this.titleBar.on('config', () => { - this.emit('showConfig'); this.legacyEvents.callEvent('configclick', {}); }); this.titleBar.on('close', () => this.startHide(true)); this.titleBar.on('unpin', () => this.restorePosition(true)); this.resizeBar = new ResizeBar(this.resizeDragHandler); - this.resizeBar.on('showBuild', () => this.emit('showBuild')); + this.resizeBar.on('showbuild', () => this.emit('showbuild')); this.headerView = this.titleBar; @@ -74,12 +72,10 @@ export default class FloatingOSKView extends OSKView { if(titleBar && titleBar instanceof TitleBar) { switch(eventName) { case 'configclick': - case 'showConfig': - titleBar.configEnabled = this.listenerCount('showConfig') + this.legacyEvents.listenerCount('configclick') > 0; + titleBar.configEnabled = this.legacyEvents.listenerCount('configclick') > 0; break; - case 'showHelp': case 'helpclick': - titleBar.helpEnabled = this.listenerCount('showHelp') + this.legacyEvents.listenerCount('helpclick') > 0; + titleBar.helpEnabled = this.legacyEvents.listenerCount('helpclick') > 0; break; default: return; @@ -90,8 +86,8 @@ export default class FloatingOSKView extends OSKView { const listenerSpyNew = new EmitterListenerSpy(this); const listenerSpyOld = new EmitterListenerSpy(this.legacyEvents); for(let listenerSpy of [listenerSpyNew, listenerSpyOld]) { - listenerSpy.on('listenerAdded', onListenedEvent); - listenerSpy.on('listenerRemoved', onListenedEvent); + listenerSpy.on('listeneradded', onListenedEvent); + listenerSpy.on('listenerremoved', onListenedEvent); } this.loadPersistedLayout(); @@ -153,7 +149,7 @@ export default class FloatingOSKView extends OSKView { let isVisible = this._Visible; let dragPromise = new ManagedPromise(); - this.emit('dragMove', dragPromise.corePromise); + this.emit('dragmove', dragPromise.corePromise); this.loadPersistedLayout(); this.userPositioned=false; @@ -652,7 +648,7 @@ export default class FloatingOSKView extends OSKView { } this.dragPromise = new ManagedPromise(); - _this.emit('dragMove', this.dragPromise.corePromise); + _this.emit('dragmove', this.dragPromise.corePromise); } onDragMove(cumulativeX: number, cumulativeY: number) { @@ -715,7 +711,7 @@ export default class FloatingOSKView extends OSKView { } this.dragPromise = new ManagedPromise(); - _this.emit('resizeMove', this.dragPromise.corePromise); + _this.emit('resizemove', this.dragPromise.corePromise); } onDragMove(cumulativeX: number, cumulativeY: number) { diff --git a/web/src/engine/osk/src/views/inlinedOskView.ts b/web/src/engine/osk/src/views/inlinedOskView.ts index 86779f0f35..c86bf21e49 100644 --- a/web/src/engine/osk/src/views/inlinedOskView.ts +++ b/web/src/engine/osk/src/views/inlinedOskView.ts @@ -116,6 +116,12 @@ export default class InlinedOSKView extends OSKView { return; // I3363 (Build 301) } + public present() { + super.present(); + + this.legacyEvents.callEvent('show', {}); + } + protected setDisplayPositioning() { // no-op; an inlined OSK cannot control its own positioning. } diff --git a/web/src/engine/osk/src/views/oskView.ts b/web/src/engine/osk/src/views/oskView.ts index 4123aa1e57..8a86c36240 100644 --- a/web/src/engine/osk/src/views/oskView.ts +++ b/web/src/engine/osk/src/views/oskView.ts @@ -21,7 +21,7 @@ import { type SystemStoreMutationHandler } from '@keymanapp/keyboard-processor'; import { createUnselectableElement, getAbsoluteX, getAbsoluteY, StylesheetManager } from 'keyman/engine/dom-utils'; -import { LegacyEventEmitter } from 'keyman/engine/events'; +import { EventListener, EventNames, LegacyEventEmitter } from 'keyman/engine/events'; import Configuration from '../config/viewConfiguration.js'; import Activator, { StaticActivator } from './activator.js'; @@ -49,7 +49,9 @@ export interface LegacyOSKEventMap { 'helpclick'(obj: {}); 'resizemove'(obj: {}); 'show'(obj: {}); - 'hide'(obj: {}); + 'hide'(obj: { + HiddenByUser: boolean + }); } /** @@ -64,41 +66,27 @@ export interface EventMap { * Note: the following code block was originally used to integrate with the keyboard & input * processors, but it requires entanglement with components external to this OSK module. */ - 'keyEvent': (event: KeyEvent) => void, - - onshow(): void; - - onhide(hiddenByUser: boolean): void; + 'keyevent': (event: KeyEvent) => void, /** * Indicates that the globe key has either been pressed (`on` == `true`) * or released (`on` == `false`). */ - globeKey: (e: KeyElement, on: boolean) => void; + globekey: (e: KeyElement, on: boolean) => void; /** * A virtual keystroke corresponding to a "hide" command has been received. */ - hideRequested: (key: KeyElement) => void; - - /** - * This event is raised when the OSK's 'config' button is clicked. - * Adding a listener for the event will cause the 'config' button to be displayed for - * FloatingOSKView instances. - */ - showConfig: () => void; - - /** - * This event is raised when the OSK's 'help' button is clicked. - * Adding a listener for the event will cause the 'help' button to be displayed for - * FloatingOSKView instances. - */ - showHelp: () => void; + hiderequested: (key: KeyElement) => void; /** * Signals the special command to display the engine's version + build number. */ - showBuild: () => void; + showbuild: () => void; + + // While the next two are near-duplicates of the legacy event `resizemove`, these + // have the advantage of providing a Promise for the end of the ongoing user + // interaction. We need that Promise for focus-management. /** * Signals that the OSK is being moved by the user via a drag operation. @@ -108,15 +96,14 @@ export interface EventMap { * Note that position-restoration (unpinning the OSK) is treated as a drag-move * event. It resolves near-instantly. */ - dragMove: (promise: Promise) => void; + dragmove: (promise: Promise) => void; /** * Signals that the OSK is being resized via a drag operation (on a resize 'handle'). * * The provided Promise will resolve once the resize operation is complete. */ - resizeMove: (promise: Promise) => void; - + resizemove: (promise: Promise) => void; /** * Signals that either the mouse or an active touchpoint is interacting with the OSK. @@ -125,7 +112,7 @@ export interface EventMap { * Note that for touch events, more than one touchpoint may coexist, each with its own * corresponding call of this event and corresponding `Promise`. */ - pointerInteraction: (promise: Promise) => void; + pointerinteraction: (promise: Promise) => void; } export default abstract class OSKView extends EventEmitter implements MinimalCodesInterface { @@ -290,7 +277,7 @@ export default abstract class OSKView extends EventEmitter implements } this.mouseEnterPromise = new ManagedPromise(); - this.emit('pointerInteraction', this.mouseEnterPromise.corePromise); + this.emit('pointerinteraction', this.mouseEnterPromise.corePromise); }; this._Box.onmouseleave = this._VKbdMouseLeave = (e) => { @@ -323,7 +310,7 @@ export default abstract class OSKView extends EventEmitter implements this._boxBaseTouchStart = (e) => { for(let i = 0; i < e.changedTouches.length; i++) { let promise = this.touchEventPromiseManager.promiseForTouchpoint(e.changedTouches[i].identifier); - this.emit('pointerInteraction', promise.corePromise); + this.emit('pointerinteraction', promise.corePromise); } this.touchEventPromiseManager.maintainTouches(e.touches); @@ -814,11 +801,11 @@ export default abstract class OSKView extends EventEmitter implements isEmbedded: this.config.isEmbedded }); - vkbd.on('keyEvent', (keyEvent) => this.emit('keyEvent', keyEvent)); - vkbd.on('globeKey', (keyElement, on) => this.emit('globeKey', keyElement, on)); - vkbd.on('hideRequested', (keyElement) => { + vkbd.on('keyevent', (keyEvent) => this.emit('keyevent', keyEvent)); + vkbd.on('globekey', (keyElement, on) => this.emit('globekey', keyElement, on)); + vkbd.on('hiderequested', (keyElement) => { this.doHide(true); - this.emit('hideRequested', keyElement); + this.emit('hiderequested', keyElement); }); // Set box class - OS and keyboard added for Build 360 @@ -918,9 +905,8 @@ export default abstract class OSKView extends EventEmitter implements this.setDisplayPositioning(); - // Do this once all properties are set; that way, consumers can safely poll - // for position, size, etc. - this.emit('onshow'); + // Each subclass is responsible for raising the 'show' event on its own, since + // certain ones supply extra information in their event param object. } /** @@ -1241,10 +1227,9 @@ export default abstract class OSKView extends EventEmitter implements * */ doHide(hiddenByUser: boolean) { - this.emit('onhide', hiddenByUser); - - const p={}; - p['HiddenByUser']=hiddenByUser; + const p={ + HiddenByUser: hiddenByUser + }; this.legacyEvents.callEvent('hide', p); } @@ -1256,11 +1241,17 @@ export default abstract class OSKView extends EventEmitter implements * @return {boolean} * Description Wrapper function to add and identify OSK-specific event handlers */ - addEventListener(event: T, fn: (arg: {}) => any): void { + addEventListener( + event: T, + fn: EventListener + ): void { this.legacyEvents.addEventListener(event, fn); } - removeEventListener(event: T, fn: (arg: {}) => any): void { + removeEventListener( + event: T, + fn: EventListener + ): void { this.legacyEvents.removeEventListener(event, fn); } } \ No newline at end of file diff --git a/web/src/engine/osk/src/views/twoStateActivator.ts b/web/src/engine/osk/src/views/twoStateActivator.ts index 85ae93f1a3..b5895158ad 100644 --- a/web/src/engine/osk/src/views/twoStateActivator.ts +++ b/web/src/engine/osk/src/views/twoStateActivator.ts @@ -1,7 +1,7 @@ import Activator from './activator.js'; interface TriggerEventMap { - triggerChange: (trigger: Type) => void; + triggerchange: (trigger: Type) => void; } export default class TwoStateActivator extends Activator> { @@ -40,7 +40,7 @@ export default class TwoStateActivator extends Activator void, + 'keyevent': (event: KeyEvent) => void, - 'hideRequested': (keyElement: KeyElement) => void, + 'hiderequested': (keyElement: KeyElement) => void, - 'globeKey': (keyElement: KeyElement, on: boolean) => void + 'globekey': (keyElement: KeyElement, on: boolean) => void } export default class VisualKeyboard extends EventEmitter implements KeyboardView { @@ -1861,10 +1861,10 @@ export default class VisualKeyboard extends EventEmitter implements Ke optionKey(e: KeyElement, keyName: string, keyDown: boolean) { if (keyName.indexOf('K_LOPT') >= 0) { - this.emit('globeKey', e, keyDown); + this.emit('globekey', e, keyDown); } else if (keyName.indexOf('K_ROPT') >= 0) { if (keyDown) { - this.emit('hideRequested', e); + this.emit('hiderequested', e); } } }; @@ -1949,6 +1949,6 @@ export default class VisualKeyboard extends EventEmitter implements Ke return true; } - this.emit('keyEvent', keyEvent); + this.emit('keyevent', keyEvent); } } diff --git a/web/src/engine/package-cache/src/stubAndKeyboardCache.ts b/web/src/engine/package-cache/src/stubAndKeyboardCache.ts index 93e2d94a79..5d85082099 100644 --- a/web/src/engine/package-cache/src/stubAndKeyboardCache.ts +++ b/web/src/engine/package-cache/src/stubAndKeyboardCache.ts @@ -1,6 +1,5 @@ import { Keyboard, KeyboardLoaderBase as KeyboardLoader } from "@keymanapp/keyboard-processor"; import EventEmitter from "eventemitter3"; -import { type PathConfiguration } from "keyman/engine/paths"; import KeyboardStub from "./keyboardStub.js"; @@ -34,12 +33,12 @@ interface EventMap { * to denote the first added stub to facilitate auto-activation of the first * keyboard to be registered. */ - stubAdded: (stub: KeyboardStub) => void; + stubadded: (stub: KeyboardStub) => void; /** * Indicates that the specified Keyboard has just been added to the cache. */ - keyboardAdded: (keyboard: Keyboard) => void; + keyboardadded: (keyboard: Keyboard) => void; } export default class StubAndKeyboardCache extends EventEmitter { @@ -117,7 +116,7 @@ export default class StubAndKeyboardCache extends EventEmitter { const keyboardID = prefixed(keyboard.id); this.keyboardTable[keyboardID] = keyboard; - this.emit('keyboardAdded', keyboard); + this.emit('keyboardadded', keyboard); } fetchKeyboardForStub(stub: KeyboardStub) : Promise { @@ -182,7 +181,7 @@ export default class StubAndKeyboardCache extends EventEmitter { const stubTable = this.stubSetTable[keyboardID] = this.stubSetTable[keyboardID] ?? {}; stubTable[stub.KLC] = stub; - this.emit('stubAdded', stub); + this.emit('stubadded', stub); } findMatchingStub(stub: KeyboardStub) { diff --git a/web/src/test/auto/dom/cases/osk/events.js b/web/src/test/auto/dom/cases/osk/events.js index 0a3f8e887a..8409863b74 100644 --- a/web/src/test/auto/dom/cases/osk/events.js +++ b/web/src/test/auto/dom/cases/osk/events.js @@ -59,25 +59,21 @@ describe('OSK events', function () { // Setup complete. // Hide the OSK + check for related event - let hideStub = sinon.fake(); let legacyHideStub = sinon.fake(); - osk.once('onhide', hideStub); osk.legacyEvents.addEventListener('hide', legacyHideStub); osk.activationModel.enabled = false; - assert.isTrue(hideStub.calledOnce); assert.isTrue(legacyHideStub.calledOnce); + // The hide actually occurs on a Promise's completion. + // Adding the 'await' makes this clearer during test maintenance. + await Promise.resolve(); + // Show the OSK + check for related event - let showStub = sinon.fake(); - // let legacyShowStub = sinon.fake(); - osk.once('onshow', showStub); - // osk.once('legacyevent', legacyShowStub); + let legacyShowStub = sinon.fake(); + osk.legacyEvents.addEventListener('show', legacyShowStub); osk.activationModel.enabled = true; - assert.isTrue(showStub.calledOnce); - // // Only called (at present) for "Floating" OSK views, given the event's parameterization. - // assert.isTrue(legacyShowStub.calledOnce); - // assert.equal(legacyShowStub.firstCall.args[0], 'osk.show'); + assert.isTrue(legacyShowStub.calledOnce); }); }); \ No newline at end of file diff --git a/web/src/test/auto/headless/events/emitterListenerSpy.js b/web/src/test/auto/headless/events/emitterListenerSpy.js index 326ab9518b..b88a49f8b3 100644 --- a/web/src/test/auto/headless/events/emitterListenerSpy.js +++ b/web/src/test/auto/headless/events/emitterListenerSpy.js @@ -15,8 +15,8 @@ describe("EmitterListenerSpy", () => { const fakeAddListener = sinon.fake(); const fakeRemoveListener = sinon.fake(); - emitterSpy.on('listenerAdded', fakeAddListener); - emitterSpy.on('listenerRemoved', fakeRemoveListener); + emitterSpy.on('listeneradded', fakeAddListener); + emitterSpy.on('listenerremoved', fakeRemoveListener); emitter.on('event', fakeEventHandler); assert.isTrue(fakeAddListener.calledOnce); @@ -35,8 +35,8 @@ describe("EmitterListenerSpy", () => { const fakeAddListener = sinon.fake(); const fakeRemoveListener = sinon.fake(); - emitterSpy.on('listenerAdded', fakeAddListener); - emitterSpy.on('listenerRemoved', fakeRemoveListener); + emitterSpy.on('listeneradded', fakeAddListener); + emitterSpy.on('listenerremoved', fakeRemoveListener); emitter.addEventListener('event', fakeEventHandler); assert.isTrue(fakeAddListener.calledOnce); diff --git a/web/src/test/auto/headless/osk/activation.js b/web/src/test/auto/headless/osk/activation.js index d412b417a3..0b74d0bd92 100644 --- a/web/src/test/auto/headless/osk/activation.js +++ b/web/src/test/auto/headless/osk/activation.js @@ -95,7 +95,7 @@ describe("Activators", () => { const activateStub = sinon.fake(); const triggerStub = sinon.fake(); activator.on('activate', activateStub); - activator.on('triggerChange', triggerStub); + activator.on('triggerchange', triggerStub); activator.activationTrigger = "foo"; // using a string, just 'cause. @@ -154,7 +154,7 @@ describe("Activators", () => { it("'triggerchange' event generation", () => { const activator = new TwoStateActivator(); const stub = sinon.fake(); - activator.on('triggerChange', stub); + activator.on('triggerchange', stub); activator.enabled = false; @@ -219,7 +219,7 @@ describe("Activators", () => { it("'activationTrigger' as object", () => { const activator = new TwoStateActivator(); const stub = sinon.fake(); - activator.on('triggerChange', stub); + activator.on('triggerchange', stub); const object = {a: {b: {c: "def"}, g: "hi"}, jk: ["l"]}; diff --git a/web/src/test/manual/web/osk/scratchspace/commands.js b/web/src/test/manual/web/osk/scratchspace/commands.js index 5f9e538ce8..b5123149a5 100644 --- a/web/src/test/manual/web/osk/scratchspace/commands.js +++ b/web/src/test/manual/web/osk/scratchspace/commands.js @@ -97,7 +97,7 @@ function setOSK(mode) { keyboard = setKeyboard('us'); } currentOSK.activeKeyboard = keyboard; - currentOSK.on('keyEvent', (event) => { + currentOSK.on('keyevent', (event) => { let eventText = JSON.stringify(event, (key, value) => { switch(key) { case 'srcKeyboard': diff --git a/web/src/tools/testing/recorder/scribe.ts b/web/src/tools/testing/recorder/scribe.ts index ab6feccf2d..00360b805f 100644 --- a/web/src/tools/testing/recorder/scribe.ts +++ b/web/src/tools/testing/recorder/scribe.ts @@ -182,7 +182,7 @@ export class Scribe extends EventEmitter { initHooks(recordingElement: HTMLElement) { let recorderScribe = this; - keyman.hardKeyboard.on('keyEvent', (e) => { + keyman.hardKeyboard.on('keyevent', (e) => { let in_output = outputTargetForElement(recordingElement); if(!in_output || keyman.contextManager.activeTarget != in_output) { return; @@ -198,7 +198,7 @@ export class Scribe extends EventEmitter { }, 1); }); - keyman.osk.on('keyEvent', (e) => { + keyman.osk.on('keyevent', (e) => { let in_output = outputTargetForElement(recordingElement); if(!in_output || keyman.contextManager.activeTarget != in_output) { return;