Enhances 'nearest target' checks and auto-cancels.

This commit is contained in:
Joshua A. Horton 2019-04-04 10:09:32 +07:00
parent 36c9d17c58
commit 05c8d734f8
4 changed files with 90 additions and 82 deletions

View file

@ -1177,8 +1177,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@ -1199,14 +1198,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -1221,20 +1218,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -1351,8 +1345,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -1364,7 +1357,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -1379,7 +1371,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -1387,14 +1378,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -1413,7 +1402,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -1494,8 +1482,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -1507,7 +1494,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -1593,8 +1579,7 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@ -1630,7 +1615,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -1650,7 +1634,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -1694,14 +1677,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},

View file

@ -87,17 +87,20 @@ namespace com.keyman.dom {
*
* @param {Event} e touch event
* @param {Object} t HTML object at touch point
* @param {boolean} omitCurrent Omits any target directly under the touch point.
* @return {Object} nearest key to touch point
*
**/
findNearestTarget(e: TouchEvent, t: HTMLElement): Target {
if((!e) || (typeof e.changedTouches == 'undefined')
|| (e.changedTouches.length == 0)) {
private findTargetFromTouch(e: TouchEvent, t: HTMLElement, forMove: boolean): Target {
let touchList = forMove ? e.touches : e.changedTouches;
if((!e) || (typeof touchList == 'undefined')
|| (touchList.length == 0)) {
return null;
}
// Get touch point on screen
var x = e.changedTouches[0].pageX;
var x = touchList[0].pageX;
// Get the UI row beneath touch point (SuggestionBanner div, 'kmw-key-row' if OSK, ...)
while(t && t.className !== undefined && t.className.indexOf(this.rowClassMatch) < 0) {
@ -108,24 +111,35 @@ namespace com.keyman.dom {
}
// Find minimum distance from any key
var k: number, bestMatch=0, dx: number, dxMax=24, dxMin=100000, x1: number, x2: number;
var k: number, bestMatch=0, dxMax=24, dxMin=100000, x1: number, x2: number;
for(k = 0; k < t.childNodes.length; k++) {
let childNode = t.childNodes[k] as HTMLElement;
/* // If integrating with the OSK, replace with an 'isInvalidTarget' check.
if(childNode.className !== undefined && childNode.className.indexOf('key-hidden') >= 0) {
if(this.isInvalidTarget(this.findTargetFrom(childNode))) {
continue;
}*/
}
x1 = childNode.offsetLeft;
x2 = x1 + childNode.offsetWidth;
dx = x1 - x;
if(dx >= 0 && dx < dxMin) {
// If it lies completely to the right and is the closest so far
let dxRight = x1 - x;
if(dxRight >= 0 && dxRight < dxMin) {
bestMatch = k;
dxMin = dx;
dxMin = dxRight;
}
dx = x - x2;
if(dx >= 0 && dx < dxMin) {
// If it lies completely to the left and is the closest so far
let dxLeft = x - x2;
if(dxLeft >= 0 && dxLeft < dxMin) {
bestMatch = k;
dxMin = dx;
dxMin = dxLeft;
}
// If it is neither completely to the left nor completely to the right,
// it's under the cursor. Stop the search!
if(dxLeft < 0 && dxRight < 0) {
return this.findTargetFrom(childNode);
}
}
@ -140,17 +154,46 @@ namespace com.keyman.dom {
}
if(((x1 - x) >= 0 && (x1 - x) < dxMax) || ((x - x2) >= 0 && (x - x2) < dxMax)) {
// If integrating with the OSK, should probably use `findTargetFrom`.
// The OSK would want this (key-square) element's child.
return <Target> t;
return this.findTargetFrom(t);
}
}
return null;
}
findBestTarget(e: TouchEvent, forMove?: boolean) {
var eventTarget: HTMLElement;
if(forMove) {
eventTarget = document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY) as HTMLElement;
} else {
eventTarget = e.changedTouches[0].target as HTMLElement;
}
let target = this.findTargetFrom(eventTarget);
// Should refactor this multi-check a bit for more overall reliability.
if(!target) {
// We didn't find a direct target, so we should look for the closest possible one.
// Filters out invalid targets.
target = this.findTargetFromTouch(e, eventTarget, forMove);
}
return target;
}
/**
* Reports whether or not a `Target` should be considered invalid. Needed by the OSK for
* hidden keys.
* @param target A `Target` element to be validated.
*/
protected isInvalidTarget(target: Target): boolean {
return false;
}
touchStart(e: TouchEvent) {
// Determine the selected Target, manage state.
this.currentTarget = this.findTargetFrom(e.changedTouches[0].target as HTMLElement);
//this.currentTarget = this.findTargetFrom(e.changedTouches[0].target as HTMLElement);
this.currentTarget = this.findBestTarget(e);
this.touchX = e.changedTouches[0].pageX;
this.touchY = e.changedTouches[0].pageY;
@ -158,16 +201,6 @@ namespace com.keyman.dom {
this.touchCount = e.touches.length;
// If option should not be selectable, how do we re-target?
// (If/when the OSK is refactored to use this, we'll need appropriate code here.)
// Probably a 'isInvalidTarget()' and 'nearestTarget()'.
if(!this.currentTarget) {
// Find nearest target.
this.currentTarget = this.findNearestTarget(e, <HTMLElement> e.changedTouches[0].target)
}
// Still no appropriate target? Reject the touch event.
if(!this.currentTarget) {
return;
}
@ -234,12 +267,7 @@ namespace com.keyman.dom {
this.pendingTarget = null;
// Always clear highlighting of current target on release (multi-touch)
} else {
var tt = e.changedTouches[0];
t = this.findTargetFrom(tt.target as HTMLElement);
if(!t) {
var t1 = document.elementFromPoint(tt.clientX,tt.clientY);
t = this.findNearestTarget(e, <HTMLElement> t1);
}
t = this.findBestTarget(e);
if(t) {
this.highlight(t,false);
@ -272,22 +300,14 @@ namespace com.keyman.dom {
}
// Get touch position
var x=typeof e.touches == 'object' ? e.touches[0].clientX : e.clientX,
y=typeof e.touches == 'object' ? e.touches[0].clientY : e.clientY;
var y=typeof e.touches == 'object' ? e.touches[0].clientY : e.clientY;
// Move target key and highlighting
var t1 = <HTMLElement> document.elementFromPoint(x,y),
key0 = this.pendingTarget,
key1 = this.findTargetFrom(t1); // For the OSK, this ALSO gets subkeys.
var key0 = this.pendingTarget,
key1 = this.findBestTarget(e, true); // For the OSK, this ALSO gets subkeys.
// If option should not be selectable, how do we re-target?
// (If/when the OSK is refactored to use this, we'll need appropriate code here.)
// Probably a 'isInvalidTarget()' and 'nearestTarget()'.
// Find the nearest key to the touch point if not on a visible key
if(!key1) {
key1 = this.findNearestTarget(e,t1);
}
// Do not move over keys if device popup visible
if(this.hasModalPopup()) {
@ -349,7 +369,11 @@ namespace com.keyman.dom {
// Cancel touch if moved up and off keyboard, unless popup keys visible
} else {
let base = this.baseElement;
if(key0 && e.touches[0].pageY < Math.max(5, base.offsetTop - 0.25 * base.offsetHeight)) {
let top = (base.offsetParent as HTMLElement).offsetTop + base.offsetTop;
let height = base.offsetHeight;
let yMin = Math.max(5, top - 0.25 * height);
let yMax = (top + height) + 0.25 * height;
if(key0 && (e.touches[0].pageY < yMin || e.touches[0].pageY > yMax)) {
this.highlight(key0,false);
this.clearHolds();
this.pendingTarget = null;

View file

@ -291,9 +291,9 @@ namespace com.keyman.osk {
if(e.parentNode && util.hasClass(<HTMLElement> e.parentNode,'kmw-suggest-option')) {
return e.parentNode as HTMLDivElement;
}
if(e.firstChild && util.hasClass(<HTMLElement> e.firstChild,'kmw-suggest-option')) {
return e.firstChild as HTMLDivElement;
}
// if(e.firstChild && util.hasClass(<HTMLElement> e.firstChild,'kmw-suggest-option')) {
// return e.firstChild as HTMLDivElement;
// }
}
} catch(ex) {}
return null;

View file

@ -1062,9 +1062,12 @@ namespace com.keyman.osk {
}
// Cancel touch if moved up and off keyboard, unless popup keys visible
} else {
let _Box = com.keyman.singleton.osk._Box;
var yMin = Math.max(5, _Box.offsetTop - 0.25*_Box.offsetHeight);
if(key0 && e.touches[0].pageY < Math.max(5,_Box.offsetTop - 0.25*_Box.offsetHeight)) {
// _Box has (most of) the useful client values.
let _Box = this.kbdDiv.offsetParent as HTMLElement; // == osk._Box
let height = (this.kbdDiv.firstChild as HTMLElement).offsetHeight; // firstChild == layer-group, has height info.
// We need to adjust the offset properties by any offsets related to the active banner.
var yMin = Math.max(5, this.kbdDiv.offsetTop + _Box.offsetTop - 0.25*height);
if(key0 && e.touches[0].pageY < yMin) {
this.highlightKey(key0,false);
this.showKeyTip(null,false);
this.keyPending = null;