Merge pull request #642 from keymanapp/web-42-input-events

Input + Change DOM event support
This commit is contained in:
Joshua Horton 2018-03-05 13:36:28 +07:00 • committed by GitHub
commit f5cbcd05db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 247 additions and 6 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

@ -823,6 +823,11 @@ class KeyboardInterface {
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;
}
@ -932,6 +937,11 @@ class KeyboardInterface {
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;
}
}
/**
@ -1185,6 +1195,32 @@ class KeyboardInterface {
}
// 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
@ -1205,7 +1241,13 @@ class KeyboardInterface {
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

@ -62,6 +62,10 @@ class DOMManager {
if(this.attachmentObserver.disconnect) {
this.attachmentObserver.disconnect();
}
for(let input of this.inputList) {
this.disableInputElement(input);
}
}
/**

View file

@ -20,6 +20,8 @@
focusing: boolean;
focusTimer: number;
changed: boolean; // Tracks if the element has been edited since gaining focus.
/* ----------------------- Static event-related methods ------------------------ */
setFocusTimer(): void {
@ -323,9 +325,31 @@ class DOMEventHandlers {
this.keyman.osk._Hide(false);
}
this.doChangeEvent(Ltarg);
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 doControlBlurred
* Scope Private
@ -699,14 +723,14 @@ class DOMEventHandlers {
// 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();
}
return false; //added 16/3/13 to fix double backspace on mnemonic layouts on desktop
}
@ -1353,8 +1377,10 @@ class DOMTouchHandlers extends DOMEventHandlers {
// 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

@ -915,7 +915,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);
@ -973,7 +973,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

@ -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.
window['DOMEventHandlers'].states.lastActiveElement = ele;
window['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.
window['DOMEventHandlers'].states.lastActiveElement = ele;
window['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>