Merge branch 'master' into web-typescript-namespacing

This commit is contained in:
Joshua A. Horton 2018-03-05 14:09:59 +07:00
commit 30d646ca99
11 changed files with 286 additions and 20 deletions

View file

@ -26,6 +26,7 @@
* Added automated testing for KeymanWeb builds. (#350)
* Fixed bugs in the handling of deadkeys. (#281)
* Change from ISO 639-3 language codes to BCP-47 language codes
* Now generates 'change' and 'input' events from keyboard and OSK input (#42) (#571)
## 2017-07-10 2.0.473 stable
* 2.0 stable release build.

0
web/source/build_recorder.sh Normal file → Executable file
View file

View file

@ -824,6 +824,11 @@ namespace com.keyman {
if(dn >= 0) {
this._DeadkeyAdjustPos(this._SelPos(Pelem), -dn + s._kmwLength()); // I3318,I3319
}
if((dn >= 0 || s) && Pelem == DOMEventHandlers.states.activeElement) {
// Record that we've made an edit.
DOMEventHandlers.states.changed = true;
}
return;
}
@ -933,7 +938,13 @@ namespace com.keyman {
if(typeof(this.keymanweb.refreshElementContent) == 'function') {
this.keymanweb.refreshElementContent(Pelem);
}
if((dn >= 0 || s) && Pelem == DOMEventHandlers.states.activeElement) {
// Record that we've made an edit.
DOMEventHandlers.states.changed = true;
}
}
/**
* Function deadkeyOutput KDO
@ -1186,6 +1197,32 @@ namespace com.keyman {
}
// I3318 - deadkey changes END
doInputEvent(_target: HTMLElement|Document) {
var event: Event;
// TypeScript doesn't yet recognize InputEvent as a type!
if(typeof window['InputEvent'] == 'function') {
event = new window['InputEvent']('input', {"bubbles": true, "cancelable": false});
} // No else - there is no supported version in some browsers.
// Ensure that touch-aliased elements fire as if from the aliased element.
if(_target['base'] && _target['base']['kmw_ip']) {
_target = _target['base'];
}
if(_target && event) {
_target.dispatchEvent(event);
}
}
defaultBackspace(Pelem?: HTMLElement|Document) {
if(!Pelem) {
Pelem = this.keymanweb.domManager.getLastActiveElement();
}
this.output(1, Pelem, "");
this.doInputEvent(Pelem);
}
/**
* Function processKeystroke
* Scope Private
@ -1206,7 +1243,13 @@ namespace com.keyman {
this.keymanweb.util.activeDevice = device;
// Calls the start-group of the active keyboard.
return this.keymanweb.keyboardManager.activeKeyboard['gs'](element, keystroke);
var matched = this.keymanweb.keyboardManager.activeKeyboard['gs'](element, keystroke);
if(matched) {
this.doInputEvent(element);
}
return matched;
}
/**

View file

@ -60,9 +60,13 @@ namespace com.keyman {
if(this.enablementObserver) {
this.enablementObserver.disconnect();
}
if(this.attachmentObserver.disconnect) {
if(this.attachmentObserver) {
this.attachmentObserver.disconnect();
}
for(let input of this.inputList) {
this.disableInputElement(input);
}
}
/**

View file

@ -21,6 +21,8 @@ namespace com.keyman {
focusing: boolean;
focusTimer: number;
changed: boolean; // Tracks if the element has been edited since gaining focus.
/* ----------------------- Static event-related methods ------------------------ */
setFocusTimer(): void {
@ -324,6 +326,8 @@ namespace com.keyman {
this.keyman.osk._Hide(false);
}
this.doChangeEvent(Ltarg);
return true;
}.bind(this);
@ -700,14 +704,14 @@ namespace com.keyman {
// Support backspace in simulated input DIV from physical keyboard where not matched in rule I3363 (Build 301)
if(Levent.Lcode == 8 && !LeventMatched && Levent.Ltarg.className != null && Levent.Ltarg.className.indexOf('keymanweb-input') >= 0) {
kbdInterface.output(1, DOMEventHandlers.states.lastActiveElement, "");
this.keyman.interface.defaultBackspace();
}
} else {
// Mnemonic layout
if(Levent.Lcode == 8) { // I1595 - Backspace for mnemonic
DOMEventHandlers.states._KeyPressToSwallow = 1;
if(!kbdInterface.processKeystroke(util.physicalDevice,Levent.Ltarg,Levent)) {
kbdInterface.output(1, DOMEventHandlers.states.lastActiveElement, ""); // I3363 (Build 301)
this.keyman.interface.defaultBackspace(); // I3363 (Build 301)
}
return false; //added 16/3/13 to fix double backspace on mnemonic layouts on desktop
}
@ -758,9 +762,30 @@ namespace com.keyman {
return false;
}
}
return true;
}.bind(this);
doChangeEvent(_target: HTMLElement|Document) {
if(DOMEventHandlers.states.changed) {
var event: Event;
if(typeof Event == 'function') {
event = new Event('change', {"bubbles": true, "cancelable": false});
} else { // IE path
event = document.createEvent("HTMLEvents");
event.initEvent('change', true, false);
}
// Ensure that touch-aliased elements fire as if from the aliased element.
if(_target['base'] && _target['base']['kmw_ip']) {
_target = _target['base'];
}
_target.dispatchEvent(event);
}
DOMEventHandlers.states.changed = false;
}
/**
* Function _KeyPress
* Scope Private
@ -1356,8 +1381,10 @@ namespace com.keyman {
// This works OK for iOS, but may need something else for other platforms
if(('relatedTarget' in e) && e.relatedTarget) {
var elem: HTMLElement = e.relatedTarget as HTMLElement;
this.doChangeEvent(elem);
if(elem.nodeName != 'DIV' || elem.className.indexOf('keymanweb-input') == -1) {
this.cancelInput(); return;
this.cancelInput();
return;
}
}

View file

@ -233,10 +233,12 @@ namespace com.keyman {
**/
mergeStub(kp: any, lp: any, options) {
var sp: KeyboardStub = this.findStub(kp['id'], lp['id']);
var isNew: boolean = false;
if(sp == null) {
sp= new KeyboardStub(kp['id'], lp['id']);
this.keyboardStubs.push(sp);
isNew = true;
}
// Accept region as number (from Cloud server), code, or name
@ -316,6 +318,12 @@ namespace com.keyman {
// Update the UI
this.doKeyboardRegistered(sp['KI'],sp['KL'],sp['KN'],sp['KLC'],sp['KP']);
// If we have no activeStub because there were no stubs, set the new keyboard as active.
// Do not trigger on merges.
if(!this.activeStub && isNew && this.keyboardStubs.length == 1) {
this.setActiveKeyboard(sp['KI'], sp['KLC']);
}
}
/**
@ -356,7 +364,12 @@ namespace com.keyman {
setActiveKeyboard(PInternalName: string, PLgCode: string) {
//TODO: This does not make sense: the callbacks should be in _SetActiveKeyboard, not here,
// since this is always called FROM the UI, which should not need notification.
// If UI callbacks are needed at all, they should be within _SetActiveKeyboard
// If UI callbacks are needed at all, they should be within _SetActiveKeyboard
if(PInternalName && PInternalName.indexOf("Keyboard_") != 0) {
PInternalName = "Keyboard_" + PInternalName;
}
this.doBeforeKeyboardChange(PInternalName,PLgCode);
this._SetActiveKeyboard(PInternalName,PLgCode,true);
if(this.keymanweb.domManager.getLastActiveElement() != null) {
@ -369,7 +382,7 @@ namespace com.keyman {
// }
this.doKeyboardChange(PInternalName, PLgCode);
}
/**
* Change active keyboard to keyboard selected by (internal) name and language code
*
@ -628,14 +641,15 @@ namespace com.keyman {
// Prepare and show the OSK for this keyboard
osk._Load();
// Remove the wait message, if defined
if(!manager.keymanweb.isEmbedded) {
util.wait(false);
}
} // A handler portion for cases where the new <script> block loads, but fails to process.
}
// Remove the wait message, if defined
if(!manager.keymanweb.isEmbedded) {
util.wait(false);
}
// A handler portion for cases where the new <script> block loads, but fails to process.
} else { // Output error messages even when embedded - they're useful when debugging the apps and KMEA/KMEI engines.
kbdStub.asyncLoader.callback('Error registering the ' + kbdName + ' keyboard for ' + kbdLang + '.', 'error');
kbdStub.asyncLoader.callback('Error registering the ' + kbdName + ' keyboard for ' + kbdLang + '.', 'error');
}
kbdStub.asyncLoader = null;
}, false);
@ -1349,6 +1363,12 @@ namespace com.keyman {
// (Uncommented for Build 360)
this.doKeyboardRegistered(Pstub['KI'],Pstub['KL'],Pstub['KN'],Pstub['KLC'],Pstub['KP']);
// If we have no activeStub because there were no stubs, set the new keyboard as active.
// Do not trigger on merges.
if(!this.activeStub && this.dfltStub == Pstub && this.keyboardStubs.length == 1) {
this.setActiveKeyboard(Pstub['KI'], Pstub['KLC']);
}
return null;
}

View file

@ -917,7 +917,7 @@ if(!window['keyman']['initialized']) {
switch(code) {
case osk.keyCodes['K_BKSP']: //Only desktop UI, not touch devices. TODO: add repeat while mouse down for desktop UI
kbdInterface.output(1, keymanweb.domManager.getLastActiveElement(), "");
kbdInterface.defaultBackspace();
break;
case osk.keyCodes['K_TAB']:
keymanweb.domManager.moveToNext(keyShiftState);
@ -975,7 +975,7 @@ if(!window['keyman']['initialized']) {
// // Failed to move right - there's nothing to delete.
// break;
// }
// kbdInterface.output(1, keymanweb.domManager.getLastActiveElement(), "");
// kbdInterface.defaultBackspace();
// }
}
}

View file

@ -769,13 +769,14 @@ describe('Engine', function() {
this.timeout(10000);
var test_callback = function() {
assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set correctly!");
assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!");
assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!");
keyman.removeKeyboards('lao_2008_basic');
assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!");
done();
}
loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", test_callback, 10000);
loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", test_callback, 10000, {passive: true});
});
});

View file

@ -0,0 +1,166 @@
var assert = chai.assert;
describe('Event Management', function() {
before(function(done) {
this.timeout(20000);
fixture.setBase('unit_tests/fixtures');
fixture.load("eventTestConfig.html");
setupKMW(null, function() {
// We use this keyboard since we only need minimal input functionality for these tests.
// Smaller is better when dealing with net latency.
loadKeyboardFromJSON("/keyboards/test_simple_deadkeys.json", function() {
// Interestingly, when auto-testing there's a Safari bug that prevents
// this from being preserved after the first forced blur command below.
keyman.globalKeyboard = "Keyboard_test_simple_deadkeys";
done();
}, 10000);
}, 10000);
});
after(function() {
teardownKMW();
});
it('Keystroke-based onChange event generation', function(done) {
var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0};
var event = new KMWRecorder.PhysicalInputEvent(simple_A);
var ele = document.getElementById("input");
var aliasing = false;
ele.onchange = function() {
ele.onchange = null;
done();
}
if(ele['kmw_ip']) {
ele = ele['kmw_ip'];
aliasing = true;
}
// A bit of a force-hack to ensure the element is seen as active for the tests.
com.keyman['DOMEventHandlers'].states.lastActiveElement = ele;
com.keyman['DOMEventHandlers'].states.activeElement = ele;
event.simulateEventOn(ele);
var focusEvent;
if(typeof FocusEvent == 'function') {
focusEvent = new FocusEvent('blur', {relatedTarget: ele});
} else {
focusEvent = document.createEvent("FocusEvent");
focusEvent.initFocusEvent("blur", true, false, ele.ownerDocument.defaultView, 0, ele);
}
if(focusEvent)
ele.dispatchEvent(focusEvent);
});
it('OSK-based onChange event generation', function(done) {
var simple_A = {"type":"osk","keyID":"default-K_A"};
var event = new KMWRecorder.OSKInputEvent(simple_A);
var ele = document.getElementById("input");
var aliasing = false;
ele.onchange = function() {
ele.onchange = null;
done();
}
if(ele['kmw_ip']) {
ele = ele['kmw_ip'];
aliasing = true;
}
// A bit of a force-hack to ensure the element is seen as active for the tests.
com.keyman['DOMEventHandlers'].states.lastActiveElement = ele;
com.keyman['DOMEventHandlers'].states.activeElement = ele;
event.simulateEventOn(ele);
var focusEvent;
if(typeof FocusEvent == 'function') {
focusEvent = new FocusEvent('blur', {relatedTarget: ele});
} else {
focusEvent = document.createEvent("FocusEvent");
focusEvent.initFocusEvent("blur", true, false, ele.ownerDocument.defaultView, 0, ele);
}
if(focusEvent)
ele.dispatchEvent(focusEvent);
});
it('Keystroke-based onInput event generation', function(done) {
// Not all browsers support InputEvent. Bypass the test for these.
if(typeof InputEvent != 'function') {
console.log("InputEvent not supported.");
done();
return;
}
var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0};
var event = new KMWRecorder.PhysicalInputEvent(simple_A);
var ele = document.getElementById("input");
var aliasing = false;
var counterObj = {i:0};
var fin = 3;
ele.addEventListener("input", function() {
counterObj.i++;
if(counterObj.i == fin) {
done();
}
});
if(ele['kmw_ip']) {
ele = ele['kmw_ip'];
aliasing = true;
}
event.simulateEventOn(ele);
event.simulateEventOn(ele);
event.simulateEventOn(ele);
});
it('OSK-based onInput event generation', function(done) {
// Not all browsers support InputEvent. Bypass the test for these.
if(typeof InputEvent != 'function') {
console.log("InputEvent not supported.");
done();
return;
}
var simple_A = {"type":"osk","keyID":"default-K_A"};
var event = new KMWRecorder.OSKInputEvent(simple_A);
var ele = document.getElementById("input");
var aliasing = false;
var counterObj = {i:0};
var fin = 3;
ele.addEventListener("input", function() {
counterObj.i++;
if(counterObj.i == fin) {
done();
}
});
if(ele['kmw_ip']) {
ele = ele['kmw_ip'];
aliasing = true;
}
event.simulateEventOn(ele);
event.simulateEventOn(ele);
event.simulateEventOn(ele);
});
});

View file

@ -0,0 +1,2 @@
<input id="input"/>
<textarea id="textarea"></textarea>

View file

@ -160,11 +160,13 @@ var onScriptLoad = function(scriptURL, callback, timeout) {
slo.observe();
};
var loadKeyboardStub = function(stub, callback, timeout) {
var loadKeyboardStub = function(stub, callback, timeout, params) {
var kbdName = "Keyboard_" + stub.id;
keyman.addKeyboards(stub);
keyman.setActiveKeyboard(kbdName, stub.languages.id);
if(!params || !params.passive) {
keyman.setActiveKeyboard(kbdName, stub.languages.id);
}
if(keyman.getActiveKeyboard() != kbdName) {
onScriptLoad(stub.filename, function() {