From 8999eecfc92fdd34c7196f6fa7a9e56e5cde7fe6 Mon Sep 17 00:00:00 2001 From: jahorton Date: Wed, 10 Mar 2021 13:12:30 +0700 Subject: [PATCH] fix(web): event handling for TouchAliasElement's blinking caret --- web/source/dom/domEventHandlers.ts | 47 +++++++++++++++++++---------- web/source/dom/touchAliasElement.ts | 35 +++++++++++++++++++++ 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/web/source/dom/domEventHandlers.ts b/web/source/dom/domEventHandlers.ts index 4f00966cbf..85af1be90c 100644 --- a/web/source/dom/domEventHandlers.ts +++ b/web/source/dom/domEventHandlers.ts @@ -204,6 +204,10 @@ namespace com.keyman.dom { Ltarg = Ltarg['body']; // Occurs in Firefox for design-mode iframes. } + // Makes sure we properly detect the TouchAliasElement root, + // rather than one of its constituent children. + Ltarg = findTouchAliasTarget(Ltarg) || Ltarg; + if(DOMEventHandlers.states._IgnoreBlurFocus) { // Prevent triggering other blur-handling events (as possible) e.cancelBubble = true; @@ -531,9 +535,15 @@ namespace com.keyman.dom { tEvent=(e as TouchEvent).touches[0]; } else { // Allow external code to set focus and thus display the OSK on touch devices if required (KMEW-123) tEvent={clientX:0, clientY:0} + // Will usually be called from setActiveElement, which should define DOMEventHandlers.states.lastActiveElement if(DOMEventHandlers.states.lastActiveElement) { - tEvent.target = DOMEventHandlers.states.lastActiveElement['kmw_ip']; + tEvent.target = DOMEventHandlers.states.lastActiveElement; + // Shouldn't happen, but... just in case. Implemented late in 14.0 beta, so + // this detail was kept, though it's likely safe to eliminate. + if(tEvent.target['kmw_ip']) { + tEvent.target = tEvent.target['kmw_ip']; + } // but will default to first input or text area on page if DOMEventHandlers.states.lastActiveElement is null } else { tEvent.target = this.keyman.domManager.sortedInputs[0]['kmw_ip']; @@ -547,28 +557,33 @@ namespace com.keyman.dom { var osk = this.keyman.osk; var touchX=tEvent.clientX,touchY=tEvent.clientY; - var tTarg=tEvent.target as HTMLElement; - var scroller: HTMLElement; - // Identify the scroller element - if(tTarg && dom.Utils.instanceof(tTarg, "HTMLSpanElement")) { - scroller=tTarg.parentNode as HTMLElement; - } else if(tTarg && (tTarg.className != null && tTarg.className.indexOf('keymanweb-input') >= 0)) { - scroller=tTarg.firstChild as HTMLElement; - } else { - scroller=tTarg; - } + // Some specifics rely upon which child of the TouchAliasElement received the actual event. + let tTarg=tEvent.target as HTMLElement; - // And the actual target element - var target=scroller.parentNode as TouchAliasElement; + // Determines the actual TouchAliasElement - the part tied to an OutputTarget. + // Ideally, we shouldn't need the second part as a fallback; it's there to preserve existing + // behavior from 13.0, as this was refactored made LATE in the 14.0 beta process. + let target = findTouchAliasTarget(tTarg) || (tTarg as TouchAliasElement); + // Some parts rely upon the scroller element. + let scroller = target.firstChild as HTMLElement; // Move the caret and refocus if necessary if(DOMEventHandlers.states.activeElement != target) { // Hide the KMW caret let prevTarget = DOMEventHandlers.states.activeElement; - if(prevTarget) { + + // We're not 100% sure whether or not the next line can occur, + // but it's a decent failsafe regardless. + if(prevTarget && prevTarget['kmw_ip']) { + prevTarget = prevTarget['kmw_ip'] as TouchAliasElement; + } + + // Make sure that we have the right type so that the expected method exists. + if(prevTarget && dom.Utils.instanceof(prevTarget, "TouchAliasElement")) { prevTarget.hideCaret(); } + DOMEventHandlers.states.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 @@ -587,8 +602,8 @@ namespace com.keyman.dom { osk._Show(); } - // If clicked on DIV, set caret to end of text - if(tTarg && dom.Utils.instanceof(tTarg, "TouchAliasElement")) { + // If clicked on DIV on the main element or on the scroller element, set caret to end of text + if(tTarg && tTarg == target || tTarg == scroller) { var x,cp; x=dom.Utils.getAbsoluteX(scroller.firstChild as HTMLElement); if(target.dir == 'rtl') { diff --git a/web/source/dom/touchAliasElement.ts b/web/source/dom/touchAliasElement.ts index 933c9b4edf..d6dc761ae9 100644 --- a/web/source/dom/touchAliasElement.ts +++ b/web/source/dom/touchAliasElement.ts @@ -27,6 +27,41 @@ namespace com.keyman.dom { return e; } + // If the specified HTMLElement is either a TouchAliasElement or one of its children elements, + // this method will return the root TouchAliasElement. + export function findTouchAliasTarget(target: HTMLElement): TouchAliasElement { + let scroller: HTMLElement; + + // Identify the scroller element + if(target && dom.Utils.instanceof(target, "HTMLSpanElement")) { + scroller=target.parentNode as HTMLElement; + } else if(target && (target.className != null && target.className.indexOf('keymanweb-input') >= 0)) { + scroller=target.firstChild as HTMLElement; + } else if(target && dom.Utils.instanceof(target, "HTMLDivElement")) { + // Two possibilities: the scroller & the blinking DIV of the caret. + // A direct click CAN trigger events on the blinking element itself if well-timed. + scroller=target; + + // Ensures we land on the scroller, not the caret. + if(scroller.parentElement && scroller.parentElement.className.indexOf('keymanweb-input') < 0) { + scroller = scroller.parentElement; + } + } else if(target['kmw_ip']) { // In case it's called on a TouchAliasElement's base (aliased) element. + return target['kmw_ip'] as TouchAliasElement; + } else { + // If it's not in any way related to a TouchAliasElement, simply return null. + return null; + } + + // And the actual target element + let root = scroller.parentNode; + if(root['base'] !== undefined) { + return root as TouchAliasElement; + } else { + return null; + } + } + export function constructTouchAlias(base?: HTMLElement): TouchAliasElement { let div = document.createElement("div"); let ele = link(div, new TouchAliasData());