From bbbf1afce77ab53391e31788d47ff0577fbb7192 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 23 Nov 2021 15:00:47 +1100 Subject: [PATCH] feat(web): basic support for newContext Relates to #3621. Adds basic support for `begin newContext`. --- .../src/text/inputProcessor.ts | 53 ++- .../src/keyboards/keyboard.ts | 4 + .../src/text/kbdInterface.ts | 54 +++ .../src/text/keyboardProcessor.ts | 13 + web/source/dom/domEventHandlers.ts | 224 +++++----- web/source/dom/domManager.ts | 394 +++++++++--------- web/source/dom/preProcessor.ts | 50 +-- web/source/keyboards/kmwkeyboards.ts | 4 + web/source/kmwbase.ts | 2 +- web/source/osk/oskView.ts | 4 +- 10 files changed, 467 insertions(+), 335 deletions(-) diff --git a/common/core/web/input-processor/src/text/inputProcessor.ts b/common/core/web/input-processor/src/text/inputProcessor.ts index 501f90ed57..d67bb7687f 100644 --- a/common/core/web/input-processor/src/text/inputProcessor.ts +++ b/common/core/web/input-processor/src/text/inputProcessor.ts @@ -10,14 +10,16 @@ namespace com.keyman.text { baseLayout: 'us' } + private device: utils.DeviceSpec; private kbdProcessor: KeyboardProcessor; private lngProcessor: prediction.LanguageProcessor; - constructor(options?: ProcessorInitOptions) { + constructor(device: utils.DeviceSpec, options?: ProcessorInitOptions) { if(!options) { options = InputProcessor.DEFAULT_OPTIONS; } + this.device = device; this.kbdProcessor = new KeyboardProcessor(options); this.lngProcessor = new prediction.LanguageProcessor(); } @@ -50,11 +52,28 @@ namespace com.keyman.text { return this.languageProcessor.activeModel; } - /** + /** + * + * @param outputTarget + * @returns + */ + processNewContextEvent(outputTarget: OutputTarget): RuleBehavior { + // We presently need the true keystroke to run on the FULL context. That index is still + // needed for some indexing operations when comparing two different output targets. + const ruleBehavior = this.keyboardProcessor.processNewContextEvent(this.device, outputTarget); + + // Should we swallow any further processing of keystroke events for this? + if(ruleBehavior != null) { + ruleBehavior.finalize(this.keyboardProcessor, outputTarget); + } + return ruleBehavior; + } + + /** * Simulate a keystroke according to the touched keyboard button element * * Handles default output and keyboard processing for both OSK and physical keystrokes. - * + * * @param {Object} keyEvent The abstracted KeyEvent to use for keystroke processing * @param {Object} outputTarget The OutputTarget receiving the KeyEvent * @returns {Object} A RuleBehavior object describing the cumulative effects of @@ -113,7 +132,7 @@ namespace com.keyman.text { if(keyEvent.kNextLayer) { this.keyboardProcessor.selectLayer(keyEvent); } - + // Should we swallow any further processing of keystroke events for this keydown-keypress sequence? if(ruleBehavior != null) { let alternates: Alternate[]; @@ -123,7 +142,7 @@ namespace com.keyman.text { if(this.languageProcessor.isActive && !ruleBehavior.triggersDefaultCommand) { let keyDistribution = keyEvent.keyDistribution; - // We don't need to track absolute indexing during alternate-generation; + // We don't need to track absolute indexing during alternate-generation; // only position-relative, so it's better to use a sliding window for context // when making alternates. (Slightly worse for short text, matters greatly // for long text.) @@ -141,7 +160,7 @@ namespace com.keyman.text { let _globalThis = com.keyman.utils.getGlobalObject(); let timer: () => number; - // Available by default on `window` in browsers, but _not_ on `global` in Node, + // Available by default on `window` in browsers, but _not_ on `global` in Node, // surprisingly. Since we can't use code dependent on `require` statements // at present, we have to condition upon it actually existing. if(_globalThis['performance'] && _globalThis['performance']['now']) { @@ -152,15 +171,15 @@ namespace com.keyman.text { TIMEOUT_THRESHOLD = timer() + 16; // + 16ms. } // else { // We _could_ just use Date.now() as a backup... but that (probably) only matters - // when unit testing. So... we actually don't _need_ time thresholding when in + // when unit testing. So... we actually don't _need_ time thresholding when in // a Node environment. // } // Tracks a minimum probability for keystroke probability. Anything less will not be - // included in alternate calculations. + // included in alternate calculations. // // Seek to match SearchSpace.EDIT_DISTANCE_COST_SCALE from the predictive-text engine. - // Reasoning for the selected value may be seen there. Short version - keystrokes + // Reasoning for the selected value may be seen there. Short version - keystrokes // that _appear_ very precise may otherwise not even consider directly-neighboring keys. let KEYSTROKE_EPSILON = Math.exp(-5); @@ -169,7 +188,7 @@ namespace com.keyman.text { let activeLayout = this.activeKeyboard.layout(keyEvent.device.formFactor); alternates = []; - + let totalMass = 0; // Tracks sum of non-error probabilities. for(let pair of keyDistribution) { if(pair.p < KEYSTROKE_EPSILON) { @@ -184,7 +203,7 @@ namespace com.keyman.text { } let mock = Mock.from(windowedMock); - + let altKey = activeLayout.getLayer(keyEvent.kbdLayer).getKey(pair.keyId); if(!altKey) { console.warn("Potential fat-finger key could not be found in layer!"); @@ -193,14 +212,14 @@ namespace com.keyman.text { let altEvent = altKey.constructKeyEvent(this.keyboardProcessor, keyEvent.device); let alternateBehavior = this.keyboardProcessor.processKeystroke(altEvent, mock); - + // If alternateBehavior.beep == true, ignore it. It's a disallowed key sequence, // so we expect users to never intend their use. // // Also possible that this set of conditions fail for all evaluated alternates. if(alternateBehavior && !alternateBehavior.beep && pair.p > 0) { let transform: Transform = alternateBehavior.transcription.transform; - + // Ensure that the alternate's token id matches that of the current keystroke, as we only // record the matched rule's context (since they match) transform.id = ruleBehavior.transcription.token; @@ -224,7 +243,7 @@ namespace com.keyman.text { ruleBehavior.finalize(this.keyboardProcessor, outputTarget); // -- All keystroke (and 'alternate') processing is now complete. Time to finalize everything! -- - + // Notify the ModelManager of new input - it's predictive text time! if(alternates && alternates.length > 0) { ruleBehavior.transcription.alternates = alternates; @@ -245,6 +264,12 @@ namespace com.keyman.text { public resetContext(outputTarget?: OutputTarget) { this.keyboardProcessor.resetContext(); this.languageProcessor.invalidateContext(outputTarget); + + // Let the keyboard do its initial group processing + //console.log('processNewContextEvent called from resetContext'); + if(outputTarget) { + this.processNewContextEvent(outputTarget); + } } } } diff --git a/common/core/web/keyboard-processor/src/keyboards/keyboard.ts b/common/core/web/keyboard-processor/src/keyboards/keyboard.ts index 857d2c223d..9b9095cf79 100644 --- a/common/core/web/keyboard-processor/src/keyboards/keyboard.ts +++ b/common/core/web/keyboard-processor/src/keyboards/keyboard.ts @@ -64,6 +64,10 @@ namespace com.keyman.keyboards { return this.scriptObject['gs'](outputTarget, keystroke); } + processNewContextEvent(outputTarget: text.OutputTarget, keystroke: text.KeyEvent): boolean { + return this.scriptObject['gn'] ? this.scriptObject['gn'](outputTarget, keystroke) : false; + } + get isHollow(): boolean { return this.scriptObject == Keyboard.DEFAULT_SCRIPT_OBJECT; } diff --git a/common/core/web/keyboard-processor/src/text/kbdInterface.ts b/common/core/web/keyboard-processor/src/text/kbdInterface.ts index fb729c0468..0576100f94 100644 --- a/common/core/web/keyboard-processor/src/text/kbdInterface.ts +++ b/common/core/web/keyboard-processor/src/text/kbdInterface.ts @@ -948,6 +948,60 @@ namespace com.keyman.text { this.output(1, outputTarget, ""); } + processNewContextEvent(outputTarget: OutputTarget, keystroke: KeyEvent): RuleBehavior { + // Clear internal state tracking data from prior keystrokes. + if(!outputTarget) { + throw "No target specified for keyboard output!"; + } else if(!this.activeKeyboard) { + throw "No active keyboard for keystroke processing!"; + } + + outputTarget.invalidateSelection(); + + outputTarget.deadkeys().resetMatched(); // I3318 + this.resetContextCache(); + + // Capture the initial state of the OutputTarget before any rules are matched. + let preInput = Mock.from(outputTarget); + + // Capture the initial state of any variable stores + const cachedVariableStores = this.activeKeyboard.variableStores; + + // Establishes the results object, allowing corresponding commands to set values here as appropriate. + this.ruleBehavior = new RuleBehavior(); + + // Ensure the settings are in place so that KIFS/ifState activates and deactivates + // the appropriate rule(s) for the modeled device. + this.activeDevice = keystroke.device; + + // Calls the start-group of the active keyboard. + this.activeTargetOutput = outputTarget; + var matched = this.activeKeyboard.processNewContextEvent(outputTarget, keystroke); + this.activeTargetOutput = null; + + // Finalize the rule's results. + this.ruleBehavior.transcription = outputTarget.buildTranscriptionFrom(preInput, keystroke); + + // We always backup the changes to variable stores to the RuleBehavior, to + // be applied during finalization, then restore them to the cached initial + // values to avoid side-effects with predictive text mocks. + this.ruleBehavior.variableStores = this.activeKeyboard.variableStores; + this.activeKeyboard.variableStores = cachedVariableStores; + + // `matched` refers to whether or not the FINAL rule (from any group) matched, rather than + // whether or not ANY rule matched. If the final rule doesn't match, we trigger the key's + // default behavior (if appropriate). + // + // See https://github.com/keymanapp/keyman/pull/4350#issuecomment-768753852 + this.ruleBehavior.triggerKeyDefault = !matched; + + // Clear our result-tracking variable to prevent any possible pollution for future processing. + let behavior = this.ruleBehavior; + this.ruleBehavior = null; + + return behavior; + } + /** * Function processKeystroke * Scope Private diff --git a/common/core/web/keyboard-processor/src/text/keyboardProcessor.ts b/common/core/web/keyboard-processor/src/text/keyboardProcessor.ts index f419d33716..7b2ced989f 100644 --- a/common/core/web/keyboard-processor/src/text/keyboardProcessor.ts +++ b/common/core/web/keyboard-processor/src/text/keyboardProcessor.ts @@ -207,6 +207,19 @@ namespace com.keyman.text { } } + processNewContextEvent(device: utils.DeviceSpec, outputTarget: OutputTarget): RuleBehavior { + // Pass this key code and state to the keyboard program + if(!this.activeKeyboard) { + return null; + } + let keyEvent = new KeyEvent(); + keyEvent.Lcode = 0; + keyEvent.kName = ''; + keyEvent.device = device; + this.setSyntheticEventDefaults(keyEvent); + return this.keyboardInterface.processNewContextEvent(outputTarget, keyEvent); + } + processKeystroke(keyEvent: KeyEvent, outputTarget: OutputTarget): RuleBehavior { var matchBehavior: RuleBehavior; diff --git a/web/source/dom/domEventHandlers.ts b/web/source/dom/domEventHandlers.ts index aa362e3a3f..ae586a73d6 100644 --- a/web/source/dom/domEventHandlers.ts +++ b/web/source/dom/domEventHandlers.ts @@ -3,19 +3,19 @@ namespace com.keyman.dom { /* - * Note that for many of the actual events represented by methods in this file, `this` is replaced + * Note that for many of the actual events represented by methods in this file, `this` is replaced * automatically by JavaScript's event handling system. As such, many 'wrapper' variants of the events * exist to restore the object-oriented hierarchy below. - * + * */ export class CommonDOMStates { _DisableInput: boolean = false; // Should input be disabled? - _IgnoreNextSelChange: number = 0; // when a visual keyboard key is mouse-down, ignore the next sel change because this stuffs up our history + _IgnoreNextSelChange: number = 0; // when a visual keyboard key is mouse-down, ignore the next sel change because this stuffs up our history _IgnoreBlurFocus: boolean = false; // Used to temporarily ignore focus changes _Selection = null; _SelectionControl: any = null; // Type behavior is as with activeElement and the like. - + _activeElement: HTMLElement; _lastActiveElement: HTMLElement; @@ -43,7 +43,7 @@ namespace com.keyman.dom { // TODO: resolve/refactor out! protected keyman: KeymanBase; - // This is only static within a given initialization of KeymanWeb. Perhaps it would be best as an initialization + // This is only static within a given initialization of KeymanWeb. Perhaps it would be best as an initialization // parameter and member field? static states: CommonDOMStates = new CommonDOMStates(); @@ -53,12 +53,12 @@ namespace com.keyman.dom { /** * Handle receiving focus by simulated input field - */ + */ setFocus: (e?: TouchEvent|MSPointerEvent) => void = function(e?: TouchEvent|MSPointerEvent): void { // Touch-only handler. }.bind(this); - - /** + + /** * Handles touch-based loss of focus events. */ setBlur: (e: FocusEvent) => void = function(e: FocusEvent) { @@ -71,8 +71,8 @@ namespace com.keyman.dom { //TODO: add more complete description of what ControlFocus really does /** - * Respond to KeymanWeb-aware input element receiving focus - */ + * Respond to KeymanWeb-aware input element receiving focus + */ _ControlFocus: (e: FocusEvent) => boolean = function(this: DOMEventHandlers, e: FocusEvent): boolean { var Ltarg: HTMLElement; var device = this.keyman.util.device; @@ -87,7 +87,7 @@ namespace com.keyman.dom { if(Ltarg['body']) { Ltarg = Ltarg['body']; // Occurs in Firefox for design-mode iframes. } - + // Prevent any action if a protected input field if(device.touchable && (Ltarg.className == null || Ltarg.className.indexOf('keymanweb-input') < 0)) { return true; @@ -102,7 +102,7 @@ namespace com.keyman.dom { } } else if(Ltarg.ownerDocument && Ltarg.ownerDocument.designMode == 'on') { // continue; don't block this one! - } else if((device.touchable || !Ltarg.isContentEditable) + } else if((device.touchable || !Ltarg.isContentEditable) && !(Ltarg.ownerDocument && Ltarg instanceof Ltarg.ownerDocument.defaultView.HTMLTextAreaElement)) { return true; } @@ -113,7 +113,7 @@ namespace com.keyman.dom { if (Ltarg.nodeType == 3) { // defeat Safari bug Ltarg = Ltarg.parentNode as HTMLElement; } - + var LfocusTarg = Ltarg; // Ensure that focussed element is visible above the keyboard @@ -122,7 +122,7 @@ namespace com.keyman.dom { (this as DOMTouchHandlers).scrollBody(Ltarg); } } - + if(Ltarg.ownerDocument && Ltarg instanceof Ltarg.ownerDocument.defaultView.HTMLIFrameElement) { //**TODO: check case reference this.keyman.domManager._AttachToIframe(Ltarg as HTMLIFrameElement); Ltarg=Ltarg.contentWindow.document.body; @@ -131,8 +131,8 @@ namespace com.keyman.dom { // Must set before _Blur / _Focus to avoid infinite recursion due to complications // in setActiveKeyboard behavior with managed keyboard settings. this.keyman.domManager.lastActiveElement = Ltarg; - this.keyman.domManager.activeElement = Ltarg; // I3363 (Build 301) - + this.keyman.domManager.activeElement = Ltarg; // I3363 (Build 301) + if(this.keyman.uiManager.justActivated) { this._BlurKeyboardSettings(Ltarg); } else { @@ -160,9 +160,9 @@ namespace com.keyman.dom { * Scope Private * @param {Object} _target element gaining focus * @param {Object} _activeControl currently active control - * @return {boolean} + * @return {boolean} * Description Execute external (UI) code needed on focus - */ + */ doControlFocused(_target: HTMLElement, _activeControl: HTMLElement): boolean { var p={}; p['target']=_target; @@ -173,9 +173,9 @@ namespace com.keyman.dom { /** * Respond to KMW losing focus on event - */ + */ _ControlBlur: (e: FocusEvent) => boolean = function(this: DOMEventHandlers, e: FocusEvent): boolean { - var Ltarg: HTMLElement; + var Ltarg: HTMLElement; e = this.keyman._GetEventObject(e); // I2404 - Manage IE events in IFRAMEs Ltarg = this.keyman.util.eventTarget(e) as HTMLElement; @@ -187,7 +187,7 @@ namespace com.keyman.dom { Ltarg = Ltarg['body']; // Occurs in Firefox for design-mode iframes. } - // Makes sure we properly detect the TouchAliasElement root, + // Makes sure we properly detect the TouchAliasElement root, // rather than one of its constituent children. if(this.keyman.util.device.touchable) { Ltarg = findTouchAliasTarget(Ltarg); @@ -209,7 +209,7 @@ namespace com.keyman.dom { let lastAlias = this.keyman.domManager.activeElement; lastAlias.hideCaret(); } - + if (Ltarg.nodeType == 3) { // defeat Safari bug Ltarg = Ltarg.parentNode as HTMLElement; } @@ -219,7 +219,7 @@ namespace com.keyman.dom { Ltarg=Ltarg.contentWindow.frameElement as HTMLElement; } } - + ////keymanweb._SelectionControl = null; if(this.keyman.domManager.lastActiveElement) { this._BlurKeyboardSettings(this.keyman.domManager.lastActiveElement); @@ -233,7 +233,7 @@ namespace com.keyman.dom { * the user is manually specifying languages on a per-control basis. */ this.keyman.uiManager.justActivated = false; - + var isActivating = this.keyman.uiManager.isActivating; let activeKeyboard = com.keyman.singleton.core.activeKeyboard; if(!isActivating && activeKeyboard) { @@ -255,9 +255,9 @@ namespace com.keyman.dom { * @param {Object} _target element losing focus * @param {Event} _event event object * @param {(boolean|number)} _isActivating activation state - * @return {boolean} + * @return {boolean} * Description Execute external (UI) code needed on blur - */ + */ doControlBlurred(_target: HTMLElement, _event: Event, _isActivating: boolean|number): boolean { var p={}; p['target']=_target; @@ -275,7 +275,7 @@ namespace com.keyman.dom { _BlurKeyboardSettings(lastElem: HTMLElement, PInternalName?: string, PLgCode?: string) { var keyboardID = this.keyman.core.activeKeyboard ? this.keyman.core.activeKeyboard.id : ''; var langCode = this.keyman.keyboardManager.getActiveLanguage(); - + if(PInternalName !== undefined && PLgCode !== undefined) { keyboardID = PInternalName; langCode = PLgCode; @@ -296,7 +296,7 @@ namespace com.keyman.dom { * Description Restores the newly active element's keyboard settings. Should be called * whenever a KMW-enabled page element gains control, but only once the prior * element's loss of control is guaranteed. - */ + */ _FocusKeyboardSettings(lastElem: HTMLElement, blockGlobalChange: boolean) { if(lastElem && lastElem._kmwAttachment.keyboard != null) { this.keyman.keyboardManager.setActiveKeyboard(lastElem._kmwAttachment.keyboard, @@ -314,10 +314,10 @@ namespace com.keyman.dom { /** * Function _CommonFocusHelper - * @param {Element} target + * @param {Element} target * @returns {boolean} - * Description Performs common state management for the various focus events of KeymanWeb. - * The return value indicates whether (true) or not (false) the calling event handler + * Description Performs common state management for the various focus events of KeymanWeb. + * The return value indicates whether (true) or not (false) the calling event handler * should be terminated immediately after the call. */ _CommonFocusHelper(target: HTMLElement): boolean { @@ -328,23 +328,25 @@ namespace com.keyman.dom { if(target.ownerDocument && target instanceof target.ownerDocument.defaultView.HTMLIFrameElement) { if(!this.keyman.domManager._IsIEEditableIframe(target, 1) || !this.keyman.domManager._IsMozillaEditableIframe(target, 1)) { - DOMEventHandlers.states._DisableInput = true; + DOMEventHandlers.states._DisableInput = true; return true; } } - DOMEventHandlers.states._DisableInput = false; + DOMEventHandlers.states._DisableInput = false; + + const outputTarget = dom.Utils.getOutputTarget(target); let activeKeyboard = keyman.core.activeKeyboard; if(!uiManager.justActivated) { - if(target && Utils.getOutputTarget(target)) { - Utils.getOutputTarget(target).deadkeys().clear(); + if(target && outputTarget) { + outputTarget.deadkeys().clear(); } - + if(activeKeyboard) { - activeKeyboard.notify(0, Utils.getOutputTarget(target), 1); // I2187 + activeKeyboard.notify(0, outputTarget, 1); // I2187 } } - + if(!uiManager.justActivated && DOMEventHandlers.states._SelectionControl != target) { uiManager.isActivating = false; } @@ -352,8 +354,20 @@ namespace com.keyman.dom { DOMEventHandlers.states._SelectionControl = target; + if(target && outputTarget) { + // + // Call the current keyboard's newContext handler; + // timeout is required in order to get the current + // selection, swhich is not ready at time of focus event + // + window.setTimeout(() => { + //console.log('processNewContextEvent called from focus'); + com.keyman.singleton.core.processNewContextEvent(outputTarget); + }); + } + if(keyman.core.languageProcessor.isActive) { - keyman.core.languageProcessor.predictFromTarget(Utils.getOutputTarget(target)); + keyman.core.languageProcessor.predictFromTarget(outputTarget); } return false; } @@ -361,12 +375,12 @@ namespace com.keyman.dom { /** * Function _SelectionChange * Scope Private - * Description Respond to selection change event + * Description Respond to selection change event */ _SelectionChange: () => boolean = function(this: DOMEventHandlers): boolean { if(DOMEventHandlers.states._IgnoreNextSelChange) { DOMEventHandlers.states._IgnoreNextSelChange--; - } + } return true; }.bind(this); @@ -374,12 +388,12 @@ namespace com.keyman.dom { /** * Function _KeyDown * Scope Private - * Description Processes keydown event and passes data to keyboard. - * + * Description Processes keydown event and passes data to keyboard. + * * Note that the test-case oriented 'recorder' stubs this method to facilitate keystroke * recording for use in test cases. If changing this function, please ensure the recorder is * not affected. - */ + */ _KeyDown: (e: KeyboardEvent) => boolean = function(this: DOMEventHandlers, e: KeyboardEvent): boolean { var activeKeyboard = this.keyman.core.activeKeyboard; var util = this.keyman.util; @@ -395,7 +409,7 @@ namespace com.keyman.dom { return true; } } else if(el && el.className.indexOf('kmw-disabled') >= 0) { - return true; + return true; } return PreProcessor.keyDown(e); @@ -421,11 +435,23 @@ namespace com.keyman.dom { DOMEventHandlers.states.changed = false; } + _Click: (e: MouseEvent) => boolean = function(this: DOMEventHandlers, e: MouseEvent): boolean { + let target = e.target as HTMLElement; + if(target && target['base']) { + target = target['base']; + } + + //console.log('processNewContextEvent called from click'); + com.keyman.singleton.core.processNewContextEvent(dom.Utils.getOutputTarget(target)); + + return true; + }.bind(this); + /** * Function _KeyPress * Scope Private * Description Processes keypress event (does not pass data to keyboard) - */ + */ _KeyPress: (e: KeyboardEvent) => boolean = function(this: DOMEventHandlers, e: KeyboardEvent): boolean { if(DOMEventHandlers.states._DisableInput || this.keyman.core.activeKeyboard == null) { return true; @@ -438,7 +464,7 @@ namespace com.keyman.dom { * Function _KeyUp * Scope Private * Description Processes keyup event and passes event data to keyboard - */ + */ _KeyUp: (e: KeyboardEvent) => boolean = function(this: DOMEventHandlers, e: KeyboardEvent): boolean { var osk = this.keyman.osk; @@ -457,7 +483,7 @@ namespace com.keyman.dom { if(outputTarget instanceof inputEle.ownerDocument.defaultView.HTMLTextAreaElement) { ignore = true; } - + if(inputEle.base && inputEle.base instanceof inputEle.base.ownerDocument.defaultView.HTMLTextAreaElement) { ignore = true; } @@ -473,8 +499,8 @@ namespace com.keyman.dom { } return true; } - } - + } + return PreProcessor.keyUp(e); }.bind(this); } @@ -490,7 +516,7 @@ namespace com.keyman.dom { y: number; }; - + constructor(keyman: KeymanBase) { super(keyman); } @@ -510,7 +536,7 @@ namespace com.keyman.dom { // whether or not individual `Touch`es may be related to this specific event for // an ongoing multitouch scenario. let target = e.target; - + // Find the first touch affected by this event that matches the current target. for(let i=0; i < e.changedTouches.length; i++) { if(isValidTouch(e.changedTouches[i], target)) { @@ -524,9 +550,9 @@ namespace com.keyman.dom { } /** - * Handle receiving focus by simulated input field - * - */ + * Handle receiving focus by simulated input field + * + */ setFocus: (e?: TouchEvent|MSPointerEvent) => void = function(this: DOMTouchHandlers, e?: TouchEvent|MSPointerEvent): void { DOMEventHandlers.states.setFocusTimer(); @@ -580,7 +606,7 @@ namespace com.keyman.dom { // Some parts rely upon the scroller element. let scroller = target.firstChild as HTMLElement; - // Move the caret and refocus if necessary + // Move the caret and refocus if necessary if(this.keyman.domManager.activeElement != target) { // Hide the KMW caret let prevTarget = this.keyman.domManager.activeElement; @@ -600,26 +626,26 @@ namespace com.keyman.dom { // 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 - } - + } + // Correct element directionality if required - this.keyman.domManager._SetTargDir(target); - + this.keyman.domManager._SetTargDir(target); + // If clicked on DIV on the main element, rather than any part of the text representation, // set caret to end of text if(tTarg && tTarg == target) { var x,cp; - x=dom.Utils.getAbsoluteX(scroller.firstChild as HTMLElement); - if(target.dir == 'rtl') { - x += (scroller.firstChild as HTMLElement).offsetWidth; + x=dom.Utils.getAbsoluteX(scroller.firstChild as HTMLElement); + if(target.dir == 'rtl') { + x += (scroller.firstChild as HTMLElement).offsetWidth; cp=(touchX > x ? 0 : 100000); } else { cp=(touchX touchY && cp > cpMin && cp != cpMax) {cpMax=cp; cp=Math.round((cp+cpMin)/2);} else if(y < touchY-yRow && cp < cpMax && cp != cpMin) {cpMin=cp; cp=Math.round((cp+cpMax)/2);} else break; @@ -656,21 +682,21 @@ namespace com.keyman.dom { var snapOrder; if(target.dir == 'rtl') { // I would use arrow functions, but IE doesn't like 'em. snapOrder = function(a, b) { - return a < b; + return a < b; }; } else { - snapOrder = function(a, b) { - return a > b; + snapOrder = function(a, b) { + return a > b; }; } for(iLoop=0; iLoop<16; iLoop++) { - x=dom.Utils.getAbsoluteX(caret); //left of caret + x=dom.Utils.getAbsoluteX(caret); //left of caret if(snapOrder(x, touchX) && cp > cpMin && cp != cpMax) { - cpMax=cp; + cpMax=cp; cp=Math.round((cp+cpMin)/2); } else if(!snapOrder(x, touchX) && cp < cpMax && cp != cpMin) { - cpMin=cp; + cpMin=cp; cp=Math.round((cp+cpMax)/2); } else { break; @@ -691,7 +717,7 @@ namespace com.keyman.dom { * for controls, we have to act here to preserve the outgoing control's keyboard settings. * * If we 'just activated' the KeymanWeb UI, we need to save the new keyboard change as appropriate. - */ + */ if(this.keyman.domManager.lastActiveElement) { this._BlurKeyboardSettings(this.keyman.domManager.lastActiveElement); } @@ -705,7 +731,7 @@ namespace com.keyman.dom { * If not, we need to activate the control's preferred keyboard. */ this._FocusKeyboardSettings(target, false); - + // Always do the common focus stuff, instantly returning if we're in an editable iframe. // This parallels the if-statement in _ControlFocus - it may be needed as this if-statement in the future, // despite its present redundancy. @@ -716,15 +742,15 @@ namespace com.keyman.dom { /** * Close OSK and remove simulated caret on losing focus - */ + */ cancelInput(): void { - this.keyman.domManager.activeElement = null; + this.keyman.domManager.activeElement = null; this.keyman.domManager.lastActiveElement = null; this.keyman.osk.hideNow(); }; /** - * Handle losing focus from simulated input field + * Handle losing focus from simulated input field */ setBlur: (e: FocusEvent) => void = function(this: DOMTouchHandlers, e: FocusEvent) { // This works OK for iOS, but may need something else for other platforms @@ -734,7 +760,7 @@ namespace com.keyman.dom { elem = e.relatedTarget as HTMLElement; } - this.executeBlur(elem); + this.executeBlur(elem); }.bind(this); executeBlur(elem: HTMLElement) { @@ -743,7 +769,7 @@ namespace com.keyman.dom { if(elem) { this.doChangeEvent(elem); if(elem.nodeName != 'DIV' || elem.className.indexOf('keymanweb-input') == -1) { - this.cancelInput(); + this.cancelInput(); return; } } @@ -756,7 +782,7 @@ namespace com.keyman.dom { /** * Display and position a scrollbar in the input field if needed - * + * * @param {Object} e input DIV element (copy of INPUT or TEXTAREA) */ setScrollBar(e: HTMLElement) { @@ -767,21 +793,21 @@ namespace com.keyman.dom { sbs.width=100*(e.offsetWidth/scroller.offsetWidth)+'%'; sbs.left=100*(-scroller.offsetLeft/scroller.offsetWidth)+'%'; sbs.top='0'; - sbs.visibility='visible'; + sbs.visibility='visible'; } else if(scroller.offsetHeight > e.offsetHeight || scroller.offsetTop < 0) { sbs.width='4px'; sbs.height=100*(e.offsetHeight/scroller.offsetHeight)+'%'; sbs.top=100*(-scroller.offsetTop/scroller.offsetHeight)+'%'; - sbs.left='0'; - sbs.visibility='visible'; + sbs.left='0'; + sbs.visibility='visible'; } else { sbs.visibility='hidden'; } - } + } /** * Handle the touch move event for an input element - */ + */ dragInput: (e: TouchEvent|MouseEvent) => void = function(this: DOMTouchHandlers, e: TouchEvent|MouseEvent) { // Prevent dragging window if(e.cancelable) { @@ -789,12 +815,12 @@ namespace com.keyman.dom { // Tends to result in a spam of console errors when e.cancelable == false. e.preventDefault(); } - e.stopPropagation(); + e.stopPropagation(); // Identify the target from the touch list or the event argument (IE 10 only) var target: HTMLElement; let touch: Touch; - + if(dom.Utils.instanceof(e, "TouchEvent")) { try { touch=DOMTouchHandlers.selectTouch(e as TouchEvent); @@ -809,7 +835,7 @@ namespace com.keyman.dom { if(target == null) { return; } - + // Identify the input element from the touch event target (touched element may be contained by input) target = findTouchAliasTarget(target); @@ -826,22 +852,22 @@ namespace com.keyman.dom { x = (e as MouseEvent).screenX; y = (e as MouseEvent).screenY; } - + // Allow content of input elements to be dragged horizontally or vertically if(typeof this.firstTouch == 'undefined' || this.firstTouch == null) { this.firstTouch={x:x,y:y}; } else { var x0=this.firstTouch.x,y0=this.firstTouch.y, scroller=target.firstChild as HTMLElement,dx,dy,x1; - + if(target.base.nodeName == 'TEXTAREA') { var yOffset=parseInt(scroller.style.top,10); if(isNaN(yOffset)) yOffset=0; dy=y0-y; if(dy < -4 || dy > 4) { scroller.style.top=(yOffset xMin) x1=xMin; if(x1 < xMax) x1=xMax; scroller.style.left=x1+'px'; - this.firstTouch.x=x; - } + this.firstTouch.x=x; + } } } // Should refactor to use TouchAliasElement's version; target is an instance of the class. @@ -865,9 +891,9 @@ namespace com.keyman.dom { /** * Scroll the document body vertically to bring the active input into view - * + * * @param {Object} e simulated input field object being focussed - */ + */ scrollBody(e: HTMLElement): void { var osk = this.keyman.osk; @@ -882,7 +908,7 @@ namespace com.keyman.dom { } else { dy=y-t-(window.innerHeight-osk._Box.offsetHeight-s2.offsetHeight-2); if(dy < 0) dy=0; - } + } // Hide OSK, then scroll, then re-anchor OSK with absolute position (on end of scroll event) if(dy != 0) { window.scrollTo(0,dy+window.pageYOffset); diff --git a/web/source/dom/domManager.ts b/web/source/dom/domManager.ts index a4e2c8a79e..4820bc0d65 100644 --- a/web/source/dom/domManager.ts +++ b/web/source/dom/domManager.ts @@ -58,8 +58,8 @@ namespace com.keyman.dom { enablementObserver: MutationObserver; /** - * Tracks a list of event-listening elements. - * + * Tracks a list of event-listening elements. + * * In touch mode, this should contain touch-aliasing DIVs, but will contain other elements in non-touch mode. */ inputList: HTMLElement[] = []; // List of simulated input divisions for touch-devices I3363 (Build 301) @@ -70,7 +70,7 @@ namespace com.keyman.dom { sortedInputs: HTMLElement[] = []; // List of all INPUT and TEXTAREA elements ordered top to bottom, left to right _BeepObjects: BeepData[] = []; // BeepObjects - maintains a list of active 'beep' visual feedback elements - _BeepTimeout: number = 0; // BeepTimeout - a flag indicating if there is an active 'beep'. + _BeepTimeout: number = 0; // BeepTimeout - a flag indicating if there is an active 'beep'. // Set to 1 if there is an active 'beep', otherwise leave as '0'. // Used for special touch-based page interactions re: element activation on touch devices. @@ -84,7 +84,7 @@ namespace com.keyman.dom { constructor(keyman: KeymanBase) { this.keyman = keyman; - + if(keyman.util.device.touchable) { this.touchHandlers = new DOMTouchHandlers(keyman); } @@ -101,7 +101,7 @@ namespace com.keyman.dom { if(this.attachmentObserver) { this.attachmentObserver.disconnect(); } - + for(let input of this.inputList) { this.disableInputElement(input); } @@ -127,7 +127,7 @@ namespace com.keyman.dom { * Scope Public * @param {Object} Pelem element to flash * Description Flash body as substitute for audible beep; notify embedded device to vibrate - */ + */ doBeep(outputTarget: targets.OutputTarget) { // Handles embedded-mode beeps. let keyman = com.keyman.singleton; @@ -149,7 +149,7 @@ namespace com.keyman.dom { if(!Pelem) { return; // There's no way to signal a 'beep' to null, so just cut everything short. } - + if(!Pelem.style || typeof(Pelem.style.backgroundColor)=='undefined') { return; } @@ -160,7 +160,7 @@ namespace com.keyman.dom { return; } } - + this._BeepObjects = com.keyman.singleton._push(this._BeepObjects, new BeepData(Pelem)); // TODO: This is probably a bad color choice if "dark mode" is enabled. A proper implementation // would probably require some 'fun' CSS work, though. @@ -172,10 +172,10 @@ namespace com.keyman.dom { } /** - * Function beepReset + * Function beepReset * Scope Public * Description Reset/terminate beep or flash (not currently used: Aug 2011) - */ + */ beepReset(): void { com.keyman.singleton.core.keyboardInterface.resetContextCache(); @@ -191,7 +191,7 @@ namespace com.keyman.dom { * Function getHandlers * Scope Private * @param {Element} Pelem An input, textarea, or touch-alias element from the page. - * @returns {Object} + * @returns {Object} */ getHandlers(Pelem: HTMLElement): DOMEventHandlers { var _attachObj = Pelem.base ? Pelem.base._kmwAttachment : Pelem._kmwAttachment; @@ -213,9 +213,9 @@ namespace com.keyman.dom { * an outer DIV, matching the position, size and style of the base element * a scrollable DIV within that outer element * two SPAN elements within the scrollable DIV, to hold the text before and after the caret - * - * The left border of the second SPAN is flashed on and off as a visible caret - * + * + * The left border of the second SPAN is flashed on and off as a visible caret + * * Also ensures the element is registered on keymanweb's internal input list. */ enableTouchElement(Pelem: HTMLElement) { @@ -229,9 +229,9 @@ namespace com.keyman.dom { return false; } else { // Initialize and protect input elements for touch-screen devices (but never for apps) - // NB: now set disabled=true rather than readonly, since readonly does not always + // NB: now set disabled=true rather than readonly, since readonly does not always // prevent element from getting focus, e.g. within a LABEL element. - // c.f. http://kreotekdev.wordpress.com/2007/11/08/disabled-vs-readonly-form-fields/ + // c.f. http://kreotekdev.wordpress.com/2007/11/08/disabled-vs-readonly-form-fields/ Pelem.kmwInput = true; } @@ -249,7 +249,7 @@ namespace com.keyman.dom { } this.inputList.push(Pelem['kmw_ip']); - + console.log("Unexpected state - this element's simulated input DIV should have been removed from the page!"); return true; // May need setup elsewhere since it's just been re-added! @@ -263,18 +263,18 @@ namespace com.keyman.dom { this.setupElementAttachment(x); // The touch-alias should have its own wrapper. } Pelem._kmwAttachment = x._kmwAttachment; // It's an object reference we need to alias. - + // Set font for base element this.enableInputElement(x, true); - // Superimpose custom input fields for each input or textarea, unless readonly or disabled - + // Superimpose custom input fields for each input or textarea, unless readonly or disabled + // On touch event, reposition the text caret and prepare for OSK input - // Removed 'onfocus=' as that resulted in handling the event twice (on iOS, anyway) + // Removed 'onfocus=' as that resulted in handling the event twice (on iOS, anyway) // We know this to be the correct set of handlers because we're setting up a touch element. var touchHandlers = this.touchHandlers; - + x.addEventListener('touchstart', touchHandlers.setFocus); x.onmspointerdown=function(e: MSPointerEvent) { e.preventDefault(); @@ -289,14 +289,14 @@ namespace com.keyman.dom { x.onmspointerup=function(e) { e.stopPropagation(); }; - - // Disable internal scroll when input element in focus + + // Disable internal scroll when input element in focus x.addEventListener('touchmove', touchHandlers.dragInput, false); x.onmspointermove=touchHandlers.dragInput; - + // Hide keyboard and caret when losing focus from simulated input field x.onblur=touchHandlers.setBlur; - + // Note that touchend event propagates and is processed by body touchend handler // re-setting the first touch point for a drag @@ -331,7 +331,7 @@ namespace com.keyman.dom { // Disable touch-related handling code. this.disableInputElement(Pelem['kmw_ip']); Pelem._kmwAttachment.interface = dom.targets.wrapElement(Pelem); - + // We get weird repositioning errors if we don't remove our simulated input element - and permanently. if(Pelem.parentNode) { Pelem.parentNode.removeChild(Pelem['kmw_ip']); @@ -342,7 +342,7 @@ namespace com.keyman.dom { this.setupNonKMWTouchElement(Pelem); } - /** + /** * Function nonKMWTouchHandler * Scope Private * Description A handler for KMW-touch-disabled elements when operating on touch devices. @@ -377,8 +377,8 @@ namespace com.keyman.dom { * Note that this method is called for both desktop and touch control routes; the touch route calls it from within * 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) { + */ + enableInputElement(Pelem: HTMLElement, isAlias?: boolean) { var baseElement = isAlias ? Pelem['base'] : Pelem; if(!this.isKMWDisabled(baseElement)) { @@ -394,14 +394,15 @@ namespace com.keyman.dom { 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); // These need to be on the actual input element, as otherwise the keyboard will disappear on touch. Pelem.onkeypress = this.getHandlers(Pelem)._KeyPress; Pelem.onkeydown = this.getHandlers(Pelem)._KeyDown; - Pelem.onkeyup = this.getHandlers(Pelem)._KeyUp; + Pelem.onkeyup = this.getHandlers(Pelem)._KeyUp; } - } - }; + } + }; /** * Function disableInputElement @@ -410,18 +411,18 @@ namespace com.keyman.dom { * @param {boolean=} isAlias A flag that indicates if the element is a simulated input element for touch. * Description Inverts the process of enableInputElement, removing all event-handling from the element. * Note that the 'kmw-disabled' property is managed by the MutationObserver and by the surface API calls. - */ - disableInputElement(Pelem: HTMLElement, isAlias?: boolean) { + */ + disableInputElement(Pelem: HTMLElement, isAlias?: boolean) { if(!Pelem) { return; } - + var baseElement = isAlias ? Pelem['base'] : Pelem; // Do NOT test for pre-disabledness - we also use this to fully detach without officially 'disabling' via kmw-disabled. if((Pelem.ownerDocument.defaultView && Pelem instanceof Pelem.ownerDocument.defaultView.HTMLIFrameElement) || Pelem instanceof HTMLIFrameElement) { this._DetachFromIframe(Pelem); - } else { + } else { var cnIndex = baseElement.className.indexOf('keymanweb-font'); if(cnIndex > 0 && !isAlias) { // See note about the alias below. baseElement.className = baseElement.className.replace('keymanweb-font', '').trim(); @@ -436,11 +437,12 @@ namespace com.keyman.dom { if(!isAlias) { // See note about the alias below. this.keyman.util.detachDOMEvent(baseElement,'focus', this.getHandlers(Pelem)._ControlFocus); this.keyman.util.detachDOMEvent(baseElement,'blur', this.getHandlers(Pelem)._ControlBlur); + this.keyman.util.detachDOMEvent(baseElement,'click', this.getHandlers(Pelem)._Click); } // These need to be on the actual input element, as otherwise the keyboard will disappear on touch. Pelem.onkeypress = null; Pelem.onkeydown = null; - Pelem.onkeyup = null; + Pelem.onkeyup = null; } // If we're disabling an alias, we should fully enable the base version. (Thinking ahead to toggleable-touch mode.) @@ -449,7 +451,7 @@ namespace com.keyman.dom { baseElement.onkeypress = this.getHandlers(Pelem)._KeyPress; baseElement.onkeydown = this.getHandlers(Pelem)._KeyDown; - baseElement.onkeyup = this.getHandlers(Pelem)._KeyUp; + baseElement.onkeyup = this.getHandlers(Pelem)._KeyUp; } var lastElem = this.lastActiveElement; @@ -460,7 +462,7 @@ namespace com.keyman.dom { this.lastActiveElement = null; this.keyman.osk.startHide(false); } - + return; }; @@ -470,7 +472,7 @@ namespace com.keyman.dom { * @param {Element} x An element from the page. * @return {boolean} true if the element's properties indicate a 'disabled' state. * Description Examines attachable elements to determine their default enablement state. - */ + */ isKMWDisabled(x: HTMLElement): boolean { var c = x.className; @@ -481,15 +483,15 @@ namespace com.keyman.dom { return true; } - return false; + return false; } /** * Function attachToControl * Scope Public * @param {Element} Pelem Element to which KMW will be attached - * Description Attaches KMW to control (or IFrame) - */ + * Description Attaches KMW to control (or IFrame) + */ attachToControl(Pelem: HTMLElement) { var touchable = this.keyman.util.device.touchable; @@ -519,8 +521,8 @@ namespace com.keyman.dom { * Function detachFromControl * Scope Public * @param {Element} Pelem Element from which KMW will detach - * Description Detaches KMW from a control (or IFrame) - */ + * Description Detaches KMW from a control (or IFrame) + */ detachFromControl(Pelem: HTMLElement) { if(!(this.isAttached(Pelem) || Pelem instanceof Pelem.ownerDocument.defaultView.HTMLIFrameElement)) { return; // We never were attached. @@ -531,10 +533,10 @@ namespace com.keyman.dom { // Is it already disabled? if(!this.isKMWDisabled(Pelem)) { this._DisableControl(Pelem); - } + } } - // #2 - clear attachment data. + // #2 - clear attachment data. this.clearElementAttachment(Pelem); } @@ -555,7 +557,7 @@ namespace com.keyman.dom { * @return {boolean} true if the element is viable for KMW attachment. * Description Examines potential input elements to determine whether or not they are viable for KMW attachment. * Also filters elements not supported for touch devices when device.touchable == true. - */ + */ isKMWInput(x: HTMLElement): boolean { var touchable = this.keyman.util.device.touchable; @@ -573,8 +575,8 @@ namespace com.keyman.dom { } } // else nothing? } - catch(err) { - /* Do not attempt to access iframes outside this site */ + catch(err) { + /* Do not attempt to access iframes outside this site */ console.warn("Error during attachment to / detachment from iframe: "); console.warn(err); } @@ -582,7 +584,7 @@ namespace com.keyman.dom { return true; } - return false; + return false; } /** @@ -590,7 +592,7 @@ namespace com.keyman.dom { * Scope Private * @param {Element} x An element from the page valid for KMW attachment * Description Establishes the base KeymanWeb data for newly-attached elements. - * Does not establish input hooks, which are instead handled during enablement. + * Does not establish input hooks, which are instead handled during enablement. */ setupElementAttachment(x: HTMLElement) { // The `_kmwAttachment` property tag maintains all relevant KMW-maintained data regarding the element. @@ -617,7 +619,7 @@ namespace com.keyman.dom { * Scope Private * @param {Element} x An element from the page valid for KMW attachment * Description Establishes the base KeymanWeb data for newly-attached elements. - * Does not establish input hooks, which are instead handled during enablement. + * Does not establish input hooks, which are instead handled during enablement. */ clearElementAttachment(x: HTMLElement) { // We need to clear the object when de-attaching; helps prevent memory leaks. @@ -628,11 +630,11 @@ namespace com.keyman.dom { * Function _AttachToIframe * Scope Private * @param {Element} Pelem IFrame to which KMW will be attached - * Description Attaches KeymanWeb to IFrame - */ + * Description Attaches KeymanWeb to IFrame + */ _AttachToIframe(Pelem: HTMLIFrameElement) { var util = this.keyman.util; - + try { var Lelem=Pelem.contentWindow.document; /* editable Iframe */ @@ -662,15 +664,15 @@ namespace com.keyman.dom { catch(err) { // do not attempt to attach to the iframe as it is from another domain - XSS denied! - } + } } /** * Function _DetachFromIframe * Scope Private * @param {Element} Pelem IFrame to which KMW will be attached - * Description Detaches KeymanWeb from an IFrame - */ + * Description Detaches KeymanWeb from an IFrame + */ _DetachFromIframe(Pelem: HTMLIFrameElement) { var util = this.keyman.util; @@ -703,7 +705,7 @@ namespace com.keyman.dom { catch(err) { // do not attempt to attach to the iframe as it is from another domain - XSS denied! - } + } } /** @@ -735,9 +737,9 @@ namespace com.keyman.dom { * Function LiTmp * Scope Private * @param {string} _colon type of element - * @return {Array} array of elements of specified type + * @return {Array} array of elements of specified type * Description Local function to get list of editable controls - */ + */ var LiTmp = function(_colon: string): HTMLElement[] { return util.arrayFromNodeList(Pelem.getElementsByTagName(_colon)); }; @@ -745,12 +747,12 @@ namespace com.keyman.dom { // Note that isKMWInput() will block IFRAME elements as necessary for touch-based devices. possibleInputs = possibleInputs.concat(LiTmp('INPUT'), LiTmp('TEXTAREA'), LiTmp('IFRAME')); } - + // Not all active browsers may support the method, but only those that do would work with contenteditables anyway. if(Pelem.querySelectorAll) { possibleInputs = possibleInputs.concat(util.arrayFromNodeList(Pelem.querySelectorAll('[contenteditable]'))); } - + if(Pelem.ownerDocument && Pelem instanceof Pelem.ownerDocument.defaultView.HTMLElement && Pelem.isContentEditable) { possibleInputs.push(Pelem); } @@ -795,13 +797,13 @@ namespace com.keyman.dom { /** * Set target element text direction (LTR or RTL), but only if the element is empty - * + * * If the element base directionality is changed after it contains content, unless all the text * has the same directionality, text runs will be re-ordered which is confusing and causes * incorrect caret positioning - * + * * @param {Object} Ptarg Target element - */ + */ _SetTargDir(Ptarg: HTMLElement) { let activeKeyboard = com.keyman.singleton.core.activeKeyboard; var elDir=(activeKeyboard && activeKeyboard.isRTL) ? 'rtl' : 'ltr'; @@ -814,7 +816,7 @@ namespace com.keyman.dom { alias.setTextCaret(10000); } } else { - if(Ptarg instanceof Ptarg.ownerDocument.defaultView.HTMLInputElement + if(Ptarg instanceof Ptarg.ownerDocument.defaultView.HTMLInputElement || Ptarg instanceof Ptarg.ownerDocument.defaultView.HTMLTextAreaElement) { if((Ptarg as HTMLInputElement|HTMLTextAreaElement).value.length == 0) { Ptarg.dir=elDir; @@ -830,8 +832,8 @@ namespace com.keyman.dom { * Function _DisableControl * Scope Private * @param {Element} Pelem Element to be disabled - * Description Disable KMW control element - */ + * Description Disable KMW control element + */ _DisableControl(Pelem: HTMLElement) { // Only operate on attached elements! Non-design-mode IFrames don't get attachment markers, so we check them specifically instead. if(this.isAttached(Pelem) || Pelem instanceof Pelem.ownerDocument.defaultView.HTMLIFrameElement) { @@ -840,7 +842,7 @@ namespace com.keyman.dom { this.setupNonKMWTouchElement(Pelem); var keyman = this.keyman; - + // If a touch alias was removed, chances are it's gonna mess up our touch-based layout scheme, so let's update the touch elements. window.setTimeout(function() { this.listInputs(); @@ -854,7 +856,7 @@ namespace com.keyman.dom { } else { this.listInputs(); // Fix up our internal input ordering scheme. } - + this.disableInputElement(Pelem); } } @@ -863,8 +865,8 @@ namespace com.keyman.dom { * Function _EnableControl * Scope Private * @param {Element} Pelem Element to be enabled - * Description Enable KMW control element - */ + * Description Enable KMW control element + */ _EnableControl(Pelem: HTMLElement) { if(this.isAttached(Pelem)) { // Only operate on attached elements! if(this.keyman.util.device.touchable) { @@ -908,36 +910,36 @@ namespace com.keyman.dom { if(t1[i].className.indexOf('kmw-disabled') < 0) { eList.push({ip:t1[i], x: dom.Utils.getAbsoluteX(t1[i]), y: dom.Utils.getAbsoluteY(t1[i])}); } - break; + break; } } - for(i=0; i= 0 : false; var disabledAfter = (mutation.target as HTMLElement).className.indexOf('kmw-disabled') >= 0; - + if(disabledBefore && !disabledAfter) { this._EnableControl(mutation.target); } else if(!disabledBefore && disabledAfter) { @@ -981,10 +983,10 @@ namespace com.keyman.dom { for(var i=0; i < mutations.length; i++) { var mutation = mutations[i]; - + for(var j=0; j < mutation.addedNodes.length; j++) { inputElementAdditions = inputElementAdditions.concat(this._GetDocumentEditables(mutation.addedNodes[j])); - } + } for(j = 0; j < mutation.removedNodes.length; j++) { inputElementRemovals = inputElementRemovals.concat(this._GetDocumentEditables(mutation.removedNodes[j])); @@ -1024,12 +1026,12 @@ namespace com.keyman.dom { } }.bind(this); - /** + /** * Function _MutationAdditionObserved * Scope Private * @param {Element} Pelem A page input, textarea, or iframe element. * Description Used by the MutationObserver event handler to properly setup any elements dynamically added to the document post-initialization. - * + * */ _MutationAdditionObserved = function(Pelem: HTMLElement) { if(Pelem instanceof Pelem.ownerDocument.defaultView.HTMLIFrameElement && !this.keyman.util.device.touchable) { @@ -1039,7 +1041,7 @@ namespace com.keyman.dom { var attachFunctor = function() { // Triggers at the same time as iframe's onload property, after its internal document loads. // Provide a minor delay to allow 'load' event handlers to set the design-mode property. - window.setTimeout(function() { + window.setTimeout(function() { domManager.attachToControl(Pelem); }, 1); }; @@ -1047,11 +1049,11 @@ namespace com.keyman.dom { Pelem.addEventListener('load', attachFunctor); // The following block breaks for design-mode iframes, at least in Chrome; a blank document may exist - // before the load of the desired actual document. + // before the load of the desired actual document. // // /* If the iframe has somehow already loaded, we can't expect the onload event to be raised. We ought just // * go ahead and perform our callback's contents. - // * + // * // * keymanweb.domManager.attachToControl() is now idempotent, so even if our call 'whiffs', it won't cause long-lasting // * problems. // */ @@ -1060,7 +1062,7 @@ namespace com.keyman.dom { // } } else { this.attachToControl(Pelem); - } + } } // Used by the mutation event handler to properly decouple any elements dynamically removed from the document. @@ -1078,12 +1080,12 @@ namespace com.keyman.dom { * Function disableControl * Scope Public * @param {Element} Pelem Element to be disabled - * Description Disables a KMW control element - */ + * Description Disables a KMW control element + */ disableControl(Pelem: HTMLElement) { if(!this.isAttached(Pelem)) { console.warn("KeymanWeb is not attached to element " + Pelem); - } + } var cn = Pelem.className; if(cn.indexOf('kmw-disabled') < 0) { // if not already explicitly disabled... @@ -1098,12 +1100,12 @@ namespace com.keyman.dom { * Function enableControl * Scope Public * @param {Element} Pelem Element to be disabled - * Description Disables a KMW control element - */ + * Description Disables a KMW control element + */ enableControl = function(Pelem: HTMLElement) { if(!this.isAttached(Pelem)) { console.warn("KeymanWeb is not attached to element " + Pelem); - } + } var cn = Pelem.className; var tagIndex = cn.indexOf('kmw-disabled'); @@ -1129,12 +1131,12 @@ namespace com.keyman.dom { /** * Function _WindowUnload * Scope Private - * Description Remove handlers before detaching KMW window - */ + * Description Remove handlers before detaching KMW window + */ _WindowUnload: () => void = function(this: DOMManager) { // Allow the UI to release its own resources this.keyman.uiManager.doUnload(); - + // Allow the OSK to release its own resources if(this.keyman.osk) { this.keyman.osk.shutdown(); @@ -1142,7 +1144,7 @@ namespace com.keyman.dom { this.keyman.osk['_Unload'](); // I3363 (Build 301) } } - + this.lastActiveElement = null; }.bind(this); @@ -1150,12 +1152,12 @@ namespace com.keyman.dom { /** * Function setKeyboardForControl - * Scope Public - * @param {Element} Pelem Control element - * @param {string|null=} Pkbd Keyboard (Clears the set keyboard if set to null.) + * Scope Public + * @param {Element} Pelem Control element + * @param {string|null=} Pkbd Keyboard (Clears the set keyboard if set to null.) * @param {string|null=} Plc Language Code - * Description Set default keyboard for the control - */ + * Description Set default keyboard for the control + */ setKeyboardForControl(Pelem: HTMLElement, Pkbd?: string, Plc?: string) { /* pass null for kbd to specify no default, or '' to specify the default system keyboard. */ if(Pkbd !== null && Pkbd !== undefined) { @@ -1196,8 +1198,8 @@ namespace com.keyman.dom { /** * Function getKeyboardForControl - * Scope Public - * @param {Element} Pelem Control element + * Scope Public + * @param {Element} Pelem Control element * @return {string|null} The independently-managed keyboard for the control. * Description Returns the keyboard ID of the current independently-managed keyboard for this control. * If it is currently following the global keyboard setting, returns null instead. @@ -1210,11 +1212,11 @@ namespace com.keyman.dom { return Pelem._kmwAttachment.keyboard; } } - + /** * Function getLanguageForControl - * Scope Public - * @param {Element} Pelem Control element + * Scope Public + * @param {Element} Pelem Control element * @return {string|null} The independently-managed keyboard for the control. * Description Returns the language code used with the current independently-managed keyboard for this control. * If it is currently following the global keyboard setting, returns null instead. @@ -1232,7 +1234,7 @@ namespace com.keyman.dom { /** * Set focus to last active target element (browser-dependent) - */ + */ focusLastActiveElement() { var lastElem = this.lastActiveElement; if(!lastElem) { @@ -1247,9 +1249,9 @@ namespace com.keyman.dom { /** * Get the last active target element *before* KMW activated (I1297) - * - * @return {Element} - */ + * + * @return {Element} + */ get lastActiveElement(): HTMLElement { return DOMEventHandlers.states._lastActiveElement; } @@ -1298,10 +1300,10 @@ namespace com.keyman.dom { } /** - * Set the active input element directly optionally setting focus - * + * Set the active input element directly optionally setting focus + * * @param {Object|string} e element id or element - * @param {boolean=} setFocus optionally set focus (KMEW-123) + * @param {boolean=} setFocus optionally set focus (KMEW-123) **/ setActiveElement(e: string|HTMLElement, setFocus?: boolean) { if(typeof e == "string") { // Can't instanceof string, and String is a different type. @@ -1347,17 +1349,21 @@ namespace com.keyman.dom { clientY: 0, target: e as HTMLElement }; - + // Kinda hacky, but gets the job done. (this.keyman.touchAliasing as DOMTouchHandlers).setFocusWithTouch(tEvent); } else { this.focusLastActiveElement(); } } + + // Let the keyboard do its initial group processing + //console.log('processNewContextEvent [not] called from setActiveElement'); + com.keyman.singleton.core.processNewContextEvent(dom.Utils.getOutputTarget(e)); } /** Sets the active input element only if it is presently null. - * + * * @param {Element} */ initActiveElement(Lelem: HTMLElement) { @@ -1369,16 +1375,16 @@ namespace com.keyman.dom { /** * Move focus to next (or previous) input or text area element on TAB * Uses list of actual input elements - * + * * Note that activeElement() on touch devices returns the DIV that overlays * the input element, not the element itself. - * + * * @param {number|boolean} bBack Direction to move (0 or 1) */ moveToNext(bBack: number|boolean) { var i,t=this.sortedInputs, activeBase = this.activeElement; var touchable = this.keyman.util.device.touchable; - + if(t.length == 0) { return; } @@ -1401,7 +1407,7 @@ namespace com.keyman.dom { // Move to the selected element if(touchable) { - // Set focusing flag to prevent OSK disappearing + // Set focusing flag to prevent OSK disappearing DOMEventHandlers.states.focusing=true; var target=t[i]['kmw_ip']; @@ -1422,17 +1428,17 @@ namespace com.keyman.dom { /** * Move focus to user-specified element - * + * * @param {string|Object} e element or element id - * + * **/ moveToElement(e:string|HTMLElement) { var i; - + if(typeof(e) == "string") { // Can't instanceof string, and String is a different type. e=document.getElementById(e); } - + if(this.keyman.util.device.touchable && e['kmw_ip']) { e['kmw_ip'].focus(); } else { @@ -1446,10 +1452,10 @@ namespace com.keyman.dom { * Function _IsIEEditableIframe * Scope Private * @param {Object} Pelem Iframe element - * {boolean|number} PtestOn 1 to test if frame content is editable (TODO: unclear exactly what this is doing!) + * {boolean|number} PtestOn 1 to test if frame content is editable (TODO: unclear exactly what this is doing!) * @return {boolean} - * Description Test if element is an IE editable IFrame - */ + * Description Test if element is an IE editable IFrame + */ _IsIEEditableIframe(Pelem: HTMLIFrameElement, PtestOn?: number) { var Ldv, Lvalid = Pelem && (Ldv=Pelem.tagName) && Ldv.toLowerCase() == 'body' && (Ldv=Pelem.ownerDocument) && Ldv.parentWindow; return (!PtestOn && Lvalid) || (PtestOn && (!Lvalid || Pelem.isContentEditable)); @@ -1459,29 +1465,29 @@ namespace com.keyman.dom { * Function _IsMozillaEditableIframe * Scope Private * @param {Object} Pelem Iframe element - * @param {boolean|number} PtestOn 1 to test if 'designMode' is 'ON' - * @return {boolean} - * Description Test if element is a Mozilla editable IFrame - */ + * @param {boolean|number} PtestOn 1 to test if 'designMode' is 'ON' + * @return {boolean} + * Description Test if element is a Mozilla editable IFrame + */ _IsMozillaEditableIframe(Pelem: HTMLIFrameElement, PtestOn?: number) { var Ldv, Lvalid = Pelem && (Ldv=(Pelem).defaultView) && Ldv.frameElement; // Probable bug! return (!PtestOn && Lvalid) || (PtestOn && (!Lvalid || Ldv.document.designMode.toLowerCase()=='on')); } /* ----------------------- Initialization methods ------------------ */ - + /** * Get the user-specified (or default) font for the first mapped input or textarea element * before applying any keymanweb styles or classes - * + * * @return {string} - **/ + **/ getBaseFont() { var util = this.keyman.util; var ipInput = document.getElementsByTagName<'input'>('input'), ipTextArea=document.getElementsByTagName<'textarea'>('textarea'), n=0,fs,fsDefault='Arial,sans-serif'; - + // Find the first input element (if it exists) if(ipInput.length == 0 && ipTextArea.length == 0) { n=0; @@ -1503,7 +1509,7 @@ namespace com.keyman.dom { n=2; } } - + // Grab that font! switch(n) { case 0: @@ -1516,7 +1522,7 @@ namespace com.keyman.dom { if(typeof(fs) == 'undefined' || fs == 'monospace') { fs=fsDefault; } - + return fs; } @@ -1524,9 +1530,9 @@ namespace com.keyman.dom { * Function Initialization * Scope Public * @param {com.keyman.OptionType} arg object of user-defined properties - * Description KMW window initialization - */ - init: (arg: com.keyman.OptionType) => Promise = function(this: DOMManager, arg): Promise { + * Description KMW window initialization + */ + init: (arg: com.keyman.OptionType) => Promise = function(this: DOMManager, arg): Promise { var p,opt,dTrailer,ds; var util = this.keyman.util; var device = util.device; @@ -1537,22 +1543,22 @@ namespace com.keyman.dom { this.keyman.core.keyboardProcessor.errorLogger = console.error.bind(console); // Local function to convert relative to absolute URLs - // with respect to the source path, server root and protocol + // with respect to the source path, server root and protocol var fixPath = function(p) { if(p.length == 0) return p; - + // Add delimiter if missing if(p.substr(p.length-1,1) != '/') p = p+'/'; // Absolute - if((p.replace(/^(http)s?:.*/,'$1') == 'http') + if((p.replace(/^(http)s?:.*/,'$1') == 'http') || (p.replace(/^(file):.*/,'$1') == 'file')) - return p; - + return p; + // Absolute (except for protocol) if(p.substr(0,2) == '//') return this.keyman.protocol+p; - + // Relative to server root if(p.substr(0,1) == '/') return this.keyman.rootPath+p.substr(1); @@ -1560,40 +1566,40 @@ namespace com.keyman.dom { // Otherwise, assume relative to source path return this.keyman.srcPath+p; }.bind(this); - - // Explicit (user-defined) parameter initialization + + // Explicit (user-defined) parameter initialization opt=this.keyman.options; if(typeof(arg) == 'object' && arg !== null) { for(p in opt) - { + { if(arg.hasOwnProperty(p)) opt[p] = arg[p]; } } - + // Get default paths and device options if(opt['root'] != '') { - this.keyman.rootPath = fixPath(opt['root']); + this.keyman.rootPath = fixPath(opt['root']); } - // Keyboards and fonts are located with respect to the server root by default + // Keyboards and fonts are located with respect to the server root by default //if(opt['keyboards'] == '') opt['keyboards'] = keymanweb.rootPath+'keyboard/'; //if(opt['fonts'] == '') opt['fonts'] = keymanweb.rootPath+'font/'; - - // Resources are located with respect to the engine by default + + // Resources are located with respect to the engine by default if(opt['resources'] == '') { opt['resources'] = this.keyman.srcPath; } - + // Convert resource, keyboard and font paths to absolute URLs opt['resources'] = fixPath(opt['resources']); opt['keyboards'] = fixPath(opt['keyboards']); - opt['fonts'] = fixPath(opt['fonts']); + opt['fonts'] = fixPath(opt['fonts']); // Set default device options - this.keyman.setDefaultDeviceOptions(opt); - - // Only do remainder of initialization once! + this.keyman.setDefaultDeviceOptions(opt); + + // Only do remainder of initialization once! if(this.keyman.initialized) { return Promise.resolve(); } @@ -1617,12 +1623,12 @@ namespace com.keyman.dom { this.keyman._MasterDocument = window.document; /** - * Initialization of touch devices and browser interfaces must be done + * Initialization of touch devices and browser interfaces must be done * after all resources are loaded, during final stage of initialization - * - */ - - // Treat Android devices as phones if either (reported) screen dimension is less than 4" + * + */ + + // Treat Android devices as phones if either (reported) screen dimension is less than 4" if(device.OS == 'Android') { // Determine actual device characteristics I3363 (Build 301) @@ -1630,8 +1636,8 @@ namespace com.keyman.dom { var dpi = device.getDPI(); //TODO: this will not work when called from HEAD!! device.formFactor=((screen.height < 4.0 * dpi) || (screen.width < 4.0 * dpi)) ? 'phone' : 'tablet'; } - - // Set exposed initialization flag member for UI (and other) code to use + + // Set exposed initialization flag member for UI (and other) code to use this.keyman.setInitialized(1); // Finish keymanweb and initialize the OSK once all necessary resources are available @@ -1641,7 +1647,7 @@ namespace com.keyman.dom { this.keyman.osk = new com.keyman.osk.FloatingOSKView(device.coreSpec); } const osk = this.keyman.osk; - + // Create and save the remote keyboard loading delay indicator util.prepareWait(); @@ -1650,7 +1656,7 @@ namespace com.keyman.dom { // Initialize the desktop UI this.initializeUI(); - + // Exit initialization here if we're using an embedded code path. if(this.keyman.isEmbedded) { if(!this.keyman.keyboardManager.setDefaultKeyboard()) { @@ -1666,7 +1672,7 @@ namespace com.keyman.dom { // Initialize touch-screen device interface I3363 (Build 301) if(device.touchable) { this.keyman.handleRotationEvents(); - } + } // Initialize browser interface if(this.keyman.options['attachType'] != 'manual') { @@ -1675,16 +1681,16 @@ namespace com.keyman.dom { // Create an ordered list of all input and textarea fields this.listInputs(); - + // Initialize the OSK and set default OSK styles // Note that this should *never* be called before the OSK has been initialized. - // However, it possibly may be called before the OSK has been fully defined with the current keyboard, need to check. - //osk._Load(); - - //document.body.appendChild(osk._Box); + // However, it possibly may be called before the OSK has been fully defined with the current keyboard, need to check. + //osk._Load(); + + //document.body.appendChild(osk._Box); //osk._Load(false); - + // I3363 (Build 301) if(device.touchable) { const osk = keyman.osk as osk.AnchoredOSKView; @@ -1698,23 +1704,23 @@ namespace com.keyman.dom { ds=dTrailer.style; ds.width='100%'; ds.height=(screen.width/2)+'px'; - document.body.appendChild(dTrailer); - + document.body.appendChild(dTrailer); + // Sets up page-default touch-based handling for activation-state management. - // These always trigger for the page, wherever a touch may occur. Does not + // These always trigger for the page, wherever a touch may occur. Does not // prevent element-specific or OSK-key-specific handling from triggering. const _this = this; this.touchStartActivationHandler=function(e) { _this.deactivateOnRelease=true; _this.touchY=e.touches[0].screenY; - // On Chrome, scrolling up or down causes the URL bar to be shown or hidden + // On Chrome, scrolling up or down causes the URL bar to be shown or hidden // according to whether or not the document is at the top of the screen. // But when doing that, each OSK row top and height gets modified by Chrome - // looking very ugly. It would be best to hide the OSK then show it again + // looking very ugly. It would be best to hide the OSK then show it again // when the user scroll finishes, but Chrome has no way to reliably report // the touch end event after a move. c.f. http://code.google.com/p/chromium/issues/detail?id=152913 - // The best compromise behaviour is simply to hide the OSK whenever any + // The best compromise behaviour is simply to hide the OSK whenever any // non-input and non-OSK element is touched. _this.deactivateOnScroll=false; if(device.OS == 'Android' && navigator.userAgent.indexOf('Chrome') > 0) { @@ -1768,17 +1774,17 @@ namespace com.keyman.dom { } //document.body.appendChild(keymanweb._StyleBlock); - + // Restore and reload the currently selected keyboard, selecting a default keyboard if necessary. - this.keyman.keyboardManager.restoreCurrentKeyboard(); + this.keyman.keyboardManager.restoreCurrentKeyboard(); /* Setup of handlers for dynamically-added and (eventually) dynamically-removed elements. * Reference: https://developer.mozilla.org/en/docs/Web/API/MutationObserver - * + * * We place it here so that it loads after most of the other UI loads, reducing the MutationObserver's overhead. * Of course, we only want to dynamically add elements if the user hasn't enabled the manual attachment option. */ - + if(typeof MutationObserver == 'function') { var observationTarget = document.querySelector('body'), observationConfig: MutationObserverInit; if(this.keyman.options['attachType'] != 'manual') { //I1961 @@ -1794,7 +1800,7 @@ namespace com.keyman.dom { this.enablementObserver = new MutationObserver(this._EnablementMutationObserverCore); this.enablementObserver.observe(observationTarget, observationConfig); } else { - console.warn("Your browser is outdated and does not support MutationObservers, a web feature " + + console.warn("Your browser is outdated and does not support MutationObservers, a web feature " + "needed by KeymanWeb to support dynamically-added elements."); } @@ -1814,12 +1820,12 @@ namespace com.keyman.dom { /** * Initialize the desktop user interface as soon as it is ready - **/ + **/ initializeUI() { if(this.keyman.ui && this.keyman.ui['initialize'] instanceof Function) { this.keyman.ui['initialize'](); // Display the OSK (again) if enabled, in order to set its position correctly after - // adding the UI to the page + // adding the UI to the page 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 diff --git a/web/source/dom/preProcessor.ts b/web/source/dom/preProcessor.ts index 24bd6e0559..1a8dac6594 100644 --- a/web/source/dom/preProcessor.ts +++ b/web/source/dom/preProcessor.ts @@ -21,13 +21,13 @@ namespace com.keyman.dom { * Scope Private * @param {Event} e Event object * @param {boolean=} keyState true if call results from a keyDown event, false if keyUp, undefined if keyPress - * @return {Object.} KMW keyboard event object: - * Description Get object with target element, key code, shift state, virtual key state + * @return {Object.} KMW keyboard event object: + * Description Get object with target element, key code, shift state, virtual key state * Lcode=keyCode * Lmodifiers=shiftState * LisVirtualKeyCode e.g. ctrl/alt key * LisVirtualKey e.g. Virtual key or non-keypress event - */ + */ static _GetKeyEventProperties(e: KeyboardEvent, keyState?: boolean): text.KeyEvent { let keyman = com.keyman.singleton; let core = keyman.core; @@ -46,7 +46,7 @@ namespace com.keyman.dom { // Stage 1 - track the true state of the keyboard's modifiers. var prevModState = core.keyboardProcessor.modStateFlags, curModState = 0x0000; var ctrlEvent = false, altEvent = false; - + let keyCodes = text.Codes.keyCodes; switch(s.Lcode) { case keyCodes['K_CTRL']: // The 3 shorter "K_*CTRL" entries exist in some legacy keyboards. @@ -68,16 +68,16 @@ namespace com.keyman.dom { /** * Two separate conditions exist that should trigger chiral modifier detection. Examples below use CTRL but also work for ALT. - * - * 1. The user literally just pressed CTRL, so the event has a valid `location` property we can utilize. + * + * 1. The user literally just pressed CTRL, so the event has a valid `location` property we can utilize. * Problem: its layer isn't presently activated within the OSK. - * + * * 2. CTRL has been held a while, so the OSK layer is valid, but the key event doesn't tell us the chirality of the active CTRL press. * Bonus issue: RAlt simulation may cause erasure of this location property, but it should ONLY be empty if pressed in this case. * We default to the 'left' variants since they're more likely to exist and cause less issues with RAlt simulation handling. - * + * * In either case, `e.getModifierState("Control")` is set to true, but as a result does nothing to tell us which case is active. - * + * * `e.location != 0` if true matches condition 1 and matches condition 2 if false. */ @@ -85,19 +85,19 @@ namespace com.keyman.dom { let modifierCodes = text.Codes.modifierCodes; if(e.getModifierState("Control")) { - curModState |= ((e.location != 0 && ctrlEvent) ? + curModState |= ((e.location != 0 && ctrlEvent) ? (e.location == 1 ? modifierCodes['LCTRL'] : modifierCodes['RCTRL']) : // Condition 1 prevModState & 0x0003); // Condition 2 } if(e.getModifierState("Alt")) { - curModState |= ((e.location != 0 && altEvent) ? + curModState |= ((e.location != 0 && altEvent) ? (e.location == 1 ? modifierCodes['LALT'] : modifierCodes['RALT']) : // Condition 1 prevModState & 0x000C); // Condition 2 } // Stage 2 - detect state key information. It can be looked up per keypress with no issue. s.Lstates = 0; - + s.Lstates |= e.getModifierState('CapsLock') ? modifierCodes['CAPS'] : modifierCodes['NO_CAPS']; s.Lstates |= e.getModifierState('NumLock') ? modifierCodes['NUM_LOCK'] : modifierCodes['NO_NUM_LOCK']; s.Lstates |= (e.getModifierState('ScrollLock') || e.getModifierState("Scroll")) // "Scroll" for IE9. @@ -134,14 +134,14 @@ namespace com.keyman.dom { } } else { // No need to sim AltGr here; we don't need chiral ALTs. - s.Lmodifiers = + s.Lmodifiers = (curModState & 0x10) | // SHIFT - ((curModState & (modifierCodes['LCTRL'] | modifierCodes['RCTRL'])) ? 0x20 : 0) | - ((curModState & (modifierCodes['LALT'] | modifierCodes['RALT'])) ? 0x40 : 0); + ((curModState & (modifierCodes['LCTRL'] | modifierCodes['RCTRL'])) ? 0x20 : 0) | + ((curModState & (modifierCodes['LALT'] | modifierCodes['RALT'])) ? 0x40 : 0); } - /* Tweak the modifiers if an OS meta key is detected; this will allow meta-key-based + /* Tweak the modifiers if an OS meta key is detected; this will allow meta-key-based * hotkeys to bypass Keyman processing. We do this AFTER the chiral modifier filtering * because some keyboards specify their own modifierBitmask, which won't include it. * We don't currently use that reference in this method, but that may change in the future. @@ -151,7 +151,7 @@ namespace com.keyman.dom { // Physically-typed keys require use of a 'desktop' form factor and thus are based on a virtual "physical" Device. s.device = keyman.util.physicalDevice.coreSpec; - // Perform any browser-specific key remapping before other remaps and mnemonic transforms. + // Perform any browser-specific key remapping before other remaps and mnemonic transforms. // (See https://github.com/keymanapp/keyman/issues/1125.) if(!keyman.isEmbedded && s.device.browser == utils.Browser.Firefox) { // Browser key identifiers are not completely consistent; Firefox has a few (for US punctuation) @@ -185,7 +185,7 @@ namespace com.keyman.dom { s.Lcode=Lbase['k'+s.Lcode]; } /* 13/03/2007 MCD: Swedish: End mapping of keystroke to US keyboard */ - + if(!activeKeyboard.definesPositionalOrMnemonic && !(s.Lmodifiers & 0x60)) { // Support version 1.0 KeymanWeb keyboards that do not define positional vs mnemonic s = { @@ -200,11 +200,11 @@ namespace com.keyman.dom { }; } } - + return s; } - public static getEventOutputTarget(e: KeyboardEvent): text.OutputTarget { + public static getEventOutputTarget(e: Event): text.OutputTarget { let keyman = com.keyman.singleton; let target = keyman.util.eventTarget(e) as HTMLElement; if (target == null) { @@ -219,17 +219,17 @@ namespace com.keyman.dom { /** * Function keyDown * Scope Public - * Description Processes keydown event and passes data to keyboard. - * + * Description Processes keydown event and passes data to keyboard. + * * Note that the test-case oriented 'recorder' stubs this method to facilitate keystroke * recording for use in test cases. If changing this function, please ensure the recorder is * not affected. - */ + */ static keyDown(e: KeyboardEvent): boolean { let core = com.keyman.singleton.core; DOMEventHandlers.states.swallowKeypress = false; - // Get event properties + // Get event properties var Levent = this._GetKeyEventProperties(e, true); if(Levent == null) { return true; @@ -298,7 +298,7 @@ namespace com.keyman.dom { return false; } /* I732 END - 13/03/2007 MCD: Swedish: End positional keyboard layout code */ - + // Only reached if it's a mnemonic keyboard. let outputTarget = PreProcessor.getEventOutputTarget(e); if(DOMEventHandlers.states.swallowKeypress || core.keyboardInterface.processKeystroke(outputTarget, Levent)) { diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 54c118baf1..17b2b18091 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -765,6 +765,10 @@ namespace com.keyman.keyboards { if(osk) { osk._Load(); } + + if(manager.keymanweb.domManager.lastActiveElement != null) { + core.processNewContextEvent(dom.Utils.getOutputTarget(manager.keymanweb.domManager.lastActiveElement)); + } } // Remove the wait message, if defined diff --git a/web/source/kmwbase.ts b/web/source/kmwbase.ts index c10fa5b7bd..29a6ab6da8 100644 --- a/web/source/kmwbase.ts +++ b/web/source/kmwbase.ts @@ -151,7 +151,7 @@ namespace com.keyman { } this._BrowserIsSafari = (navigator.userAgent.indexOf('AppleWebKit') >= 0); // I732 END - Support for European underlying keyboards #1 - this.core = new text.InputProcessor({ + this.core = new text.InputProcessor(this.util.device.coreSpec, { baseLayout: baseLayout, variableStoreSerializer: new dom.VariableStoreCookieSerializer() }); diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index c3bb941df5..362fcd5f57 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -616,10 +616,10 @@ namespace com.keyman.osk { // This handler is also triggered on state-key state changes (K_CAPS) that // may not actually change the layer. if(this.vkbd) { - this.vkbd._UpdateVKShiftStyle(); + this.vkbd._UpdateVKShiftStyle(newValue); } - if(source.value != newValue) { + if((this.vkbd && this.vkbd.layerId != newValue) || source.value != newValue) { // Prevents console errors when a keyboard only displays help. // Can occur when using SHIFT with sil_euro_latin on a desktop form-factor. if(this.vkbd) {