chore(web): modularization of blur, focus, and text-change events

This commit is contained in:
Joshua A. Horton 2023-04-24 12:47:08 +07:00
parent 629b8c8d64
commit e9fa472f69
8 changed files with 76 additions and 57 deletions

View file

@ -40,7 +40,7 @@ export class BrowserConfiguration extends EngineConfiguration {
const ruleTransform = ruleBehavior.transcription.transform;
if(!isEmptyTransform(ruleTransform)) {
if(outputTarget instanceof DOMOutputTarget) {
dom.DOMEventHandlers.states.changed = true;
outputTarget.changed = true;
}
}
}

View file

@ -2,10 +2,12 @@ import { type Keyboard, Mock } from '@keymanapp/keyboard-processor';
import { type KeyboardStub } from 'keyman/engine/package-cache';
import { CookieSerializer } from 'keyman/engine/dom-utils';
import { PageContextAttachment } from 'keyman/engine/attachment';
import { LegacyEventEmitter } from 'keyman/engine/events';
import { DesignIFrame, OutputTarget, nestedInstanceOf } from 'keyman/engine/element-wrappers';
import {
ContextManagerBase,
type KeyboardInterface
type KeyboardInterface,
LegacyAPIEvents
} from 'keyman/engine/main';
import { BrowserConfiguration } from './configuration.js';
import { FocusAssistant } from './context/focusAssistant.js';
@ -102,6 +104,16 @@ export default class ContextManager extends ContextManagerBase<BrowserConfigurat
private globalKeyboard: {keyboard: Keyboard, metadata: KeyboardStub};
private _eventsObj: () => LegacyEventEmitter<LegacyAPIEvents>;
constructor(engineConfig: BrowserConfiguration, eventsClosure: () => LegacyEventEmitter<LegacyAPIEvents>) {
super(engineConfig);
}
get apiEvents(): LegacyEventEmitter<LegacyAPIEvents> {
return this._eventsObj();
}
initialize(): void {
this.on('keyboardasyncload', (stub, completion) => {
// TODO: app/browser - display the loader UI if configured?
@ -416,13 +428,19 @@ export default class ContextManager extends ContextManagerBase<BrowserConfigurat
// Ltarg=Ltarg.contentWindow.document.body; // And we only care about Ltarg b/c of finding the OutputTarget.
// }
// Step 2: Make it active.
// Save it for the event in step 3... but now, before we mutate the field's value!
const previousTarget = this.lastActiveTarget;
// Step 2: Make the newly-focused control the active control, and thus the active context.
this.setActiveTarget(target);
// Step 3: related events
// //Execute external (UI) code needed on focus if required
// this.doControlFocused(LfocusTarg, this.keyman.domManager.lastActiveElement);
this.apiEvents.callEvent('controlfocused', {
target: target.getElement(),
activeControl: previousTarget.getElement()
});
return true;
}
@ -450,8 +468,8 @@ export default class ContextManager extends ContextManagerBase<BrowserConfigurat
}
// Step 1: determine the corresponding OutputTarget instance.
let Ltarg = eventOutputTarget(e);
if (Ltarg == null) {
let target = eventOutputTarget(e);
if (target == null) {
return true;
}
@ -465,8 +483,9 @@ export default class ContextManager extends ContextManagerBase<BrowserConfigurat
// Step 3: Now that we've handled all prior-element maintenance, update the active and 'last-active element'.
// (The "context target" state fields)
const previousTarget = this.activeTarget;
this.currentTarget = null; // I3363 (Build 301)
this.mostRecentTarget = Ltarg;
this.mostRecentTarget = target;
// Step 4: any and all related events
/* If the KeymanWeb UI is active as a user changes controls, all UI-based effects
@ -478,13 +497,30 @@ export default class ContextManager extends ContextManagerBase<BrowserConfigurat
let activeKeyboard = this.activeKeyboard;
const maintainingFocus = this.focusAssistant.maintainingFocus;
if(!maintainingFocus && activeKeyboard) {
activeKeyboard.keyboard.notify(0, Ltarg, 0); // I2187
activeKeyboard.keyboard.notify(0, target, 0); // I2187
}
if(previousTarget && !this.activeTarget) {
this.emit('targetchange', null);
}
// TODO: these related events.
// this.doControlBlurred(Ltarg, e, maintainingFocus);
// this.doChangeEvent(Ltarg);
this.apiEvents.callEvent('controlblurred', {
target: target.getElement(),
event: e,
isActivating: maintainingFocus
});
// Is not an "API event"; it models a native browser event instead.
this.doChangeEvent(target);
this.resetContext();
return true;
}
doChangeEvent(target: OutputTarget<any>) {
if(target.changed) {
let event = new Event('change', {"bubbles": true, "cancelable": false});
target.getElement().dispatchEvent(event);
}
target.changed = false;
}
}

View file

@ -17,8 +17,10 @@ export class KeymanEngine extends KeymanEngineBase<ContextManager, KeyEventKeybo
this.contextManager.restoreLastActiveTarget();
}
constructor(worker: Worker, config: BrowserConfiguration) {
super(worker, config, new ContextManager(config));
constructor(worker: Worker, sourceUri: string) {
const config = new BrowserConfiguration(sourceUri); // currently set to perform device auto-detect.
super(worker, config, new ContextManager(config, () => this.legacyAPIEvents));
// Scrolls the document-body to ensure that a focused element remains visible after the OSK appears.
this.contextManager.on('targetchange', (target: OutputTarget<any>) => {

View file

@ -5,6 +5,12 @@ export default abstract class OutputTarget<EventMap extends EventEmitter.ValidEv
// JS/TS can't do true multiple inheritance, so we maintain class events on a readonly field.
public readonly events: EventEmitter<EventMap, this> = new EventEmitter<EventMap, this>();
/**
* A field that may be used to track whether or not the represented context has changed over an
* arbitrary period of time.
*/
public changed = false;
/**
* Returns the underlying element / document modeled by the wrapper.
*/

View file

@ -3,4 +3,5 @@ export { ContextManagerBase, ContextManagerConfiguration } from './contextManage
export { default as HardKeyboard } from './hardKeyboard.js';
export { default as KeyboardInterface } from './keyboardInterface.js';
export { default as KeymanEngine } from './keymanEngine.js';
export { LegacyAPIEvents } from './legacyAPIEvents.js';
export { VariableStoreCookieSerializer } from './variableStoreCookieSerializer.js';

View file

@ -24,7 +24,7 @@ export default class KeymanEngine<
readonly keyboardRequisitioner: KeyboardRequisitioner;
readonly modelCache: ModelCache;
private legacyAPIEvents = new LegacyEventEmitter<LegacyAPIEvents>();
protected legacyAPIEvents = new LegacyEventEmitter<LegacyAPIEvents>();
private _hardKeyboard: HardKeyboard;
private _osk: OSKView;

View file

@ -26,6 +26,23 @@ export interface LegacyAPIEvents extends LegacyEventMap {
languageCode: string
}) => boolean;
'controlfocused': (p: {
/**
* The element gaining focus
*/
target: HTMLElement,
/**
* The previous element to have focus
*/
activeControl: HTMLElement
}) => boolean;
'controlblurred': (p: {
target: HTMLElement,
event: FocusEvent,
isActivating: boolean
}) => boolean;
// TODO: more of the documented API events. Note that any remaining events not seen here
// yet go unused within the mobile apps.
}

View file

@ -54,49 +54,6 @@ namespace com.keyman.dom {
// Universal DOM event handlers (both desktop + touch)
/**
* Function doControlFocused
* Scope Private
* @param {Object} _target element gaining focus
* @param {Object} _activeControl currently active control
* @return {boolean}
* Description Execute external (UI) code needed on focus
*/
doControlFocused(_target: HTMLElement, _activeControl: HTMLElement): boolean {
var p={};
p['target']=_target;
p['activeControl']=_activeControl;
return this.keyman.util.callEvent('kmw.controlfocused',p);
}
/**
* Function doControlBlurred
* Scope Private
* @param {Object} _target element losing focus
* @param {Event} _event event object
* @param {(boolean|number)} _isActivating activation state
* @return {boolean}
* Description Execute external (UI) code needed on blur
*/
doControlBlurred(_target: HTMLElement, _event: Event, _isActivating: boolean|number): boolean {
var p={};
p['target']=_target;
p['event']=_event;
p['isActivating']=_isActivating;
return this.keyman.util.callEvent('kmw.controlblurred',p);
}
doChangeEvent(_target: HTMLElement) {
if(DOMEventHandlers.states.changed) {
let event = new Event('change', {"bubbles": true, "cancelable": false});
_target.dispatchEvent(event);
}
DOMEventHandlers.states.changed = false;
}
/**
* Function _KeyDown
* Scope Private