Merge pull request #7395 from keymanapp/fix/web/touch-inputmode-preservation

fix(web): better element inputmode management 📴
This commit is contained in:
Joshua Horton 2022-10-17 10:19:12 +07:00 committed by GitHub
commit c690ed727a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 83 additions and 15 deletions

View file

@ -55,6 +55,11 @@ namespace com.keyman.dom {
*/
enablementObserver: MutationObserver;
/**
* Tracks changes in inputmode state.
*/
inputModeObserver: MutationObserver;
/**
* Tracks a list of event-listening elements.
*
@ -99,6 +104,9 @@ namespace com.keyman.dom {
if(this.attachmentObserver) {
this.attachmentObserver.disconnect();
}
if(this.inputModeObserver) {
this.inputModeObserver.disconnect();
}
for(let input of this.inputList) {
this.disableInputElement(input);
@ -233,11 +241,10 @@ namespace com.keyman.dom {
if(!this.isAttached(Pelem)) {
this.setupElementAttachment(Pelem);
Pelem.inputMode = 'none';
}
// Set font for base element
this.enableInputElement(Pelem, false);
this.enableInputElement(Pelem);
// Superimpose custom input fields for each input or textarea, unless readonly or disabled
@ -269,7 +276,15 @@ namespace com.keyman.dom {
*/
disableTouchElement(Pelem: HTMLElement) {
// Do not check for the element being officially disabled - it's also used for detachment.
Pelem.inputMode = 'text';
if(this.isAttached(Pelem)) {
const intendedInputMode = Pelem._kmwAttachment.inputMode;
this.disableInputModeObserver();
Pelem.inputMode = intendedInputMode;
this.enableInputModeObserver();
}
this.setupNonKMWTouchElement(Pelem);
}
/**
@ -308,23 +323,24 @@ namespace com.keyman.dom {
* enableTouchElement as it must first establish the simulated touch element to serve as the alias "input element" here.
* Note that the 'kmw-disabled' property is managed by the MutationObserver and by the surface API calls.
*/
enableInputElement(Pelem: HTMLElement, isAlias?: boolean) {
var baseElement = isAlias ? Pelem['base'] : Pelem;
if(!this.isKMWDisabled(baseElement)) {
enableInputElement(Pelem: HTMLElement) {
if(!this.isKMWDisabled(Pelem)) {
if(Pelem instanceof Pelem.ownerDocument.defaultView.HTMLIFrameElement) {
this._AttachToIframe(Pelem);
} else {
if(!isAlias) {
this.setupElementAttachment(Pelem);
}
this.setupElementAttachment(Pelem);
baseElement.className = baseElement.className ? baseElement.className + ' keymanweb-font' : 'keymanweb-font';
Pelem._kmwAttachment.inputMode = Pelem.inputMode ?? 'text';
this.disableInputModeObserver();
Pelem.inputMode = 'none';
this.enableInputModeObserver();
Pelem.classList.add('keymanweb-font');
this.inputList.push(Pelem);
this.keyman.util.attachDOMEvent(baseElement,'focus', this.getHandlers(Pelem)._ControlFocus);
this.keyman.util.attachDOMEvent(baseElement,'blur', this.getHandlers(Pelem)._ControlBlur);
this.keyman.util.attachDOMEvent(baseElement,'click', this.getHandlers(Pelem)._Click);
this.keyman.util.attachDOMEvent(Pelem,'focus', this.getHandlers(Pelem)._ControlFocus);
this.keyman.util.attachDOMEvent(Pelem,'blur', this.getHandlers(Pelem)._ControlBlur);
this.keyman.util.attachDOMEvent(Pelem,'click', this.getHandlers(Pelem)._Click);
// These need to be on the actual input element, as otherwise the keyboard will disappear on touch.
Pelem.onkeypress = this.getHandlers(Pelem)._KeyPress;
@ -912,6 +928,37 @@ namespace com.keyman.dom {
}
}.bind(this);
/**
* The core method for a MutationObserver that checks for changes to the `.inputMode` property
* of controls that KMW is attached to in touch mode.
*
* In touch mode, KMW requires that their `.inputMode` property be set to 'none' in order
* to hide the device's default OSK. That said, we should still aim to honor the setting
* and restore it if and when detachment occurs. Should we ever support intents, we'll want
* to utilize the incoming value for use with that feature too.
*/
_InputModeObserverCore = function(this: DOMManager, mutations: MutationRecord[]) {
const keyman = com.keyman.singleton;
// Prevent infinite recursion from any changes / updates made within the observation handler.
this.disableInputModeObserver();
try {
for(const mutation of mutations) {
const target = mutation.target as HTMLElement;
if(!this.isAttached(target)) {
continue;
}
target._kmwAttachment.inputMode = target.inputMode;
if(keyman.util.device.touchable) {
target.inputMode = 'none';
}
}
} finally {
this.enableInputModeObserver();
}
}.bind(this);
/**
* Function _MutationAdditionObserved
* Scope Private
@ -1616,6 +1663,12 @@ namespace com.keyman.dom {
observationConfig = { subtree: true, attributes: true, attributeOldValue: true, attributeFilter: ['class', 'readonly']};
this.enablementObserver = new MutationObserver(this._EnablementMutationObserverCore);
this.enablementObserver.observe(observationTarget, observationConfig);
/**
* Setup of handlers for dynamic detection of change in inputMode state.
*/
this.inputModeObserver = new MutationObserver(this._InputModeObserverCore);
this.enableInputModeObserver();
} else {
console.warn("Your browser is outdated and does not support MutationObservers, a web feature " +
"needed by KeymanWeb to support dynamically-added elements.");
@ -1635,6 +1688,16 @@ namespace com.keyman.dom {
return Promise.resolve();
}.bind(this);
enableInputModeObserver() {
const observationTarget = document.querySelector('body');
const observationConfig = { subtree: true, attributes: true, attributeFilter: ['inputmode'] };
this.inputModeObserver?.observe(observationTarget, observationConfig);
}
disableInputModeObserver() {
this.inputModeObserver?.disconnect();
}
/**
* Initialize the desktop user interface as soon as it is ready
*/

View file

@ -17,12 +17,17 @@ namespace com.keyman {
/**
* Tracks if the control has an aliased control for touch functionality.
*
*
* Future note - could be changed to track the DOMEventHandler instance used by this control;
* this may be useful for an eventual hybrid touch/non-touch implementation.
*/
touchEnabled: boolean;
/**
* Tracks the inputmode originally set by the webpage.
*/
inputMode?: string;
constructor(eleInterface: dom.targets.OutputTarget, kbd: string, touch?: boolean) {
this.interface = eleInterface;
this.keyboard = kbd;