} p Array object with OSK left, top
- * Description Set position of OSK window, but limit to screen, and ignore if a touch input device
+ * Set position of OSK window, but limited to the screen.
+ *
+ * Designed solely for use with the FloatingOSKView use pattern, but is a
+ * long-standing API endpoint that needs preservation.
+ * @param p Array object with OSK left, top
*/
['setPos'](p: OSKPos) {
return; // I3363 (Build 301)
@@ -156,8 +161,8 @@ namespace com.keyman.osk {
/**
* Allow UI to respond to OSK being shown (passing position and properties)
*
- * @param {Object=} p object with coordinates and userdefined flag
- * @return {boolean}
+ * @param p object with coordinates and userdefined flag
+ * @return
*
*/
doShow(p) {
@@ -165,11 +170,10 @@ namespace com.keyman.osk {
}
/**
- * Allow UI to update respond to OSK being hidden
- *
- * @param {Object=} p object with coordinates and userdefined flag
- * @return {boolean}
+ * Allows UI modules to update state when the OSK is being hidden
*
+ * @param p object with coordinates and userdefined flag
+ * @return
*/
doHide(p) {
return com.keyman.singleton.util.callEvent('osk.hide',p);
diff --git a/web/source/osk/inputEventEngine.ts b/web/source/osk/inputEventEngine.ts
index cf3646d4a7..82e4e501c2 100644
--- a/web/source/osk/inputEventEngine.ts
+++ b/web/source/osk/inputEventEngine.ts
@@ -1,32 +1,59 @@
///
namespace com.keyman.osk {
- export abstract class InputEventEngine {
- protected readonly vkbd: VisualKeyboard;
- protected readonly eventRoot: HTMLElement;
+ export type InputHandler = (coord: InputEventCoordinate) => void;
- public constructor(vkbd: VisualKeyboard, eventRoot: HTMLElement) {
- this.vkbd = vkbd;
- this.eventRoot = eventRoot;
+ export interface InputEventEngineConfig {
+ /**
+ * Specifies the element that input listeners should be attached to.
+ */
+ readonly eventRoot: HTMLElement;
+ /**
+ * Specifies the most specific common ancestor element of any event target
+ * that the InputEventEngine should consider.
+ */
+ readonly targetRoot: HTMLElement;
+
+ readonly coordConstrainedWithinInteractiveBounds: (coord: InputEventCoordinate) => boolean;
+
+ readonly inputStartHandler?: InputHandler;
+ readonly inputMoveHandler?: InputHandler;
+ readonly inputMoveCancelHandler?: InputHandler;
+ readonly inputEndHandler?: InputHandler;
+ }
+
+ export abstract class InputEventEngine {
+ protected readonly config: InputEventEngineConfig;
+
+ public constructor(config: InputEventEngineConfig) {
+ this.config = config;
}
abstract registerEventHandlers();
abstract unregisterEventHandlers();
onInputStart(coord: InputEventCoordinate) {
- this.vkbd.touch(coord);
+ if(this.config.inputStartHandler) {
+ this.config.inputStartHandler(coord);
+ }
}
onInputMove(coord: InputEventCoordinate) {
- this.vkbd.moveOver(coord);
+ if(this.config.inputMoveHandler) {
+ this.config.inputMoveHandler(coord);
+ }
}
onInputMoveCancel(coord: InputEventCoordinate) {
- this.vkbd.moveCancel(coord);
+ if(this.config.inputMoveCancelHandler) {
+ this.config.inputMoveCancelHandler(coord);
+ }
}
onInputEnd(coord: InputEventCoordinate) {
- this.vkbd.release(coord);
+ if(this.config.inputEndHandler) {
+ this.config.inputEndHandler(coord);
+ }
}
}
}
\ No newline at end of file
diff --git a/web/source/osk/mouseEventEngine.ts b/web/source/osk/mouseEventEngine.ts
index 3ad6bf8e39..ee77db7844 100644
--- a/web/source/osk/mouseEventEngine.ts
+++ b/web/source/osk/mouseEventEngine.ts
@@ -9,27 +9,56 @@ namespace com.keyman.osk {
private hasActiveClick: boolean = false;
private ignoreSequence: boolean = false;
- public constructor(vkbd: VisualKeyboard) {
- // document.body is the event root b/c we need to track the mouse if it leaves
- // the VisualKeyboard's hierarchy.
- super(vkbd, document.body);
+ public constructor(config: InputEventEngineConfig) {
+ super(config);
this._mouseStart = this.onMouseStart.bind(this);
this._mouseMove = this.onMouseMove.bind(this);
this._mouseEnd = this.onMouseEnd.bind(this);
}
+ public static forVisualKeyboard(vkbd: VisualKeyboard) {
+ const config: InputEventEngineConfig = {
+ targetRoot: vkbd.element,
+ // document.body is the event root b/c we need to track the mouse if it leaves
+ // the VisualKeyboard's hierarchy.
+ eventRoot: document.body,
+ inputStartHandler: vkbd.touch.bind(vkbd),
+ inputMoveHandler: vkbd.moveOver.bind(vkbd),
+ inputMoveCancelHandler: vkbd.moveCancel.bind(vkbd),
+ inputEndHandler: vkbd.release.bind(vkbd),
+ coordConstrainedWithinInteractiveBounds: vkbd.detectWithinInteractiveBounds.bind(vkbd)
+ };
+
+ return new MouseEventEngine(config);
+ }
+
+ public static forPredictiveBanner(banner: SuggestionBanner, handlerRoot: SuggestionManager) {
+ const config: InputEventEngineConfig = {
+ targetRoot: banner.getDiv(),
+ // document.body is the event root b/c we need to track the mouse if it leaves
+ // the VisualKeyboard's hierarchy.
+ eventRoot: document.body,
+ inputStartHandler: handlerRoot.touchStart.bind(handlerRoot),
+ inputMoveHandler: handlerRoot.touchMove.bind(handlerRoot),
+ inputEndHandler: handlerRoot.touchEnd.bind(handlerRoot),
+ coordConstrainedWithinInteractiveBounds: function() { return true; }
+ };
+
+ return new MouseEventEngine(config);
+ }
+
registerEventHandlers() {
- this.eventRoot.addEventListener('mousedown', this._mouseStart, true);
- this.eventRoot.addEventListener('mousemove', this._mouseMove, false);
+ this.config.eventRoot.addEventListener('mousedown', this._mouseStart, true);
+ this.config.eventRoot.addEventListener('mousemove', this._mouseMove, false);
// The listener below fails to capture when performing automated testing checks in Chrome emulation unless 'true'.
- this.eventRoot.addEventListener('mouseup', this._mouseEnd, true);
+ this.config.eventRoot.addEventListener('mouseup', this._mouseEnd, true);
}
unregisterEventHandlers() {
- this.eventRoot.removeEventListener('mousedown', this._mouseStart, true);
- this.eventRoot.removeEventListener('mousemove', this._mouseMove, false);
- this.eventRoot.removeEventListener('mouseup', this._mouseEnd, true);
+ this.config.eventRoot.removeEventListener('mousedown', this._mouseStart, true);
+ this.config.eventRoot.removeEventListener('mousemove', this._mouseMove, false);
+ this.config.eventRoot.removeEventListener('mouseup', this._mouseEnd, true);
}
private preventPropagation(e: MouseEvent) {
@@ -46,7 +75,7 @@ namespace com.keyman.osk {
}
onMouseStart(event: MouseEvent) {
- if(!this.vkbd.element.contains(event.target as Node)) {
+ if(!this.config.targetRoot.contains(event.target as Node)) {
this.ignoreSequence = true;
return;
}
@@ -76,7 +105,7 @@ namespace com.keyman.osk {
this.preventPropagation(event);
- if(this.vkbd.detectWithinInteractiveBounds(coord)) {
+ if(this.config.coordConstrainedWithinInteractiveBounds(coord)) {
this.onInputMove(coord);
} else {
this.onInputMoveCancel(coord);
diff --git a/web/source/osk/oskBaseKey.ts b/web/source/osk/oskBaseKey.ts
index b12d7c8ba5..d8fc20f165 100644
--- a/web/source/osk/oskBaseKey.ts
+++ b/web/source/osk/oskBaseKey.ts
@@ -140,7 +140,9 @@ namespace com.keyman.osk {
let util = com.keyman.singleton.util;
const device = vkbd.device;
- const resizeLabels = (device.OS == 'ios' && device.formFactor == 'phone' && util.landscapeView());
+ const resizeLabels = (device.OS == utils.OperatingSystem.iOS &&
+ device.formFactor == utils.FormFactor.Phone
+ && util.landscapeView());
// Rescale keycap labels on iPhone (iOS 7)
if(resizeLabels && this.capLabel) {
diff --git a/web/source/osk/oskKey.ts b/web/source/osk/oskKey.ts
index 278e7cbe52..c05f278f28 100644
--- a/web/source/osk/oskKey.ts
+++ b/web/source/osk/oskKey.ts
@@ -478,7 +478,7 @@ namespace com.keyman.osk {
ts.fontSize = this.getIdealFontSize(vkbd, styleSpec);
// Finalize the key's text.
- t.innerHTML = keyText;
+ t.innerText = keyText;
return t;
}
diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts
index 871cc92414..2a8756854f 100644
--- a/web/source/osk/oskView.ts
+++ b/web/source/osk/oskView.ts
@@ -94,7 +94,7 @@ namespace com.keyman.osk {
this._Box.style.userSelect = 'none';
// Initializes the two constant OSKComponentView fields.
- this.bannerView = new BannerManager();
+ this.bannerView = new BannerManager(this.hostDevice);
this.keyboardView = null;
let keymanweb = com.keyman.singleton;
@@ -514,12 +514,20 @@ namespace com.keyman.osk {
}
const bs = this._Box.style;
+ // OSK size settings can only be reliably applied to standard VisualKeyboard
+ // visualizations, not to help text or empty views.
bs.width = bs.maxWidth = this.computedWidth + 'px';
bs.height = bs.maxHeight = this.computedHeight + 'px';
- let keyman = com.keyman.singleton;
- keyman.alignInputs();
+ } else {
+ const bs = this._Box.style;
+ bs.width = 'auto';
+ bs.height = 'auto';
+ bs.maxWidth = bs.maxHeight = '';
}
+
+ let keyman = com.keyman.singleton;
+ keyman.alignInputs();
}
public refreshLayoutIfNeeded(pending?: boolean) {
diff --git a/web/source/osk/touchEventEngine.ts b/web/source/osk/touchEventEngine.ts
index bddac59ea0..4e141d20ed 100644
--- a/web/source/osk/touchEventEngine.ts
+++ b/web/source/osk/touchEventEngine.ts
@@ -6,25 +6,54 @@ namespace com.keyman.osk {
private readonly _touchMove: typeof TouchEventEngine.prototype.onTouchMove;
private readonly _touchEnd: typeof TouchEventEngine.prototype.onTouchEnd;
- public constructor(vkbd: VisualKeyboard) {
- super(vkbd, vkbd.element);
+ public constructor(config: InputEventEngineConfig) {
+ super(config);
this._touchStart = this.onTouchStart.bind(this);
this._touchMove = this.onTouchMove.bind(this);
this._touchEnd = this.onTouchEnd.bind(this);
}
+ public static forVisualKeyboard(vkbd: VisualKeyboard) {
+ let config: InputEventEngineConfig = {
+ targetRoot: vkbd.element,
+ eventRoot: vkbd.element,
+ inputStartHandler: vkbd.touch.bind(vkbd),
+ inputMoveHandler: vkbd.moveOver.bind(vkbd),
+ inputMoveCancelHandler: vkbd.moveCancel.bind(vkbd),
+ inputEndHandler: vkbd.release.bind(vkbd),
+ coordConstrainedWithinInteractiveBounds: vkbd.detectWithinInteractiveBounds.bind(vkbd)
+ };
+
+ return new TouchEventEngine(config);
+ }
+
+ public static forPredictiveBanner(banner: SuggestionBanner, handlerRoot: SuggestionManager) {
+ const config: InputEventEngineConfig = {
+ targetRoot: banner.getDiv(),
+ // document.body is the event root b/c we need to track the mouse if it leaves
+ // the VisualKeyboard's hierarchy.
+ eventRoot: banner.getDiv(),
+ inputStartHandler: handlerRoot.touchStart.bind(handlerRoot),
+ inputMoveHandler: handlerRoot.touchMove.bind(handlerRoot),
+ inputEndHandler: handlerRoot.touchEnd.bind(handlerRoot),
+ coordConstrainedWithinInteractiveBounds: function() { return true; }
+ };
+
+ return new TouchEventEngine(config);
+ }
+
registerEventHandlers() {
- this.eventRoot.addEventListener('touchstart', this._touchStart, true);
- this.eventRoot.addEventListener('touchmove', this._touchMove, false);
+ this.config.eventRoot.addEventListener('touchstart', this._touchStart, true);
+ this.config.eventRoot.addEventListener('touchmove', this._touchMove, false);
// The listener below fails to capture when performing automated testing checks in Chrome emulation unless 'true'.
- this.eventRoot.addEventListener('touchend', this._touchEnd, true);
+ this.config.eventRoot.addEventListener('touchend', this._touchEnd, true);
}
unregisterEventHandlers() {
- this.eventRoot.removeEventListener('touchstart', this._touchStart, true);
- this.eventRoot.removeEventListener('touchmove', this._touchMove, false);
- this.eventRoot.removeEventListener('touchend', this._touchEnd, true);
+ this.config.eventRoot.removeEventListener('touchstart', this._touchStart, true);
+ this.config.eventRoot.removeEventListener('touchmove', this._touchMove, false);
+ this.config.eventRoot.removeEventListener('touchend', this._touchEnd, true);
}
private preventPropagation(e: TouchEvent) {
@@ -47,7 +76,7 @@ namespace com.keyman.osk {
this.preventPropagation(event);
const coord = InputEventCoordinate.fromEvent(event);
- if(this.vkbd.detectWithinInteractiveBounds(coord)) {
+ if(this.config.coordConstrainedWithinInteractiveBounds(coord)) {
this.onInputMove(coord);
} else {
this.onInputMoveCancel(coord);
diff --git a/web/source/osk/uiTouchHandlerBase.ts b/web/source/osk/uiTouchHandlerBase.ts
index a841001511..cd9c5fb861 100644
--- a/web/source/osk/uiTouchHandlerBase.ts
+++ b/web/source/osk/uiTouchHandlerBase.ts
@@ -16,15 +16,15 @@ namespace com.keyman.osk {
// before interpreting the currently-ongoing touch command as having scrolled.
static readonly HAS_SCROLLED_FUDGE_FACTOR = 10;
- constructor(touch: Touch) {
- this.x = touch.pageX;
+ constructor(coord: InputEventCoordinate) {
+ this.x = coord.x;
this.totalLength = 0;
}
- updateTo(touch: Touch): {deltaX: number} {
+ updateTo(coord: InputEventCoordinate): {deltaX: number} {
let x = this.x;
- this.x = touch.pageX;
+ this.x = coord.x;
let deltas = {deltaX: this.x - x};
this.totalLength += Math.abs(deltas.deltaX);
@@ -126,22 +126,14 @@ namespace com.keyman.osk {
* Identify the key nearest to (but NOT under) the touch point if at the end of a key row,
* but return null more than about 0.6 key width from the nearest key.
*
- * @param {Event} e touch event
+ * @param {Object} coord A pre-analyzed input coordinate
* @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
*
**/
- 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 = touchList[0].pageX;
+ private findTargetFromTouch(coord: InputEventCoordinate, t: HTMLElement, forMove: boolean): Target {
+ var x = coord.x;
// 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) {
@@ -201,13 +193,15 @@ namespace com.keyman.osk {
return null;
}
- findBestTarget(e: TouchEvent, forMove?: boolean) {
+ findBestTarget(coord: InputEventCoordinate, forMove?: boolean) {
var eventTarget: HTMLElement;
if(forMove) {
- eventTarget = document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY) as HTMLElement;
+ const clientX = coord.x + document.body.scrollLeft;
+ const clientY = coord.y + document.body.scrollTop;
+ eventTarget = document.elementFromPoint(clientX, clientY) as HTMLElement;
} else {
- eventTarget = e.changedTouches[0].target as HTMLElement;
+ eventTarget = coord.target as HTMLElement;
}
let target = this.findTargetFrom(eventTarget);
@@ -216,7 +210,7 @@ namespace com.keyman.osk {
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);
+ target = this.findTargetFromTouch(coord, eventTarget, forMove);
}
return target;
@@ -231,16 +225,15 @@ namespace com.keyman.osk {
return false;
}
- touchStart(e: TouchEvent) {
+ touchStart(coord: InputEventCoordinate) {
// Determine the selected Target, manage state.
- //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;
+ this.currentTarget = this.findBestTarget(coord);
+ this.touchX = coord.x;
+ this.touchY = coord.y;
// If popup stuff, immediately return.
- this.touchCount = e.touches.length;
+ this.touchCount = coord.activeInputCount;
if(!this.currentTarget) {
return;
@@ -248,7 +241,7 @@ namespace com.keyman.osk {
// Establish scroll tracking.
let shouldScroll = (this.currentTarget.clientWidth < this.currentTarget.scrollWidth);
- this.scrollTouchState = shouldScroll ? new ScrollState(e.changedTouches[0]) : null;
+ this.scrollTouchState = shouldScroll ? new ScrollState(coord) : null;
// Alright, Target acquired! Now to use it:
@@ -271,13 +264,13 @@ namespace com.keyman.osk {
this.pendingTarget = this.currentTarget;
}
- touchEnd(e: TouchEvent): void {
+ touchEnd(coord: InputEventCoordinate): void {
// Prevent incorrect multi-touch behaviour if native or device popup visible
let t = this.currentTarget;
if(this.isSubmenuActive() || this.hasModalPopup()) {
// Ignore release if a multiple touch
- if(e.touches.length > 0) {
+ if(coord.activeInputCount > 0) {
return;
}
@@ -291,7 +284,7 @@ namespace com.keyman.osk {
// Test if moved off screen (effective release point must be corrected for touch point horizontal speed)
// This is not completely effective and needs some tweaking, especially on Android
- var x = e.changedTouches[0].pageX;
+ var x = coord.x;
var beyondEdge = ((x < 2 && this.touchX > 5) || (x > window.innerWidth - 2 && this.touchX < window.innerWidth - 5));
if(this.scrollTouchState) {
@@ -316,7 +309,7 @@ namespace com.keyman.osk {
this.pendingTarget = null;
// Always clear highlighting of current target on release (multi-touch)
} else {
- t = this.findBestTarget(e);
+ t = this.findBestTarget(coord);
if(t) {
this.highlight(t,false);
@@ -327,40 +320,31 @@ namespace com.keyman.osk {
/**
* OSK touch move event handler
*
- * @param {Event} e touch move event object
+ * @param {Object} coord A pre-analyzed input coordinate
*
**/
- touchMove(e: TouchEvent): void {
+ touchMove(coord: InputEventCoordinate) : void {
let keyman = com.keyman.singleton;
let util = keyman.util;
- e.preventDefault();
- e.cancelBubble=true;
-
- if(typeof e.stopImmediatePropagation == 'function') {
- e.stopImmediatePropagation();
- } else if(typeof e.stopPropagation == 'function') {
- e.stopPropagation();
- }
-
// Do not attempt to support reselection of target key for overlapped keystrokes
- if(e.touches.length > 1 || this.touchCount == 0) {
+ if(coord.activeInputCount > 1 || this.touchCount == 0) {
return;
}
if(this.currentTarget && this.scrollTouchState != null) {
- let deltaX = this.scrollTouchState.updateTo(e.changedTouches[0]).deltaX;
+ let deltaX = this.scrollTouchState.updateTo(coord).deltaX;
this.currentTarget.scrollLeft -= window.devicePixelRatio * deltaX;
return;
}
// Get touch position
- var y=typeof e.touches == 'object' ? e.touches[0].clientY : e.clientY;
+ var y = coord.y;
// Move target key and highlighting
var key0 = this.pendingTarget,
- key1 = this.findBestTarget(e, true); // For the OSK, this ALSO gets subkeys.
+ key1 = this.findBestTarget(coord, true); // For the OSK, this ALSO gets subkeys.
// If option should not be selectable, how do we re-target?
@@ -425,15 +409,11 @@ namespace com.keyman.osk {
// Cancel touch if moved up and off keyboard, unless popup keys visible
} else {
let base = this.baseElement;
- let parent = base.parentElement; // parentElement is _Box
- let top = base.offsetTop;
- if(parent) {
- top += parent.offsetTop;
- }
+ let top = dom.Utils.getAbsoluteY(base);
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)) {
+ if(key0 && (coord.y < yMin || coord.y > yMax)) {
this.highlight(key0,false);
this.clearHolds();
this.pendingTarget = null;
diff --git a/web/source/osk/visualKeyboard.ts b/web/source/osk/visualKeyboard.ts
index 61c70ed177..2e25ebba27 100644
--- a/web/source/osk/visualKeyboard.ts
+++ b/web/source/osk/visualKeyboard.ts
@@ -193,9 +193,9 @@ namespace com.keyman.osk {
// Needs to occur AFTER this.kbdDiv is initialized.
if (!this.isStatic) {
if (this.hostDevice.touchable) {
- this.inputEngine = new TouchEventEngine(this);
+ this.inputEngine = TouchEventEngine.forVisualKeyboard(this);
} else {
- this.inputEngine = new MouseEventEngine(this);
+ this.inputEngine = MouseEventEngine.forVisualKeyboard(this);
}
this.inputEngine.registerEventHandlers();
}
@@ -1211,7 +1211,7 @@ namespace com.keyman.osk {
var fs = 1.0;
// TODO: Logically, this should be needed for Android, too - may need to be changed for the next version!
- if (device.OS == 'ios' && !keyman.isEmbedded) {
+ if (device.OS == utils.OperatingSystem.iOS && !keyman.isEmbedded) {
fs = fs / keyman.util.getViewportScale();
}
diff --git a/web/source/utils/styleConstants.ts b/web/source/utils/styleConstants.ts
index 69e51296dc..0ea2e3be22 100644
--- a/web/source/utils/styleConstants.ts
+++ b/web/source/utils/styleConstants.ts
@@ -12,7 +12,7 @@ namespace com.keyman.utils {
export class StyleConstants {
constructor(device: com.keyman.utils.DeviceSpec) {
// popupCanvasBackgroundColor
- if(device.OS == 'android') {
+ if(device.OS == utils.OperatingSystem.Android) {
this.popupCanvasBackgroundColor = '#999';
} else {
this.popupCanvasBackgroundColor = StyleConstants.prefersDarkMode() ? '#0f1319' : '#ffffff';
diff --git a/web/testing/common/test.css b/web/testing/common/test.css
new file mode 100644
index 0000000000..88eacbf7e4
--- /dev/null
+++ b/web/testing/common/test.css
@@ -0,0 +1,22 @@
+body {
+ font-family: Tahoma,helvetica;
+}
+
+h3 {
+ font-size: 1em;
+ font-weight:normal;
+ color: darkred;
+ margin-bottom: 4px
+}
+
+.test {
+ font-size: 1.5em;
+ width:80%;
+ min-height:30px;
+ border: 1px solid gray;
+}
+
+#KeymanWebControl {
+ width:50%;
+ min-width:600px;
+}
diff --git a/web/testing/empty-row/afghan_turkmen-1.1.js b/web/testing/empty-row/afghan_turkmen-1.1.js
new file mode 100644
index 0000000000..85ed447eda
--- /dev/null
+++ b/web/testing/empty-row/afghan_turkmen-1.1.js
@@ -0,0 +1 @@
+KeymanWeb.KR(new Keyboard_afghan_turkmen());function Keyboard_afghan_turkmen(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_afghan_turkmen";this.KN="Afghan Turkmen";this.KMINVER="9.0";this.KRTL=1;this.KV={F:' 1em "Arial"',K102:0};this.KV.KLS={"shift": ["~","!","٬","٫","ریال","٪","×","،","*",")","(","ـ","+","","","","ْ","ٌ","ٍ","ً","ُ","ِ","َ","ّ","]","[","}","{","|","","","","ؤ","ئ","ي","إ","أ","آ","ݩ","»","«",":","؛","","","","","","","ك","ې","ژ","ٰ","","ٔ","ۉ",">","<","؟","","","","","",""],"default": ["","۱","۲","۳","۴","۵","۶","۷","۸","۹","۰","-","=","","","","ض","ص","ث","ق","ف","غ","ع","ه","خ","ح","ج","چ","\\","","","","ش","س","ی","ب","ل","ا","ت","ن","م","ک","گ","","","","","","","ظ","ط","ز","ر","ذ","د","پ","و",".","/","","","","","",""]};this.KV.BK=(function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,""),r=[],v,i,m=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.0";this.KMBM=0x0010;this.KVKL={"phone":{"displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1"},{"id":"2","key":[{"id":"K_Q","text":"\u0636"},{"id":"K_W","text":"\u0635"},{"id":"K_E","text":"\u062B"},{"id":"K_R","text":"\u0642"},{"id":"K_T","text":"\u0641"},{"id":"K_Y","text":"\u063A"},{"id":"K_U","text":"\u0639"},{"id":"K_I","text":"\u0647"},{"id":"K_O","text":"\u062E"},{"id":"K_P","text":"\u062D"},{"id":"K_LBRKT","text":"\u062C"}]},{"id":"3","key":[{"id":"K_A","text":"\u0634"},{"id":"K_S","text":"\u0633"},{"id":"K_D","text":"\u06CC"},{"id":"K_F","text":"\u0628"},{"id":"K_G","text":"\u0644"},{"id":"K_H","text":"\u0627"},{"id":"K_J","text":"\u062A"},{"id":"K_K","text":"\u0646"},{"id":"K_L","text":"\u0645"},{"id":"K_COLON","text":"\u06A9"},{"id":"K_QUOTE","text":"\u06AF"}]},{"id":"4","key":[{"id":"K_Z","text":"\u0638"},{"id":"K_X","text":"\u0637"},{"layer":"shift","id":"K_C","text":"\u0698"},{"id":"K_C","text":"\u0632"},{"id":"K_V","text":"\u0631"},{"id":"K_B","text":"\u0630"},{"id":"K_N","text":"\u062F"},{"id":"K_M","text":"\u067E"},{"id":"K_COMMA","text":"\u0648"},{"id":"K_RBRKT","text":"\u0686"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"5","key":[{"width":"160","id":"K_SHIFT","sp":"1","text":"*Shift*"},{"layer":"shift","id":"K_7","text":"\u060C"},{"width":"140","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"415","id":"K_SPACE"},{"id":"U_200C","text":this._v>13?"*ZWNJ*":"<|>"},{"id":"K_PERIOD","text":"."},{"width":"145","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_1","text":"\u06F1"},{"id":"K_2","text":"\u06F2"},{"id":"K_3","text":"\u06F3"},{"id":"K_4","text":"\u06F4"},{"id":"K_5","text":"\u06F5"},{"id":"K_6","text":"\u06F6"},{"id":"K_7","text":"\u06F7"},{"id":"K_8","text":"\u06F8"},{"id":"K_9","text":"\u06F9"},{"id":"K_0","text":"\u06F0"},{"id":"K_HYPHEN","text":"-"},{"id":"K_EQUAL","text":"="}]},{"id":"2","key":[{"id":"K_Q","text":"\u0652"},{"id":"K_W","text":"\u064C"},{"id":"K_E","text":"\u064D"},{"id":"K_R","text":"\u064B"},{"id":"K_T","text":"\u064F"},{"id":"K_Y","text":"\u0650"},{"id":"K_U","text":"\u064E"},{"id":"K_I","text":"\u0651"},{"id":"K_O","text":"]"},{"id":"K_P","text":"["},{"id":"K_LBRKT","text":"}"},{"id":"K_RBRKT","text":"{"}]},{"id":"3","key":[{"id":"K_A","text":"\u0624"},{"id":"K_S","text":"\u0626"},{"id":"K_D","text":"\u064A"},{"id":"K_F","text":"\u0625"},{"id":"K_G","text":"\u0623"},{"id":"K_H","text":"\u0622"},{"id":"K_J","text":"\u0629"},{"id":"K_K","text":"\u00BB"},{"id":"K_L","text":"\u00AB"},{"id":"K_COLON","text":":"},{"id":"K_QUOTE","text":"\u061B"},{"id":"K_BKSLASH","text":"|"}]},{"id":"4","key":[{"id":"K_oE2"},{"id":"K_Z","text":"\u0643"},{"id":"K_X","text":"\u0637"},{"id":"K_C","text":"\u0698"},{"id":"K_V","text":"\u0670"},{"id":"K_B","text":"\u200C"},{"id":"K_N","text":"\u0654"},{"id":"K_M","text":"\u0621"},{"id":"K_COMMA","text":">"},{"id":"K_PERIOD","text":"<"},{"id":"K_SLASH","text":"\u061F"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"5","key":[{"width":"160","id":"K_SHIFT","sp":"1","text":"*Shift*"},{"width":"140","id":"K_LOPT","sp":"1","text":"*Menu*"},{"layer":"default","width":"875","id":"K_SPACE"},{"width":"145","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]},"tablet":{"displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_1","text":"\u06F1"},{"id":"K_2","text":"\u06F2"},{"id":"K_3","text":"\u06F3"},{"id":"K_4","text":"\u06F4"},{"id":"K_5","text":"\u06F5"},{"id":"K_6","text":"\u06F6"},{"id":"K_7","text":"\u06F7"},{"id":"K_8","text":"\u06F8"},{"id":"K_9","text":"\u06F9"},{"id":"K_0","text":"\u06F0"},{"id":"K_HYPHEN","text":"-"},{"id":"K_EQUAL","text":"="},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"2","key":[{"id":"K_Q","pad":"75","text":"\u0636"},{"id":"K_W","text":"\u0635"},{"id":"K_E","text":"\u062B"},{"id":"K_R","text":"\u0642"},{"id":"K_T","text":"\u0641"},{"id":"K_Y","text":"\u063A"},{"id":"K_U","text":"\u0639"},{"id":"K_I","text":"\u0647"},{"id":"K_O","text":"\u062E"},{"id":"K_P","text":"\u062D"},{"id":"K_LBRKT","text":"\u062C"},{"id":"K_RBRKT","text":"\u0686"},{"width":"10","id":"T_new_136","sp":"10"}]},{"id":"3","key":[{"id":"K_BKQUOTE"},{"id":"K_A","text":"\u0634"},{"id":"K_S","text":"\u0633"},{"id":"K_D","text":"\u06CC"},{"id":"K_F","text":"\u0628"},{"id":"K_G","text":"\u0644"},{"id":"K_H","text":"\u0627"},{"id":"K_J","text":"\u062A"},{"id":"K_K","text":"\u0646"},{"id":"K_L","text":"\u0645"},{"id":"K_COLON","text":"\u06A9"},{"id":"K_QUOTE","text":"\u06AF"},{"id":"K_BKSLASH","text":"\\"}]},{"id":"4","key":[{"width":"160","id":"K_SHIFT","sp":"1","text":"*Shift*"},{"id":"K_oE2"},{"id":"K_Z","text":"\u0638"},{"id":"K_X","text":"\u0637"},{"id":"K_C","text":"\u0632"},{"id":"K_V","text":"\u0631"},{"id":"K_B","text":"\u0630"},{"id":"K_N","text":"\u062F"},{"id":"K_M","text":"\u067E"},{"id":"K_COMMA","text":"\u0648"},{"id":"K_PERIOD","text":"."},{"id":"K_SLASH","text":"\/"},{"width":"10","id":"T_new_162","sp":"10"}]},{"id":"5","key":[{"width":"140","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"930","id":"K_SPACE"},{"width":"145","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_1","text":"!"},{"id":"K_2","text":"\u066C"},{"id":"K_3","text":"\u066B"},{"id":"K_4","text":"\u0631\u06CC\u0627\u0644"},{"id":"K_5","text":"\u066A"},{"id":"K_6","text":"\u00D7"},{"id":"K_7","text":"\u060C"},{"id":"K_8","text":"*"},{"id":"K_9","text":")"},{"id":"K_0","text":"("},{"id":"K_HYPHEN","text":"\u0640"},{"id":"K_EQUAL","text":"+"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"2","key":[{"id":"K_Q","pad":"75","text":"\u0652"},{"id":"K_W","text":"\u064C"},{"id":"K_E","text":"\u064D"},{"id":"K_R","text":"\u064B"},{"id":"K_T","text":"\u064F"},{"id":"K_Y","text":"\u0650"},{"id":"K_U","text":"\u064E"},{"id":"K_I","text":"\u0651"},{"id":"K_O","text":"]"},{"id":"K_P","text":"["},{"id":"K_LBRKT","text":"}"},{"id":"K_RBRKT","text":"{"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"id":"K_BKQUOTE","text":"~"},{"id":"K_A","text":"\u0624"},{"id":"K_S","text":"\u0626"},{"id":"K_D","text":"\u064A"},{"id":"K_F","text":"\u0625"},{"id":"K_G","text":"\u0623"},{"id":"K_H","text":"\u0622"},{"id":"K_J","text":"\u0629"},{"id":"K_K","text":"\u00BB"},{"id":"K_L","text":"\u00AB"},{"id":"K_COLON","text":":"},{"id":"K_QUOTE","text":"\u061B"},{"id":"K_BKSLASH","text":"|"}]},{"id":"4","key":[{"width":"160","id":"K_SHIFT","sp":"1","text":"*Shift*"},{"id":"K_oE2"},{"id":"K_Z","text":"\u0643"},{"id":"K_X","text":"\u0637"},{"id":"K_C","text":"\u0698"},{"id":"K_V","text":"\u0670"},{"id":"K_B","text":"\u200C"},{"id":"K_N","text":"\u0654"},{"id":"K_M","text":"\u0621"},{"id":"K_COMMA","text":">"},{"id":"K_PERIOD","text":"<"},{"id":"K_SLASH","text":"\u061F"},{"width":"10","sp":"10"}]},{"id":"5","key":[{"width":"140","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"930","id":"K_SPACE"},{"width":"145","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.KVER="14.0.276.0";this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16400,32)) {if(1){r=m=1;k.KO(0,t,"");}}else if(k.KKM(e,16400,49)) {if(1){r=m=1;k.KO(0,t,"!");}}else if(k.KKM(e,16400,222)) {if(1){r=m=1;k.KO(0,t,"؛");}}else if(k.KKM(e,16400,51)) {if(1){r=m=1;k.KO(0,t,"٫");}}else if(k.KKM(e,16400,52)) {if(1){r=m=1;k.KO(0,t,"ریال");}}else if(k.KKM(e,16400,53)) {if(1){r=m=1;k.KO(0,t,"٪");}}else if(k.KKM(e,16400,55)) {if(1){r=m=1;k.KO(0,t,"،");}}else if(k.KKM(e,16384,222)) {if(1){r=m=1;k.KO(0,t,"گ");}}else if(k.KKM(e,16400,57)) {if(1){r=m=1;k.KO(0,t,")");}}else if(k.KKM(e,16400,48)) {if(1){r=m=1;k.KO(0,t,"(");}}else if(k.KKM(e,16400,56)) {if(1){r=m=1;k.KO(0,t,"*");}}else if(k.KKM(e,16400,187)) {if(1){r=m=1;k.KO(0,t,"+");}}else if(k.KKM(e,16384,188)) {if(1){r=m=1;k.KO(0,t,"و");}}else if(k.KKM(e,16384,189)) {if(1){r=m=1;k.KO(0,t,"-");}}else if(k.KKM(e,16384,190)) {if(1){r=m=1;k.KO(0,t,".");}}else if(k.KKM(e,16384,191)) {if(1){r=m=1;k.KO(0,t,"/");}}else if(k.KKM(e,16384,48)) {if(1){r=m=1;k.KO(0,t,"۰");}}else if(k.KKM(e,16384,49)) {if(1){r=m=1;k.KO(0,t,"۱");}}else if(k.KKM(e,16384,50)) {if(1){r=m=1;k.KO(0,t,"۲");}}else if(k.KKM(e,16384,51)) {if(1){r=m=1;k.KO(0,t,"۳");}}else if(k.KKM(e,16384,52)) {if(1){r=m=1;k.KO(0,t,"۴");}}else if(k.KKM(e,16384,53)) {if(1){r=m=1;k.KO(0,t,"۵");}}else if(k.KKM(e,16384,54)) {if(1){r=m=1;k.KO(0,t,"۶");}}else if(k.KKM(e,16384,55)) {if(1){r=m=1;k.KO(0,t,"۷");}}else if(k.KKM(e,16384,56)) {if(1){r=m=1;k.KO(0,t,"۸");}}else if(k.KKM(e,16384,57)) {if(1){r=m=1;k.KO(0,t,"۹");}}else if(k.KKM(e,16400,186)) {if(1){r=m=1;k.KO(0,t,":");}}else if(k.KKM(e,16384,186)) {if(1){r=m=1;k.KO(0,t,"ک");}}else if(k.KKM(e,16400,188)) {if(1){r=m=1;k.KO(0,t,">");}}else if(k.KKM(e,16384,187)) {if(1){r=m=1;k.KO(0,t,"=");}}else if(k.KKM(e,16400,190)) {if(1){r=m=1;k.KO(0,t,"<");}}else if(k.KKM(e,16400,191)) {if(1){r=m=1;k.KO(0,t,"؟");}}else if(k.KKM(e,16400,50)) {if(1){r=m=1;k.KO(0,t,"٬");}}else if(k.KKM(e,16400,65)) {if(1){r=m=1;k.KO(0,t,"ؤ");}}else if(k.KKM(e,16400,66)) {if(1){r=m=1;k.KO(0,t,"");}}else if(k.KKM(e,16400,67)) {if(1){r=m=1;k.KO(0,t,"ژ");}}else if(k.KKM(e,16400,68)) {if(1){r=m=1;k.KO(0,t,"ي");}}else if(k.KKM(e,16400,69)) {if(1){r=m=1;k.KO(0,t,"ٍ");}}else if(k.KKM(e,16400,70)) {if(1){r=m=1;k.KO(0,t,"إ");}}else if(k.KKM(e,16400,71)) {if(1){r=m=1;k.KO(0,t,"أ");}}else if(k.KKM(e,16400,72)) {if(1){r=m=1;k.KO(0,t,"آ");}}else if(k.KKM(e,16400,73)) {if(1){r=m=1;k.KO(0,t,"ّ");}}else if(k.KKM(e,16400,74)) {if(1){r=m=1;k.KO(0,t,"ݩ");}}else if(k.KKM(e,16400,75)) {if(1){r=m=1;k.KO(0,t,"»");}}else if(k.KKM(e,16400,76)) {if(1){r=m=1;k.KO(0,t,"«");}}else if(k.KKM(e,16400,77)) {if(1){r=m=1;k.KO(0,t,"ۉ");}}else if(k.KKM(e,16400,78)) {if(1){r=m=1;k.KO(0,t,"ٔ");}}else if(k.KKM(e,16400,79)) {if(1){r=m=1;k.KO(0,t,"]");}}else if(k.KKM(e,16400,80)) {if(1){r=m=1;k.KO(0,t,"[");}}else if(k.KKM(e,16400,81)) {if(1){r=m=1;k.KO(0,t,"ْ");}}else if(k.KKM(e,16400,82)) {if(1){r=m=1;k.KO(0,t,"ً");}}else if(k.KKM(e,16400,83)) {if(1){r=m=1;k.KO(0,t,"ئ");}}else if(k.KKM(e,16400,84)) {if(1){r=m=1;k.KO(0,t,"ُ");}}else if(k.KKM(e,16400,85)) {if(1){r=m=1;k.KO(0,t,"َ");}}else if(k.KKM(e,16400,86)) {if(1){r=m=1;k.KO(0,t,"ٰ");}}else if(k.KKM(e,16400,87)) {if(1){r=m=1;k.KO(0,t,"ٌ");}}else if(k.KKM(e,16400,88)) {if(1){r=m=1;k.KO(0,t,"ې");}}else if(k.KKM(e,16400,89)) {if(1){r=m=1;k.KO(0,t,"ِ");}}else if(k.KKM(e,16400,90)) {if(1){r=m=1;k.KO(0,t,"ك");}}else if(k.KKM(e,16384,219)) {if(1){r=m=1;k.KO(0,t,"ج");}}else if(k.KKM(e,16384,220)) {if(1){r=m=1;k.KO(0,t,"\\");}}else if(k.KKM(e,16384,221)) {if(1){r=m=1;k.KO(0,t,"چ");}}else if(k.KKM(e,16400,54)) {if(1){r=m=1;k.KO(0,t,"×");}}else if(k.KKM(e,16400,189)) {if(1){r=m=1;k.KO(0,t,"ـ");}}else if(k.KKM(e,16384,65)) {if(1){r=m=1;k.KO(0,t,"ش");}}else if(k.KKM(e,16384,66)) {if(1){r=m=1;k.KO(0,t,"ذ");}}else if(k.KKM(e,16384,67)) {if(1){r=m=1;k.KO(0,t,"ز");}}else if(k.KKM(e,16384,68)) {if(1){r=m=1;k.KO(0,t,"ی");}}else if(k.KKM(e,16384,69)) {if(1){r=m=1;k.KO(0,t,"ث");}}else if(k.KKM(e,16384,70)) {if(1){r=m=1;k.KO(0,t,"ب");}}else if(k.KKM(e,16384,71)) {if(1){r=m=1;k.KO(0,t,"ل");}}else if(k.KKM(e,16384,72)) {if(1){r=m=1;k.KO(0,t,"ا");}}else if(k.KKM(e,16384,73)) {if(1){r=m=1;k.KO(0,t,"ه");}}else if(k.KKM(e,16384,74)) {if(1){r=m=1;k.KO(0,t,"ت");}}else if(k.KKM(e,16384,75)) {if(1){r=m=1;k.KO(0,t,"ن");}}else if(k.KKM(e,16384,76)) {if(1){r=m=1;k.KO(0,t,"م");}}else if(k.KKM(e,16384,77)) {if(1){r=m=1;k.KO(0,t,"پ");}}else if(k.KKM(e,16384,78)) {if(1){r=m=1;k.KO(0,t,"د");}}else if(k.KKM(e,16384,79)) {if(1){r=m=1;k.KO(0,t,"خ");}}else if(k.KKM(e,16384,80)) {if(1){r=m=1;k.KO(0,t,"ح");}}else if(k.KKM(e,16384,81)) {if(1){r=m=1;k.KO(0,t,"ض");}}else if(k.KKM(e,16384,82)) {if(1){r=m=1;k.KO(0,t,"ق");}}else if(k.KKM(e,16384,83)) {if(1){r=m=1;k.KO(0,t,"س");}}else if(k.KKM(e,16384,84)) {if(1){r=m=1;k.KO(0,t,"ف");}}else if(k.KKM(e,16384,85)) {if(1){r=m=1;k.KO(0,t,"ع");}}else if(k.KKM(e,16384,86)) {if(1){r=m=1;k.KO(0,t,"ر");}}else if(k.KKM(e,16384,87)) {if(1){r=m=1;k.KO(0,t,"ص");}}else if(k.KKM(e,16384,88)) {if(1){r=m=1;k.KO(0,t,"ط");}}else if(k.KKM(e,16384,89)) {if(1){r=m=1;k.KO(0,t,"غ");}}else if(k.KKM(e,16384,90)) {if(1){r=m=1;k.KO(0,t,"ظ");}}else if(k.KKM(e,16400,219)) {if(1){r=m=1;k.KO(0,t,"}");}}else if(k.KKM(e,16400,220)) {if(1){r=m=1;k.KO(0,t,"|");}}else if(k.KKM(e,16400,221)) {if(1){r=m=1;k.KO(0,t,"{");}}else if(k.KKM(e,16400,192)) {if(1){r=m=1;k.KO(0,t,"~");}}return r;};}
diff --git a/web/testing/empty-row/empty_row-1.0.js b/web/testing/empty-row/empty_row-1.0.js
new file mode 100644
index 0000000000..58c62cb6bb
--- /dev/null
+++ b/web/testing/empty-row/empty_row-1.0.js
@@ -0,0 +1,522 @@
+KeymanWeb.KR(new Keyboard_empty_row());
+
+function Keyboard_empty_row() {
+ this.KI = "Keyboard_empty_row";
+ this.KN = "empty row";
+ this.KMINVER = "9.0";
+ this.KV = null;
+ this.KDU = 0;
+ this.KH = '';
+ this.KM = 0;
+ this.KBVER = "1.0";
+ this.KMBM = 0x0000;
+ this.KVKL = {
+ "phone": {
+ "font": "Tahoma",
+ "displayUnderlying": false,
+ "layer": [{
+ "id": "default",
+ "row": [{
+ "id": "1",
+ "key": []
+ }, {
+ "id": "2",
+ "key": [{
+ "id": "K_Q",
+ "text": "q"
+ }, {
+ "id": "K_W",
+ "text": "w"
+ }, {
+ "id": "K_E",
+ "text": "e"
+ }, {
+ "id": "K_R",
+ "text": "r"
+ }, {
+ "id": "K_T",
+ "text": "t"
+ }, {
+ "id": "K_Y",
+ "text": "y"
+ }, {
+ "id": "K_U",
+ "text": "u"
+ }, {
+ "id": "K_I",
+ "text": "i"
+ }, {
+ "id": "K_O",
+ "text": "o"
+ }, {
+ "id": "K_P",
+ "text": "p"
+ }]
+ }, {
+ "id": "3",
+ "key": [{
+ "id": "K_A",
+ "pad": "50",
+ "text": "a"
+ }, {
+ "id": "K_S",
+ "text": "s"
+ }, {
+ "id": "K_D",
+ "text": "d"
+ }, {
+ "id": "K_F",
+ "text": "f"
+ }, {
+ "id": "K_G",
+ "text": "g"
+ }, {
+ "id": "K_H",
+ "text": "h"
+ }, {
+ "id": "K_J",
+ "text": "j"
+ }, {
+ "id": "K_K",
+ "text": "k"
+ }, {
+ "id": "K_L",
+ "text": "l"
+ }, {
+ "width": "10",
+ "id": "T_new_88",
+ "sp": "10"
+ }]
+ }, {
+ "id": "4",
+ "key": [{
+ "nextlayer": "shift",
+ "id": "K_SHIFT",
+ "sp": "1",
+ "text": "*Shift*"
+ }, {
+ "id": "K_Z",
+ "text": "z"
+ }, {
+ "id": "K_X",
+ "text": "x"
+ }, {
+ "id": "K_C",
+ "text": "c"
+ }, {
+ "id": "K_V",
+ "text": "v"
+ }, {
+ "id": "K_B",
+ "text": "b"
+ }, {
+ "id": "K_N",
+ "text": "n"
+ }, {
+ "id": "K_M",
+ "text": "m"
+ }, {
+ "id": "K_PERIOD",
+ "text": ".",
+ "sk": [{
+ "id": "K_COMMA",
+ "text": ","
+ }, {
+ "layer": "shift",
+ "id": "K_1",
+ "text": "!"
+ }, {
+ "layer": "shift",
+ "id": "K_SLASH",
+ "text": "?"
+ }, {
+ "id": "K_QUOTE",
+ "text": "'"
+ }, {
+ "layer": "shift",
+ "id": "K_QUOTE",
+ "text": "\""
+ }, {
+ "id": "K_BKSLASH",
+ "text": "\\"
+ }, {
+ "layer": "shift",
+ "id": "K_COLON",
+ "text": ":"
+ }, {
+ "id": "K_COLON",
+ "text": ";"
+ }]
+ }, {
+ "width": "100",
+ "id": "K_BKSP",
+ "sp": "1",
+ "text": "*BkSp*"
+ }]
+ }, {
+ "id": "5",
+ "key": [{
+ "nextlayer": "numeric",
+ "width": "150",
+ "id": "K_NUMLOCK",
+ "sp": "1",
+ "text": "*123*"
+ }, {
+ "width": "120",
+ "id": "K_LOPT",
+ "sp": "1",
+ "text": "*Menu*"
+ }, {
+ "width": "610",
+ "id": "K_SPACE"
+ }, {
+ "width": "150",
+ "id": "K_ENTER",
+ "sp": "1",
+ "text": "*Enter*"
+ }]
+ }]
+ }, {
+ "id": "shift",
+ "row": [{
+ "id": "1",
+ "key": [{
+ "id": "K_Q",
+ "text": "Q"
+ }, {
+ "id": "K_W",
+ "text": "W"
+ }, {
+ "id": "K_E",
+ "text": "E"
+ }, {
+ "id": "K_R",
+ "text": "R"
+ }, {
+ "id": "K_T",
+ "text": "T"
+ }, {
+ "id": "K_Y",
+ "text": "Y"
+ }, {
+ "id": "K_U",
+ "text": "U"
+ }, {
+ "id": "K_I",
+ "text": "I"
+ }, {
+ "id": "K_O",
+ "text": "O"
+ }, {
+ "id": "K_P",
+ "text": "P"
+ }]
+ }, {
+ "id": "2"
+ }, {
+ "id": "3",
+ "key": [{
+ "id": "K_A",
+ "pad": "50",
+ "text": "A"
+ }, {
+ "id": "K_S",
+ "text": "S"
+ }, {
+ "id": "K_D",
+ "text": "D"
+ }, {
+ "id": "K_F",
+ "text": "F"
+ }, {
+ "id": "K_G",
+ "text": "G"
+ }, {
+ "id": "K_H",
+ "text": "H"
+ }, {
+ "id": "K_J",
+ "text": "J"
+ }, {
+ "id": "K_K",
+ "text": "K"
+ }, {
+ "id": "K_L",
+ "text": "L"
+ }, {
+ "width": "10",
+ "id": "T_new_122",
+ "sp": "10"
+ }]
+ }, {
+ "id": "4",
+ "key": [{
+ "nextlayer": "default",
+ "id": "K_SHIFT",
+ "sp": "2",
+ "text": "*Shift*"
+ }, {
+ "id": "K_Z",
+ "text": "Z"
+ }, {
+ "id": "K_X",
+ "text": "X"
+ }, {
+ "id": "K_C",
+ "text": "C"
+ }, {
+ "id": "K_V",
+ "text": "V"
+ }, {
+ "id": "K_B",
+ "text": "B"
+ }, {
+ "id": "K_N",
+ "text": "N"
+ }, {
+ "id": "K_M",
+ "text": "M"
+ }, {
+ "layer": "default",
+ "id": "K_PERIOD",
+ "text": ".",
+ "sk": [{
+ "layer": "default",
+ "id": "K_COMMA",
+ "text": ","
+ }, {
+ "layer": "shift",
+ "id": "K_1",
+ "text": "!"
+ }, {
+ "layer": "shift",
+ "id": "K_SLASH",
+ "text": "?"
+ }, {
+ "layer": "default",
+ "id": "K_QUOTE",
+ "text": "'"
+ }, {
+ "layer": "shift",
+ "id": "K_QUOTE",
+ "text": "\""
+ }, {
+ "layer": "default",
+ "id": "K_BKSLASH",
+ "text": "\\"
+ }, {
+ "layer": "shift",
+ "id": "K_COLON",
+ "text": ":"
+ }, {
+ "layer": "default",
+ "id": "K_COLON",
+ "text": ";"
+ }]
+ }, {
+ "id": "K_BKSP",
+ "sp": "1",
+ "text": "*BkSp*"
+ }]
+ }, {
+ "id": "5",
+ "key": [{
+ "nextlayer": "numeric",
+ "width": "150",
+ "id": "K_NUMLOCK",
+ "sp": "1",
+ "text": "*123*"
+ }, {
+ "width": "120",
+ "id": "K_LOPT",
+ "sp": "1",
+ "text": "*Menu*"
+ }, {
+ "width": "610",
+ "id": "K_SPACE"
+ }, {
+ "width": "150",
+ "id": "K_ENTER",
+ "sp": "1",
+ "text": "*Enter*"
+ }]
+ }]
+ }, {
+ "id": "numeric",
+ "row": [{
+ "id": "1",
+ "key": [{
+ "id": "K_1",
+ "text": "1"
+ }, {
+ "id": "K_2",
+ "text": "2"
+ }, {
+ "id": "K_3",
+ "text": "3"
+ }, {
+ "id": "K_4",
+ "text": "4"
+ }, {
+ "id": "K_5",
+ "text": "5"
+ }, {
+ "id": "K_6",
+ "text": "6"
+ }, {
+ "id": "K_7",
+ "text": "7"
+ }, {
+ "id": "K_8",
+ "text": "8"
+ }, {
+ "id": "K_9",
+ "text": "9"
+ }, {
+ "id": "K_0",
+ "text": "0"
+ }]
+ }, {
+ "id": "2",
+ "key": [{
+ "layer": "shift",
+ "id": "K_4",
+ "pad": "50",
+ "text": "$"
+ }, {
+ "layer": "shift",
+ "id": "K_2",
+ "text": "@"
+ }, {
+ "layer": "shift",
+ "id": "K_3",
+ "text": "#"
+ }, {
+ "layer": "shift",
+ "id": "K_5",
+ "text": "%"
+ }, {
+ "layer": "shift",
+ "id": "K_7",
+ "text": "&"
+ }, {
+ "layer": "shift",
+ "id": "K_HYPHEN",
+ "text": "_"
+ }, {
+ "layer": "default",
+ "id": "K_EQUAL",
+ "text": "="
+ }, {
+ "layer": "shift",
+ "id": "K_BKSLASH",
+ "text": "|"
+ }, {
+ "layer": "default",
+ "id": "K_BKSLASH",
+ "text": "\\"
+ }, {
+ "width": "10",
+ "id": "T_new_156",
+ "sp": "10"
+ }]
+ }, {
+ "id": "3"
+ }, {
+ "id": "4",
+ "key": [{
+ "id": "K_LBRKT",
+ "pad": "110",
+ "text": "[",
+ "sk": [{
+ "id": "U_00AB",
+ "text": "\u00AB"
+ }, {
+ "layer": "shift",
+ "id": "K_COMMA",
+ "text": "<"
+ }, {
+ "layer": "shift",
+ "id": "K_LBRKT",
+ "text": "{"
+ }]
+ }, {
+ "layer": "shift",
+ "id": "K_9",
+ "text": "("
+ }, {
+ "layer": "shift",
+ "id": "K_0",
+ "text": ")"
+ }, {
+ "id": "K_RBRKT",
+ "text": "]",
+ "sk": [{
+ "id": "U_00BB",
+ "text": "\u00BB"
+ }, {
+ "layer": "shift",
+ "id": "K_PERIOD",
+ "text": ">"
+ }, {
+ "layer": "shift",
+ "id": "K_RBRKT",
+ "text": "}"
+ }]
+ }, {
+ "layer": "shift",
+ "id": "K_EQUAL",
+ "text": "+"
+ }, {
+ "id": "K_HYPHEN",
+ "text": "-"
+ }, {
+ "layer": "shift",
+ "id": "K_8",
+ "text": "*"
+ }, {
+ "id": "K_SLASH",
+ "text": "\/"
+ }, {
+ "width": "100",
+ "id": "K_BKSP",
+ "sp": "1",
+ "text": "*BkSp*"
+ }]
+ }, {
+ "id": "5",
+ "key": [{
+ "nextlayer": "default",
+ "width": "150",
+ "id": "K_LOWER",
+ "sp": "1",
+ "text": "*abc*"
+ }, {
+ "width": "120",
+ "id": "K_LOPT",
+ "sp": "1",
+ "text": "*Menu*"
+ }, {
+ "width": "610",
+ "id": "K_SPACE"
+ }, {
+ "width": "150",
+ "id": "K_ENTER",
+ "sp": "1",
+ "text": "*Enter*"
+ }]
+ }]
+ }]
+ }
+ };
+ this.KVER = "13.0.118.0";
+ this.gs = function(t, e) {
+ return this.g0(t, e);
+ };
+ this.g0 = function(t, e) {
+ var k = KeymanWeb,
+ r = 0,
+ m = 0;
+ return r;
+ };
+}
diff --git a/web/testing/empty-row/index.html b/web/testing/empty-row/index.html
new file mode 100644
index 0000000000..c53b4dad5e
--- /dev/null
+++ b/web/testing/empty-row/index.html
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ KeymanWeb Test Page - Empty Row
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ KeymanWeb Sample Page - Empty Row Testing
+ This page is designed to test that the OSK removes empty rows in touch-layout keyboards
+ so they're not displayed. Refer to issue #5327.
+ The empty_row keyboard is a custom keyboard compiled with Developer 13.0, and contains an empty row in each of the 3 layers.
+ The afghan_turkmen keyboard comes directly from the community site reporting the issue, so disregard that
+ the shift key does not switch layers.
+
+ Load the page in mobile viewport and also check developer console does not have exceptions.
+
+
+
+
+
+
Type in your language in this text area:
+
+
+
or in this input field:
+
+
+
+
+
+
+
+
diff --git a/web/testing/index.html b/web/testing/index.html
index 940be7eb43..c8821e4ade 100644
--- a/web/testing/index.html
+++ b/web/testing/index.html
@@ -26,6 +26,7 @@
Return to main index.