feat(web): basic support for newContext

Relates to #3621.

Adds basic support for `begin newContext`.
This commit is contained in:
Marc Durdin 2021-11-23 15:00:47 +11:00
parent 80db98a5f9
commit bbbf1afce7
10 changed files with 467 additions and 335 deletions

View file

@ -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);
}
}
}
}

View file

@ -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;
}

View file

@ -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

View file

@ -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;

View file

@ -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<FocusEvent>(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 = <TouchAliasElement> 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 = <TouchAliasElement> 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<x ? 0 : 100000);
}
target.setTextCaret(cp);
target.scrollInput();
// nextSibling - the scrollbar element.
// nextSibling - the scrollbar element.
} else if(tTarg != scroller.nextSibling) { // Otherwise, if clicked on text in SPAN, set at touch position
var caret,cp,cpMin,cpMax,x,y,dy,yRow,iLoop;
caret=scroller.childNodes[1]; //caret span
@ -630,10 +656,10 @@ namespace com.keyman.dom {
// Vertical scrolling
if(target.base instanceof target.base.ownerDocument.defaultView.HTMLTextAreaElement) {
yRow=Math.round(target.base.offsetHeight/(target.base as HTMLTextAreaElement).rows);
yRow=Math.round(target.base.offsetHeight/(target.base as HTMLTextAreaElement).rows);
for(iLoop=0; iLoop<16; iLoop++)
{
y=dom.Utils.getAbsoluteY(caret)-dy; //top of caret
y=dom.Utils.getAbsoluteY(caret)-dy; //top of caret
if(y > 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<dy?yOffset-dy:0)+'px';
this.firstTouch.y=y;
}
this.firstTouch.y=y;
}
} else {
var xOffset=parseInt(scroller.style.left,10);
if(isNaN(xOffset)) xOffset=0;
@ -850,13 +876,13 @@ namespace com.keyman.dom {
{
// Limit dragging beyond the defined text (to avoid dragging the text completely out of view)
var xMin=0, xMax= dom.Utils.getAbsoluteX(target)+target.offsetWidth-scroller.offsetWidth-32;
if(target.base.dir == 'rtl')xMin=16; else xMax=xMax-24;
if(target.base.dir == 'rtl')xMin=16; else xMax=xMax-24;
x1=xOffset-dx;
if(x1 > 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);

File diff suppressed because it is too large Load diff

View file

@ -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.<string,*>} KMW keyboard event object:
* Description Get object with target element, key code, shift state, virtual key state
* @return {Object.<string,*>} 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)) {

View file

@ -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

View file

@ -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()
});

View file

@ -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) {