From 5cbb3cfeeb465994dcc105be667e5f734f823479 Mon Sep 17 00:00:00 2001 From: jahorton Date: Fri, 27 Aug 2021 12:46:37 +0700 Subject: [PATCH 01/19] refactor(web): OSK view activeTarget, lastActiveTarget no longer tied to DOMManager --- web/source/dom/domManager.ts | 16 +++++++++++++--- web/source/osk/oskView.ts | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/web/source/dom/domManager.ts b/web/source/dom/domManager.ts index 48c234f9f2..e5e00f8de4 100644 --- a/web/source/dom/domManager.ts +++ b/web/source/dom/domManager.ts @@ -1241,8 +1241,14 @@ namespace com.keyman.dom { set lastActiveElement(Pelem: HTMLElement) { DOMEventHandlers.states._lastActiveElement = Pelem; - if(this.lastActiveElement == null && this.activeElement == null) { - this.keyman.osk.hideNow(); // originally from a different one, seemed to serve the same role? + const osk = this.keyman.osk; + if(osk) { + const target = dom.Utils.getOutputTarget(Pelem); + osk.lastActiveTarget = target; + + if(this.lastActiveElement == null && this.activeElement == null) { + this.keyman.osk.hideNow(); // originally from a different one, seemed to serve the same role? + } } } @@ -1258,7 +1264,11 @@ namespace com.keyman.dom { // Hide the OSK when the control is blurred, unless the UI is being temporarily selected const osk = this.keyman.osk; const device = this.keyman.util.device; - if(this.keyman.osk) { + + if(osk) { + const target = dom.Utils.getOutputTarget(Pelem); + osk.activeTarget = target; + if(!Pelem) { if(this.keyman.osk && !isActivating) { this.keyman.osk._Hide(false); diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index 9853efe157..081c4ef078 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -32,6 +32,9 @@ namespace com.keyman.osk { private _boxBaseTouchEventCancel: (e: TouchEvent) => boolean; private keyboard: keyboards.Keyboard; + + private _target: dom.targets.OutputTarget; + private _lastTarget: dom.targets.OutputTarget; /** * The configured width for this OSKManager. May be `undefined` or `null` @@ -159,13 +162,19 @@ namespace com.keyman.osk { } public get activeTarget(): dom.targets.OutputTarget { - const el=com.keyman.singleton.domManager.activeElement; - return dom.Utils.getOutputTarget(el); + return this._target; + } + + public set activeTarget(targ: dom.targets.OutputTarget) { + this._target = targ; } public get lastActiveTarget(): dom.targets.OutputTarget { - const el = com.keyman.singleton.domManager.lastActiveElement; - return dom.Utils.getOutputTarget(el); + return this._lastTarget; + } + + public set lastActiveTarget(targ: dom.targets.OutputTarget) { + this._lastTarget = targ; } public get vkbd(): VisualKeyboard { From fe797fe6575600616c4a3f82d25cc4696ff3dda3 Mon Sep 17 00:00:00 2001 From: jahorton Date: Mon, 30 Aug 2021 12:27:37 +0700 Subject: [PATCH 02/19] refactor(web): osk now controls own display state --- web/source/dom/domEventHandlers.ts | 4 +- web/source/dom/domManager.ts | 54 ++++++------ web/source/keyboards/kmwkeyboards.ts | 2 +- web/source/osk/anchoredOskView.ts | 7 +- web/source/osk/floatingOskView.ts | 25 +++--- web/source/osk/oskView.ts | 124 ++++++++++++++++++++++----- 6 files changed, 152 insertions(+), 64 deletions(-) diff --git a/web/source/dom/domEventHandlers.ts b/web/source/dom/domEventHandlers.ts index dd4dcae78a..2429a1b376 100644 --- a/web/source/dom/domEventHandlers.ts +++ b/web/source/dom/domEventHandlers.ts @@ -594,7 +594,7 @@ namespace com.keyman.dom { prevTarget.hideCaret(); } - this.keyman.domManager.activeElement=target; + this.keyman.domManager.activeElement = target; // The issue here is that touching a DIV does not actually set the focus for iOS, even when enabled to accept focus (by setting tabIndex=0) // We must explicitly set the focus in order to remove focus from any non-KMW input target.focus(); //Android native browsers may not like this, but it is needed for Chrome, Safari @@ -717,7 +717,7 @@ namespace com.keyman.dom { if(this.keyman.domManager.activeElement && Utils.instanceof(this.keyman.domManager.activeElement, "TouchAliasElement")) { (this.keyman.domManager.activeElement as TouchAliasElement).hideCaret(); } - this.keyman.domManager.activeElement=null; + this.keyman.domManager.activeElement = null; this.keyman.domManager.lastActiveElement = null; this.keyman.osk.hideNow(); }; diff --git a/web/source/dom/domManager.ts b/web/source/dom/domManager.ts index e5e00f8de4..36852e9125 100644 --- a/web/source/dom/domManager.ts +++ b/web/source/dom/domManager.ts @@ -1243,9 +1243,6 @@ namespace com.keyman.dom { const osk = this.keyman.osk; if(osk) { - const target = dom.Utils.getOutputTarget(Pelem); - osk.lastActiveTarget = target; - if(this.lastActiveElement == null && this.activeElement == null) { this.keyman.osk.hideNow(); // originally from a different one, seemed to serve the same role? } @@ -1263,33 +1260,36 @@ namespace com.keyman.dom { // Hide the OSK when the control is blurred, unless the UI is being temporarily selected const osk = this.keyman.osk; - const device = this.keyman.util.device; + // const device = this.keyman.util.device; if(osk) { - const target = dom.Utils.getOutputTarget(Pelem); - osk.activeTarget = target; - - if(!Pelem) { - if(this.keyman.osk && !isActivating) { - this.keyman.osk._Hide(false); - } - } else { - // Force display of OSK for touch input device, or if a CJK keyboard, to ensure visibility of pick list - if(device.touchable) { - osk._Enabled = true; - osk._Show(); - } else { - // Conditionally show the OSK when control receives the focus - if(this.keyman.isCJK()) { - osk._Enabled = true; - } - if(osk._Enabled) { - osk._Show(); - } else { - osk._Hide(false); - } - } + const target = Pelem?._kmwAttachment?.interface || null; + if(osk && (target || !isActivating)) { + // Do not unset the field if the UI is activated. + osk.activeTarget = target; } + + // if(!Pelem) { + // if(osk && !isActivating) { + // osk._Hide(false); + // } + // } else { + // // Force display of OSK for touch input device, or if a CJK keyboard, to ensure visibility of pick list + // if(device.touchable) { + // osk.displayIfActive = true; + // osk._Show(); + // } else { + // // Conditionally show the OSK when control receives the focus + // if(this.keyman.isCJK()) { + // osk.displayIfActive = true; + // } + // if(osk.displayIfActive) { + // osk._Show(); + // } else { + // osk._Hide(false); + // } + // } + // } } } diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 72fff7c716..01e1fe5ea6 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -614,7 +614,7 @@ namespace com.keyman.keyboards { && ((this.keyboardStubs[Ln]['KLC'] == PLgCode) || (PLgCode == '---'))) { // Force OSK display for CJK keyboards (keyboards using a pick list) if(this.isCJK(this.keyboardStubs[Ln]) || util.device.touchable) { - osk._Enabled = true; + osk.displayIfActive = true; } // Create a script to load from the server - when it finishes loading, it will register itself, diff --git a/web/source/osk/anchoredOskView.ts b/web/source/osk/anchoredOskView.ts index 73b0d71b02..51c77b60b0 100644 --- a/web/source/osk/anchoredOskView.ts +++ b/web/source/osk/anchoredOskView.ts @@ -57,7 +57,7 @@ namespace com.keyman.osk { this._Box.onmouseover = this._VKbdMouseOver; this._Box.onmouseout = this._VKbdMouseOut; - if(this._Enabled) { + if(this.displayIfActive) { this._Show(); } } @@ -225,7 +225,6 @@ namespace com.keyman.osk { Ls.border='none'; Ls.borderTop='1px solid gray'; - this._Enabled=true; this._Visible=true; // I3363 (Build 301) } } @@ -253,7 +252,7 @@ namespace com.keyman.osk { if(hiddenByUser) { //osk.loadCookie(); // preserve current offset and userlocated state - this._Enabled = ((keymanweb.isCJK() || device.touchable)? true : false); // I3363 (Build 301) + this.displayIfActive = ((keymanweb.isCJK() || device.touchable)? true : false); // I3363 (Build 301) } else if(device.formFactor == 'desktop') { //Allow desktop OSK to remain visible on blur if body class set if(document.body.className.indexOf('osk-always-visible') >= 0) { @@ -303,7 +302,7 @@ namespace com.keyman.osk { // If hidden by the UI, be sure to restore the focus if(hiddenByUser) { - this.lastActiveTarget.focus(); + this.activeTarget?.focus(); } } diff --git a/web/source/osk/floatingOskView.ts b/web/source/osk/floatingOskView.ts index 6a250cb17a..4cdbe697d0 100644 --- a/web/source/osk/floatingOskView.ts +++ b/web/source/osk/floatingOskView.ts @@ -74,7 +74,9 @@ namespace com.keyman.osk { this._Box.appendChild(this.footerView.element); } - if(this._Enabled) { + this.loadCookie(); + + if(this.displayIfActive) { this._Show(); } } @@ -89,7 +91,7 @@ namespace com.keyman.osk { ['restorePosition']: (keepDefaultPosition?: boolean) => void = function(this: FloatingOSKView, keepDefaultPosition?: boolean) { let isVisible = this._Visible; if(isVisible) { - this.lastActiveTarget.focus(); // I2036 - OSK does not unpin to correct location + this.activeTarget?.focus(); // I2036 - OSK does not unpin to correct location } this.loadCookie(); @@ -115,7 +117,7 @@ namespace com.keyman.osk { * Description Test if KMW OSK is enabled */ ['isEnabled'](): boolean { - return this._Enabled; + return this.displayIfActive; } /** @@ -158,7 +160,7 @@ namespace com.keyman.osk { var c = util.loadCookie('KeymanWeb_OnScreenKeyboard'); var p = this.getPos(); - c['visible'] = this._Enabled ? 1 : 0; + c['visible'] = this.displayIfActive ? 1 : 0; c['userSet'] = this.userPositioned ? 1 : 0; c['left'] = p.left; c['top'] = p.top; @@ -181,7 +183,11 @@ namespace com.keyman.osk { var c = util.loadCookie('KeymanWeb_OnScreenKeyboard'); - this._Enabled = util.toNumber(c['visible'], 1) == 1; + const displayIfActive = util.toNumber(c['visible'], 1) == 1; + if(this.displayIfActive != displayIfActive) { + // Setter triggers functions that can go recursive without the conditional check. + this.displayIfActive = displayIfActive; + } this.userPositioned = util.toNumber(c['userSet'], 0) == 1; this.x = util.toNumber(c['left'],-1); this.y = util.toNumber(c['top'],-1); @@ -448,7 +454,7 @@ namespace com.keyman.osk { } // Never display the OSK for desktop browsers unless KMW element is focused, and a keyboard selected - if(this.activeKeyboard == null || !this._Enabled) { + if(this.activeKeyboard == null || !this.displayIfActive) { return; } @@ -458,7 +464,6 @@ namespace com.keyman.osk { Ls.position='absolute'; Ls.display='block'; //Ls.visibility='visible'; Ls.left='0px'; - this.loadCookie(); if(Px >= 0) { //probably never happens, legacy support only Ls.left = Px + 'px'; Ls.top = Py + 'px'; } else { @@ -481,7 +486,7 @@ namespace com.keyman.osk { } } } - this._Enabled=true; + this._Visible=true; if(this.vkbd) { @@ -518,7 +523,7 @@ namespace com.keyman.osk { if(hiddenByUser) { //osk.loadCookie(); // preserve current offset and userlocated state - this._Enabled = ((keymanweb.isCJK() || device.touchable)? true : false); // I3363 (Build 301) + this.displayIfActive = ((keymanweb.isCJK() || device.touchable)? true : false); // I3363 (Build 301) this.saveCookie(); // Save current OSK state, size and position (desktop only) } else if(device.formFactor == 'desktop') { //Allow desktop OSK to remain visible on blur if body class set @@ -569,7 +574,7 @@ namespace com.keyman.osk { // If hidden by the UI, be sure to restore the focus if(hiddenByUser) { - this.lastActiveTarget.focus(); + this.activeTarget?.focus(); } } diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index 081c4ef078..669bc8b956 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -17,6 +17,12 @@ namespace com.keyman.osk { 'nomove'?: boolean }; + export enum ActivationMode { + disabled = "disabled", + permanent = "permanent", + conditional = "conditional" + } + export abstract class OSKView { _Box: HTMLDivElement; @@ -25,7 +31,8 @@ namespace com.keyman.osk { keyboardView: KeyboardView; // Which implements OSKViewComponent footerView: OSKViewComponent; - protected device: com.keyman.utils.DeviceSpec; + protected readonly device: com.keyman.utils.DeviceSpec; + protected readonly hostDevice: com.keyman.utils.DeviceSpec; private _boxBaseMouseDown: (e: MouseEvent) => boolean; private _boxBaseTouchStart: (e: TouchEvent) => boolean; @@ -34,7 +41,6 @@ namespace com.keyman.osk { private keyboard: keyboards.Keyboard; private _target: dom.targets.OutputTarget; - private _lastTarget: dom.targets.OutputTarget; /** * The configured width for this OSKManager. May be `undefined` or `null` @@ -68,9 +74,19 @@ namespace com.keyman.osk { private needsLayout: boolean = true; - constructor(deviceSpec: com.keyman.utils.DeviceSpec) { + // + private _activationMode: ActivationMode = ActivationMode.conditional; + private _displayIfActive: boolean = true; + private _currentlyBuffering: boolean = false; + + constructor(deviceSpec: com.keyman.utils.DeviceSpec, hostDevice?: com.keyman.utils.DeviceSpec) { this.device = deviceSpec; + if(!hostDevice) { + hostDevice = deviceSpec; + } + this.hostDevice = hostDevice; + // OSK initialization - create DIV and set default styles this._Box = document.createElement('div'); // Container for OSK (Help DIV, displayed when user clicks Help icon) this._Box.style.userSelect = 'none'; @@ -167,14 +183,75 @@ namespace com.keyman.osk { public set activeTarget(targ: dom.targets.OutputTarget) { this._target = targ; + this.commonCheckAndDisplay(); } - public get lastActiveTarget(): dom.targets.OutputTarget { - return this._lastTarget; + get activationMode(): ActivationMode { + if(!this._activationMode) { + this._activationMode = ActivationMode.conditional; + } + + return this._activationMode; } - public set lastActiveTarget(targ: dom.targets.OutputTarget) { - this._lastTarget = targ; + set activationMode(mode: ActivationMode) { + this._activationMode = mode; + this.commonCheckAndDisplay(); + } + + get activationConditionsMet(): boolean { + switch(this.activationMode) { + case 'permanent': + return true; + case 'disabled': + return false; + case 'conditional': + return !!this.activeTarget; + default: + console.error("Unexpected activation mode set for the OSK."); + return false; + } + } + + get displayIfActive(): boolean { + return this._displayIfActive; + } + + set displayIfActive(flag: boolean) { + // if is touch device or is CJK keyboard, this.displayIfActive must remain true. + if(this.keyboard?.isCJK && !flag) { + console.warn("Cannot hide display of OSK for CJK keyboards."); + flag = true; + } else if(this.hostDevice.touchable && !flag) { + console.warn("Cannot hide display of OSK when hosted on touch-based devices."); + flag = true; + } + + this._displayIfActive = flag; + this.commonCheckAndDisplay(); + } + + protected get currentlyBuffering(): boolean { + return this._currentlyBuffering; + } + + protected set currentlyBuffering(flag: boolean) { + this._currentlyBuffering = flag; + this.commonCheckAndDisplay(); + } + + private commonCheckAndDisplay() { + // If other code has noted that display-related changes are currently accumulating, + // bypass display logic. + if(this._currentlyBuffering) { + return; + } + + if(this.activationConditionsMet && this.displayIfActive) { + this._Show(); + } else { + this._Hide(false); + } } public get vkbd(): VisualKeyboard { @@ -280,6 +357,10 @@ namespace com.keyman.osk { public set activeKeyboard(keyboard: keyboards.Keyboard) { this.keyboard = keyboard; this.loadActiveKeyboard(); + + if(this.keyboard?.isCJK) { + this.displayIfActive = true; + } } /* private */ computeFrameHeight(): number { @@ -675,7 +756,6 @@ namespace com.keyman.osk { // They're not very well defined or encapsulated; there's definitely room for more // "polish" here. _Visible: boolean = false; - _Enabled: boolean = true; /** * Function enabled @@ -684,7 +764,7 @@ namespace com.keyman.osk { * Description Test if KMW OSK is enabled */ ['isEnabled'](): boolean { - return this._Enabled; + return this.displayIfActive; } /** @@ -704,7 +784,7 @@ namespace com.keyman.osk { * Description Prevent display of OSK window on focus */ ['hide']() { - this._Enabled = false; + this.displayIfActive = false; this._Hide(true); } @@ -716,18 +796,22 @@ namespace com.keyman.osk { */ ['show'](bShow: boolean) { if(arguments.length > 0) { - this._Enabled=bShow; - if(bShow) { - this._Show(); - } else { - this._Hide(true); - } + this.displayIfActive=bShow; + // if(bShow) { + // this._Show(); + // } else { + // this._Hide(true); + // } } else { - if(this._Visible) { - this._Hide(true); - } else { - this._Show(); + if(this.activationConditionsMet) { + this.displayIfActive = !this.displayIfActive; } + + // // if(this._Visible) { + // this._Hide(true); + // // } else { + // this._Show(); + // // } } } From ffef7c2bd4fbb8b4e938b586e650223603a2c07c Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 31 Aug 2021 08:25:16 +0700 Subject: [PATCH 03/19] chore(web): mild cleanup --- web/source/dom/domManager.ts | 22 ---------------------- web/source/osk/anchoredOskView.ts | 8 ++++++-- web/source/osk/oskView.ts | 28 +++++++++++----------------- 3 files changed, 17 insertions(+), 41 deletions(-) diff --git a/web/source/dom/domManager.ts b/web/source/dom/domManager.ts index 36852e9125..189d9941a0 100644 --- a/web/source/dom/domManager.ts +++ b/web/source/dom/domManager.ts @@ -1268,28 +1268,6 @@ namespace com.keyman.dom { // Do not unset the field if the UI is activated. osk.activeTarget = target; } - - // if(!Pelem) { - // if(osk && !isActivating) { - // osk._Hide(false); - // } - // } else { - // // Force display of OSK for touch input device, or if a CJK keyboard, to ensure visibility of pick list - // if(device.touchable) { - // osk.displayIfActive = true; - // osk._Show(); - // } else { - // // Conditionally show the OSK when control receives the focus - // if(this.keyman.isCJK()) { - // osk.displayIfActive = true; - // } - // if(osk.displayIfActive) { - // osk._Show(); - // } else { - // osk._Hide(false); - // } - // } - // } } } diff --git a/web/source/osk/anchoredOskView.ts b/web/source/osk/anchoredOskView.ts index 51c77b60b0..8ab123e086 100644 --- a/web/source/osk/anchoredOskView.ts +++ b/web/source/osk/anchoredOskView.ts @@ -32,6 +32,11 @@ namespace com.keyman.osk { super(modeledDevice); document.body.appendChild(this._Box); + + let keymanweb = com.keyman.singleton; + if(keymanweb.isEmbedded) { + this.activationMode == ActivationMode.manual; + } } /** @@ -241,8 +246,7 @@ namespace com.keyman.osk { // Once picklist functionality is separated out, this will no longer be needed. // Logic is: execute always if hidden on lost focus, but if requested by user, only if not CJK - if(keymanweb.isEmbedded) { - // We never hide the keyboard in embedded mode + if(this.activationMode != 'conditional' && this.displayIfActive) { return; } diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index 669bc8b956..90ef96a03c 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -18,8 +18,8 @@ namespace com.keyman.osk { }; export enum ActivationMode { - disabled = "disabled", - permanent = "permanent", + static = "static", // For use by documentation keyboards, eventually. + manual = "manual", conditional = "conditional" } @@ -77,6 +77,7 @@ namespace com.keyman.osk { // private _activationMode: ActivationMode = ActivationMode.conditional; private _displayIfActive: boolean = true; + private _manualShouldDisplay: boolean = true; private _currentlyBuffering: boolean = false; constructor(deviceSpec: com.keyman.utils.DeviceSpec, hostDevice?: com.keyman.utils.DeviceSpec) { @@ -201,10 +202,10 @@ namespace com.keyman.osk { get activationConditionsMet(): boolean { switch(this.activationMode) { - case 'permanent': + case 'manual': + return true; + case 'static': return true; - case 'disabled': - return false; case 'conditional': return !!this.activeTarget; default: @@ -225,6 +226,10 @@ namespace com.keyman.osk { } else if(this.hostDevice.touchable && !flag) { console.warn("Cannot hide display of OSK when hosted on touch-based devices."); flag = true; + } else if(this.activationMode == 'static') { + // Silently fail; it's a documentation keyboard. + // This is the primary difference between 'manual' and 'static'. + flag = true; } this._displayIfActive = flag; @@ -796,22 +801,11 @@ namespace com.keyman.osk { */ ['show'](bShow: boolean) { if(arguments.length > 0) { - this.displayIfActive=bShow; - // if(bShow) { - // this._Show(); - // } else { - // this._Hide(true); - // } + this.displayIfActive = bShow; } else { if(this.activationConditionsMet) { this.displayIfActive = !this.displayIfActive; } - - // // if(this._Visible) { - // this._Hide(true); - // // } else { - // this._Show(); - // // } } } From 09790c11342fa0fc112485e288461269c4cdb650 Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 31 Aug 2021 11:52:58 +0700 Subject: [PATCH 04/19] refactor(web): separates hide() functionality into clearer components --- web/source/osk/anchoredOskView.ts | 69 +---------- web/source/osk/floatingOskView.ts | 86 +------------ web/source/osk/oskView.ts | 199 ++++++++++++++++++++++++------ 3 files changed, 169 insertions(+), 185 deletions(-) diff --git a/web/source/osk/anchoredOskView.ts b/web/source/osk/anchoredOskView.ts index 8ab123e086..40afd5d160 100644 --- a/web/source/osk/anchoredOskView.ts +++ b/web/source/osk/anchoredOskView.ts @@ -240,74 +240,7 @@ namespace com.keyman.osk { * @param {boolean} hiddenByUser Distinguish between hiding on loss of focus and explicit hiding by user */ _Hide(hiddenByUser: boolean) { - let keymanweb = com.keyman.singleton; - let device = keymanweb.util.device; - // The test for CJK languages is necessary to prevent a picklist (displayed in the OSK) from being hidden by the user - // Once picklist functionality is separated out, this will no longer be needed. - // Logic is: execute always if hidden on lost focus, but if requested by user, only if not CJK - - if(this.activationMode != 'conditional' && this.displayIfActive) { - return; - } - - // Save current size if visible - const priorDisplayStyle = this._Box.style.display; - this.makeHidden(hiddenByUser); - - if(hiddenByUser) { - //osk.loadCookie(); // preserve current offset and userlocated state - this.displayIfActive = ((keymanweb.isCJK() || device.touchable)? true : false); // I3363 (Build 301) - } else if(device.formFactor == 'desktop') { - //Allow desktop OSK to remain visible on blur if body class set - if(document.body.className.indexOf('osk-always-visible') >= 0) { - return; - } - } - - this._Visible = false; - if(this._Box && device.touchable && this._Box.offsetHeight > 0) { // I3363 (Build 301) - var os=this._Box.style; - // Prevent insta-hide behavior; we want an animated fadeout here. - os.display = priorDisplayStyle; - - //Firefox doesn't transition opacity if start delay is explicitly set to 0! - if(typeof(os.MozBoxSizing) == 'string') { - os.transition='opacity 0.8s linear'; - } else { - os.transition=os.msTransition=os.WebkitTransition='opacity 0.5s linear 0'; - } - - // Cannot hide the OSK smoothly using a transitioned drop, since for - // position:fixed elements transitioning is incompatible with translate3d(), - // and also does not work with top, bottom or height styles. - // Opacity can be transitioned and is probably the simplest alternative. - // We must condition on osk._Visible in case focus has since been moved to another - // input (in which case osk._Visible will be non-zero) - window.setTimeout(function(this: AnchoredOSKView) { - var os=this._Box.style; - if(this._Visible) { - // Leave opacity alone and clear transition if another element activated - os.transition=os.msTransition=os.MozTransition=os.WebkitTransition=''; - } else { - // Set opacity to zero, should decrease smoothly - os.opacity='0'; - - // Actually hide the OSK at the end of the transition - this._Box.addEventListener('transitionend', this.hideNow, false); - this._Box.addEventListener('webkitTransitionEnd', this.hideNow, false); - } - }.bind(this), 200); // Wait a bit before starting, to allow for moving to another element - } - - // Allow UI to execute code when hiding the OSK - var p={}; - p['HiddenByUser']=hiddenByUser; - this.doHide(p); - - // If hidden by the UI, be sure to restore the focus - if(hiddenByUser) { - this.activeTarget?.focus(); - } + this.startHide(hiddenByUser); } /** diff --git a/web/source/osk/floatingOskView.ts b/web/source/osk/floatingOskView.ts index 4cdbe697d0..9e04b66b0a 100644 --- a/web/source/osk/floatingOskView.ts +++ b/web/source/osk/floatingOskView.ts @@ -511,93 +511,15 @@ namespace com.keyman.osk { * @param {boolean} hiddenByUser Distinguish between hiding on loss of focus and explicit hiding by user */ _Hide(hiddenByUser: boolean) { - let keymanweb = com.keyman.singleton; - let device = keymanweb.util.device; - // The test for CJK languages is necessary to prevent a picklist (displayed in the OSK) from being hidden by the user - // Once picklist functionality is separated out, this will no longer be needed. - // Logic is: execute always if hidden on lost focus, but if requested by user, only if not CJK + this.startHide(hiddenByUser); + } - // Save current size if visible - const priorDisplayStyle = this._Box.style.display; - this.makeHidden(hiddenByUser); + public startHide(hiddenByUser: boolean) { + super.startHide(hiddenByUser); if(hiddenByUser) { - //osk.loadCookie(); // preserve current offset and userlocated state - this.displayIfActive = ((keymanweb.isCJK() || device.touchable)? true : false); // I3363 (Build 301) this.saveCookie(); // Save current OSK state, size and position (desktop only) - } else if(device.formFactor == 'desktop') { - //Allow desktop OSK to remain visible on blur if body class set - if(document.body.className.indexOf('osk-always-visible') >= 0) { - return; - } } - - this._Visible = false; - if(this._Box && device.touchable && this._Box.offsetHeight > 0) { // I3363 (Build 301) - var os=this._Box.style; - // Prevent insta-hide behavior; we want an animated fadeout here. - os.display = priorDisplayStyle; - - //Firefox doesn't transition opacity if start delay is explicitly set to 0! - if(typeof(os.MozBoxSizing) == 'string') { - os.transition='opacity 0.8s linear'; - } else { - os.transition=os.msTransition=os.WebkitTransition='opacity 0.5s linear 0'; - } - - // Cannot hide the OSK smoothly using a transitioned drop, since for - // position:fixed elements transitioning is incompatible with translate3d(), - // and also does not work with top, bottom or height styles. - // Opacity can be transitioned and is probably the simplest alternative. - // We must condition on osk._Visible in case focus has since been moved to another - // input (in which case osk._Visible will be non-zero) - window.setTimeout(function(this: AnchoredOSKView) { - var os=this._Box.style; - if(this._Visible) { - // Leave opacity alone and clear transition if another element activated - os.transition=os.msTransition=os.MozTransition=os.WebkitTransition=''; - } else { - // Set opacity to zero, should decrease smoothly - os.opacity='0'; - - // Actually hide the OSK at the end of the transition - this._Box.addEventListener('transitionend', this.hideNow, false); - this._Box.addEventListener('webkitTransitionEnd', this.hideNow, false); - } - }.bind(this), 200); // Wait a bit before starting, to allow for moving to another element - } - - // Allow UI to execute code when hiding the OSK - var p={}; - p['HiddenByUser']=hiddenByUser; - this.doHide(p); - - // If hidden by the UI, be sure to restore the focus - if(hiddenByUser) { - this.activeTarget?.focus(); - } - } - - /** - * Allow UI to respond to OSK being shown (passing position and properties) - * - * @param {Object=} p object with coordinates and userdefined flag - * @return {boolean} - * - */ - doShow(p) { - return com.keyman.singleton.util.callEvent('osk.show',p); - } - - /** - * Allow UI to update respond to OSK being hidden - * - * @param {Object=} p object with coordinates and userdefined flag - * @return {boolean} - * - */ - doHide(p) { - return com.keyman.singleton.util.callEvent('osk.hide',p); } /** diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index 90ef96a03c..d812af7b3b 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -78,7 +78,10 @@ namespace com.keyman.osk { private _activationMode: ActivationMode = ActivationMode.conditional; private _displayIfActive: boolean = true; private _manualShouldDisplay: boolean = true; + private _currentlyBuffering: boolean = false; + private _animatedHideTimeout: number; + private _animatedHideResolver: () => void; constructor(deviceSpec: com.keyman.utils.DeviceSpec, hostDevice?: com.keyman.utils.DeviceSpec) { this.device = deviceSpec; @@ -627,14 +630,151 @@ namespace com.keyman.osk { } } - protected makeHidden(hiddenByUser: boolean) { + public startHide(hiddenByUser: boolean) { + if(!this.mayHide(hiddenByUser)) { + return; + } + + if(hiddenByUser) { + this._displayIfActive = ((this.keyboard.isCJK || this.hostDevice.touchable)? true : false); // I3363 (Build 301) + } + + let promise: Promise = null; + if(this._Box && this.hostDevice.touchable && !(this.keyboardView instanceof EmptyView)) { + promise = this.useHideAnimation(); + } else { + promise = Promise.resolve(true); + } + + const _this = this; + promise.then(function(shouldHide: boolean) { + if(shouldHide) { + _this.finalizeHide(); + } + }); + + // Allow UI to execute code when hiding the OSK + var p={}; + p['HiddenByUser']=hiddenByUser; + this.doHide(p); + + // If hidden by the UI, be sure to restore the focus + if(hiddenByUser) { + this.activeTarget?.focus(); + } + } + + protected finalizeHide() { // Save current size if visible if(this._Box && this._Box.style.display == 'block' && this.keyboardView instanceof VisualKeyboard) { this.keyboardView.refit(); } + if(document.body.className.indexOf('osk-always-visible') >= 0) { + return; + } + if(this._Box) { - this._Box.style.display = 'none'; + let bs=this._Box.style; + bs.display = 'none'; + bs.transition = ''; + bs.opacity = '1'; + this._Visible=false; + } + + if(this.vkbd) { + this.vkbd.onHide(); + } + } + + protected mayHide(hiddenByUser: boolean): boolean { + if(this.activationMode != 'conditional' && this.displayIfActive) { + return false; + } + + if(!hiddenByUser && this.hostDevice.formFactor == 'desktop') { + //Allow desktop OSK to remain visible on blur if body class set + if(document.body.className.indexOf('osk-always-visible') >= 0) { + return false; + } + } + + return true; + } + + protected useHideAnimation(): Promise { + const os = this._Box.style; + const _this = this; + const promise = new Promise(function(resolve) { + os.transition='opacity 0.5s linear 0'; + + _this._animatedHideResolver = resolve; + + // Cannot hide the OSK smoothly using a transitioned drop, since for + // position:fixed elements transitioning is incompatible with translate3d(), + // and also does not work with top, bottom or height styles. + // Opacity can be transitioned and is probably the simplest alternative. + // We must condition on osk._Visible in case focus has since been moved to another + // input (in which case osk._Visible will be non-zero) + _this._animatedHideTimeout = window.setTimeout(function(this: AnchoredOSKView) { + if(_this._animatedHideResolver) { + _this._animatedHideResolver(); + } + + _this._animatedHideTimeout = 0; + _this._animatedHideResolver = null; + }.bind(_this), 200); // Wait a bit before starting, to allow for moving to another element + }); + + return promise.then(function() { + if(_this._Visible) { + // Leave opacity alone and clear transition if another element activated + os.transition=''; + return false; + } else { + // Set opacity to zero, should decrease smoothly. Starts the actual animation. + os.opacity='0'; + + // Listen for the animation's end. + return new Promise(function(resolve) { + _this._animatedHideResolver = resolve; + // Actually hide the OSK at the end of the transition + _this._Box.addEventListener('transitionend', _this._animatedHideResolver, false); + _this._Box.addEventListener('webkitTransitionEnd', _this._animatedHideResolver, false); + }).then(function() { + // Remove the promise's resolver method from future handling. + _this._Box.removeEventListener('transitionend', _this._animatedHideResolver, false); + _this._Box.removeEventListener('webkitTransitionEnd', _this._animatedHideResolver, false); + + // The hide animation is considered complete now. + return true; + }); + } + }); + } + + public hideNow() { + // Two possible uses for _animatedHideResolver: + // - _animatedHideTimeout is set: animation is waiting to start + // - _animatedHideTimeout is null: animation has already started. + + // Was an animated hide waiting to start? Just cancel it. + if(this._animatedHideTimeout) { + window.clearTimeout(this._animatedHideTimeout); + this._animatedHideResolver = null; + this._animatedHideTimeout = 0; + } + + // Was an animated hide already in progress? If so, just trigger it early. + if(this._animatedHideResolver) { + this._animatedHideResolver(); // also triggers finalizeHide(). + this._animatedHideResolver = null; + } else { + const os = this._Box.style; + os.transition=''; + os.opacity='0'; + + this.finalizeHide(); } } @@ -723,39 +863,6 @@ namespace com.keyman.osk { menu.show(); } - /** - * Function hideNow - * Scope Private - * Description Hide the OSK unconditionally and immediately, cancel any pending transition - * - * Usages: - * - during rotations to temporarily hide the OSK during layout ops - * - when controls lose focus (N/A to embedded mode) - * - * Somewhat conflated with the _Show / _Hide methods, which often serve more as an - * "enable" vs "disable" feature on the OSK - though that distinction isn't super-clear. - * - * Definitely needs clearer design & modeling, at the least. - */ - hideNow: () => void = function(this: OSKView) { // I3363 (Build 301) - this._Box.removeEventListener('transitionend', this.hideNow, false); - this._Box.removeEventListener('webkitTransitionEnd', this.hideNow, false); - - if(document.body.className.indexOf('osk-always-visible') >= 0) { - return; - } - - var os=this._Box.style; - os.display='none'; - os.opacity='1'; - this._Visible=false; - os.transition=os.msTransition=os.MozTransition=os.WebkitTransition=''; - - if(this.vkbd) { - this.vkbd.onHide(); - } - }.bind(this); - // OSK state fields // // They're not very well defined or encapsulated; there's definitely room for more @@ -809,6 +916,28 @@ namespace com.keyman.osk { } } + /** + * Allow UI to respond to OSK being shown (passing position and properties) + * + * @param {Object=} p object with coordinates and userdefined flag + * @return {boolean} + * + */ + doShow(p) { + return com.keyman.singleton.util.callEvent('osk.show',p); + } + + /** + * Allow UI to update respond to OSK being hidden + * + * @param {Object=} p object with coordinates and userdefined flag + * @return {boolean} + * + */ + doHide(p) { + return com.keyman.singleton.util.callEvent('osk.hide',p); + } + /** * Function addEventListener * Scope Public From ef90da4659c64f4a918f103ea5429fd0931f30ac Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 31 Aug 2021 12:06:50 +0700 Subject: [PATCH 05/19] chore(web): minor cleanup --- web/source/osk/anchoredOskView.ts | 2 +- web/source/osk/floatingOskView.ts | 3 +-- web/source/osk/oskView.ts | 17 ----------------- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/web/source/osk/anchoredOskView.ts b/web/source/osk/anchoredOskView.ts index 40afd5d160..2408ad8565 100644 --- a/web/source/osk/anchoredOskView.ts +++ b/web/source/osk/anchoredOskView.ts @@ -206,7 +206,7 @@ namespace com.keyman.osk { */ _Show(Px?: number, Py?: number) { // Do not try to display OSK if no active element - if(!this.activeTarget) { + if(!this.activationConditionsMet || !this.displayIfActive) { return; } diff --git a/web/source/osk/floatingOskView.ts b/web/source/osk/floatingOskView.ts index 9e04b66b0a..4a4741c363 100644 --- a/web/source/osk/floatingOskView.ts +++ b/web/source/osk/floatingOskView.ts @@ -448,8 +448,7 @@ namespace com.keyman.osk { * @param {number=} Py y-coordinate for OSK rectangle */ _Show(Px?: number, Py?: number) { - // Do not try to display OSK if no active element - if(!this.activeTarget) { + if(!this.activationConditionsMet) { return; } diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index d812af7b3b..d61820f009 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -77,9 +77,7 @@ namespace com.keyman.osk { // private _activationMode: ActivationMode = ActivationMode.conditional; private _displayIfActive: boolean = true; - private _manualShouldDisplay: boolean = true; - private _currentlyBuffering: boolean = false; private _animatedHideTimeout: number; private _animatedHideResolver: () => void; @@ -239,22 +237,7 @@ namespace com.keyman.osk { this.commonCheckAndDisplay(); } - protected get currentlyBuffering(): boolean { - return this._currentlyBuffering; - } - - protected set currentlyBuffering(flag: boolean) { - this._currentlyBuffering = flag; - this.commonCheckAndDisplay(); - } - private commonCheckAndDisplay() { - // If other code has noted that display-related changes are currently accumulating, - // bypass display logic. - if(this._currentlyBuffering) { - return; - } - if(this.activationConditionsMet && this.displayIfActive) { this._Show(); } else { From 7547c93019bb7fdd7cda0d1e79155656ca7c02c0 Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 31 Aug 2021 13:31:06 +0700 Subject: [PATCH 06/19] refactor(web): _Show modularization --- web/source/osk/anchoredOskView.ts | 38 +++++----------- web/source/osk/floatingOskView.ts | 76 ++++++++++++++----------------- web/source/osk/oskView.ts | 40 ++++++++++++++++ 3 files changed, 87 insertions(+), 67 deletions(-) diff --git a/web/source/osk/anchoredOskView.ts b/web/source/osk/anchoredOskView.ts index 2408ad8565..f8cb92394a 100644 --- a/web/source/osk/anchoredOskView.ts +++ b/web/source/osk/anchoredOskView.ts @@ -198,30 +198,8 @@ namespace com.keyman.osk { return; // I3363 (Build 301) } - /** - * Display KMW OSK at specified position (returns nothing) - * - * @param {number=} Px x-coordinate for OSK rectangle - * @param {number=} Py y-coordinate for OSK rectangle - */ - _Show(Px?: number, Py?: number) { - // Do not try to display OSK if no active element - if(!this.activationConditionsMet || !this.displayIfActive) { - return; - } - - this.makeVisible(); - - var Ls = this._Box.style; - - /* In case it's still '0' from a hide() operation. - * Happens when _Show is called before the transitionend events are processed, - * which can happen in bulk-rendering contexts. - * - * (Opacity is only modified when device.touchable = true, though a couple of extra - * conditions may apply.) - */ - Ls.opacity='1'; + protected setDisplayPositioning() { + let Ls = this._Box.style; // The following code will always be executed except for externally created OSK such as EuroLatin if(this.vkbd) { @@ -229,11 +207,19 @@ namespace com.keyman.osk { Ls.left=Ls.bottom='0px'; Ls.border='none'; Ls.borderTop='1px solid gray'; - - this._Visible=true; // I3363 (Build 301) } } + /** + * Display KMW OSK at specified position (returns nothing) + * + * @param {number=} Px x-coordinate for OSK rectangle + * @param {number=} Py y-coordinate for OSK rectangle + */ + _Show(Px?: number, Py?: number) { + this.present(); + } + /** * Hide Keymanweb On Screen Keyboard * diff --git a/web/source/osk/floatingOskView.ts b/web/source/osk/floatingOskView.ts index 4a4741c363..6d013e5551 100644 --- a/web/source/osk/floatingOskView.ts +++ b/web/source/osk/floatingOskView.ts @@ -22,6 +22,7 @@ namespace com.keyman.osk { // OSK positioning fields userPositioned: boolean = false; + specifiedPosition: boolean = false; x: number; y: number; noDrag: boolean = false; @@ -441,6 +442,31 @@ namespace com.keyman.osk { } } + public setDisplayPositioning() { + var Ls = this._Box.style; + + Ls.position='absolute'; Ls.display='block'; //Ls.visibility='visible'; + Ls.left='0px'; + if(this.specifiedPosition) { + Ls.left = this.x+'px'; + Ls.top = this.y+'px'; + } else { + var el = this.activeTarget?.getElement(); + + if(this.dfltX) { + Ls.left=this.dfltX; + } else if(typeof el != 'undefined' && el != null) { + Ls.left=dom.Utils.getAbsoluteX(el) + 'px'; + } + + if(this.dfltY) { + Ls.top=this.dfltY; + } else if(typeof el != 'undefined' && el != null) { + Ls.top=(dom.Utils.getAbsoluteY(el) + el.offsetHeight)+'px'; + } + } + } + /** * Display KMW OSK at specified position (returns nothing) * @@ -448,54 +474,22 @@ namespace com.keyman.osk { * @param {number=} Py y-coordinate for OSK rectangle */ _Show(Px?: number, Py?: number) { - if(!this.activationConditionsMet) { + if(!this.mayShow()) { return; } - // Never display the OSK for desktop browsers unless KMW element is focused, and a keyboard selected - if(this.activeKeyboard == null || !this.displayIfActive) { - return; + this.specifiedPosition = Px >= 0 || Py >= 0; //probably never happens, legacy support only + if(this.specifiedPosition) { + this.x = Px; + this.y = Py; } - this.makeVisible(); - - var Ls = this._Box.style; - - Ls.position='absolute'; Ls.display='block'; //Ls.visibility='visible'; - Ls.left='0px'; - if(Px >= 0) { //probably never happens, legacy support only - Ls.left = Px + 'px'; Ls.top = Py + 'px'; - } else { - if(this.userPositioned) { - Ls.left=this.x+'px'; - Ls.top=this.y+'px'; - } else { - var el = this.activeTarget?.getElement(); - - if(this.dfltX) { - Ls.left=this.dfltX; - } else if(typeof el != 'undefined' && el != null) { - Ls.left=dom.Utils.getAbsoluteX(el) + 'px'; - } - - if(this.dfltY) { - Ls.top=this.dfltY; - } else if(typeof el != 'undefined' && el != null) { - Ls.top=(dom.Utils.getAbsoluteY(el) + el.offsetHeight)+'px'; - } - } - } - - this._Visible=true; - - if(this.vkbd) { - this.vkbd.refit(); - } - - this.saveCookie(); - + // Combines the two paths with set positioning. + this.specifiedPosition = this.specifiedPosition || this.userPositioned; this.desktopLayout.titleBar.showPin(this.userPositioned); + this.present(); + // Allow desktop UI to execute code when showing the OSK var Lpos={}; Lpos['x']=this._Box.offsetLeft; diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index d61820f009..2413cf0dc7 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -584,6 +584,20 @@ namespace com.keyman.osk { return vkbd; } + public present() { + // Do not try to display OSK if no active element + if(!this.mayShow()) { + return; + } + + this.makeVisible(); + this.setDisplayPositioning(); + + if(this.vkbd) { + this.vkbd.refit(); + } + } + // Corresponds to the desktop OSK's _Show, but acts as a core, common method // usable by all display patterns. protected makeVisible() { @@ -604,6 +618,17 @@ namespace com.keyman.osk { this.keyboardView.showLanguage(); } + this._Visible=true; + + /* In case it's still '0' from a hide() operation. + * Happens when _Show is called before the transitionend events are processed, + * which can happen in bulk-rendering contexts. + * + * (Opacity is only modified when device.touchable = true, though a couple of extra + * conditions may apply.) + */ + this._Box.style.opacity = '1'; + // If OSK still hidden, make visible only after all calculation finished if(this._Box.style.visibility == 'hidden') { let _this = this; @@ -613,6 +638,8 @@ namespace com.keyman.osk { } } + protected abstract setDisplayPositioning(); + public startHide(hiddenByUser: boolean) { if(!this.mayHide(hiddenByUser)) { return; @@ -670,6 +697,19 @@ namespace com.keyman.osk { } } + protected mayShow(): boolean { + if(!this.activationConditionsMet) { + return false; + } + + // Never display the OSK for desktop browsers unless KMW element is focused, and a keyboard selected + if(this.keyboardView instanceof EmptyView || !this.displayIfActive) { + return false; + } + + return true; + } + protected mayHide(hiddenByUser: boolean): boolean { if(this.activationMode != 'conditional' && this.displayIfActive) { return false; From f070df079d629ac8e56a2c6dfc4375853940f5e1 Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 31 Aug 2021 13:41:12 +0700 Subject: [PATCH 07/19] refactor(web): osk._Hide -> osk.startHide --- web/source/dom/domManager.ts | 2 +- web/source/keyboards/kmwkeyboards.ts | 4 ++-- web/source/osk/anchoredOskView.ts | 9 --------- web/source/osk/floatingOskView.ts | 9 --------- web/source/osk/layouts/titleBar.ts | 2 +- web/source/osk/oskView.ts | 11 ++--------- web/source/osk/visualKeyboard.ts | 2 +- web/source/text/domKbdInterface.ts | 2 +- 8 files changed, 8 insertions(+), 33 deletions(-) diff --git a/web/source/dom/domManager.ts b/web/source/dom/domManager.ts index 189d9941a0..74e591ac57 100644 --- a/web/source/dom/domManager.ts +++ b/web/source/dom/domManager.ts @@ -442,7 +442,7 @@ namespace com.keyman.dom { this.activeElement = null; } this.lastActiveElement = null; - this.keyman.osk._Hide(false); + this.keyman.osk.startHide(false); } return; diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 01e1fe5ea6..d23769df25 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -577,7 +577,7 @@ namespace com.keyman.keyboards { // Hide OSK and do not update keyboard list if using internal keyboard (desktops) if(PInternalName == '') { - osk._Hide(false); + osk.startHide(false); if(!this.keymanweb.isEmbedded) { util.wait(false); @@ -624,7 +624,7 @@ namespace com.keyman.keyboards { if(!this.keyboardStubs[Ln].asyncLoader) { // Always (temporarily) hide the OSK when loading a new keyboard, to ensure that a failure to load doesn't leave the current OSK displayed if(osk) { - osk._Hide(false); + osk.startHide(false); } var loadingStub = this.keyboardStubs[Ln]; diff --git a/web/source/osk/anchoredOskView.ts b/web/source/osk/anchoredOskView.ts index f8cb92394a..45306767f6 100644 --- a/web/source/osk/anchoredOskView.ts +++ b/web/source/osk/anchoredOskView.ts @@ -220,15 +220,6 @@ namespace com.keyman.osk { this.present(); } - /** - * Hide Keymanweb On Screen Keyboard - * - * @param {boolean} hiddenByUser Distinguish between hiding on loss of focus and explicit hiding by user - */ - _Hide(hiddenByUser: boolean) { - this.startHide(hiddenByUser); - } - /** * Allow UI to respond to OSK being shown (passing position and properties) * diff --git a/web/source/osk/floatingOskView.ts b/web/source/osk/floatingOskView.ts index 6d013e5551..5039cda94f 100644 --- a/web/source/osk/floatingOskView.ts +++ b/web/source/osk/floatingOskView.ts @@ -498,15 +498,6 @@ namespace com.keyman.osk { this.doShow(Lpos); } - /** - * Hide Keymanweb On Screen Keyboard - * - * @param {boolean} hiddenByUser Distinguish between hiding on loss of focus and explicit hiding by user - */ - _Hide(hiddenByUser: boolean) { - this.startHide(hiddenByUser); - } - public startHide(hiddenByUser: boolean) { super.startHide(hiddenByUser); diff --git a/web/source/osk/layouts/titleBar.ts b/web/source/osk/layouts/titleBar.ts index 411fcf2a11..d3d060e640 100644 --- a/web/source/osk/layouts/titleBar.ts +++ b/web/source/osk/layouts/titleBar.ts @@ -73,7 +73,7 @@ namespace com.keyman.osk.layouts { } this._closeButton.onclick = function () { - osk._Hide(true); + osk.startHide(true); return false; }; diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index 2413cf0dc7..70e3a9fa33 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -241,7 +241,7 @@ namespace com.keyman.osk { if(this.activationConditionsMet && this.displayIfActive) { this._Show(); } else { - this._Hide(false); + this.startHide(false); } } @@ -853,13 +853,6 @@ namespace com.keyman.osk { */ public abstract _Show(Px?: number, Py?: number); - /** - * Hide Keymanweb On Screen Keyboard - * - * @param {boolean} hiddenByUser Distinguish between hiding on loss of focus and explicit hiding by user - */ - public abstract _Hide(hiddenByUser: boolean); - /** * Display build number * @@ -920,7 +913,7 @@ namespace com.keyman.osk { */ ['hide']() { this.displayIfActive = false; - this._Hide(true); + this.startHide(true); } /** diff --git a/web/source/osk/visualKeyboard.ts b/web/source/osk/visualKeyboard.ts index 6273f897e7..d386e9f059 100644 --- a/web/source/osk/visualKeyboard.ts +++ b/web/source/osk/visualKeyboard.ts @@ -1591,7 +1591,7 @@ namespace com.keyman.osk { oskManager.showLanguageMenu(); } else if(keyName.indexOf('K_ROPT') >= 0) { keyman.uiManager.setActivatingUI(false); - oskManager._Hide(true); + oskManager.startHide(true); let active = keyman.domManager.activeElement; if(dom.Utils.instanceof(active, "TouchAliasElement")) { (active as dom.TouchAliasElement).hideCaret(); diff --git a/web/source/text/domKbdInterface.ts b/web/source/text/domKbdInterface.ts index 2718014226..c5e1411f95 100644 --- a/web/source/text/domKbdInterface.ts +++ b/web/source/text/domKbdInterface.ts @@ -101,7 +101,7 @@ namespace com.keyman.text { //The following entry points are defined but should not normally be used in a keyboard, as OSK display is no longer determined by the keyboard KeyboardInterface.prototype['hideHelp'] = function(): void { let keyman = com.keyman.singleton; - keyman.osk._Hide(true); + keyman.osk.startHide(true); } KeyboardInterface.prototype['showHelp'] = function(Px: number, Py: number): void { From e8afd71491a3906a0df61e8273c7cafe2c2447fe Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 31 Aug 2021 14:16:29 +0700 Subject: [PATCH 08/19] refactor(web): _Show -> present(), presentAtPosition() --- web/source/dom/domManager.ts | 2 +- web/source/osk/anchoredOskView.ts | 12 +------- web/source/osk/floatingOskView.ts | 17 ++++++++--- web/source/osk/oskView.ts | 47 +++++++++--------------------- web/source/osk/visualKeyboard.ts | 3 +- web/source/text/domKbdInterface.ts | 11 +++++-- 6 files changed, 40 insertions(+), 52 deletions(-) diff --git a/web/source/dom/domManager.ts b/web/source/dom/domManager.ts index 74e591ac57..37c13f977f 100644 --- a/web/source/dom/domManager.ts +++ b/web/source/dom/domManager.ts @@ -1775,7 +1775,7 @@ namespace com.keyman.dom { this.keyman.ui['initialize'](); // Display the OSK (again) if enabled, in order to set its position correctly after // adding the UI to the page - this.keyman.osk._Show(); + this.keyman.osk.present(); } else if(this.keyman.isEmbedded) { // UI modules aren't utilized in embedded mode. There's nothing to init, so we simply // return instead of waiting for a UI module that will never come. diff --git a/web/source/osk/anchoredOskView.ts b/web/source/osk/anchoredOskView.ts index 45306767f6..8adb207b0b 100644 --- a/web/source/osk/anchoredOskView.ts +++ b/web/source/osk/anchoredOskView.ts @@ -63,7 +63,7 @@ namespace com.keyman.osk { this._Box.onmouseout = this._VKbdMouseOut; if(this.displayIfActive) { - this._Show(); + this.present(); } } @@ -210,16 +210,6 @@ namespace com.keyman.osk { } } - /** - * Display KMW OSK at specified position (returns nothing) - * - * @param {number=} Px x-coordinate for OSK rectangle - * @param {number=} Py y-coordinate for OSK rectangle - */ - _Show(Px?: number, Py?: number) { - this.present(); - } - /** * Allow UI to respond to OSK being shown (passing position and properties) * diff --git a/web/source/osk/floatingOskView.ts b/web/source/osk/floatingOskView.ts index 5039cda94f..a1e567d165 100644 --- a/web/source/osk/floatingOskView.ts +++ b/web/source/osk/floatingOskView.ts @@ -78,7 +78,7 @@ namespace com.keyman.osk { this.loadCookie(); if(this.displayIfActive) { - this._Show(); + this.present(); } } @@ -104,7 +104,7 @@ namespace com.keyman.osk { this.saveCookie(); if(isVisible) { - this._Show(); + this.present(); } this.doResizeMove(); //allow the UI to respond to OSK movements @@ -465,6 +465,10 @@ namespace com.keyman.osk { Ls.top=(dom.Utils.getAbsoluteY(el) + el.offsetHeight)+'px'; } } + + // Unset the flag, keeping 'specified position' specific to single + // presentAtPosition calls. + this.specifiedPosition = false; } /** @@ -473,7 +477,7 @@ namespace com.keyman.osk { * @param {number=} Px x-coordinate for OSK rectangle * @param {number=} Py y-coordinate for OSK rectangle */ - _Show(Px?: number, Py?: number) { + presentAtPosition(Px?: number, Py?: number) { if(!this.mayShow()) { return; } @@ -486,9 +490,14 @@ namespace com.keyman.osk { // Combines the two paths with set positioning. this.specifiedPosition = this.specifiedPosition || this.userPositioned; - this.desktopLayout.titleBar.showPin(this.userPositioned); this.present(); + } + + present() { + this.desktopLayout.titleBar.showPin(this.userPositioned); + + super.present(); // Allow desktop UI to execute code when showing the OSK var Lpos={}; diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index 70e3a9fa33..dabb96e6e7 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -239,7 +239,7 @@ namespace com.keyman.osk { private commonCheckAndDisplay() { if(this.activationConditionsMet && this.displayIfActive) { - this._Show(); + this.present(); } else { this.startHide(false); } @@ -590,22 +590,6 @@ namespace com.keyman.osk { return; } - this.makeVisible(); - this.setDisplayPositioning(); - - if(this.vkbd) { - this.vkbd.refit(); - } - } - - // Corresponds to the desktop OSK's _Show, but acts as a core, common method - // usable by all display patterns. - protected makeVisible() { - // Do not try to display/render the OSK if undefined or no keyboard is loaded. - if(!this._Box || !this.keyboardView) { - return; - } - // Ensure the keyboard view is modeling the correct state. (Correct layer, etc.) this.keyboardView.updateState(); @@ -621,11 +605,9 @@ namespace com.keyman.osk { this._Visible=true; /* In case it's still '0' from a hide() operation. - * Happens when _Show is called before the transitionend events are processed, - * which can happen in bulk-rendering contexts. * - * (Opacity is only modified when device.touchable = true, though a couple of extra - * conditions may apply.) + * (Opacity is only modified when device.touchable = true, + * though a couple of extra conditions may apply.) */ this._Box.style.opacity = '1'; @@ -636,6 +618,12 @@ namespace com.keyman.osk { _this._Box.style.visibility = 'visible'; }, 0); } + + this.setDisplayPositioning(); + + if(this.vkbd) { + this.vkbd.refit(); + } } protected abstract setDisplayPositioning(); @@ -703,10 +691,14 @@ namespace com.keyman.osk { } // Never display the OSK for desktop browsers unless KMW element is focused, and a keyboard selected - if(this.keyboardView instanceof EmptyView || !this.displayIfActive) { + if(!this.keyboardView || this.keyboardView instanceof EmptyView || !this.displayIfActive) { return false; } + if(!this._Box) { + return; + } + return true; } @@ -841,17 +833,6 @@ namespace com.keyman.osk { * The simplest way forward is to maintain them, then resolve them independently, * one at a time. */ - - /* - * Display KMW OSK at specified position (returns nothing) - * - * The positioning parameters only make sense for the FloatingOSKView type; - * other implementations should use no parameters whatsoever. - * - * @param {number=} Px x-coordinate for OSK rectangle - * @param {number=} Py y-coordinate for OSK rectangle - */ - public abstract _Show(Px?: number, Py?: number); /** * Display build number diff --git a/web/source/osk/visualKeyboard.ts b/web/source/osk/visualKeyboard.ts index d386e9f059..cbcc52ebf3 100644 --- a/web/source/osk/visualKeyboard.ts +++ b/web/source/osk/visualKeyboard.ts @@ -1175,7 +1175,8 @@ namespace com.keyman.osk { layerElement.style.display='block'; //b[n].style.visibility='visible'; - // If osk._Show has been called, there's probably been a change in modifier or state key state. Keep it updated! + // Most functions that call this one often indicate a change in modifier + // or state key state. Keep it updated! this._UpdateVKShiftStyle(); } else { layerElement.style.display='none'; diff --git a/web/source/text/domKbdInterface.ts b/web/source/text/domKbdInterface.ts index c5e1411f95..ea12108797 100644 --- a/web/source/text/domKbdInterface.ts +++ b/web/source/text/domKbdInterface.ts @@ -106,7 +106,11 @@ namespace com.keyman.text { KeyboardInterface.prototype['showHelp'] = function(Px: number, Py: number): void { let keyman = com.keyman.singleton; - keyman.osk._Show(Px,Py); + if(keyman.osk instanceof osk.FloatingOSKView) { + keyman.osk.presentAtPosition(Px,Py); + } else { + keyman.osk.present(); + } } KeyboardInterface.prototype['showPinnedHelp'] = function(): void { @@ -114,7 +118,10 @@ namespace com.keyman.text { if(keyman.osk instanceof osk.FloatingOSKView) { keyman.osk.userPositioned=true; } - keyman.osk._Show(-1,-1); + // Automatically reuses previously-set positioning. + // Other OSK API functions must have previously been used to set the + // pinned position. + keyman.osk.present(); } // Also needed for some legacy CJK keyboards. From 5a80ecaacaf85ec8fb05bd4de9079d7a38075ea3 Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 31 Aug 2021 16:03:11 +0700 Subject: [PATCH 09/19] fix(web): erroneous initial display of UI component --- web/source/osk/floatingOskView.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/source/osk/floatingOskView.ts b/web/source/osk/floatingOskView.ts index a1e567d165..06cde674e3 100644 --- a/web/source/osk/floatingOskView.ts +++ b/web/source/osk/floatingOskView.ts @@ -495,6 +495,10 @@ namespace com.keyman.osk { } present() { + if(!this.mayShow()) { + return; + } + this.desktopLayout.titleBar.showPin(this.userPositioned); super.present(); From 707a7ec745ccc8ed26a7ed1ad1a918024f5b9400 Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 31 Aug 2021 16:13:55 +0700 Subject: [PATCH 10/19] refactor(web): nixes refit() method --- web/source/osk/floatingOskView.ts | 7 +++++-- web/source/osk/oskView.ts | 27 +++++++++++++-------------- web/source/osk/visualKeyboard.ts | 9 --------- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/web/source/osk/floatingOskView.ts b/web/source/osk/floatingOskView.ts index 06cde674e3..ff10d80673 100644 --- a/web/source/osk/floatingOskView.ts +++ b/web/source/osk/floatingOskView.ts @@ -91,7 +91,7 @@ namespace com.keyman.osk { */ ['restorePosition']: (keepDefaultPosition?: boolean) => void = function(this: FloatingOSKView, keepDefaultPosition?: boolean) { let isVisible = this._Visible; - if(isVisible) { + if(isVisible && this.activeTarget instanceof dom.targets.OutputTarget) { this.activeTarget?.focus(); // I2036 - OSK does not unpin to correct location } @@ -451,7 +451,10 @@ namespace com.keyman.osk { Ls.left = this.x+'px'; Ls.top = this.y+'px'; } else { - var el = this.activeTarget?.getElement(); + let el: HTMLElement = null; + if(this.activeTarget instanceof dom.targets.OutputTarget) { + el = this.activeTarget?.getElement(); + } if(this.dfltX) { Ls.left=this.dfltX; diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index dabb96e6e7..a780cb34ff 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -40,7 +40,7 @@ namespace com.keyman.osk { private keyboard: keyboards.Keyboard; - private _target: dom.targets.OutputTarget; + private _target: text.OutputTarget; /** * The configured width for this OSKManager. May be `undefined` or `null` @@ -179,11 +179,11 @@ namespace com.keyman.osk { this._boxBaseTouchStart = null; } - public get activeTarget(): dom.targets.OutputTarget { + public get activeTarget(): text.OutputTarget { return this._target; } - public set activeTarget(targ: dom.targets.OutputTarget) { + public set activeTarget(targ: text.OutputTarget) { this._target = targ; this.commonCheckAndDisplay(); } @@ -584,7 +584,15 @@ namespace com.keyman.osk { return vkbd; } - public present() { + /** + * The main function for presenting the OSKView. + * + * This includes: + * - refreshing its layout + * - displaying it + * - positioning it + */ + public present(): void { // Do not try to display OSK if no active element if(!this.mayShow()) { return; @@ -620,10 +628,6 @@ namespace com.keyman.osk { } this.setDisplayPositioning(); - - if(this.vkbd) { - this.vkbd.refit(); - } } protected abstract setDisplayPositioning(); @@ -657,17 +661,12 @@ namespace com.keyman.osk { this.doHide(p); // If hidden by the UI, be sure to restore the focus - if(hiddenByUser) { + if(hiddenByUser && this.activeTarget instanceof dom.targets.OutputTarget) { this.activeTarget?.focus(); } } protected finalizeHide() { - // Save current size if visible - if(this._Box && this._Box.style.display == 'block' && this.keyboardView instanceof VisualKeyboard) { - this.keyboardView.refit(); - } - if(document.body.className.indexOf('osk-always-visible') >= 0) { return; } diff --git a/web/source/osk/visualKeyboard.ts b/web/source/osk/visualKeyboard.ts index cbcc52ebf3..abff959eed 100644 --- a/web/source/osk/visualKeyboard.ts +++ b/web/source/osk/visualKeyboard.ts @@ -308,15 +308,6 @@ namespace com.keyman.osk { } } - /** - * Called by OSKManager after resize operations in order to determine the final - * size actually used by the visual keyboard. - */ - public refit() { // TODO: Should probably remove; VisualKeyboard shouldn't be responsible for it. - this._width = this.kbdDiv.offsetWidth; - this._height = this.kbdDiv.offsetHeight; - } - /** * Returns the default properties for a key object, used to construct * both a base keyboard key and popup keys From 863674f47551f0c7e8b09b012d71243fe1c53a65 Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 31 Aug 2021 16:40:01 +0700 Subject: [PATCH 11/19] docs(web): documents the new presentation & activation modeling props / methods --- web/source/osk/oskView.ts | 85 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index a780cb34ff..44cf56377e 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -179,6 +179,13 @@ namespace com.keyman.osk { this._boxBaseTouchStart = null; } + /** + * Gets and sets the IME-like interface (`OutputTarget`) to be affected by events from + * the OSK. + * + * If `activationMode` is `'conditional'`, this property's state controls the visibility + * of the OSKView. + */ public get activeTarget(): text.OutputTarget { return this._target; } @@ -188,6 +195,14 @@ namespace com.keyman.osk { this.commonCheckAndDisplay(); } + /** + * Determines the activation state model used to control presentation of the OSK. + * - `'conditional'`: Only displays if `activeTarget` is non-null - if there is an active + * target that can receive the OSK's context-manipulation events. + * - `'manual'`: Display is directly controlled by manipulating the value of `displayIfActive`. + * It may be displayed while `activeTarget` is `null`. + * - `'static'`: The OSK should be permanently displayed and may never be hidden. + */ get activationMode(): ActivationMode { if(!this._activationMode) { this._activationMode = ActivationMode.conditional; @@ -201,7 +216,11 @@ namespace com.keyman.osk { this.commonCheckAndDisplay(); } - get activationConditionsMet(): boolean { + /** + * Implementation of the activation modeling described in the documentation for + * `activationMode`. + */ + protected get activationConditionsMet(): boolean { switch(this.activationMode) { case 'manual': return true; @@ -215,6 +234,15 @@ namespace com.keyman.osk { } } + /** + * A property denoting whether or not the OSK should be presented if it meets its + * activation conditions. + * + * When `activationMode == 'manual'`, `displayIfActive == true` is the lone + * activation condition. + * + * Note: cannot be set to `false` if `activationMode == 'static'`. + */ get displayIfActive(): boolean { return this._displayIfActive; } @@ -237,6 +265,10 @@ namespace com.keyman.osk { this.commonCheckAndDisplay(); } + /** + * Used by the activation & visibility properties as a common helper; all of their + * setters rely on this function to manage presentation (showing / hiding) of the OSK. + */ private commonCheckAndDisplay() { if(this.activationConditionsMet && this.displayIfActive) { this.present(); @@ -630,19 +662,34 @@ namespace com.keyman.osk { this.setDisplayPositioning(); } + /** + * Method usable by subclasses of OSKView to control that OSKView type's + * positioning behavior when needed by the present() method. + */ protected abstract setDisplayPositioning(); - public startHide(hiddenByUser: boolean) { + /** + * Method used to start a potentially-asynchronous hide of the OSK. + * @param hiddenByUser `true` if this hide operation was directly requested by the user. + */ + public startHide(hiddenByUser: boolean): void { if(!this.mayHide(hiddenByUser)) { return; } if(hiddenByUser) { + // The one location outside of the `displayIfActive` property that bypasses the setter. + // Avoids needless recursion that could be triggered by it, as we're already in the + // process of hiding the OSK anyway. this._displayIfActive = ((this.keyboard.isCJK || this.hostDevice.touchable)? true : false); // I3363 (Build 301) } let promise: Promise = null; if(this._Box && this.hostDevice.touchable && !(this.keyboardView instanceof EmptyView)) { + /** + * Note: this refactored code appears to reflect a currently-dead code path. 14.0's + * equivalent is either extremely niche or is actually inaccessible. + */ promise = this.useHideAnimation(); } else { promise = Promise.resolve(true); @@ -666,6 +713,9 @@ namespace com.keyman.osk { } } + /** + * Performs the _actual_ logic and functionality involved in hiding the OSK. + */ protected finalizeHide() { if(document.body.className.indexOf('osk-always-visible') >= 0) { return; @@ -684,6 +734,10 @@ namespace com.keyman.osk { } } + /** + * + * @returns `false` if the OSK is in an invalid state for being presented to the user. + */ protected mayShow(): boolean { if(!this.activationConditionsMet) { return false; @@ -701,6 +755,11 @@ namespace com.keyman.osk { return true; } + /** + * + * @param hiddenByUser + * @returns `false` if the OSK is in an invalid state for being hidden from the user. + */ protected mayHide(hiddenByUser: boolean): boolean { if(this.activationMode != 'conditional' && this.displayIfActive) { return false; @@ -716,6 +775,18 @@ namespace com.keyman.osk { return true; } + /** + * Applies CSS styling and handling needed to perform a fade animation when + * hiding the OSK. + * + * Note: currently reflects an effectively-dead code path, though this is + * likely not intentional. Other parts of the KMW engine seem to call hideNow() + * synchronously after each and every part of the engine that calls this function, + * cancelling the Promise. + * + * @returns A Promise denoting either cancellation of the hide (`false`) or + * completion of the hide & its animation (`true`) + */ protected useHideAnimation(): Promise { const os = this._Box.style; const _this = this; @@ -767,6 +838,11 @@ namespace com.keyman.osk { }); } + /** + * Used to synchronously hide the OSK, cancelling any async hide animations that have + * not started and immediately completing the hide of any hide ops pending completion + * of their animation. + */ public hideNow() { // Two possible uses for _animatedHideResolver: // - _animatedHideTimeout is set: animation is waiting to start @@ -859,10 +935,9 @@ namespace com.keyman.osk { menu.show(); } - // OSK state fields + // OSK state fields & events // - // They're not very well defined or encapsulated; there's definitely room for more - // "polish" here. + // These are relatively stable and may be preserved as they are. _Visible: boolean = false; /** From 32d650ee41a8256a46c4dbe473b18d3fa4ef977e Mon Sep 17 00:00:00 2001 From: jahorton Date: Wed, 1 Sep 2021 10:59:06 +0700 Subject: [PATCH 12/19] fix(web): osk font size init --- web/source/osk/oskView.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index 5c9061be17..26bb6dce81 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -440,6 +440,13 @@ namespace com.keyman.osk { this.needsLayout = false; // Step 3: perform layout operations. + if(!this._baseFontSize && this.parsedBaseFontSize) { + // Make sure to initialize the default font size if it hasn't already been set! + this.banner.element.style.fontSize = this.baseFontSize; + if(this.vkbd) { + this.vkbd.fontSize = this.parsedBaseFontSize; + } + } if(!pending) { this.headerView?.refreshLayout(); this.bannerView.refreshLayout(); @@ -511,13 +518,9 @@ namespace com.keyman.osk { // Add suggestion banner bar to OSK this._Box.appendChild(this.banner.element); - this.banner.element.style.fontSize = this.baseFontSize; let kbdView: KeyboardView = this.keyboardView = this._GenerateKeyboardView(this.activeKeyboard); this._Box.appendChild(kbdView.element); - if(kbdView instanceof VisualKeyboard) { - kbdView.fontSize = this.parsedBaseFontSize; - } kbdView.postInsert(); // Add footer element to OSK only for desktop browsers From 29a8e3ad63064efbab3b8c4ad09fe6097fbf4677 Mon Sep 17 00:00:00 2001 From: jahorton Date: Fri, 3 Sep 2021 13:34:46 +0700 Subject: [PATCH 13/19] chore(web): extra sanity check for activeTarget management --- web/source/dom/domManager.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/source/dom/domManager.ts b/web/source/dom/domManager.ts index 37c13f977f..e57edad556 100644 --- a/web/source/dom/domManager.ts +++ b/web/source/dom/domManager.ts @@ -1244,6 +1244,9 @@ namespace com.keyman.dom { const osk = this.keyman.osk; if(osk) { if(this.lastActiveElement == null && this.activeElement == null) { + if(this.keyman.osk.activeTarget) { + this.keyman.osk.activeTarget = null; + } this.keyman.osk.hideNow(); // originally from a different one, seemed to serve the same role? } } From f46bc3cd88ca3973d8e05778403a9cc88a2f6c84 Mon Sep 17 00:00:00 2001 From: jahorton Date: Wed, 15 Sep 2021 09:13:59 +0700 Subject: [PATCH 14/19] fix(web): embedded popup + hardware keys --- web/source/kmwembedded.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/source/kmwembedded.ts b/web/source/kmwembedded.ts index 72fb7d14d9..514f042998 100644 --- a/web/source/kmwembedded.ts +++ b/web/source/kmwembedded.ts @@ -124,7 +124,7 @@ namespace com.keyman.text { (function() { // Declare KeymanWeb and related objects - var keymanweb=window['keyman'],util=keymanweb['util'],device=util.device; + var keymanweb=window['keyman'], util=keymanweb['util'],device=util.device; var dom = com.keyman.dom; // Allow definition of application name @@ -373,7 +373,7 @@ namespace com.keyman.text { // Note: this assumes Lelem is properly attached and has an element interface. // Currently true in the Android and iOS apps. - var Lelem=keymanweb.domManager.getLastActiveElement(); + var Lelem=keymanweb.domManager.lastActiveElement; keymanweb.domManager.initActiveElement(Lelem); // This should be set if we're within this method... but it's best to guard against nulls here, just in case. @@ -405,7 +405,7 @@ namespace com.keyman.text { // Note: this assumes Lelem is properly attached and has an element interface. // Currently true in the Android and iOS apps. - var Lelem = keymanweb.domManager.getLastActiveElement(); + var Lelem = keymanweb.domManager.lastActiveElement; keymanweb.domManager.initActiveElement(Lelem); From 8826bec8fe0de527675dbee5ce73d2037e06bdbb Mon Sep 17 00:00:00 2001 From: jahorton Date: Fri, 17 Sep 2021 14:58:18 +0700 Subject: [PATCH 15/19] fix(web): incorporates some review suggestions --- web/source/dom/domManager.ts | 6 ++++-- web/source/osk/oskView.ts | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/web/source/dom/domManager.ts b/web/source/dom/domManager.ts index 8f3191fe63..9891ba9c31 100644 --- a/web/source/dom/domManager.ts +++ b/web/source/dom/domManager.ts @@ -1244,8 +1244,10 @@ namespace com.keyman.dom { const osk = this.keyman.osk; if(osk) { if(this.lastActiveElement == null && this.activeElement == null) { - if(this.keyman.osk.activeTarget) { - this.keyman.osk.activeTarget = null; + // Assigning to the property does have side-effects. + // If the property is already unset, it's best to not unset it again. + if(osk.activeTarget) { + osk.activeTarget = null; } this.keyman.osk.hideNow(); // originally from a different one, seemed to serve the same role? } diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index 137dedfdc9..f02aba56e1 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -20,7 +20,7 @@ namespace com.keyman.osk { export enum ActivationMode { static = "static", // For use by documentation keyboards, eventually. manual = "manual", - conditional = "conditional" + automatic = "automatic" } export abstract class OSKView { @@ -75,7 +75,7 @@ namespace com.keyman.osk { private needsLayout: boolean = true; // - private _activationMode: ActivationMode = ActivationMode.conditional; + private _activationMode: ActivationMode = ActivationMode.automatic; private _displayIfActive: boolean = true; private _animatedHideTimeout: number; @@ -205,7 +205,7 @@ namespace com.keyman.osk { */ get activationMode(): ActivationMode { if(!this._activationMode) { - this._activationMode = ActivationMode.conditional; + this._activationMode = ActivationMode.automatic; } return this._activationMode; @@ -764,7 +764,7 @@ namespace com.keyman.osk { } if(!this._Box) { - return; + return false; } return true; From 09de360789fbb679e2383ee96c796831f9c25115 Mon Sep 17 00:00:00 2001 From: jahorton Date: Fri, 17 Sep 2021 15:02:43 +0700 Subject: [PATCH 16/19] fix(web): missed bits of last commit, adds some polish --- web/source/dom/domManager.ts | 4 +--- web/source/osk/oskView.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/web/source/dom/domManager.ts b/web/source/dom/domManager.ts index 9891ba9c31..8e6476294d 100644 --- a/web/source/dom/domManager.ts +++ b/web/source/dom/domManager.ts @@ -1246,9 +1246,7 @@ namespace com.keyman.dom { if(this.lastActiveElement == null && this.activeElement == null) { // Assigning to the property does have side-effects. // If the property is already unset, it's best to not unset it again. - if(osk.activeTarget) { - osk.activeTarget = null; - } + osk.activeTarget = null; this.keyman.osk.hideNow(); // originally from a different one, seemed to serve the same role? } } diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index f02aba56e1..d5ea8b3766 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -191,6 +191,11 @@ namespace com.keyman.osk { } public set activeTarget(targ: text.OutputTarget) { + // If already null & set to null again, take no action. + if(this._target == null && targ == null) { + return; + } + this._target = targ; this.commonCheckAndDisplay(); } @@ -226,7 +231,7 @@ namespace com.keyman.osk { return true; case 'static': return true; - case 'conditional': + case 'automatic': return !!this.activeTarget; default: console.error("Unexpected activation mode set for the OSK."); @@ -776,7 +781,7 @@ namespace com.keyman.osk { * @returns `false` if the OSK is in an invalid state for being hidden from the user. */ protected mayHide(hiddenByUser: boolean): boolean { - if(this.activationMode != 'conditional' && this.displayIfActive) { + if(this.activationMode != 'automatic' && this.displayIfActive) { return false; } From 3f48ae7e99609d6ad54a8ff16db508f0f89d4e2b Mon Sep 17 00:00:00 2001 From: jahorton Date: Fri, 17 Sep 2021 15:15:53 +0700 Subject: [PATCH 17/19] fix(web): more polish, fixes pinned position management --- web/source/osk/floatingOskView.ts | 8 ++------ web/source/osk/oskView.ts | 4 ++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/source/osk/floatingOskView.ts b/web/source/osk/floatingOskView.ts index 49a490ef4f..b5cf1acaa4 100644 --- a/web/source/osk/floatingOskView.ts +++ b/web/source/osk/floatingOskView.ts @@ -200,11 +200,7 @@ namespace com.keyman.osk { var c = util.loadCookie('KeymanWeb_OnScreenKeyboard'); - const displayIfActive = util.toNumber(c['visible'], 1) == 1; - if(this.displayIfActive != displayIfActive) { - // Setter triggers functions that can go recursive without the conditional check. - this.displayIfActive = displayIfActive; - } + this.displayIfActive = util.toNumber(c['visible'], 1) == 1; this.userPositioned = util.toNumber(c['userSet'], 0) == 1; this.x = util.toNumber(c['left'],-1); this.y = util.toNumber(c['top'],-1); @@ -482,7 +478,7 @@ namespace com.keyman.osk { Ls.position='absolute'; Ls.display='block'; //Ls.visibility='visible'; Ls.left='0px'; - if(this.specifiedPosition) { + if(this.specifiedPosition || this.userPositioned) { Ls.left = this.x+'px'; Ls.top = this.y+'px'; } else { diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index d5ea8b3766..03db9a5845 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -253,6 +253,10 @@ namespace com.keyman.osk { } set displayIfActive(flag: boolean) { + if(this.displayIfActive == flag) { + return; + } + // if is touch device or is CJK keyboard, this.displayIfActive must remain true. if(this.keyboard?.isCJK && !flag) { console.warn("Cannot hide display of OSK for CJK keyboards."); From d687c9e1c32ff243eb983e19c1da435e7ef1a551 Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 21 Sep 2021 10:37:08 +0700 Subject: [PATCH 18/19] fix(web): workaround for unwanted CJK pinning --- web/source/text/domKbdInterface.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/web/source/text/domKbdInterface.ts b/web/source/text/domKbdInterface.ts index ea12108797..00da01aad9 100644 --- a/web/source/text/domKbdInterface.ts +++ b/web/source/text/domKbdInterface.ts @@ -116,7 +116,17 @@ namespace com.keyman.text { KeyboardInterface.prototype['showPinnedHelp'] = function(): void { let keyman = com.keyman.singleton; if(keyman.osk instanceof osk.FloatingOSKView) { - keyman.osk.userPositioned=true; + // An old KMW bug previously auto-unset the affected field when this function was + // used by CJK keyboards during rule processing. As a result, we need to condition + // on whether or not: + // 1. The active keyboard is CJK + // 2. A keyboard rule is actively processing. + // + // If BOTH are true, we do NOT mutate keyman.osk.userPositioned. + // Otherwise, not all conditions are met, so we still allow OSK pinning. + if(!keyman.core.activeKeyboard.isCJK || !this.ruleBehavior) { + keyman.osk.userPositioned=true; + } } // Automatically reuses previously-set positioning. // Other OSK API functions must have previously been used to set the From ebd673fdb2c36c3d90ea97106005fda15dd408d9 Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 21 Sep 2021 12:19:11 +0700 Subject: [PATCH 19/19] fix(web): osk dismissal after focus timer --- web/source/osk/oskView.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index 03db9a5845..9399ffbe00 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -836,7 +836,12 @@ namespace com.keyman.osk { }); return promise.then(function() { - if(_this._Visible) { + // Repro for passing this condition: + // 1. Touch an input element of the page + // 2. Within a second (before the focusing timer expires), touch the base page. + // See domEventHandlers.ts, `focusTimer` / `setFocusTimer`. + // 3. After the timer expires, touch the base page again. + if(_this._Visible && _this.activationConditionsMet) { // Leave opacity alone and clear transition if another element activated os.transition=''; return false;