mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-24 16:47:41 +00:00
Merge branch 'master' into web-typescript-namespacing
This commit is contained in:
commit
30d646ca99
11 changed files with 286 additions and 20 deletions
|
|
@ -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
0
web/source/build_recorder.sh
Normal file → Executable 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
166
web/unit_tests/cases/events.js
Normal file
166
web/unit_tests/cases/events.js
Normal 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);
|
||||
});
|
||||
});
|
||||
2
web/unit_tests/fixtures/eventTestConfig.html
Normal file
2
web/unit_tests/fixtures/eventTestConfig.html
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<input id="input"/>
|
||||
<textarea id="textarea"></textarea>
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue