feat(web): restores subkey-selection, extends accepted path range

This commit is contained in:
Joshua A. Horton 2023-10-06 08:52:42 +07:00
parent cf82a2f840
commit df8a14f87c
12 changed files with 293 additions and 226 deletions

View file

@ -107,6 +107,7 @@ export class PaddedZoneSource implements RecognitionZoneSource {
w: 2 * edgePadding[1],
h: edgePadding[0] + edgePadding[2]
};
break;
case 4:
// top, right, bottom, left
this._edgePadding = {

View file

@ -72,10 +72,12 @@ interface PopConfig {
count: number
}
export type ConfigChangeClosure<Type> = (configStackCommand: PushConfig<Type> | PopConfig) => void;
interface EventMap<Type> {
stage: (
stageReport: GestureStageReport<Type>,
changeConfiguration: (configStackCommand: PushConfig<Type> | PopConfig) => void
changeConfiguration: ConfigChangeClosure<Type>
) => void;
complete: () => void;
}

View file

@ -159,6 +159,9 @@ export class MatcherSelector<Type> extends EventEmitter<EventMap<Type>> {
}
const matchPromise = new ManagedPromise<MatcherSelection<Type>>();
matchPromise.finally(() => {
this._sourceSelector = this._sourceSelector.filter((source) => sources.indexOf(source.source) == -1);
});
/*
* First...

View file

@ -6,7 +6,7 @@ export { GestureRecognizerConfiguration } from "./configuration/gestureRecognize
export { InputEngineBase } from "./headless/inputEngineBase.js";
export { InputSample } from "./headless/inputSample.js";
export { SerializedGesturePath, GesturePath } from "./headless/gesturePath.js";
export { GestureStageReport, GestureSequence } from "./headless/gestures/matchers/gestureSequence.js";
export { ConfigChangeClosure, GestureStageReport, GestureSequence } from "./headless/gestures/matchers/gestureSequence.js";
export { SerializedGestureSource, GestureSource, buildGestureMatchInspector } from "./headless/gestureSource.js";
export { MouseEventEngine } from "./mouseEventEngine.js";
export { PathSegmenter, Subsegmentation } from "./headless/subsegmentation/pathSegmenter.js";

View file

@ -6,7 +6,7 @@ export abstract class InputEventEngine<HoveredItemType, StateToken> extends Inpu
abstract registerEventHandlers(): void;
abstract unregisterEventHandlers(): void;
protected buildSampleFor(clientX: number, clientY: number, target: EventTarget, timestamp: number, stateToken: StateToken): InputSample<HoveredItemType, StateToken> {
protected buildSampleFor(clientX: number, clientY: number, target: EventTarget, timestamp: number, source: GestureSource<HoveredItemType, StateToken>): InputSample<HoveredItemType, StateToken> {
const targetRect = this.config.targetRoot.getBoundingClientRect();
const sample: InputSample<HoveredItemType, StateToken> = {
clientX: clientX,
@ -14,10 +14,11 @@ export abstract class InputEventEngine<HoveredItemType, StateToken> extends Inpu
targetX: clientX - targetRect.left,
targetY: clientY - targetRect.top,
t: timestamp,
stateToken: stateToken
stateToken: source?.stateToken ?? this.stateToken
};
const hoveredItem = this.config.itemIdentifier(sample, target);
const itemIdentifier = source?.currentRecognizerConfig.itemIdentifier ?? this.config.itemIdentifier;
const hoveredItem = itemIdentifier(sample, target);
sample.item = hoveredItem;
return sample;
@ -68,7 +69,7 @@ export abstract class InputEventEngine<HoveredItemType, StateToken> extends Inpu
}
const lastEntry = touchpoint.path.coords[touchpoint.path.coords.length-1];
const sample = this.buildSampleFor(lastEntry.clientX, lastEntry.clientY, target, lastEntry.t, lastEntry.stateToken);
const sample = this.buildSampleFor(lastEntry.clientX, lastEntry.clientY, target, lastEntry.t, touchpoint);
/* While an 'end' event immediately follows a 'move' if it occurred simultaneously,
* this is decidedly _not_ the case if the touchpoint was held for a while without

View file

@ -69,7 +69,7 @@ export class MouseEventEngine<HoveredItemType, StateToken = any> extends InputEv
private buildSampleFromEvent(event: MouseEvent, identifier: number) {
// WILL be null for newly-starting `GestureSource`s / contact points.
const source = this.getTouchpointWithId(identifier);
return this.buildSampleFor(event.clientX, event.clientY, event.target, performance.now(), source?.stateToken ?? this.stateToken);
return this.buildSampleFor(event.clientX, event.clientY, event.target, performance.now(), source);
}
onMouseStart(event: MouseEvent) {

View file

@ -86,7 +86,7 @@ export class TouchEventEngine<HoveredItemType, StateToken = any> extends InputEv
private buildSampleFromTouch(touch: Touch, timestamp: number) {
// WILL be null for newly-starting `GestureSource`s / contact points.
const source = this.getTouchpointWithId(touch.identifier);
return this.buildSampleFor(touch.clientX, touch.clientY, touch.target, timestamp, source?.stateToken ?? this.stateToken);
return this.buildSampleFor(touch.clientX, touch.clientY, touch.target, timestamp, source);
}
onTouchStart(event: TouchEvent) {

View file

@ -59,7 +59,7 @@ class ActiveKeyBase {
nextlayer: string;
sp?: ButtonClass;
_baseKeyEvent: KeyEvent;
private _baseKeyEvent: KeyEvent;
isMnemonic: boolean = false;
proportionalPad: number;

View file

@ -25,7 +25,9 @@ export default class OSKSubKey extends OSKKey {
let ks=kDiv.style;
for(var tp in tKey) {
if(typeof spec[tp] != 'string') {
// The subkey has already had its _baseKeyEvent field constructed; don't overwrite it!
// Layout properties, however, are safe to overwrite.
if(typeof spec[tp] != 'string' && tp != '_baseKeyEvent') {
spec[tp]=tKey[tp];
}
}

View file

@ -1,73 +1,73 @@
import { type KeyElement } from '../../../keyElement.js';
import VisualKeyboard from '../../../visualKeyboard.js';
import PendingGesture from '../pendingGesture.interface.js';
import SubkeyPopup from './subkeyPopup.js';
// import { type KeyElement } from '../../../keyElement.js';
// import VisualKeyboard from '../../../visualKeyboard.js';
// import PendingGesture from '../pendingGesture.interface.js';
// import SubkeyPopup from './subkeyPopup.js';
/**
* (Conceptually) represents a finite-state-machine that determines
* whether or not a series of touch events corresponds to a longpress
* touch input. The `resolve` method may be used to trigger the
* subkey menu early, as with the upward quick-display shortcut.
*
* This is the default implementation of longpress behavior for KMW.
* Alterate implementations are modeled through the `embedded`
* namespace's equivalent, which is designed to facilitate custom
* modeling for such gestures.
*
* Once the conditions to recognize a longpress gesture have been
* fulfilled, this class's `promise` will resolve with a `SubkeyPopup`
* matching the gesture's 'base' key, which itself provides a
* `promise` field that will resolve to a `KeyEvent` once the touch
* sequence is completed.
*/
export default class PendingLongpress implements PendingGesture {
public readonly baseKey: KeyElement;
public readonly promise: Promise<SubkeyPopup>;
// /**
// * (Conceptually) represents a finite-state-machine that determines
// * whether or not a series of touch events corresponds to a longpress
// * touch input. The `resolve` method may be used to trigger the
// * subkey menu early, as with the upward quick-display shortcut.
// *
// * This is the default implementation of longpress behavior for KMW.
// * Alterate implementations are modeled through the `embedded`
// * namespace's equivalent, which is designed to facilitate custom
// * modeling for such gestures.
// *
// * Once the conditions to recognize a longpress gesture have been
// * fulfilled, this class's `promise` will resolve with a `SubkeyPopup`
// * matching the gesture's 'base' key, which itself provides a
// * `promise` field that will resolve to a `KeyEvent` once the touch
// * sequence is completed.
// */
// export default class PendingLongpress implements PendingGesture {
// public readonly baseKey: KeyElement;
// public readonly promise: Promise<SubkeyPopup>;
public readonly subkeyUI: SubkeyPopup;
// public readonly subkeyUI: SubkeyPopup;
private readonly vkbd: VisualKeyboard;
private resolver: (subkeyPopup: SubkeyPopup) => void;
// private readonly vkbd: VisualKeyboard;
// private resolver: (subkeyPopup: SubkeyPopup) => void;
private timerId: number;
private popupDelay: number = 500;
// private timerId: number;
// private popupDelay: number = 500;
constructor(vkbd: VisualKeyboard, baseKey: KeyElement) {
this.vkbd = vkbd;
this.baseKey = baseKey;
// constructor(vkbd: VisualKeyboard, baseKey: KeyElement) {
// this.vkbd = vkbd;
// this.baseKey = baseKey;
let _this = this;
this.promise = new Promise<SubkeyPopup>(function(resolve, reject) {
_this.resolver = resolve;
// After the timeout, it's no longer deferred; it's being fulfilled.
// Even if the actual subkey itself is still async.
_this.timerId = window.setTimeout(_this.resolve.bind(_this), _this.popupDelay);
});
}
// let _this = this;
// this.promise = new Promise<SubkeyPopup>(function(resolve, reject) {
// _this.resolver = resolve;
// // After the timeout, it's no longer deferred; it's being fulfilled.
// // Even if the actual subkey itself is still async.
// _this.timerId = window.setTimeout(_this.resolve.bind(_this), _this.popupDelay);
// });
// }
public cancel() {
if(this.timerId) {
window.clearTimeout(this.timerId);
this.timerId = null;
}
// public cancel() {
// if(this.timerId) {
// window.clearTimeout(this.timerId);
// this.timerId = null;
// }
if(this.resolver) {
this.resolver(null);
this.resolver = null;
}
}
// if(this.resolver) {
// this.resolver(null);
// this.resolver = null;
// }
// }
public resolve() {
// User has flicked up to get to the longpress, before
// the timeout has expired. We need to cancel the timeout.
// See #5950
if(this.timerId) {
window.clearTimeout(this.timerId);
this.timerId = null;
}
// public resolve() {
// // User has flicked up to get to the longpress, before
// // the timeout has expired. We need to cancel the timeout.
// // See #5950
// if(this.timerId) {
// window.clearTimeout(this.timerId);
// this.timerId = null;
// }
if(this.resolver) {
this.resolver(new SubkeyPopup(this.vkbd, this.baseKey));
}
}
}
// if(this.resolver) {
// this.resolver(new SubkeyPopup(this.vkbd, this.baseKey));
// }
// }
// }

View file

@ -1,12 +1,10 @@
import OSKSubKey from './oskSubKey.js';
import RealizedGesture from '../realizedGesture.interface.js';
import { type KeyElement } from '../../../keyElement.js';
import OSKBaseKey from '../../../keyboard-layout/oskBaseKey.js';
import VisualKeyboard from '../../../visualKeyboard.js';
import InputEventCoordinate from '../../../input/inputEventCoordinate.js';
import { DeviceSpec, KeyEvent, ActiveSubkey } from '@keymanapp/keyboard-processor';
import { InputSample } from '@keymanapp/gesture-recognizer';
import { ConfigChangeClosure, GestureRecognizerConfiguration, GestureSequence, PaddedZoneSource } from '@keymanapp/gesture-recognizer';
/**
* Represents a 'realized' longpress gesture's default implementation
@ -24,11 +22,10 @@ import { InputSample } from '@keymanapp/gesture-recognizer';
* The `Promise` may also resolve to `null` if the user indicates
* the desire to cancel subkey selection.
*/
export default class SubkeyPopup implements RealizedGesture {
export default class SubkeyPopup {
public readonly element: HTMLDivElement;
public readonly shim: HTMLDivElement;
private vkbd: VisualKeyboard;
private currentSelection: KeyElement;
private callout: HTMLDivElement;
@ -36,19 +33,34 @@ export default class SubkeyPopup implements RealizedGesture {
public readonly baseKey: KeyElement;
public readonly promise: Promise<KeyEvent>;
// Resolves the promise that generated this SubkeyPopup.
private resolver: (keyEvent: KeyEvent) => void;
public readonly subkeys: KeyElement[];
constructor(vkbd: VisualKeyboard, e: KeyElement) {
let _this = this;
this.promise = new Promise<KeyEvent>(function(resolve) {
_this.resolver = resolve;
})
this.vkbd = vkbd;
constructor(
source: GestureSequence<KeyElement>,
configChanger: ConfigChangeClosure<KeyElement>,
vkbd: VisualKeyboard,
e: KeyElement
) {
this.baseKey = e;
source.on('complete', () => {
this.currentSelection.key.highlight(false);
this.clear();
});
// From here, we want to make decisions based on only the subkey-menu portion of the gesture path.
const subkeyComponent = source.stageReports[0].sources[0].constructSubview(true, false);
// Watch for touchpoint selection of new keys.
subkeyComponent.path.on('step', (sample) => {
// Require a fudge-factor before dropping the default key.
if(subkeyComponent.path.stats.netDistance >= 4) {
this.currentSelection.key.highlight(false);
sample.item.key.highlight(true);
this.currentSelection = sample.item;
}
});
// If the user doesn't move their finger and releases, we'll output the base key
// by default.
this.currentSelection = e;
@ -60,15 +72,15 @@ export default class SubkeyPopup implements RealizedGesture {
// The holder is position:fixed, but the keys do not need to be, as no scrolling
// is possible while the array is visible. So it is simplest to let the keys have
// position:static and display:inline-block
var subKeys = this.element = document.createElement('div');
const elements = this.element = document.createElement('div');
var i;
subKeys.id='kmw-popup-keys';
elements.id='kmw-popup-keys';
// #3718: No longer prepend base key to popup array
// Must set position dynamically, not in CSS
var ss=subKeys.style;
var ss=elements.style;
// Set key font according to layout, or defaulting to OSK font
// (copied, not inherited, since OSK is not a parent of popup keys)
@ -85,6 +97,7 @@ export default class SubkeyPopup implements RealizedGesture {
ss.width=(nCols*e.offsetWidth+nCols*5)+'px';
// Add nested button elements for each sub-key
this.subkeys = [];
for(i=0; i<nKeys; i++) {
var needsTopMargin = false;
let nRow=Math.floor(i/nCols);
@ -99,8 +112,9 @@ export default class SubkeyPopup implements RealizedGesture {
}
let keyGenerator = new OSKSubKey(subKeySpec[i], layer);
let kDiv = keyGenerator.construct(vkbd, <KeyElement> e, needsTopMargin);
this.subkeys.push(kDiv.firstChild as KeyElement);
subKeys.appendChild(kDiv);
elements.appendChild(kDiv);
}
// And add a filter to fade main keyboard
@ -109,20 +123,82 @@ export default class SubkeyPopup implements RealizedGesture {
// Highlight the duplicated base key or ideal subkey (if a phone)
if(vkbd.device.formFactor == DeviceSpec.FormFactor.Phone) {
this.selectDefaultSubkey(vkbd, e, subKeys /* == this.element */);
this.selectDefaultSubkey(vkbd, e, elements /* == this.element */);
}
vkbd.topContainer.appendChild(this.element);
vkbd.topContainer.appendChild(this.shim);
// Must be placed after its `.element` has been inserted into the DOM.
this.reposition(vkbd);
const config = this.buildPopupRecognitionConfig(vkbd);
configChanger({
type: 'push',
config: config
});
}
finalize(input: InputSample<KeyElement>) {
if(this.resolver) {
let keyEvent: KeyEvent = null;
if(this.currentSelection) {
keyEvent = this.vkbd.initKeyEvent(this.currentSelection, input);
this.currentSelection.key.highlight(false);
private buildPopupRecognitionConfig(vkbd: VisualKeyboard): GestureRecognizerConfiguration<KeyElement, string> {
const baseBounding = this.element.getBoundingClientRect();
const underlyingKeyBounding = this.baseKey.getBoundingClientRect();
const subkeyStyle = this.subkeys[0].style;
const subkeyHeight = Number.parseInt(subkeyStyle.height, 10);
const basePadding = -0.333 * subkeyHeight; // extends bounds by the absolute value.
const bottomDistance = underlyingKeyBounding.bottom - baseBounding.bottom;
const roamBounding = new PaddedZoneSource(this.element, [
// top
basePadding,
// left, right
basePadding,
// bottom: ensure the recognition zone includes the row of the base key.
// basePadding is already negative, but bottomDistance isn't.
bottomDistance > basePadding ? -bottomDistance : basePadding
]);
return {
targetRoot: this.element,
inputStartBounds: vkbd.element,
maxRoamingBounds: roamBounding,
itemIdentifier: (coord, target) => {
let bestMatchKey: KeyElement = null;
let bestYdist = Number.MAX_VALUE;
let bestXdist = Number.MAX_VALUE;
for(let key of this.subkeys) {
const keyBounds = key.getBoundingClientRect();
let xDist = Number.MAX_VALUE;
let yDist = Number.MAX_VALUE;
if(keyBounds.left <= coord.clientX && coord.clientX < keyBounds.right) {
xDist = 0;
} else {
xDist = (keyBounds.left >= coord.clientX) ? keyBounds.left - coord.clientX : coord.clientX - keyBounds.right;
}
if(keyBounds.top <= coord.clientY && coord.clientY < keyBounds.bottom) {
yDist = 0;
} else {
yDist = (keyBounds.top >= coord.clientY) ? keyBounds.top - coord.clientY : coord.clientY - keyBounds.bottom;
}
if(xDist == 0 && yDist == 0) {
// Perfect match!
return key;
} else if(xDist < bestXdist || (xDist == bestXdist && yDist < bestYdist)) {
bestXdist = xDist;
bestMatchKey = key;
bestYdist = yDist;
}
}
return bestMatchKey;
}
this.resolver(keyEvent);
}
this.resolver = null;
}
reposition(vkbd: VisualKeyboard) {
@ -168,7 +244,7 @@ export default class SubkeyPopup implements RealizedGesture {
// Add the callout
if(vkbd.device.formFactor == DeviceSpec.FormFactor.Phone && vkbd.device.OS == DeviceSpec.OperatingSystem.iOS) {
this.callout = this.addCallout(e, delta);
this.callout = this.addCallout(e, delta, vkbd.topContainer);
}
}
@ -178,9 +254,7 @@ export default class SubkeyPopup implements RealizedGesture {
* @param {Object} key HTML key element
* @return {Object} callout object
*/
addCallout(key: KeyElement, delta?: number): HTMLDivElement {
const _Box = this.vkbd.topContainer;
addCallout(key: KeyElement, delta: number, _Box: HTMLElement): HTMLDivElement {
delta = delta || 0;
let calloutHeight = key.offsetHeight - delta + 6;
@ -248,11 +322,6 @@ export default class SubkeyPopup implements RealizedGesture {
}
clear() {
// Discard the reference to the Promise's resolve method, allowing
// GC to clean it up. The corresponding Promise's contract allows
// passive cancellation.
this.resolver = null;
// Remove the displayed subkey array, if any
if(this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
@ -266,33 +335,4 @@ export default class SubkeyPopup implements RealizedGesture {
this.callout.parentNode.removeChild(this.callout);
}
}
updateTouch(input: InputEventCoordinate) {
this.currentSelection = null;
this.baseKey.key.highlight(false);
for(let i=0; i < this.baseKey['subKeys'].length; i++) {
try {
let sk = this.element.childNodes[i].firstChild as KeyElement;
let onKey = sk.key.isUnderTouch(input);
if(onKey) {
this.currentSelection = sk;
}
sk.key.highlight(onKey);
} catch(ex) {
if(ex.message) {
console.error("Unexpected error when attempting to update selected subkey:" + ex.message);
} else {
console.error("Unexpected error (and error type) when attempting to update selected subkey.");
}
}
}
// Use the popup duplicate of the base key if a phone with a visible popup array
if(!this.currentSelection && this.baseKey.key.isUnderTouch(input)) {
this.baseKey.key.highlight(true);
this.currentSelection = this.baseKey;
}
}
}

View file

@ -39,7 +39,6 @@ import RealizedGesture from './input/gestures/realizedGesture.interface.js';
import { defaultFontSize, getFontSizeStyle } from './fontSizeUtils.js';
import PendingMultiTap, { PendingMultiTapState } from './input/gestures/browser/pendingMultiTap.js';
import InternalSubkeyPopup from './input/gestures/browser/subkeyPopup.js';
import InternalPendingLongpress from './input/gestures/browser/pendingLongpress.js';
import InternalKeyTip from './input/gestures/browser/keytip.js';
import CommonConfiguration from './config/commonConfiguration.js';
@ -47,6 +46,7 @@ import { gestureSetForLayout } from './input/gestures/specsForLayout.js';
import { getViewportScale } from './screenUtils.js';
import { HeldRepeater } from './input/gestures/heldRepeater.js';
import SubkeyPopup from './input/gestures/browser/subkeyPopup.js';
export interface VisualKeyboardConfiguration extends CommonConfiguration {
/**
@ -355,6 +355,9 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
* > The read-only target property of the Touch interface returns the (EventTarget) on which the touch contact
* started when it was first placed on the surface, even if the touch point has since moved outside the
* interactive area of that element[...]
*
* Therefore, `target` is for the initial element, not necessarily the one currently under
* the touchpoint - which matters during a 'touchmove'.
*/
return this.layerGroup.findNearestKey(sample);
@ -442,44 +445,59 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
// First, if we've configured the gesture to generate a keystroke, let's handle that.
const gestureKey = gestureStage.item;
let coordSource = gestureStage.sources[0];
let coord: InputSample<KeyElement, string> = null;
if(coordSource) {
// TODO: should probably vary depending upon `gestureStage.matchedId`
// (certain types should probably use the base coord... or even from
// a prior stage of the sequence as appropriate.)
//
// This is the coordinate used as the basis for fat-finger calculations.
coord = coordSource.currentSample;
}
if(gestureKey) {
let coordSource = gestureStage.sources[0];
let coord: InputSample<KeyElement, string> = null;
if(coordSource) {
// TODO: should probably vary depending upon `gestureStage.matchedId`
// (certain types should probably use the base coord... or even from
// a prior stage of the sequence as appropriate.)
//
// This is the coordinate used as the basis for fat-finger calculations.
coord = coordSource.currentSample;
}
if(gestureStage.matchedId == 'multitap') {
// TODO: examine sequence, determine rota-style index to apply; select THAT item instead.
}
// Once the best coord to use for fat-finger calculations has been determined:
this.modelKeyClick(gestureStage.item, coord);
// -- Scratch-space as gestures start becoming integrated --
// Reordering may follow at some point.
//
// Potential long-term idea: only handle the first stage; delegate future stages to
// specialized handlers for the remainder of the sequence.
// Should work for modipresses, too... I think.
if(gestureStage.matchedId == 'special-key-start' && gestureKey.key.spec.baseKeyID == 'K_BKSP') {
// Possible enhancement: maybe update the held location for the backspace if there's movement?
// But... that seems pretty low-priority.
//
// Merely constructing the instance is enough; it'll link into the sequence's events and
// handle everything that remains for the backspace from here.
new HeldRepeater(gestureSequence, () => this.modelKeyClick(gestureKey, coord));
if(gestureStage.matchedId == 'subkey-select') {
// TODO: examine subkey menu, determine proper set of fat-finger alternates.
}
// TODO: depending upon the gesture type, what sort of UI shifts should happen to
// facilitate follow-up stages?
// Once the best coord to use for fat-finger calculations has been determined:
this.modelKeyClick(gestureStage.item, coord);
}
// Outside of passing keys along... the handling of later stages is delegated
// to gesture-specific handling classes.
if(gestureSequence.stageReports.length > 1) {
return;
}
// So, if this is the first stage, this is where we need to perform that delegation.
// -- Scratch-space as gestures start becoming integrated --
// Reordering may follow at some point.
//
// Potential long-term idea: only handle the first stage; delegate future stages to
// specialized handlers for the remainder of the sequence.
// Should work for modipresses, too... I think.
if(gestureStage.matchedId == 'special-key-start' && gestureKey.key.spec.baseKeyID == 'K_BKSP') {
// Possible enhancement: maybe update the held location for the backspace if there's movement?
// But... that seems pretty low-priority.
//
// Merely constructing the instance is enough; it'll link into the sequence's events and
// handle everything that remains for the backspace from here.
new HeldRepeater(gestureSequence, () => this.modelKeyClick(gestureKey, coord));
} else if(gestureStage.matchedId == 'longpress') {
// Likewise.
new SubkeyPopup(gestureSequence, configChanger, this, gestureSequence.stageReports[0].sources[0].baseItem);
}
// TODO: depending upon the gesture type, what sort of UI shifts should happen to
// facilitate follow-up stages?
})
});
@ -1764,30 +1782,30 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
}
}
/**
* Starts an implementation-specific longpress gesture. Separately implemented for
* in-browser and embedded modes.
* @param key The base key of the longpress.
* @returns
*/
startLongpress(key: KeyElement): PendingGesture {
// First-level object/Promise: will produce a subkey popup when the longpress gesture completes.
// 'Returns' a second-level object/Promise: resolves when a subkey is selected or is cancelled.
let pendingLongpress = new InternalPendingLongpress(this, key);
pendingLongpress.promise.then((subkeyPopup) => {
// In-browser-specific handling.
if (subkeyPopup) {
// Append the touch-hold (subkey) array to the OSK
this.topContainer.appendChild(subkeyPopup.element);
this.topContainer.appendChild(subkeyPopup.shim);
// /**
// * Starts an implementation-specific longpress gesture. Separately implemented for
// * in-browser and embedded modes.
// * @param key The base key of the longpress.
// * @returns
// */
// startLongpress(key: KeyElement): PendingGesture {
// // First-level object/Promise: will produce a subkey popup when the longpress gesture completes.
// // 'Returns' a second-level object/Promise: resolves when a subkey is selected or is cancelled.
// let pendingLongpress = new InternalPendingLongpress(this, key);
// pendingLongpress.promise.then((subkeyPopup) => {
// // In-browser-specific handling.
// if (subkeyPopup) {
// // Append the touch-hold (subkey) array to the OSK
// this.topContainer.appendChild(subkeyPopup.element);
// this.topContainer.appendChild(subkeyPopup.shim);
// Must be placed after its `.element` has been inserted into the DOM.
subkeyPopup.reposition(this);
}
});
// // Must be placed after its `.element` has been inserted into the DOM.
// subkeyPopup.reposition(this);
// }
// });
return pendingLongpress;
}
// return pendingLongpress;
// }
/**
* Initializes all supported gestures given a base key and the triggering touch coordinates.
@ -1821,36 +1839,36 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
}
if (key['subKeys']) {
let _this = this;
// if (key['subKeys']) {
// let _this = this;
let pendingLongpress = this.startLongpress(key);
if (pendingLongpress == null) {
return;
}
this.pendingSubkey = pendingLongpress;
// let pendingLongpress = this.startLongpress(key);
// if (pendingLongpress == null) {
// return;
// }
// this.pendingSubkey = pendingLongpress;
pendingLongpress.promise.then(function (subkeyPopup) {
if (_this.pendingSubkey == pendingLongpress) {
_this.pendingSubkey = null;
}
// pendingLongpress.promise.then(function (subkeyPopup) {
// if (_this.pendingSubkey == pendingLongpress) {
// _this.pendingSubkey = null;
// }
if (subkeyPopup) {
// Clear key preview if any
_this.showKeyTip(null, false);
// if (subkeyPopup) {
// // Clear key preview if any
// _this.showKeyTip(null, false);
_this.subkeyGesture = subkeyPopup;
subkeyPopup.promise.then(function (keyEvent: KeyEvent) {
// Allow active cancellation, even if the source should allow passive.
// It's an easy and cheap null guard.
if (keyEvent) {
_this.raiseKeyEvent(keyEvent, null);
}
_this.clearPopup();
});
}
});
}
// _this.subkeyGesture = subkeyPopup;
// subkeyPopup.promise.then(function (keyEvent: KeyEvent) {
// // Allow active cancellation, even if the source should allow passive.
// // It's an easy and cheap null guard.
// if (keyEvent) {
// _this.raiseKeyEvent(keyEvent, null);
// }
// _this.clearPopup();
// });
// }
// });
// }
}
/**
@ -1885,16 +1903,16 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
this.currentTarget = null;
// If popup is visible, need to move over popup, not over main keyboard
// Could be turned into a browser-longpress specific implementation within browser.PendingLongpress?
if (key1 && key1['subKeys'] != null && this.initTouchCoord) {
if(this.pendingSubkey && this.pendingSubkey instanceof InternalPendingLongpress) {
// Show popup keys immediately if touch moved up towards key array (KMEW-100, Build 353)
if (this.initTouchCoord.y - input.y > this.getLongpressFlickThreshold()) {
this.pendingSubkey.resolve();
}
}
}
// // If popup is visible, need to move over popup, not over main keyboard
// // Could be turned into a browser-longpress specific implementation within browser.PendingLongpress?
// if (key1 && key1['subKeys'] != null && this.initTouchCoord) {
// if(this.pendingSubkey && this.pendingSubkey instanceof InternalPendingLongpress) {
// // Show popup keys immediately if touch moved up towards key array (KMEW-100, Build 353)
// if (this.initTouchCoord.y - input.y > this.getLongpressFlickThreshold()) {
// this.pendingSubkey.resolve();
// }
// }
// }
// If there is an active popup menu (which can occur from the previous block),
// a subkey popup exists; do not allow base key output.