diff --git a/kmw/engine/17.0.168/keymanweb.js b/kmw/engine/17.0.168/keymanweb.js new file mode 100644 index 000000000..ffbbdd50f --- /dev/null +++ b/kmw/engine/17.0.168/keymanweb.js @@ -0,0 +1,2 @@ +(function(){var Qi=Object.create;var Rt=Object.defineProperty;var Ci=Object.getOwnPropertyDescriptor;var bi=Object.getOwnPropertyNames;var hi=Object.getPrototypeOf,Ui=Object.prototype.hasOwnProperty;var En=function(i,t){return function(){return t||i((t={exports:{}}).exports,t),t.exports}},kn=function(i,t){for(var e in t)Rt(i,e,{get:t[e],enumerable:!0})},vt=function(i,t,e,n){if(t&&typeof t=="object"||typeof t=="function")for(var c=bi(t),l=0,r=c.length,s;l=0;o--)(B=e[o])&&(s=(r<3?B(s):r>3?B(n,c,s):B(n,c))||s);return r>3&&s&&Object.defineProperty(n,c,s),s},Dn=function(e,n,c,l){function r(s){return s instanceof c?s:new c(function(B){B(s)})}return new(c||(c=Promise))(function(s,B){function o(F){try{a(l.next(F))}catch(y){B(y)}}function g(F){try{a(l.throw(F))}catch(y){B(y)}}function a(F){F.done?s(F.value):r(F.value).then(o,g)}a((l=l.apply(e,n||[])).next())})},Mn=function(e,n){var c={label:0,sent:function(){if(s[0]&1)throw s[1];return s[1]},trys:[],ops:[]},l,r,s,B;return B={next:o(0),throw:o(1),return:o(2)},typeof Symbol=="function"&&(B[Symbol.iterator]=function(){return this}),B;function o(a){return function(F){return g([a,F])}}function g(a){if(l)throw new TypeError("Generator is already executing.");for(;B&&(B=0,a[0]&&(c=0)),c;)try{if(l=1,r&&(s=a[0]&2?r.return:a[0]?r.throw||((s=r.return)&&s.call(r),0):r.next)&&!(s=s.call(r,a[1])).done)return s;switch(r=0,s&&(a=[a[0]&2,s.value]),a[0]){case 0:case 1:s=a;break;case 4:return c.label++,{value:a[1],done:!1};case 5:c.label++,r=a[1],a=[0];continue;case 7:a=c.ops.pop(),c.trys.pop();continue;default:if(s=c.trys,!(s=s.length>0&&s[s.length-1])&&(a[0]===6||a[0]===2)){c=0;continue}if(a[0]===3&&(!s||a[1]>s[0]&&a[1]?~",'{|}"']],isKnownOSKModifierKey:function(i){switch(i){case"K_SHIFT":case"K_LOPT":case"K_ROPT":case"K_NUMLOCK":case"K_CAPS":return!0;default:if(q.keyCodes[i]>=5e4)return!0;var t=q[i];if(t>5e4&&t<50011)return!0}return!1},getModifierState:function(i){var t=0;i.indexOf("shift")>=0&&(t|=q.modifierCodes.SHIFT);var e=!1;i.indexOf("leftctrl")>=0&&(t|=q.modifierCodes.LCTRL,e=!0),i.indexOf("rightctrl")>=0&&(t|=q.modifierCodes.RCTRL,e=!0),i.indexOf("ctrl")>=0&&!e&&(t|=q.modifierCodes.CTRL);var n=!1;return i.indexOf("leftalt")>=0&&(t|=q.modifierCodes.LALT,n=!0),i.indexOf("rightalt")>=0&&(t|=q.modifierCodes.RALT,n=!0),i.indexOf("alt")>=0&&!n&&(t|=q.modifierCodes.ALT),t},getStateFromLayer:function(i){var t=0;return i.indexOf("caps")>=0?t|=q.modifierCodes.CAPS:t|=q.modifierCodes.NO_CAPS,t}},I=q;var xe;(function(i){i.Enter="\n",i.Backspace="\b"})(xe||(xe={}));var Xi=function(){function i(){}return i.prototype.codeForEvent=function(t){return I.keyCodes[t.kName]||t.Lcode},i.prototype.forAny=function(t,e,n){var c="";if((c=this.forSpecialEmulation(t))!=null)return c;if(!e&&(c=this.forNumpadKeys(t))!=null)return c;if((c=this.forUnicodeKeynames(t,n))!=null)return c;if((c=this.forBaseKeys(t,n))!=null)return c;var l=this.codeForEvent(t);switch(l){default:return null}},i.prototype.isCommand=function(t){var e=this.codeForEvent(t);switch(e){default:return!1}},i.prototype.applyCommand=function(t,e){},i.prototype.forSpecialEmulation=function(t){var e=this.codeForEvent(t);switch(e){case I.keyCodes.K_BKSP:return xe.Backspace;case I.keyCodes.K_ENTER:return xe.Enter;default:return null}},i.prototype.forNumpadKeys=function(t){if(t.Lcode>=I.keyCodes.K_NP0&&t.Lcode<=I.keyCodes.K_NPSLASH){if(t.Lcode<106)var e=t.Lcode-48;else e=t.Lcode-64;var n=String._kmwFromCharCode(e);return n}else return null},i.prototype.forUnicodeKeynames=function(t,e){var n=t.kName;if(!n||n.substr(0,2)!="U_")return null;for(var c="",l=n.substr(2).split("_"),r=0,s=l;r=I.keyCodes.K_0&&n<=I.keyCodes.K_9)return I.codesUS[c][0][n-I.keyCodes.K_0];if(n>=I.keyCodes.K_A&&n<=I.keyCodes.K_Z)return String.fromCharCode(n+(c?0:32));if(n>=I.keyCodes.K_COLON&&n<=I.keyCodes.K_BKQUOTE)return I.codesUS[c][1][n-I.keyCodes.K_COLON];if(n>=I.keyCodes.K_LBRKT&&n<=I.keyCodes.K_QUOTE)return I.codesUS[c][2][n-I.keyCodes.K_LBRKT];if(n==I.keyCodes.K_oE2)return c?"|":"\\"}catch(l){e&&(e.errorLog="Error detected with default mapping for key: code = "+n+", shift state = "+(c==1?"shift":"default"))}return null},i}(),Ge=Xi;var pi=new Ge,mi=function(){function i(t){this.isSynthetic=!0;for(var e in t)t[e]!==void 0&&(this[e]=t[e])}return i.constructNullKeyEvent=function(t){var e=new i({Lcode:0,kName:"",device:t,Lstates:void 0,Lmodifiers:void 0,vkCode:void 0,LisVirtualKey:void 0});return e},Object.defineProperty(i.prototype,"isModifier",{get:function(){switch(this.Lcode){case 16:case 17:case 18:case 20:case 144:case 145:return!0;default:return!1}},enumerable:!1,configurable:!0}),i.prototype.setMnemonicCode=function(t,e){if(this.Lcode!=I.keyCodes.K_SPACE){var n=new i(this);for(var c in this)n[c]=this[c];n.kName="K_xxxx",n.Lmodifiers=t?16:0;var l=pi.forAny(n,!0);this.vkCode=this.Lcode,l?this.Lcode=l.charCodeAt(0):this.isModifier||delete this.Lcode}e&&(this.Lcode>=65&&this.Lcode<=90||this.Lcode>=97&&this.Lcode<=122)&&(this.Lmodifiers^=16,this.Lcode^=32)},i}(),$=mi;var Xe=function(){function i(){}return i}(),Zi=function(){function i(){this.FF=new Xe,this.Safari=new Xe,this.Opera=new Xe,this.FF.k61=187,this.FF.k59=186,this.FF.k173=189}return i}(),Li=function(){function i(){this.se=new Xe,this.se.k220=192,this.se.k187=189,this.se.k219=187,this.se.k221=219,this.se.k186=221,this.se.k191=220,this.se.k192=186,this.se.k189=191,this.uk=new Xe,this.uk.k223=192,this.uk.k192=222,this.uk.k222=226,this.uk.k220=220}return i}(),Ji=function(){function i(){}return i._usCodeInit=function(){var t=new Xe,e=new Xe;t.k192=96,t.k49=49,t.k50=50,t.k51=51,t.k52=52,t.k53=53,t.k54=54,t.k55=55,t.k56=56,t.k57=57,t.k48=48,t.k189=45,t.k187=61,t.k81=113,t.k87=119,t.k69=101,t.k82=114,t.k84=116,t.k89=121,t.k85=117,t.k73=105,t.k79=111,t.k80=112,t.k219=91,t.k221=93,t.k220=92,t.k65=97,t.k83=115,t.k68=100,t.k70=102,t.k71=103,t.k72=104,t.k74=106,t.k75=107,t.k76=108,t.k186=59,t.k222=39,t.k90=122,t.k88=120,t.k67=99,t.k86=118,t.k66=98,t.k78=110,t.k77=109,t.k188=44,t.k190=46,t.k191=47,e.k192=126,e.k49=33,e.k50=64,e.k51=35,e.k52=36,e.k53=37,e.k54=94,e.k55=38,e.k56=42,e.k57=40,e.k48=41,e.k189=95,e.k187=43,e.k81=81,e.k87=87,e.k69=69,e.k82=82,e.k84=84,e.k89=89,e.k85=85,e.k73=73,e.k79=79,e.k80=80,e.k219=123,e.k221=125,e.k220=124,e.k65=65,e.k83=83,e.k68=68,e.k70=70,e.k71=71,e.k72=72,e.k74=74,e.k75=75,e.k76=76,e.k186=58,e.k222=34,e.k90=90,e.k88=88,e.k67=67,e.k86=86,e.k66=66,e.k78=78,e.k77=77,e.k188=60,e.k190=62,e.k191=63,i._usCharCodes=[t,e]},i._USKeyCodeToCharCode=function(t){return i.usCharCodes[t.Lmodifiers&16?1:0]["k"+t.Lcode]},Object.defineProperty(i,"usCharCodes",{get:function(){return i._usCharCodes||i._usCodeInit(),i._usCharCodes},enumerable:!1,configurable:!0}),i.browserMap=new Zi,i.languageMap=new Li,i}(),de=Ji;function ke(i,t,e){e.enumerable=!0}var M=function(){function i(){this.isMnemonic=!1}return Object.defineProperty(i.prototype,"baseKeyID",{get:function(){if(typeof this.id!="undefined")return this.id},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"isPadding",{get:function(){return this.sp==10},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"coreID",{get:function(){if(typeof this.id!="undefined"){var t=this.id||"";return this.displayLayer!=this.layer&&(t=t+"+"+this.layer),t}},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"elementID",{get:function(){if(typeof this.id!="undefined")return this.displayLayer+"-"+this.coreID},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"baseKeyEvent",{get:function(){return new $(this._baseKeyEvent)},enumerable:!1,configurable:!0}),i.unicodeIDToText=function(t,e){if(!t||t.substring(0,2)!="U_")return null;for(var n="",c=t.substring(2).split("_"),l=0,r=c;l0){var u=s[s.length-1];if(s.length==1&&u.pad<0){var Q=u.width/l,C=1-(F+Q+y);a(u,C,Q,F)}else{var C=u.pad/l,Q=1-(F+C+y);a(u,C,Q,F)}}var h=new i;for(var o in h)t.hasOwnProperty(o)||(t[o]=h[o]);var b=t;b.proportionalY=r},i.prototype.populateKeyMap=function(t){this.key.forEach(function(e){e.coreID&&(t[e.coreID]=e)})},i.SPECIAL_LABEL=/\*\w+\*/,i}();var _n=function(){function i(){}return i.sanitize=function(t){for(var e=0,n=t.row;el&&(l=o)}n.formFactor=="desktop"?l+=5:l+=M.DEFAULT_RIGHT_MARGIN;for(var u=t.row.length,Q=0;Q.5*r.proportionalWidth?(o=s-.5*r.proportionalWidth,s=.5):(o=0,s/=r.proportionalWidth),B>.5*n.rowProportionalHeight?(g=B-.5*n.rowProportionalHeight,B=.5):(g=0,B/=n.rowProportionalHeight),o*=e,o+=s*n.rowProportionalHeight,g+=B*n.rowProportionalHeight;var a=o*o+g*g;c[r.coreID]=a})}),c},i.prototype.getKey=function(t){t.indexOf(this.id+"-")==0&&(t=t.replace(this.id+"-",""));var e=t.split("::");if(e.length>1){var n=this.keyMap[e[0]];return n.getSubkey(e[1])}else return this.keyMap[t]},i}();var st=function(){function i(){}return i.prototype.getLayer=function(t){return this.layerMap[t]},i.correctLayerEmptyRowBug=function(t){for(var e=0;e=0;l--)(!Array.isArray(c[l].key)||c[l].key.length==0)&&c.splice(l,1)}},i.sanitize=function(t){i.correctLayerEmptyRowBug(t.layer);for(var e=0,n=t.layer;e0)return e?-1:1;c++}while(c1114111||Math.floor(n)!==n)throw new RangeError("Invalid code point "+n);n<65536?t.push(n):(n-=65536,t.push((n>>10)+55296),t.push(n%1024+56320))}return String.fromCharCode.apply(void 0,t)},String.prototype.kmwCharCodeAt=function(i){var t=String(this),e=0;if(i<0||i>=t.length)return NaN;for(var n=0;n=55296&&c<=56319&&t.length>e+1){var l=t.charCodeAt(e+1);if(l>=56320&&l<=57343)return(c-55296<<10)+(l-56320)+65536}return c},String.prototype.kmwIndexOf=function(i,t){var e=String(this),n=e.indexOf(i,t);if(n<0)return n;for(var c=0,l=0;l!==null&&lt){var l=i;i=t,t=l}n=e.kmwCodePointToCodeUnit(i),c=e.kmwCodePointToCodeUnit(t)}return(isNaN(n)||n===null)&&(n=0),(isNaN(c)||c===null)&&(c=e.length),e.substring(n,c)},String.prototype.kmwNextChar=function(i){var t=String(this);if(i===null||i<0||i>=t.length-1)return null;var e=t.charCodeAt(i);if(e>=55296&&e<=56319&&t.length>i+1){var n=t.charCodeAt(i+1);if(n>=56320&&n<=57343)return i==t.length-2?null:i+2}return i+1},String.prototype.kmwPrevChar=function(i){var t=String(this);if(i==null||i<=0||i>t.length)return null;var e=t.charCodeAt(i-1);if(e>=56320&&e<=57343&&i>1){var n=t.charCodeAt(i-2);if(n>=55296&&n<=56319)return i-2}return i-1},String.prototype.kmwCodePointToCodeUnit=function(i){if(i===null)return null;var t=String(this),e=0;if(i<0){e=t.length;for(var n=0;n>i;n--)e=t.kmwPrevChar(e);return e}if(i==t.kmwLength())return t.length;for(var n=0;n=0?t.kmwSubstr(i,1):""},String.prototype.kmwBMPNextChar=function(i){var t=String(this);return i<0||i>=t.length-1?null:i+1},String.prototype.kmwBMPPrevChar=function(i){var t=String(this);return i<=0||i>t.length?null:i-1},String.prototype.kmwBMPCodePointToCodeUnit=function(i){return i},String.prototype.kmwBMPCodeUnitToCodePoint=function(i){return i},String.prototype.kmwBMPLength=function(){var i=String(this);return i.length},String.prototype.kmwBMPSubstr=function(i,t){var e=String(this);return i>-1?e.substr(i,t):e.substr(e.length+i,-i)},String.kmwEnableSupplementaryPlane=function(i){var t=String.prototype;String._kmwFromCharCode=i?String.kmwFromCharCode:String.fromCharCode,t._kmwCharAt=i?t.kmwCharAt:t.charAt,t._kmwCharCodeAt=i?t.kmwCharCodeAt:t.charCodeAt,t._kmwIndexOf=i?t.kmwIndexOf:t.indexOf,t._kmwLastIndexOf=i?t.kmwLastIndexOf:t.lastIndexOf,t._kmwSlice=i?t.kmwSlice:t.slice,t._kmwSubstring=i?t.kmwSubstring:t.substring,t._kmwSubstr=i?t.kmwSubstr:t.kmwBMPSubstr,t._kmwLength=i?t.kmwLength:t.kmwBMPLength,t._kmwNextChar=i?t.kmwNextChar:t.kmwBMPNextChar,t._kmwPrevChar=i?t.kmwPrevChar:t.kmwBMPPrevChar,t._kmwCodePointToCodeUnit=i?t.kmwCodePointToCodeUnit:t.kmwBMPCodePointToCodeUnit,t._kmwCodeUnitToCodePoint=i?t.kmwCodeUnitToCodePoint:t.kmwBMPCodeUnitToCodePoint},String._kmwFromCharCode||String.kmwEnableSupplementaryPlane(!1)}Oe();var Ri=function(){function i(t){var e=this;this._hasResolved=!1,this._hasRejected=!1,this._promise=new Promise(function(n,c){e.resolve=function(l){e._hasResolved=!0,n(l)},e.reject=function(l){e._hasRejected=!0,c(l)},t&&t(e.resolve,e.reject)})}return Object.defineProperty(i.prototype,"hasResolved",{get:function(){return this._hasResolved},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"hasRejected",{get:function(){return this._hasRejected},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"hasFinalized",{get:function(){return this.hasResolved||this.hasRejected},enumerable:!1,configurable:!0}),i.prototype.then=function(t,e){return this._promise.then(t,e)},i.prototype.catch=function(t){return this._promise.catch(t)},i.prototype.finally=function(t){return this._promise.finally(t)},Object.defineProperty(i.prototype,"corePromise",{get:function(){return this._promise},enumerable:!1,configurable:!0}),i}(),A=Ri;var ee=function(){function i(){}return i.buildDefaultLayout=function(t,e,n){var c=n;typeof i.dfltLayout[c]!="object"&&(c="desktop");var l=I.modifierBitmasks.NON_CHIRAL,r=z.CURRENT;e&&(l=e.modifierBitmask,r=e.compilerVersion),t||(t=this.DEFAULT_RAW_SPEC);var s=Re(i.dfltLayout[c]),B,o=s.layer,g=t.KLS,a=t.K102,F,y,u,Q,C,h,b,U,V=(l&I.modifierBitmasks.IS_CHIRAL)!=0;if(t.F){var X=/^(?:(?:italic|bold) )* *[0-9.eE-]+(?:[a-z]+) "(.+)"$/.exec(t.F);X&&(s.font=X[1])}var L=!(typeof g=="undefined"||!g);L||(g=t.KLS=i.processLegacyDefinitions(t.BK));var P=/\*\w+\*/,p=Object.getOwnPropertyNames(g),E=[];if(p.splice(p.indexOf("default"),1),p=["default"].concat(p),e&&e.emulatesAltGr&&(p.indexOf("leftctrl-leftalt")==-1&&p.indexOf("rightalt")!=-1&&(p.push("leftctrl-leftalt"),g["leftctrl-leftalt"]=g.rightalt),p.indexOf("leftctrl-leftalt-shift")==-1&&p.indexOf("rightalt-shift")!=-1&&(p.push("leftctrl-leftalt-shift"),g["leftctrl-leftalt-shift"]=g["rightalt-shift"])),s.displayUnderlying=e?!!e.scriptObject.KDU:!1,n=="desktop")for(E=i.generateLayerIds(V),B=0;B0&&(o[B]=Re(o[0])),o[B].id=oe[B],o[B].nextlayer=oe[B],i.formatDefaultLayer(o[B],V,n,!!a);for(B=0;B=0&&ge0&&S!=null&&(S.sp=i.buttonClasses["SHIFT-ON"],S.sk=null,S.text=i.modifierSpecials[o[B].id]?i.modifierSpecials[o[B].id]:"*Shift*")}return s},i.getLayerId=function(t){var e=I.modifierCodes,n="";return t==0?"default":(t&e.LCTRL&&(n=(n.length>0?n+"-":"")+"leftctrl"),t&e.RCTRL&&(n=(n.length>0?n+"-":"")+"rightctrl"),t&e.LALT&&(n=(n.length>0?n+"-":"")+"leftalt"),t&e.RALT&&(n=(n.length>0?n+"-":"")+"rightalt"),t&e.SHIFT&&(n=(n.length>0?n+"-":"")+"shift"),t&e.CTRL&&(n=(n.length>0?n+"-":"")+"ctrl"),t&e.ALT&&(n=(n.length>0?n+"-":"")+"alt"),n)},i.generateLayerIds=function(t){var e,n;t?(e=32,n=1):(e=8,n=16);for(var c=[],l=0;l?~~~~~ ",i.DEFAULT_RAW_SPEC={F:"Tahoma",BK:i.dfltText},i.buttonClasses={DEFAULT:0,SHIFT:1,"SHIFT-ON":2,SPECIAL:3,"SPECIAL-ON":4,DEADKEY:8,BLANK:9,HIDDEN:10},i.modifierSpecials={leftalt:"*LAlt*",rightalt:"*RAlt*",alt:"*Alt*",leftctrl:"*LCtrl*",rightctrl:"*RCtrl*",ctrl:"*Ctrl*","ctrl-alt":"*AltGr*","leftctrl-leftalt":"*LAltCtrl*","rightctrl-rightalt":"*RAltCtrl*","leftctrl-leftalt-shift":"*LAltCtrlShift*","rightctrl-rightalt-shift":"*RAltCtrlShift*",shift:"*Shift*","shift-alt":"*AltShift*","shift-ctrl":"*CtrlShift*","shift-ctrl-alt":"*AltCtrlShift*","leftalt-shift":"*LAltShift*","rightalt-shift":"*RAltShift*","leftctrl-shift":"*LCtrlShift*","rightctrl-shift":"*RCtrlShift*"},i.dfltLayout={desktop:{font:"Tahoma,Helvetica",layer:[{id:"default",row:[{id:"1",key:[{id:"K_BKQUOTE"},{id:"K_1"},{id:"K_2"},{id:"K_3"},{id:"K_4"},{id:"K_5"},{id:"K_6"},{id:"K_7"},{id:"K_8"},{id:"K_9"},{id:"K_0"},{id:"K_HYPHEN"},{id:"K_EQUAL"},{id:"K_BKSP",text:"*BkSp*",sp:"1",width:"130"}]},{id:"2",key:[{id:"K_TAB",text:"*Tab*",sp:"1",width:"130"},{id:"K_Q"},{id:"K_W"},{id:"K_E"},{id:"K_R"},{id:"K_T"},{id:"K_Y"},{id:"K_U"},{id:"K_I"},{id:"K_O"},{id:"K_P"},{id:"K_LBRKT"},{id:"K_RBRKT"},{id:"K_BKSLASH"}]},{id:"3",key:[{id:"K_CAPS",text:"*Caps*",sp:"1",width:"165"},{id:"K_A"},{id:"K_S"},{id:"K_D"},{id:"K_F"},{id:"K_G"},{id:"K_H"},{id:"K_J"},{id:"K_K"},{id:"K_L"},{id:"K_COLON"},{id:"K_QUOTE"},{id:"K_ENTER",text:"*Enter*",sp:"1",width:"165"}]},{id:"4",key:[{id:"K_SHIFT",text:"*Shift*",sp:"1",width:"130"},{id:"K_oE2"},{id:"K_Z"},{id:"K_X"},{id:"K_C"},{id:"K_V"},{id:"K_B"},{id:"K_N"},{id:"K_M"},{id:"K_COMMA"},{id:"K_PERIOD"},{id:"K_SLASH"},{id:"K_RSHIFT",text:"*Shift*",sp:"1",width:"130"}]},{id:"5",key:[{id:"K_LCONTROL",text:"*Ctrl*",sp:"1",width:"170"},{id:"K_LALT",text:"*Alt*",sp:"1",width:"160"},{id:"K_SPACE",text:"",width:"770"},{id:"K_RALT",text:"*Alt*",sp:"1",width:"160"},{id:"K_RCONTROL",text:"*Ctrl*",sp:"1",width:"170"}]}]}]},tablet:{font:"Tahoma,Helvetica",layer:[{id:"default",row:[{id:"0",key:[{id:"K_1"},{id:"K_2"},{id:"K_3"},{id:"K_4"},{id:"K_5"},{id:"K_6"},{id:"K_7"},{id:"K_8"},{id:"K_9"},{id:"K_0"},{id:"K_HYPHEN"},{id:"K_EQUAL"},{sp:"10",width:"1"}]},{id:"1",key:[{id:"K_Q",pad:"25"},{id:"K_W"},{id:"K_E"},{id:"K_R"},{id:"K_T"},{id:"K_Y"},{id:"K_U"},{id:"K_I"},{id:"K_O"},{id:"K_P"},{id:"K_LBRKT"},{id:"K_RBRKT"},{sp:"10",width:"1"}]},{id:"2",key:[{id:"K_A",pad:"50"},{id:"K_S"},{id:"K_D"},{id:"K_F"},{id:"K_G"},{id:"K_H"},{id:"K_J"},{id:"K_K"},{id:"K_L"},{id:"K_COLON"},{id:"K_QUOTE"},{id:"K_BKSLASH",width:"90"}]},{id:"3",key:[{id:"K_oE2",width:"90"},{id:"K_Z"},{id:"K_X"},{id:"K_C"},{id:"K_V"},{id:"K_B"},{id:"K_N"},{id:"K_M"},{id:"K_COMMA"},{id:"K_PERIOD"},{id:"K_SLASH"},{id:"K_BKQUOTE"},{sp:"10",width:"1"}]},{id:"4",key:[{id:"K_SHIFT",text:"*Shift*",sp:"1",width:"200",sk:[{id:"K_LCONTROL",text:"*Ctrl*",sp:"1",width:"50",nextlayer:"ctrl"},{id:"K_LCONTROL",text:"*LCtrl*",sp:"1",width:"50",nextlayer:"leftctrl"},{id:"K_RCONTROL",text:"*RCtrl*",sp:"1",width:"50",nextlayer:"rightctrl"},{id:"K_LALT",text:"*Alt*",sp:"1",width:"50",nextlayer:"alt"},{id:"K_LALT",text:"*LAlt*",sp:"1",width:"50",nextlayer:"leftalt"},{id:"K_RALT",text:"*RAlt*",sp:"1",width:"50",nextlayer:"rightalt"},{id:"K_ALTGR",text:"*AltGr*",sp:"1",width:"50",nextlayer:"ctrl-alt"}]},{id:"K_LOPT",text:"*Menu*",sp:"1",width:"150"},{id:"K_SPACE",text:"",width:"570"},{id:"K_BKSP",text:"*BkSp*",sp:"1",width:"150"},{id:"K_ENTER",text:"*Enter*",sp:"1",width:"200"}]}]}]},phone:{font:"Tahoma,Helvetica",layer:[{id:"default",row:[{id:"0",key:[{id:"K_1"},{id:"K_2"},{id:"K_3"},{id:"K_4"},{id:"K_5"},{id:"K_6"},{id:"K_7"},{id:"K_8"},{id:"K_9"},{id:"K_0"},{id:"K_HYPHEN"},{id:"K_EQUAL"},{sp:"10",width:"1"}]},{id:"1",key:[{id:"K_Q",pad:"25"},{id:"K_W"},{id:"K_E"},{id:"K_R"},{id:"K_T"},{id:"K_Y"},{id:"K_U"},{id:"K_I"},{id:"K_O"},{id:"K_P"},{id:"K_LBRKT"},{id:"K_RBRKT"},{sp:"10",width:"1"}]},{id:"2",key:[{id:"K_A",pad:"50"},{id:"K_S"},{id:"K_D"},{id:"K_F"},{id:"K_G"},{id:"K_H"},{id:"K_J"},{id:"K_K"},{id:"K_L"},{id:"K_COLON"},{id:"K_QUOTE"},{id:"K_BKSLASH",width:"90"}]},{id:"3",key:[{id:"K_oE2",width:"90"},{id:"K_Z"},{id:"K_X"},{id:"K_C"},{id:"K_V"},{id:"K_B"},{id:"K_N"},{id:"K_M"},{id:"K_COMMA"},{id:"K_PERIOD"},{id:"K_SLASH"},{id:"K_BKQUOTE"},{sp:"10",width:"1"}]},{id:"4",key:[{id:"K_SHIFT",text:"*Shift*",sp:"1",width:"200",sk:[{id:"K_LCONTROL",text:"*Ctrl*",sp:"1",width:"50",nextlayer:"ctrl"},{id:"K_LCONTROL",text:"*LCtrl*",sp:"1",width:"50",nextlayer:"leftctrl"},{id:"K_RCONTROL",text:"*RCtrl*",sp:"1",width:"50",nextlayer:"rightctrl"},{id:"K_LALT",text:"*Alt*",sp:"1",width:"50",nextlayer:"alt"},{id:"K_LALT",text:"*LAlt*",sp:"1",width:"50",nextlayer:"leftalt"},{id:"K_RALT",text:"*RAlt*",sp:"1",width:"50",nextlayer:"rightalt"},{id:"K_ALTGR",text:"*AltGr*",sp:"1",width:"50",nextlayer:"ctrl-alt"}]},{id:"K_LOPT",text:"*Menu*",width:"150",sp:"1"},{id:"K_SPACE",width:"570",text:""},{id:"K_BKSP",text:"*BkSp*",width:"150",sp:"1"},{id:"K_ENTER",text:"*Enter*",width:"200",sp:"1"}]}]}]}},i}();var Ai=function(){function i(){this.stores={}}return i}(),pe;(function(i){i[i.NOT_LOADED=void 0]="NOT_LOADED",i[i.POLYFILLED=1]="POLYFILLED",i[i.CALIBRATED=2]="CALIBRATED"})(pe||(pe={}));var fi=function(){function i(t){t?this.scriptObject=t:this.scriptObject=i.DEFAULT_SCRIPT_OBJECT,this.layoutStates={}}return i.prototype.process=function(t,e){return this.scriptObject.gs(t,e)},i.prototype.processNewContextEvent=function(t,e){return this.scriptObject.gn?this.scriptObject.gn(t,e):!1},i.prototype.processPostKeystroke=function(t,e){return this.scriptObject.gpk?this.scriptObject.gpk(t,e):!1},Object.defineProperty(i.prototype,"isHollow",{get:function(){return this.scriptObject==i.DEFAULT_SCRIPT_OBJECT},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"id",{get:function(){return this.scriptObject.KI},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"name",{get:function(){return this.scriptObject.KN},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"variableStores",{get:function(){var t=this.scriptObject.KVS,e={};if(Array.isArray(t))for(var n=0,c=t;n0){e=this._legacyLayoutSpec;break}}if(!e&&(this.helpText==""||t!=x.FormFactor.Desktop)&&(e={F:"Tahoma",BK:ee.dfltText}),this._layouts||(this._layouts={}),e){var l=this._layouts[t]=ee.buildDefaultLayout(e,this,t);return l.isDefault=!0,l}else return this._layouts[t]=null,null},i.prototype.layout=function(t){var e=this.findOrConstructLayout(t);return e?(this.layoutStates[t]==pe.NOT_LOADED&&(e=st.polyfill(e,this,t),this.layoutStates[t]=pe.POLYFILLED),e):null},i.prototype.refreshLayouts=function(){var t=[x.FormFactor.Desktop,x.FormFactor.Phone,x.FormFactor.Tablet],e=this;t.forEach(function(n){e.layoutStates[n]=pe.NOT_LOADED})},i.prototype.markLayoutCalibrated=function(t){this.layoutStates[t]!=pe.NOT_LOADED&&(this.layoutStates[t]=pe.CALIBRATED)},i.prototype.getLayoutState=function(t){return this.layoutStates[t]},i.prototype.constructNullKeyEvent=function(t,e){e=e||{K_CAPS:!1,K_NUMLOCK:!1,K_SCROLL:!1};var n=$.constructNullKeyEvent(t);return this.setSyntheticEventDefaults(n,e),n},i.prototype.constructKeyEvent=function(t,e,n){var c=t.baseKeyEvent;c.device=e,this.isMnemonic&&c.setMnemonicCode(t.layer.indexOf("shift")!=-1,n.K_CAPS),this.setSyntheticEventDefaults(c,n);var l={K_CAPS:I.stateBitmasks.CAPS,K_NUMLOCK:I.stateBitmasks.NUM_LOCK,K_SCROLL:I.stateBitmasks.SCROLL_LOCK},r=l[c.kName];return r&&(c.Lstates^=r,c.LmodifierChange=!0),c},i.prototype.setSyntheticEventDefaults=function(t,e){t.device.touchable||(t.Lstates=0,t.Lstates|=e.K_CAPS?I.modifierCodes.CAPS:I.modifierCodes.NO_CAPS,t.Lstates|=e.K_NUMLOCK?I.modifierCodes.NUM_LOCK:I.modifierCodes.NO_NUM_LOCK,t.Lstates|=e.K_SCROLL?I.modifierCodes.SCROLL_LOCK:I.modifierCodes.NO_SCROLL_LOCK),t.kName&&t.kName.substr(0,2)=="U_"&&(t.LisVirtualKey=!1),typeof t.Lcode=="undefined"&&(t.Lcode=this.getVKDictionaryCode(t.kName),t.Lcode||(t.Lcode=1)),(t.Lmodifiers&I.modifierBitmasks.ALT_GR_SIM)==I.modifierBitmasks.ALT_GR_SIM&&this.emulatesAltGr&&(t.Lmodifiers&=~I.modifierBitmasks.ALT_GR_SIM,t.Lmodifiers|=I.modifierCodes.RALT)},i.prototype.getVKDictionaryCode=function(t){if(!this.scriptObject.VKDictionary){var e=[];if(typeof this.scriptObject.KVKD=="string")for(var n=this.scriptObject.KVKD.split(" "),c=0;ct&&(l.p+=e)}},i.prototype.count=function(){return this.dks.length},i}();var jn=function(){function i(t){this.id=t}return i.prototype.set=function(t){throw new Error("System store with ID "+this.id+" may not be directly set.")},i}();var Bt=function(i){(0,d.__extends)(t,i);function t(e,n){var c=i.call(this,e)||this;return c.handler=null,c._value=n,c}return Object.defineProperty(t.prototype,"value",{get:function(){return this._value},enumerable:!1,configurable:!0}),t.prototype.matches=function(e){return this._value==e},t.prototype.set=function(e){this.handler&&this.handler(this,e)||(this._value=e)},t}(jn);var qn=function(i){(0,d.__extends)(t,i);function t(e){var n=i.call(this,f.TSS_PLATFORM)||this;return n.kbdInterface=e,n}return t.prototype.matches=function(e){var n,c,l=e.split(" "),r=this.kbdInterface.activeDevice;for(n=0;n0&&o=55296&&y<=56319){var C=u>=56320&&u<=57343;C=C||Q>=56320&&Q<=57343,C&&(o=o-1)}}var h=n._kmwCodeUnitToCodePoint(o),b=c-h,U=l-h,V=e._kmwSubstr(h,U),X=e._kmwLength()-l,L=n._kmwLength()-c,P=L-X;return P<0&&(V=V+e._kmwSubstr(l,-P),P=0),new Ti(V,b,P)},i.prototype.buildTranscriptionFrom=function(t,e,n,c){var l=this.buildTransformFrom(t);return new Ei(e,l,H.from(t,n),c)},i.prototype.restoreTo=function(t){this.setTextBeforeCaret(t.getTextBeforeCaret()),this.setTextAfterCaret(t.getTextAfterCaret()),this._dks=t._dks.clone()},i.prototype.apply=function(t){t.deleteRight&&this.setTextAfterCaret(this.getTextAfterCaret()._kmwSubstr(t.deleteRight)),t.deleteLeft&&this.deleteCharsBeforeCaret(t.deleteLeft),t.insert&&this.insertTextBeforeCaret(t.insert),this._dks.clear()},i.prototype.setTextBeforeCaret=function(t){this.deleteCharsBeforeCaret(this.getTextBeforeCaret()._kmwLength()),this.insertTextBeforeCaret(t)},i.prototype.saveProperties=function(){},i.prototype.restoreProperties=function(){},i}(),wt=$n,H=function(i){(0,d.__extends)(t,i);function t(e,n,c){var l=i.call(this)||this;l.selForward=!0,l.text=e||"";var r=l.text._kmwLength();return l.selStart=typeof n=="number"?n:r,l.selEnd=typeof c=="number"?c:l.selStart,l.selForward=l.selEnd>=l.selStart,l}return t.from=function(e,n){var c;if(e instanceof t){var l=e;c=new t(l.text,l.selStart,l.selEnd)}else{var r=e.getText(),s=r._kmwLength(),B=s,o=0;if(e.hasSelection()){var g=e.getTextBeforeCaret(),a=e.getTextAfterCaret();B=g._kmwLength(),o=s-a._kmwLength()}c=new t(r,B,o)}return c.setDeadkeys(e.deadkeys()),c},t.prototype.clearSelection=function(){this.text=this.getTextBeforeCaret()+this.getTextAfterCaret(),this.selEnd=this.selStart,this.selForward=!0},t.prototype.invalidateSelection=function(){},t.prototype.isSelectionEmpty=function(){return this.selStart==this.selEnd},t.prototype.hasSelection=function(){return!0},t.prototype.getDeadkeyCaret=function(){return this.selStart},t.prototype.setSelection=function(e,n){if(this.selStart=e,this.selEnd=typeof n=="number"?n:e,this.selForward=n>=e,!this.selForward){var c=this.selStart;this.selStart=this.selEnd,this.selEnd=c}},t.prototype.getTextBeforeCaret=function(){return this.text.kmwSubstr(0,this.selStart)},t.prototype.getSelectedText=function(){return this.text.kmwSubstr(this.selStart,this.selEnd-this.selStart)},t.prototype.getTextAfterCaret=function(){return this.text.kmwSubstr(this.selEnd)},t.prototype.getText=function(){return this.text},t.prototype.deleteCharsBeforeCaret=function(e){e>=0&&(e>this.selStart&&(e=this.selStart),this.adjustDeadkeys(-e),this.text=this.text.kmwSubstr(0,this.selStart-e)+this.text.kmwSubstr(this.selStart),this.selStart-=e,this.selEnd-=e)},t.prototype.insertTextBeforeCaret=function(e){this.adjustDeadkeys(e._kmwLength()),this.text=this.getTextBeforeCaret()+e+this.text.kmwSubstr(this.selStart),this.selStart+=e.kmwLength(),this.selEnd+=e.kmwLength()},t.prototype.handleNewlineAtCaret=function(){this.insertTextBeforeCaret("\n")},t.prototype.setTextAfterCaret=function(e){this.text=this.getTextBeforeCaret()+e},t.prototype.doInputEvent=function(){},t}($n);var ki=function(){function i(){this.transcription=null,this.setStore={},this.saveStore={},this.variableStores={},this.triggersDefaultCommand=!1}return i.prototype.finalize=function(t,e,n){if(!this.transcription)throw"Cannot finalize a RuleBehavior with no transcription.";t.beepHandler&&this.beep&&t.beepHandler(e);for(var c in this.setStore){var l=t.keyboardInterface.systemStores[c];if(l)try{l.set(this.setStore[c])}catch(s){t.errorLogger&&t.errorLogger("Rule attempted to perform illegal operation - 'platform' may not be changed.")}else t.warningLogger&&t.warningLogger("Unknown store affected by keyboard rule: "+c)}if(t.keyboardInterface.applyVariableStores(this.variableStores),t.keyboardInterface.variableStoreSerializer)for(var c in this.saveStore)t.keyboardInterface.variableStoreSerializer.saveStore(t.activeKeyboard.id,c,this.saveStore[c]);if(this.triggersDefaultCommand){var r=this.transcription.keystroke;t.defaultRules.applyCommand(r,e)}t.warningLogger&&this.warningLog?t.warningLogger(this.warningLog):t.errorLogger&&this.errorLog&&t.errorLogger(this.errorLog)},i.prototype.mergeInDefaults=function(t){var e=this.transcription.keystroke,n=t.transcription.keystroke;if(e.Lcode!=n.Lcode||e.Lmodifiers!=n.Lmodifiers)throw"RuleBehavior default-merge not supported unless keystrokes are identical!";this.triggersDefaultCommand=this.triggersDefaultCommand||t.triggersDefaultCommand;var c=H.from(this.transcription.preInput,!1);c.apply(this.transcription.transform),c.apply(t.transcription.transform),this.transcription=c.buildTranscriptionFrom(this.transcription.preInput,e,!1,this.transcription.alternates)},i}(),le=ki;var Yi=function(){function i(){}return i}();var Xs=function(){function i(){}return i}(),ps=function(){function i(){}return i}(),ms=function(){function i(){}return i}(),Zs=function(){function i(){}return i}(),Ls=function(){function i(){}return i}(),Js=function(){function i(){}return i}(),wi=function(){function i(){}return i.prototype.reset=function(){this._cache=[]},i.prototype.get=function(t,e){return typeof this._cache[t]=="undefined"||typeof this._cache[t][e]=="undefined"?null:this._cache[t][e]},i.prototype.set=function(t,e,n){typeof this._cache[t]=="undefined"&&(this._cache[t]=[]),this._cache[t][e]=n},i}(),Oi=function(){function i(){}return i.prototype.reset=function(){this._cache=[]},i.prototype.get=function(t,e){return typeof this._cache[t]=="undefined"||typeof this._cache[t][e]=="undefined"?null:this._cache[t][e]},i.prototype.set=function(t,e,n){typeof this._cache[t]=="undefined"&&(this._cache[t]=[]),this._cache[t][e]=n},i.prototype.clone=function(){var t=new i;return t._cache=this._cache,t},i}(),f;(function(i){i[i.TSS_LAYER=33]="TSS_LAYER",i[i.TSS_PLATFORM=31]="TSS_PLATFORM",i[i.TSS_NEWLAYER=42]="TSS_NEWLAYER",i[i.TSS_OLDLAYER=43]="TSS_OLDLAYER"})(f||(f={}));var ec=function(i){(0,d.__extends)(t,i);function t(e,n,c){c===void 0&&(c=null);var l=i.call(this,e,n)||this;return l.cachedContext=new wi,l.cachedContextEx=new Oi,l._AnyIndices=[],l.systemStores={},l.systemStores[f.TSS_PLATFORM]=new qn(l),l.systemStores[f.TSS_LAYER]=new Bt(f.TSS_LAYER,"default"),l.systemStores[f.TSS_NEWLAYER]=new Bt(f.TSS_NEWLAYER,""),l.systemStores[f.TSS_OLDLAYER]=new Bt(f.TSS_OLDLAYER,""),l.variableStoreSerializer=c,l}return Object.defineProperty(t.prototype,"Codes",{get:function(){return I},enumerable:!1,configurable:!0}),t.prototype.saveFocus=function(){},t.prototype.registerKeyboard=function(e){var n=new w(e);this.loadedKeyboard=n},t.prototype.context=function(e,n,c){var l=this.cachedContext.get(e,n);if(l!==null)return l;var r=this.KC_(e,n,c);return this.cachedContext.set(e,n,r),r},t.prototype.KC_=function(e,n,c){var l="";return l=c.isSelectionEmpty()?c.getTextBeforeCaret():"",l._kmwLength()0&&r[0].p>o){r.splice(0,1);continue}else if(r.length>0&&r[0].p==o)l.deadContext[e-l.valContext.length-1]=r[0],l.valContext=[r[0].d].concat(l.valContext),r.splice(0,1);else{var g=this.context(++s,1,c);l.valContext=[g].concat(l.valContext)}}this.cachedContextEx.set(e,e,l)}var a=l;a.valContext=a.valContext.slice(0,n);for(var F=0;F255&&(r=e.vkCode),e.LisVirtualKey||r>255?((n&16384)==16384||r>255)&&(l=c==r&&(n&B)==g,l=l&&this.stateMatch(e,n&o)):(n&16384)==0&&(l=r==c),l||this.activeTargetOutput.deadkeys().resetMatched(),l},t.prototype.stateMatch=function(e,n){return(n&e.Lstates)==n},t.prototype.keyInformation=function(e){var n=new Yi;return n.vk=e.LisVirtualKey,n.code=e.Lcode,n.modifiers=e.Lmodifiers,n},t.prototype.deadkeyMatch=function(e,n,c){return n.hasDeadkeyMatch(e,c)},t.prototype.beep=function(e){this.resetContextCache(),this.ruleBehavior.beep=!0},t.prototype._ExplodeStore=function(e){if(typeof e=="string"){var n=this.activeKeyboard.explodedStores;if(n[e])return n[e];for(var c=[],l=0;l=0},t.prototype._Index=function(e,n){return e=this._ExplodeStore(e),this._AnyIndices[n-1]0){c=this._BuildExtendedContext(e,e,n);for(var l=0,r=0;rB&&(e=B)}n.deadkeys().resetMatched(),this.output(e,n,"")},t.prototype.output=function(e,n,c){this.resetContextCache(),n.saveProperties(),n.clearSelection(),n.deadkeys().deleteMatched(),e>=0&&n.deleteCharsBeforeCaret(e),n.insertTextBeforeCaret(c),n.restoreProperties()},t.prototype.contextExOutput=function(e,n,c,l){this.resetContextCache(),e>=0&&this.output(e,n,"");var r=this.ruleContextEx.get(c,c),s=r.deadContext[l-1],B=r.valContext[l-1];if(s)n.insertDeadkeyBeforeCaret(s.d);else if(typeof B=="string")this.output(-1,n,B);else throw new Error("contextExOutput: should never be a numeric valContext with no corresponding deadContext")},t.prototype.deadkeyOutput=function(e,n,c){this.resetContextCache(),e>=0&&this.output(e,n,""),n.insertDeadkeyBeforeCaret(c)},t.prototype.ifStore=function(e,n,c){var l=!0,r=this.systemStores[e];return r&&(l=r.matches(n)),l},t.prototype.setStore=function(e,n,c){if(this.resetContextCache(),e==f.TSS_LAYER&&this.activeDevice.touchable)this.ruleBehavior.setStore[e]=n;else return!1},t.prototype.loadStore=function(e,n,c){if(this.resetContextCache(),this.variableStoreSerializer){var l=this.variableStoreSerializer.loadStore(e,n);return l[n]||c}else return c},t.prototype.saveStore=function(e,n){this.resetContextCache();var c=this.activeKeyboard;if(!c||typeof c.id=="undefined"||c.id=="")return!1;var l={};return l[e]=n,this.ruleBehavior?this.ruleBehavior.saveStore[e]=l:this.variableStoreSerializer.saveStore(this.activeKeyboard.id,e,l),!0},t.prototype.resetContextCache=function(){this.cachedContext.reset(),this.cachedContextEx.reset()},t.prototype.defaultBackspace=function(e){e.isSelectionEmpty()?this.output(1,e,""):this.output(0,e,"")},t.prototype.processNewContextEvent=function(e,n){if(!this.activeKeyboard)throw"No active keyboard for keystroke processing!";return this.process(this.activeKeyboard.processNewContextEvent.bind(this.activeKeyboard),e,n,!0)},t.prototype.processPostKeystroke=function(e,n){if(!this.activeKeyboard)throw"No active keyboard for keystroke processing!";return this.process(this.activeKeyboard.processPostKeystroke.bind(this.activeKeyboard),e,n,!0)},t.prototype.processKeystroke=function(e,n){if(!this.activeKeyboard)throw"No active keyboard for keystroke processing!";return this.process(this.activeKeyboard.process.bind(this.activeKeyboard),e,n,!1)},t.prototype.process=function(e,n,c,l){if(n)if(this.activeKeyboard){if(!e)throw"No callee for keystroke processing!"}else throw"No active keyboard for keystroke processing!";else throw"No target specified for keyboard output!";n.invalidateSelection(),n.deadkeys().resetMatched(),this.resetContextCache();var r=H.from(n,!0),s=this.activeKeyboard.variableStores;this.ruleBehavior=new le,this.activeDevice=c.device,this.activeTargetOutput=n;var B=e(n,c);this.activeTargetOutput=null,this.ruleBehavior.transcription=n.buildTranscriptionFrom(r,c,l),this.ruleBehavior.variableStores=this.activeKeyboard.variableStores,this.activeKeyboard.variableStores=s,this.ruleBehavior.triggerKeyDefault=!B;var o=this.ruleBehavior;return this.ruleBehavior=null,o},t.prototype.applyVariableStores=function(e){this.activeKeyboard.variableStores=e},t.__publishShorthandAPI=function(){var e=this.prototype,n=function(c,l){e[l]&&(e[c]=e[l])};n("KSF","saveFocus"),n("KBR","beepReset"),n("KT","insertText"),n("KR","registerKeyboard"),n("KRS","registerStub"),n("KC","context"),n("KN","nul"),n("KCM","contextMatch"),n("KFCM","fullContextMatch"),n("KIK","isKeypress"),n("KKM","keyMatch"),n("KSM","stateMatch"),n("KKI","keyInformation"),n("KDM","deadkeyMatch"),n("KB","beep"),n("KA","any"),n("KDC","deleteContext"),n("KO","output"),n("KDO","deadkeyOutput"),n("KCXO","contextExOutput"),n("KIO","indexOutput"),n("KIFS","ifStore"),n("KSETS","setStore"),n("KLOAD","loadStore"),n("KSAVE","saveStore")},t.GLOBAL_NAME="KeymanWeb",t}(Me),Ke=ec;(function(){ec.__publishShorthandAPI()})();var tc=J(R(),1);var Di=function(i){(0,d.__extends)(t,i);function t(e,n){var c=i.call(this)||this;return c.stateKeys={K_CAPS:!1,K_NUMLOCK:!1,K_SCROLL:!1},c.modStateFlags=0,n||(n=t.DEFAULT_OPTIONS),c.contextDevice=e,c.baseLayout=n.baseLayout||t.DEFAULT_OPTIONS.baseLayout,c.keyboardInterface=n.keyboardInterface||new Ke(Ae(),De),c.defaultRules=n.defaultOutputRules||t.DEFAULT_OPTIONS.defaultOutputRules,c}return Object.defineProperty(t.prototype,"activeKeyboard",{get:function(){return this.keyboardInterface.activeKeyboard},set:function(e){this.keyboardInterface.activeKeyboard=e,this.resetContext()},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"layerStore",{get:function(){return this.keyboardInterface.systemStores[f.TSS_LAYER]},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"newLayerStore",{get:function(){return this.keyboardInterface.systemStores[f.TSS_NEWLAYER]},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"oldLayerStore",{get:function(){return this.keyboardInterface.systemStores[f.TSS_OLDLAYER]},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"layerId",{get:function(){return this.layerStore.value},set:function(e){this.layerStore.set(e)},enumerable:!1,configurable:!0}),t.prototype.defaultRuleBehavior=function(e,n,c){var l=H.from(n,c),r=new le,s=!1,B="",o;if(e.isSynthetic||n.isSynthetic)if(s=!0,this.defaultRules.isCommand(e))r.triggersDefaultCommand=!0;else if((o=this.defaultRules.forSpecialEmulation(e))!=null)switch(o){case xe.Backspace:this.keyboardInterface.defaultBackspace(n);break;case xe.Enter:n.handleNewlineAtCaret();break;default:r.errorLog="Unexpected 'special emulation' character (\\u"+o.kmwCharCodeAt(0).toString(16)+") went unhandled!"}else s=!1;var g=this.activeKeyboard&&this.activeKeyboard.isMnemonic;if(!s)if((B=this.defaultRules.forAny(e,g))!=null)if(o=this.defaultRules.forSpecialEmulation(e),o==xe.Backspace)this.keyboardInterface.defaultBackspace(n);else{if(o||this.defaultRules.isCommand(e))return null;this.keyboardInterface.output(0,n,B)}else return null;if(r.errorLog)return r;var a=n.buildTranscriptionFrom(l,e,c);return r.transcription=a,r},t.prototype.processNewContextEvent=function(e,n){return this.activeKeyboard?this.keyboardInterface.processNewContextEvent(n,this.activeKeyboard.constructNullKeyEvent(e,this.stateKeys)):null},t.prototype.processPostKeystroke=function(e,n){return this.activeKeyboard?this.keyboardInterface.processPostKeystroke(n,this.activeKeyboard.constructNullKeyEvent(e,this.stateKeys)):null},t.prototype.processKeystroke=function(e,n){var c;if(this.activeKeyboard&&e.Lcode!=0&&(c=this.keyboardInterface.processKeystroke(n,e)),!c||c.triggerKeyDefault){e.Lcode=e.vkCode||e.Lcode,this.keyboardInterface.activeTargetOutput=n;var l=this.defaultRuleBehavior(e,n,!1);l&&(c?c.mergeInDefaults(l):c=l,c.triggerKeyDefault=!1),this.keyboardInterface.activeTargetOutput=null}return c},t.prototype._UpdateVKShift=function(e){var n=0,c=["CAPS","NUM_LOCK","SCROLL_LOCK"],l=["K_CAPS","K_NUMLOCK","K_SCROLL"];if(!this.activeKeyboard)return!0;if(e){n=e.Lmodifiers,this.activeKeyboard.isChiral&&this.activeKeyboard.emulatesAltGr&&(this.modStateFlags&I.modifierBitmasks.ALT_GR_SIM)==I.modifierBitmasks.ALT_GR_SIM&&(n|=I.modifierBitmasks.ALT_GR_SIM,n&=~I.modifierCodes.RALT);for(var r=!1,s=0;s=0)this.OS="iOS",this.formFactor="tablet",this.dyPortrait=this.dyLandscape=0;else if(e.indexOf("iPhone")>=0)this.OS="iOS",this.formFactor="phone",this.dyPortrait=this.dyLandscape=25;else if(e.indexOf("Android")>=0){this.OS="Android",this.formFactor="phone",this.dyPortrait=75,this.dyLandscape=25;try{var n=new RegExp("(?:Android\\s+)(\\d+\\.\\d+\\.\\d+)");this.version=e.match(n)[1]}catch(u){}}else if(e.indexOf("Linux")>=0)this.OS="Linux";else if(e.indexOf("Macintosh")>=0){var c=/Intel Mac OS X (\d+(?:[_\.]\d+)+)/i,l=c.exec(e);if(!l)console.warn("KMW could not properly parse the user agent string.A suboptimal keyboard layout may result."),this.OS="MacOSX";else if(l.length>1&&l[1]){var r=l[1].replace("_","."),s=new z(r);t=z.MAC_POSSIBLE_IPAD_ALIAS.compareTo(s)<=0,this.OS="MacOSX"}}else e.indexOf("Windows NT")>=0&&(this.OS="Windows",e.indexOf("Touch")>=0&&(this.formFactor="phone"),typeof navigator.msMaxTouchPoints=="number"&&navigator.msMaxTouchPoints>0&&(this.touchable=!0))}var B=Math.min(screen.width,screen.height),o=Math.max(screen.width,screen.height),g=B/o;this.OS!="iOS"&&this.formFactor=="phone"&&(B>=600&&g>.5625||g>=.625)&&(this.formFactor="tablet");var a=navigator.platform=="Win32"||navigator.platform=="MacIntel";this.OS=="iOS"&&!("ongesturestart"in window)&&!a&&(this.OS="Android"),this.browser="web",(this.OS=="iOS"||this.OS.toLowerCase()=="macosx")&&(this.browser="safari");var F=/Firefox|Chrome|OPR|Safari|Edge/;if(F.test(navigator.userAgent)&&(navigator.userAgent.indexOf("Firefox")>=0&&"onmozorientationchange"in screen?this.browser="firefox":navigator.userAgent.indexOf("OPR")>=0?this.browser="opera":navigator.userAgent.indexOf(" Edge/")>=0?this.browser="edge":navigator.userAgent.indexOf("Chrome")>=0?this.browser="chrome":navigator.userAgent.indexOf("Safari")>=0&&(this.browser="safari")),t&&this.browser=="safari"&&window.TouchEvent){this.OS="iOS",this.formFactor="tablet",this.dyPortrait=this.dyLandscape=0;var y=screen.height/screen.width;y<1&&(y=1/y),y>1.6&&(this.formFactor="phone",this.dyPortrait=this.dyLandscape=25)}return this.colorScheme=cc.prefersDarkMode()?"dark":"light",this.detected=!0,this.coreSpec},Object.defineProperty(i.prototype,"coreSpec",{get:function(){return new x(this.browser,this.formFactor,this.OS,this.touchable)},enumerable:!1,configurable:!0}),i}();var zt=function(i){(0,d.__extends)(t,i);function t(e,n){var c=i.call(this)||this;if(c.applyCacheBusting=!1,!n){var l=new at;l.detect(),n=l.coreSpec}return c.sourcePath=e,c.hostDevice=n,c.deferForInitialization=new A,c}return t.prototype.initialize=function(e){var n=this;this._paths?this._paths.updateFromOptions(e):this._paths=new Dt(e,this.sourcePath),typeof e.setActiveOnRegister=="boolean"?this.activateFirstKeyboard=e.setActiveOnRegister:this.activateFirstKeyboard=!0,this._spacebarText=e.spacebarText,fe.spacebarTextMode=function(){return n.spacebarText}},t.prototype.finalizeInit=function(){this.deferForInitialization.resolve()},Object.defineProperty(t.prototype,"paths",{get:function(){return this._paths},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"spacebarText",{get:function(){return this._spacebarText},set:function(e){this._spacebarText!=e&&(this._spacebarText=e,this.emit("spacebartext",e))},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"softDevice",{get:function(){return this.hostDevice},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"hardDevice",{get:function(){return Ht(this.hostDevice)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"stubNamespacer",{get:function(){return this._stubNamespacer},set:function(e){this._stubNamespacer=e},enumerable:!1,configurable:!0}),t.prototype.debugReport=function(){return{hostDevice:this.hostDevice,initialized:this.deferForInitialization.hasFinalized}},t.prototype.onRuleFinalization=function(e,n){},t}(ic.default);var Kt=(0,d.__assign)({setActiveOnRegister:!0,spacebarText:ie.LANGUAGE_KEYBOARD},Mt);var lc=J(R(),1);var _t=function(i){(0,d.__extends)(t,i);function t(e){var n=i.call(this)||this;return n.pendingActivations=[],n.engineConfig=e,n}return Object.defineProperty(t.prototype,"predictionContext",{get:function(){return this._predictionContext},enumerable:!1,configurable:!0}),t.prototype.configure=function(e){this._resetContext=e.resetContext,this._predictionContext=e.predictionContext,this.keyboardCache=e.keyboardCache},t.prototype.insertText=function(e,n,c){var l=this.activeTarget;return l!=null?(n!=null&&e.output(0,l,n),typeof c!="undefined"&&c!==null&&e.deadkeyOutput(0,l,c),l.invalidateSelection(),!0):!1},t.prototype.resetContext=function(){this._resetContext(this.activeTarget),this.predictionContext.resetContext()},t.prototype.findAndPopActivation=function(e){var n;for(n=0;n9?c=0:c=n-1;else if(typeof n=="string"){for(var l=n.length==2?$t:qt,r=0;r1&&n?"languages":"keyboards"),r="?jsonp=keyman.register&languageidtype=bcp47&version="+z.CURRENT.toString(),s=l+r+e,B=this.requestEngine.request(s),o=B.promise,g=B.queryId;return this.cloudResolutionPromises[g]=o,o.finally(function(){delete c.cloudResolutionPromises[g]}),o.corePromise},t.prototype._registerCore=function(e){var n=e.options,c=n.fontBaseUri;if(this.pathConfig.fonts!=""?c=this.pathConfig.fonts:this.pathConfig.updateFontPath(c),typeof e.error=="string"){var l="";if(typeof e.options.keyboardid=="string"){var r=e.options.keyboardid;l=r.substring(0,1).toUpperCase()+r.substring(1)}return new Error($i(l))}if(typeof n=="undefined"||typeof n.context=="undefined")return new Error(en);var s=[];if(n.context=="keyboard"){var B=void 0,o=e.keyboard;if(Array.isArray(o))for(B=0;B0)r.push(g);else if(g.KI||g.KL||g.KLC||g.KFont||g.KOskFont)l.push(new re(g));else{var a=g;if(typeof a.language!="undefined"&&console.warn("The 'language' property for keyboard stubs has been deprecated. Please use the 'languages' property instead."),a.languages||(a.languages=a.language),typeof a.languages=="undefined"){var F="To use keyboard '"+a.id+"', you must specify languages.";s.push(nn(a,F))}else if(Array.isArray(a.languages))for(var y=re.toStubs(a,this.pathConfig.keyboards,this.pathConfig.fonts),u=0,Q=y;u1&&(j=k[1].split(","));for(var ae=0;ae0&&j[ae]=="")){var E=dc(k[0],j[ae],k[2]);Fc(this.cache,X,E)&&X.push(E)}}n.forEach(function(Y){return e.cache.addStub(Y)});var Lt=this.cloudQueryEngine.fetchCloudStubs(X.map(function(Y){return Y.toString()}));return Lt.then(function(Y){for(var ge=0,nt=Y;ge0?t=n[0]:t=document.body}this.linkNode=t,this.doCacheBusting=e||!1}return i.prototype.linkStylesheet=function(t){this.linkedSheets.push(t),this.linkNode.appendChild(t)},i.prototype.addStyleSheetForFont=function(t,e,n){if(!!t&&typeof t.files!="undefined"){var c=t.family,l,r="",s="",B="",o="",g=[];n||(n=x.OperatingSystem.Other);var a=this.fontStyleDefinitions[n]=this.fontStyleDefinitions[n]||{};if(a[c]){var F=a[c];F.parentNode||this.linkStylesheet(F);return}for(typeof t.files=="string"?g[0]=t.files:g=t.files,l=0;l0&&(r=g[l]),g[l].toLowerCase().indexOf(".ttf")>0&&(r=g[l]),g[l].toLowerCase().indexOf(".woff")>0&&(s=g[l]),g[l].toLowerCase().indexOf(".eot")>0&&(B=g[l]),g[l].toLowerCase().indexOf(".svg")>0&&(o=g[l]);r!=""&&r.indexOf("/")<0&&(r=e+r),s!=""&&s.indexOf("/")<0&&(s=e+s),B!=""&&B.indexOf("/")<0&&(B=e+B),o!=""&&o.indexOf("/")<0&&(o=e+o);var y="@font-face {\nfont-family:"+t.family+";\nfont-style:normal;\nfont-weight:normal;\n";if(n==x.OperatingSystem.iOS)if(r!="")this.doCacheBusting&&(r=this.cacheBust(r)),y=y+"src:url('"+r+"') format('truetype');";else return null;else{var u=[];if(n==x.OperatingSystem.Android?(o!=""&&u.push("url('"+o+"') format('svg')"),s!=""&&u.push("url('"+s+"') format('woff')"),r!=""&&u.push("url('"+r+"') format('truetype')")):(s!=""&&u.push("url('"+s+"') format('woff')"),r!=""&&u.push("url('"+r+"') format('truetype')"),o!=""&&u.push("url('"+o+"') format('svg')")),u.length==0)return null;y+="src:"+u.join(",")+";"}y=y+"\n}\n";var Q=Ve(y);return a[c]=Q,this.linkStylesheet(Q),Q}},i.prototype.cacheBust=function(t){return t+"?v="+new Date().getTime()},i.prototype.linkExternalSheet=function(t){try{if(document.querySelector("link[href="+JSON.stringify(t)+"]")!=null)return}catch(n){return}var e=document.createElement("link");e.type="text/css",e.rel="stylesheet",e.href=t,this.linkStylesheet(e)},i.prototype.unlink=function(t){var e=this.linkedSheets.indexOf(t);return e>-1?(this.linkedSheets.splice(e,1),t.parentNode.removeChild(t),!0):!1},i.prototype.unlinkAll=function(){for(var t=0,e=this.linkedSheets;t1){var o=B[0],g=B[1];n[o]=e(g,o)}else n[B[0]]=""}return n},i.prototype.saveCookie=function(t,e,n){var c="";for(var l in e)c+=l+"="+n(e[l],l)+";";var r=new Date(new Date().valueOf()+1e3*60*60*24*30).toUTCString(),s=" path=/; expires="+r;document.cookie="".concat(t,"=").concat(encodeURIComponent(c),"; ").concat(s)},i}(),se=il;function v(i){var t;if(!i)return 0;var e=i.offsetLeft?i.offsetLeft:0;if(t=i,t.offsetParent){for(;t.offsetParent;)t=t.offsetParent,e+=t.offsetLeft;var n=t.ownerDocument;t.style.position=="fixed"&&n&&n.scrollingElement&&(e+=n.scrollingElement.scrollLeft)}if(t&&t.ownerDocument&&i.ownerDocument!=window.document){var c=t.ownerDocument;if(c&&c.defaultView&&c.defaultView.frameElement)return e+v(c.defaultView.frameElement)-c.documentElement.scrollLeft}return e}function Z(i){var t;if(!i)return 0;var e=i.offsetTop?i.offsetTop:0;if(t=i,t.ownerDocument&&t instanceof t.ownerDocument.defaultView.HTMLElement){for(;t.offsetParent;)t=t.offsetParent,e+=t.offsetTop;var n=t.ownerDocument;t.style.position=="fixed"&&n&&n.scrollingElement&&(e+=n.scrollingElement.scrollTop)}if(t&&t.ownerDocument&&i.ownerDocument!=window.document){var c=t.ownerDocument;if(c&&c.defaultView&&c.defaultView.frameElement)return e+Z(c.defaultView.frameElement)}return e}var uc=function(i){(0,d.__extends)(t,i);function t(e,n){return i.call(this,"KeymanWeb_".concat(e,"_Option_").concat(n))||this}return t.prototype.load=function(){return i.prototype.load.call(this,decodeURIComponent)},t.prototype.save=function(e){i.prototype.save.call(this,e,encodeURIComponent)},t}(se),rn=function(){function i(){}return i.prototype.loadStore=function(t,e){var n=new uc(t,e);return n.load()},i.prototype.saveStore=function(t,e,n){var c=new uc(t,e);c.save(n)},i}();var Ic=function(i){(0,d.__extends)(t,i);function t(e,n,c){var l=i.call(this,e,n,new rn)||this;return l.insertText=function(r,s){l.resetContextCache(),l.engine.contextManager.insertText(l,r,s)},l.KT=l.insertText,l.engine=n,l.stubNamespacer=c,l}return t.prototype.preserveID=function(e){var n;if(document.currentScript)n=document.currentScript.id;else{var c=document.getElementsByTagName("script"),l=c[c.length-1];n=l.id}if(n)n.indexOf(ye(e.KI))!=-1?e.KI=n:console.error("Error when registering keyboard: current SCRIPT tag's ID does not match!");else return},t.prototype.registerKeyboard=function(e){var n=this;i.prototype.registerKeyboard.call(this,e);var c=this.loadedKeyboard;this.preserveID(e),this.engine.config.deferForInitialization.then(function(){n.engine.keyboardRequisitioner.cache.isFetchingKeyboard(c.id)||(n.engine.keyboardRequisitioner.cache.addKeyboard(c),n.loadedKeyboard=null)})},t.prototype.registerStub=function(e){var n=this,c;this.stubNamespacer&&this.stubNamespacer(e);var l=this.engine.config.paths,r=new re(e,l.keyboards,l.fonts);return!((c=this.engine.keyboardRequisitioner)===null||c===void 0)&&c.cache.findMatchingStub(r)?1:(this.engine.config.deferForInitialization.hasFinalized?this.engine.keyboardRequisitioner.cache.addStub(r):this.engine.config.deferForInitialization.then(function(){return n.engine.keyboardRequisitioner.cache.addStub(r)}),null)},t}(Ke),sn=Ic;(function(){Ic.__publishShorthandAPI()})();var Qc=function(i){(0,d.__extends)(t,i);function t(e,n){var c=this;return e&&e._jsGlobal!=window&&(e._jsGlobal.String=window.String),e?c=i.call(this,e)||this:c=i.call(this,new Me(window,De))||this,c.performCacheBusting=n||!1,c}return t.prototype.loadKeyboardInternal=function(e,n,c){var l=this,r=new A;this.performCacheBusting&&(e=this.cacheBust(e));try{var s=this.harness._jsGlobal.document,B=s.createElement("script");c&&(B.id=c),s.head.appendChild(B),B.onerror=function(o){r.reject(n.missingError(o))},B.onload=function(){if(l.harness.loadedKeyboard){var o=l.harness.loadedKeyboard;l.harness.loadedKeyboard=null,r.resolve(o)}else r.reject(n.scriptError())},r.then(function(){B.remove()}).catch(function(){B.remove()}),B.src=e}catch(o){return Promise.reject(o)}return r.corePromise},t.prototype.cacheBust=function(e){return e+"?v="+new Date().getTime()},t}(Tt);var ll=function(){function i(t,e,n){this.left=t.getTextBeforeCaret(),this.startOfBuffer=this.left._kmwLength()<=e.leftContextCodePoints,this.startOfBuffer||(this.left=this.left._kmwSubstr(-e.leftContextCodePoints)),this.right=t.getTextAfterCaret(),this.endOfBuffer=this.right._kmwLength()<=e.rightContextCodePoints,this.endOfBuffer||(this.right=this.right._kmwSubstr(0,e.rightContextCodePoints)),this.casingForm=n=="shift"?"initial":n=="caps"?"upper":null}return i.prototype.toMock=function(){var t=this.left._kmwLength();return new H(this.left+(this.right||""),t)},i.ENGINE_RULE_WINDOW={leftContextCodePoints:64,rightContextCodePoints:32},i}(),ue=ll;var Uc=J(R(),1);var rl=function(){function i(){this._promises=new Map}return Object.defineProperty(i.prototype,"length",{get:function(){return this._promises.size},enumerable:!1,configurable:!0}),i.prototype.make=function(t,e,n){if(this._promises.has(t))return n("Existing request with token ".concat(t));this._promises.set(t,{reject:n,resolve:e})},i.prototype.keep=function(t,e){var n=this._promises.get(t);if(!n)throw new Error("No promise associated with token: ".concat(t));var c=n.resolve;return this._promises.delete(t),c(e)},i.prototype.break=function(t,e){var n=this._promises.get(t);if(!n)throw new Error("No promise associated with token: ".concat(t));this._promises.delete(t),n.reject(e)},i}(),Pe=rl;var sl=function(){function i(t,e,n){this._worker=e,this._worker.onmessage=this.onMessage.bind(this),this._declareLMLayerReady=null,this._predictPromises=new Pe,this._wordbreakPromises=new Pe,this._acceptPromises=new Pe,this._revertPromises=new Pe,this._nextToken=Number.MIN_SAFE_INTEGER,this.sendConfig(t,!!n)}return i.prototype.sendConfig=function(t,e){this._worker.postMessage({message:"config",capabilities:t,testMode:e})},i.prototype.loadModel=function(t,e){var n=this;return e===void 0&&(e="file"),new Promise(function(c,l){n._declareLMLayerReady=c;var r={type:e};e=="file"?r.file=t:r.code=t,n._worker.postMessage({message:"load",source:r})})},i.prototype.unloadModel=function(){this._worker.postMessage({message:"unload"})},i.prototype.predict=function(t,e){var n=this,c=this._nextToken++;return new Promise(function(l,r){n._predictPromises.make(c,l,r),n._worker.postMessage({message:"predict",token:c,transform:t,context:e})})},i.prototype.wordbreak=function(t){var e=this,n=this._nextToken++;return new Promise(function(c,l){e._wordbreakPromises.make(n,c,l),e._worker.postMessage({message:"wordbreak",token:n,context:t})})},i.prototype.acceptSuggestion=function(t,e,n){var c=this,l=this._nextToken++;return new Promise(function(r,s){c._acceptPromises.make(l,r,s),c._worker.postMessage({message:"accept",token:l,suggestion:t,context:e,postTransform:n})})},i.prototype.revertSuggestion=function(t,e){var n=this,c=this._nextToken++;return new Promise(function(l,r){n._revertPromises.make(c,l,r),n._worker.postMessage({message:"revert",token:c,reversion:t,context:e})})},i.prototype.resetContext=function(t){this._worker.postMessage({message:"reset-context",context:t})},i.prototype.onMessage=function(t){var e=t.data;if(e.message==="error")console.error(e.log),e.error&&console.error(e.error);else if(e.message==="ready")this._declareLMLayerReady(t.data.configuration);else if(e.message==="suggestions")this._predictPromises.keep(e.token,e.suggestions);else if(e.message==="currentword")this._wordbreakPromises.keep(e.token,e.word);else if(e.message==="postaccept")this._acceptPromises.keep(e.token,e.reversion);else if(e.message==="postrevert")this._revertPromises.keep(e.token,e.suggestions);else throw new Error("Message not implemented: ".concat(e.message))},i.prototype.shutdown=function(){this._worker.terminate()},i}(),Bn=sl;function Ft(i){return i}var Cc='var ae=Object.defineProperty;var v=function(r,e){return ae(r,"name",{value:e,configurable:!0})};/*! https://mths.be/codepointat v0.2.0 by @mathias */String.prototype.codePointAt||function(){"use strict";var r=function(){try{var t={},n=Object.defineProperty,i=n(t,t,t)&&n}catch(a){}return i}(),e=v(function(t){if(this==null)throw TypeError();var n=String(this),i=n.length,a=t?Number(t):0;if(a!=a&&(a=0),!(a<0||a>=i)){var u=n.charCodeAt(a),o;return u>=55296&&u<=56319&&i>a+1&&(o=n.charCodeAt(a+1),o>=56320&&o<=57343)?(u-55296)*1024+o-56320+65536:u}},"codePointAt");r?r(String.prototype,"codePointAt",{value:e,configurable:!0,writable:!0}):String.prototype.codePointAt=e}(),Array.prototype.fill||Object.defineProperty(Array.prototype,"fill",{value:function(r){if(this==null)throw new TypeError("this is null or not defined");for(var e=Object(this),t=e.length>>>0,n=arguments[1],i=n>>0,a=i<0?Math.max(t+i,0):Math.min(i,t),u=arguments[2],o=u===void 0?t:u>>0,l=o<0?Math.max(t+o,0):Math.min(o,t);a>>0;if(typeof r!="function")throw new TypeError("predicate must be a function");for(var n=arguments[1],i=0;i0?1:-1)*Math.floor(Math.abs(u))},"toInteger"),n=Math.pow(2,53)-1,i=v(function(a){var u=t(a);return Math.min(Math.max(u,0),n)},"toLength");return v(function(u){var o=this,l=Object(u);if(u==null)throw new TypeError("Array.from requires an array-like object - not null or undefined");var s=arguments.length>1?arguments[1]:void 0,f;if(typeof s!="undefined"){if(!e(s))throw new TypeError("Array.from: when provided, the second argument must be a function");arguments.length>2&&(f=arguments[2])}for(var c=i(l.length),d=e(o)?Object(new o(c)):new Array(c),p=0,g;p=0&&typeof m.length=="number"&&m.length>=0){var O=Math.floor(h.length),k=Math.floor(m.length),A=0;for(h.length=O+k;A=0&&this._nextIndex=0))return(I=new k(0)).length=0,I;for(A=Math.floor(h.length),(I=new k(A)).length=A;E0&&g[g.length-1])&&(S[0]===6||S[0]===2)){c=0;continue}if(S[0]===3&&(!g||S[1]>g[0]&&S[1]=s.length&&(s=void 0),{value:s&&s[d++],done:!s}}};throw new TypeError(f?"Object is not iterable.":"Symbol.iterator is not defined.")},"__values7"),u=v(function(s,f){var c=typeof Symbol=="function"&&s[Symbol.iterator];if(!c)return s;var d=c.call(s),p,g=[],w;try{for(;(f===void 0||f-- >0)&&!(p=d.next()).done;)g.push(p.value)}catch(b){w={error:b}}finally{try{p&&!p.done&&(c=d.return)&&c.call(d)}finally{if(w)throw w.error}}return g},"__read3"),o("__extends",t),o("__assign",n),o("__generator",i),o("__values",a),o("__read",u)})}}),build_exports={};__export(build_exports,{tslib:function(){return tslib_1}}),__reExport(build_exports,__toESM(require_tslib(),1));var tslib_1=__toESM(require_tslib(),1),DeviceSpec=function(){function r(e,t,n,i){switch(e.toLowerCase()){case r.Browser.Chrome:case r.Browser.Edge:case r.Browser.Firefox:case r.Browser.Native:case r.Browser.Opera:case r.Browser.Safari:this.browser=e.toLowerCase();break;default:this.browser=r.Browser.Other}switch(t.toLowerCase()){case r.FormFactor.Desktop:case r.FormFactor.Phone:case r.FormFactor.Tablet:this.formFactor=t.toLowerCase();break;default:throw"Invalid form factor specified for device: "+t}switch(n.toLowerCase()){case r.OperatingSystem.Windows.toLowerCase():case r.OperatingSystem.macOS.toLowerCase():case r.OperatingSystem.Linux.toLowerCase():case r.OperatingSystem.Android.toLowerCase():case r.OperatingSystem.iOS.toLowerCase():this.OS=n.toLowerCase();break;default:this.OS=r.OperatingSystem.Other}this.touchable=i}return v(r,"DeviceSpec2"),r}();(function(r){var e;(function(i){i.Chrome="chrome",i.Edge="edge",i.Firefox="firefox",i.Native="native",i.Opera="opera",i.Safari="safari",i.Other="other"})(e=r.Browser||(r.Browser={}));var t;(function(i){i.Windows="windows",i.macOS="macosx",i.Linux="linux",i.Android="android",i.iOS="ios",i.Other="other"})(t=r.OperatingSystem||(r.OperatingSystem={}));var n;(function(i){i.Desktop="desktop",i.Phone="phone",i.Tablet="tablet"})(n=r.FormFactor||(r.FormFactor={}))})(DeviceSpec||(DeviceSpec={}));function extendString(){String.kmwFromCharCode=function(r){var e=[],t;for(t=0;t1114111||Math.floor(n)!==n)throw new RangeError("Invalid code point "+n);n<65536?e.push(n):(n-=65536,e.push((n>>10)+55296),e.push(n%1024+56320))}return String.fromCharCode.apply(void 0,e)},String.prototype.kmwCharCodeAt=function(r){var e=String(this),t=0;if(r<0||r>=e.length)return NaN;for(var n=0;n=55296&&i<=56319&&e.length>t+1){var a=e.charCodeAt(t+1);if(a>=56320&&a<=57343)return(i-55296<<10)+(a-56320)+65536}return i},String.prototype.kmwIndexOf=function(r,e){var t=String(this),n=t.indexOf(r,e);if(n<0)return n;for(var i=0,a=0;a!==null&&ae){var a=r;r=e,e=a}n=t.kmwCodePointToCodeUnit(r),i=t.kmwCodePointToCodeUnit(e)}return(isNaN(n)||n===null)&&(n=0),(isNaN(i)||i===null)&&(i=t.length),t.substring(n,i)},String.prototype.kmwNextChar=function(r){var e=String(this);if(r===null||r<0||r>=e.length-1)return null;var t=e.charCodeAt(r);if(t>=55296&&t<=56319&&e.length>r+1){var n=e.charCodeAt(r+1);if(n>=56320&&n<=57343)return r==e.length-2?null:r+2}return r+1},String.prototype.kmwPrevChar=function(r){var e=String(this);if(r==null||r<=0||r>e.length)return null;var t=e.charCodeAt(r-1);if(t>=56320&&t<=57343&&r>1){var n=e.charCodeAt(r-2);if(n>=55296&&n<=56319)return r-2}return r-1},String.prototype.kmwCodePointToCodeUnit=function(r){if(r===null)return null;var e=String(this),t=0;if(r<0){t=e.length;for(var n=0;n>r;n--)t=e.kmwPrevChar(t);return t}if(r==e.kmwLength())return e.length;for(var n=0;n=0?e.kmwSubstr(r,1):""},String.prototype.kmwBMPNextChar=function(r){var e=String(this);return r<0||r>=e.length-1?null:r+1},String.prototype.kmwBMPPrevChar=function(r){var e=String(this);return r<=0||r>e.length?null:r-1},String.prototype.kmwBMPCodePointToCodeUnit=function(r){return r},String.prototype.kmwBMPCodeUnitToCodePoint=function(r){return r},String.prototype.kmwBMPLength=function(){var r=String(this);return r.length},String.prototype.kmwBMPSubstr=function(r,e){var t=String(this);return r>-1?t.substr(r,e):t.substr(t.length+r,-r)},String.kmwEnableSupplementaryPlane=function(r){var e=String.prototype;String._kmwFromCharCode=r?String.kmwFromCharCode:String.fromCharCode,e._kmwCharAt=r?e.kmwCharAt:e.charAt,e._kmwCharCodeAt=r?e.kmwCharCodeAt:e.charCodeAt,e._kmwIndexOf=r?e.kmwIndexOf:e.indexOf,e._kmwLastIndexOf=r?e.kmwLastIndexOf:e.lastIndexOf,e._kmwSlice=r?e.kmwSlice:e.slice,e._kmwSubstring=r?e.kmwSubstring:e.substring,e._kmwSubstr=r?e.kmwSubstr:e.kmwBMPSubstr,e._kmwLength=r?e.kmwLength:e.kmwBMPLength,e._kmwNextChar=r?e.kmwNextChar:e.kmwBMPNextChar,e._kmwPrevChar=r?e.kmwPrevChar:e.kmwBMPPrevChar,e._kmwCodePointToCodeUnit=r?e.kmwCodePointToCodeUnit:e.kmwBMPCodePointToCodeUnit,e._kmwCodeUnitToCodePoint=r?e.kmwCodeUnitToCodePoint:e.kmwBMPCodeUnitToCodePoint},String._kmwFromCharCode||String.kmwEnableSupplementaryPlane(!1)}v(extendString,"extendString"),extendString();var models_exports={};__export(models_exports,{DummyModel:function(){return dummy_model_default},PriorityQueue:function(){return priority_queue_default},QuoteBehavior:function(){return quote_behavior_default},SENTINEL_CODE_UNIT:function(){return SENTINEL_CODE_UNIT},TrieModel:function(){return trie_model_default},applyTransform:function(){return applyTransform},buildMergedTransform:function(){return buildMergedTransform},defaultApplyCasing:function(){return defaultApplyCasing},getLastPreCaretToken:function(){return getLastPreCaretToken},isHighSurrogate:function(){return isHighSurrogate},isLowSurrogate:function(){return isLowSurrogate},isSentinel:function(){return isSentinel},tokenize:function(){return tokenize},transformToSuggestion:function(){return transformToSuggestion},wordbreak:function(){return wordbreak}}),extendString();var SENTINEL_CODE_UNIT="\\uFDD0";function applyTransform(r,e){var t,n,i=e.left||"",a=i.kmwLength(),u=a=55296&&r<=56319}v(isHighSurrogate,"isHighSurrogate");function isLowSurrogate(r){return typeof r=="string"&&(r=r.charCodeAt(0)),r>=56320&&r<=57343}v(isLowSurrogate,"isLowSurrogate");function isSentinel(r){return r==SENTINEL_CODE_UNIT}v(isSentinel,"isSentinel");function transformToSuggestion(r,e){var t={transform:r,transformId:r.id,displayAs:r.insert};return(e===0||e)&&(t.p=e),t}v(transformToSuggestion,"transformToSuggestion");function defaultApplyCasing(r,e){switch(r){case"lower":return e.toLowerCase();case"upper":return e.toUpperCase();case"initial":var t=1;return e.length>1&&isHighSurrogate(e.charAt(0))&&isLowSurrogate(e.charCodeAt(1))&&(t=2),e.substring(0,t).toUpperCase().concat(e.substring(t))}}v(defaultApplyCasing,"defaultApplyCasing");var PriorityQueue=function(){function r(e,t){t===void 0&&(t=[]),this.comparator=e,this.heap=Array.from(t),this.heapify()}return v(r,"PriorityQueue2"),r.leftChildIndex=function(e){return e*2+1},r.rightChildIndex=function(e){return e*2+2},r.parentIndex=function(e){return Math.floor((e-1)/2)},r.prototype.heapify=function(e,t){if(e==null||t==null){this.heapify(0,this.count-1);return}for(var n=[],i=-1,a=t;a>=e;a--){var u=r.parentIndex(a);this.siftDown(a)&&u0;){var o=n.shift(),u=r.parentIndex(o);this.siftDown(o)&&u>=0&&i!=u&&(n.push(u),i=u)}},Object.defineProperty(r.prototype,"count",{get:function(){return this.heap.length},enumerable:!1,configurable:!0}),r.prototype.peek=function(){return this.heap[0]},r.prototype.enqueue=function(e){var t=this.heap.length;this.heap.push(e);for(var n=r.parentIndex,i=n(t);t!==0&&this.comparator(this.heap[t],this.heap[i])<0;){var a=this.heap[t];this.heap[t]=this.heap[i],this.heap[i]=a,t=i,i=n(t)}},r.prototype.enqueueAll=function(e){if(e.length!=0){var t=this.count;this.heap=this.heap.concat(e);var n=r.parentIndex(t);this.heapify(n>=0?n:0,r.parentIndex(this.count-1))}},r.prototype.dequeue=function(){if(this.count!=0){var e=this.heap[0],t=this.heap.pop();return this.heap.length>0&&(this.heap[0]=t,this.siftDown(0)),e}},r.prototype.siftDown=function(e){var t=r.leftChildIndex(e),n=r.rightChildIndex(e),i=e;if(t0&&(i=t[t.length-1]),t.length>1){var a=t[t.length-2];if(a.end==i.start&&i.text=="\'"){var u={text:a.text+i.text,start:a.start,end:i.end,length:a.length+i.length};t.pop(),t.pop(),t.push(u),i=u}}var o={left:t.map(function(c){return c.text}),right:n.map(function(c){return c.text}),caretSplitsToken:!1};if(t.length>0&&n.length>0){var l=n[0],s=i.end!=e.left.length,f=l.start!=0;if(s||f)return o;r(i.text+l.text).length==1&&(o.caretSplitsToken=!0)}return o}v(tokenize,"tokenize");function getLastPreCaretToken(r,e){var t=tokenize(r,e);return t.left.length>0?t.left.pop():""}v(getLastPreCaretToken,"getLastPreCaretToken");function wordbreak(r,e){return getLastPreCaretToken(r,e)}v(wordbreak,"wordbreak");var obj_exports={};__export(obj_exports,{ascii:function(){return ascii},default:function(){return default_},defaultWordbreaker:function(){return default_},placeholder:function(){return placeholder}});function placeholder(r){var e=0;return r.split(/\\s+/).map(function(t){var n={start:e,end:e+t.length,text:t,length:t.length};return e=n.end,n})}v(placeholder,"placeholder");function ascii(r){for(var e=/[A-Za-z0-9\']+/g,t=[],n;(n=e.exec(r))!==null;)t.push(new RegExpDerivedSpan(n[0],n.index));return t}v(ascii,"ascii");var RegExpDerivedSpan=function(){function r(e,t){this.text=e,this.start=t}return v(r,"RegExpDerivedSpan2"),Object.defineProperty(r.prototype,"length",{get:function(){return this.text.length},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"end",{get:function(){return this.start+this.text.length},enumerable:!1,configurable:!0}),r}(),propertyMap=["Other","LF","Newline","CR","WSegSpace","Double_Quote","Single_Quote","MidNum","MidNumLet","Numeric","MidLetter","ALetter","ExtendNumLet","Format","Extend","Hebrew_Letter","ZWJ","Katakana","Regional_Indicator","sot","eot"],WORD_BREAK_PROPERTY=[[0,0],[10,1],[11,2],[13,3],[14,0],[32,4],[33,0],[34,5],[35,0],[39,6],[40,0],[44,7],[45,0],[46,8],[47,0],[48,9],[58,10],[59,7],[60,0],[65,11],[91,0],[95,12],[96,0],[97,11],[123,0],[133,2],[134,0],[170,11],[171,0],[173,13],[174,0],[181,11],[182,0],[183,10],[184,0],[186,11],[187,0],[192,11],[215,0],[216,11],[247,0],[248,11],[728,0],[734,11],[768,14],[880,11],[885,0],[886,11],[888,0],[890,11],[894,7],[895,11],[896,0],[902,11],[903,10],[904,11],[907,0],[908,11],[909,0],[910,11],[930,0],[931,11],[1014,0],[1015,11],[1154,0],[1155,14],[1162,11],[1328,0],[1329,11],[1367,0],[1369,11],[1373,0],[1374,11],[1375,10],[1376,11],[1417,7],[1418,11],[1419,0],[1425,14],[1470,0],[1471,14],[1472,0],[1473,14],[1475,0],[1476,14],[1478,0],[1479,14],[1480,0],[1488,15],[1515,0],[1519,15],[1523,11],[1524,10],[1525,0],[1536,13],[1542,0],[1548,7],[1550,0],[1552,14],[1563,0],[1564,13],[1565,0],[1568,11],[1611,14],[1632,9],[1642,0],[1643,9],[1644,7],[1645,0],[1646,11],[1648,14],[1649,11],[1748,0],[1749,11],[1750,14],[1757,13],[1758,0],[1759,14],[1765,11],[1767,14],[1769,0],[1770,14],[1774,11],[1776,9],[1786,11],[1789,0],[1791,11],[1792,0],[1807,13],[1808,11],[1809,14],[1810,11],[1840,14],[1867,0],[1869,11],[1958,14],[1969,11],[1970,0],[1984,9],[1994,11],[2027,14],[2036,11],[2038,0],[2040,7],[2041,0],[2042,11],[2043,0],[2045,14],[2046,0],[2048,11],[2070,14],[2074,11],[2075,14],[2084,11],[2085,14],[2088,11],[2089,14],[2094,0],[2112,11],[2137,14],[2140,0],[2144,11],[2155,0],[2208,11],[2229,0],[2230,11],[2248,0],[2259,14],[2274,13],[2275,14],[2308,11],[2362,14],[2365,11],[2366,14],[2384,11],[2385,14],[2392,11],[2402,14],[2404,0],[2406,9],[2416,0],[2417,11],[2433,14],[2436,0],[2437,11],[2445,0],[2447,11],[2449,0],[2451,11],[2473,0],[2474,11],[2481,0],[2482,11],[2483,0],[2486,11],[2490,0],[2492,14],[2493,11],[2494,14],[2501,0],[2503,14],[2505,0],[2507,14],[2510,11],[2511,0],[2519,14],[2520,0],[2524,11],[2526,0],[2527,11],[2530,14],[2532,0],[2534,9],[2544,11],[2546,0],[2556,11],[2557,0],[2558,14],[2559,0],[2561,14],[2564,0],[2565,11],[2571,0],[2575,11],[2577,0],[2579,11],[2601,0],[2602,11],[2609,0],[2610,11],[2612,0],[2613,11],[2615,0],[2616,11],[2618,0],[2620,14],[2621,0],[2622,14],[2627,0],[2631,14],[2633,0],[2635,14],[2638,0],[2641,14],[2642,0],[2649,11],[2653,0],[2654,11],[2655,0],[2662,9],[2672,14],[2674,11],[2677,14],[2678,0],[2689,14],[2692,0],[2693,11],[2702,0],[2703,11],[2706,0],[2707,11],[2729,0],[2730,11],[2737,0],[2738,11],[2740,0],[2741,11],[2746,0],[2748,14],[2749,11],[2750,14],[2758,0],[2759,14],[2762,0],[2763,14],[2766,0],[2768,11],[2769,0],[2784,11],[2786,14],[2788,0],[2790,9],[2800,0],[2809,11],[2810,14],[2816,0],[2817,14],[2820,0],[2821,11],[2829,0],[2831,11],[2833,0],[2835,11],[2857,0],[2858,11],[2865,0],[2866,11],[2868,0],[2869,11],[2874,0],[2876,14],[2877,11],[2878,14],[2885,0],[2887,14],[2889,0],[2891,14],[2894,0],[2901,14],[2904,0],[2908,11],[2910,0],[2911,11],[2914,14],[2916,0],[2918,9],[2928,0],[2929,11],[2930,0],[2946,14],[2947,11],[2948,0],[2949,11],[2955,0],[2958,11],[2961,0],[2962,11],[2966,0],[2969,11],[2971,0],[2972,11],[2973,0],[2974,11],[2976,0],[2979,11],[2981,0],[2984,11],[2987,0],[2990,11],[3002,0],[3006,14],[3011,0],[3014,14],[3017,0],[3018,14],[3022,0],[3024,11],[3025,0],[3031,14],[3032,0],[3046,9],[3056,0],[3072,14],[3077,11],[3085,0],[3086,11],[3089,0],[3090,11],[3113,0],[3114,11],[3130,0],[3133,11],[3134,14],[3141,0],[3142,14],[3145,0],[3146,14],[3150,0],[3157,14],[3159,0],[3160,11],[3163,0],[3168,11],[3170,14],[3172,0],[3174,9],[3184,0],[3200,11],[3201,14],[3204,0],[3205,11],[3213,0],[3214,11],[3217,0],[3218,11],[3241,0],[3242,11],[3252,0],[3253,11],[3258,0],[3260,14],[3261,11],[3262,14],[3269,0],[3270,14],[3273,0],[3274,14],[3278,0],[3285,14],[3287,0],[3294,11],[3295,0],[3296,11],[3298,14],[3300,0],[3302,9],[3312,0],[3313,11],[3315,0],[3328,14],[3332,11],[3341,0],[3342,11],[3345,0],[3346,11],[3387,14],[3389,11],[3390,14],[3397,0],[3398,14],[3401,0],[3402,14],[3406,11],[3407,0],[3412,11],[3415,14],[3416,0],[3423,11],[3426,14],[3428,0],[3430,9],[3440,0],[3450,11],[3456,0],[3457,14],[3460,0],[3461,11],[3479,0],[3482,11],[3506,0],[3507,11],[3516,0],[3517,11],[3518,0],[3520,11],[3527,0],[3530,14],[3531,0],[3535,14],[3541,0],[3542,14],[3543,0],[3544,14],[3552,0],[3558,9],[3568,0],[3570,14],[3572,0],[3633,14],[3634,0],[3636,14],[3643,0],[3655,14],[3663,0],[3664,9],[3674,0],[3761,14],[3762,0],[3764,14],[3773,0],[3784,14],[3790,0],[3792,9],[3802,0],[3840,11],[3841,0],[3864,14],[3866,0],[3872,9],[3882,0],[3893,14],[3894,0],[3895,14],[3896,0],[3897,14],[3898,0],[3902,14],[3904,11],[3912,0],[3913,11],[3949,0],[3953,14],[3973,0],[3974,14],[3976,11],[3981,14],[3992,0],[3993,14],[4029,0],[4038,14],[4039,0],[4139,14],[4159,0],[4160,9],[4170,0],[4182,14],[4186,0],[4190,14],[4193,0],[4194,14],[4197,0],[4199,14],[4206,0],[4209,14],[4213,0],[4226,14],[4238,0],[4239,14],[4240,9],[4250,14],[4254,0],[4256,11],[4294,0],[4295,11],[4296,0],[4301,11],[4302,0],[4304,11],[4347,0],[4348,11],[4681,0],[4682,11],[4686,0],[4688,11],[4695,0],[4696,11],[4697,0],[4698,11],[4702,0],[4704,11],[4745,0],[4746,11],[4750,0],[4752,11],[4785,0],[4786,11],[4790,0],[4792,11],[4799,0],[4800,11],[4801,0],[4802,11],[4806,0],[4808,11],[4823,0],[4824,11],[4881,0],[4882,11],[4886,0],[4888,11],[4955,0],[4957,14],[4960,0],[4992,11],[5008,0],[5024,11],[5110,0],[5112,11],[5118,0],[5121,11],[5741,0],[5743,11],[5760,4],[5761,11],[5787,0],[5792,11],[5867,0],[5870,11],[5881,0],[5888,11],[5901,0],[5902,11],[5906,14],[5909,0],[5920,11],[5938,14],[5941,0],[5952,11],[5970,14],[5972,0],[5984,11],[5997,0],[5998,11],[6001,0],[6002,14],[6004,0],[6068,14],[6100,0],[6109,14],[6110,0],[6112,9],[6122,0],[6155,14],[6158,13],[6159,0],[6160,9],[6170,0],[6176,11],[6265,0],[6272,11],[6277,14],[6279,11],[6313,14],[6314,11],[6315,0],[6320,11],[6390,0],[6400,11],[6431,0],[6432,14],[6444,0],[6448,14],[6460,0],[6470,9],[6480,0],[6608,9],[6618,0],[6656,11],[6679,14],[6684,0],[6741,14],[6751,0],[6752,14],[6781,0],[6783,14],[6784,9],[6794,0],[6800,9],[6810,0],[6832,14],[6849,0],[6912,14],[6917,11],[6964,14],[6981,11],[6988,0],[6992,9],[7002,0],[7019,14],[7028,0],[7040,14],[7043,11],[7073,14],[7086,11],[7088,9],[7098,11],[7142,14],[7156,0],[7168,11],[7204,14],[7224,0],[7232,9],[7242,0],[7245,11],[7248,9],[7258,11],[7294,0],[7296,11],[7305,0],[7312,11],[7355,0],[7357,11],[7360,0],[7376,14],[7379,0],[7380,14],[7401,11],[7405,14],[7406,11],[7412,14],[7413,11],[7415,14],[7418,11],[7419,0],[7424,11],[7616,14],[7674,0],[7675,14],[7680,11],[7958,0],[7960,11],[7966,0],[7968,11],[8006,0],[8008,11],[8014,0],[8016,11],[8024,0],[8025,11],[8026,0],[8027,11],[8028,0],[8029,11],[8030,0],[8031,11],[8062,0],[8064,11],[8117,0],[8118,11],[8125,0],[8126,11],[8127,0],[8130,11],[8133,0],[8134,11],[8141,0],[8144,11],[8148,0],[8150,11],[8156,0],[8160,11],[8173,0],[8178,11],[8181,0],[8182,11],[8189,0],[8192,4],[8199,0],[8200,4],[8203,0],[8204,14],[8205,16],[8206,13],[8208,0],[8216,8],[8218,0],[8228,8],[8229,0],[8231,10],[8232,2],[8234,13],[8239,12],[8240,0],[8255,12],[8257,0],[8260,7],[8261,0],[8276,12],[8277,0],[8287,4],[8288,13],[8293,0],[8294,13],[8304,0],[8305,11],[8306,0],[8319,11],[8320,0],[8336,11],[8349,0],[8400,14],[8433,0],[8450,11],[8451,0],[8455,11],[8456,0],[8458,11],[8468,0],[8469,11],[8470,0],[8473,11],[8478,0],[8484,11],[8485,0],[8486,11],[8487,0],[8488,11],[8489,0],[8490,11],[8494,0],[8495,11],[8506,0],[8508,11],[8512,0],[8517,11],[8522,0],[8526,11],[8527,0],[8544,11],[8585,0],[9398,11],[9450,0],[11264,11],[11311,0],[11312,11],[11359,0],[11360,11],[11493,0],[11499,11],[11503,14],[11506,11],[11508,0],[11520,11],[11558,0],[11559,11],[11560,0],[11565,11],[11566,0],[11568,11],[11624,0],[11631,11],[11632,0],[11647,14],[11648,11],[11671,0],[11680,11],[11687,0],[11688,11],[11695,0],[11696,11],[11703,0],[11704,11],[11711,0],[11712,11],[11719,0],[11720,11],[11727,0],[11728,11],[11735,0],[11736,11],[11743,0],[11744,14],[11776,0],[11823,11],[11824,0],[12288,4],[12289,0],[12293,11],[12294,0],[12330,14],[12336,0],[12337,17],[12342,0],[12347,11],[12349,0],[12441,14],[12443,17],[12445,0],[12448,17],[12539,0],[12540,17],[12544,0],[12549,11],[12592,0],[12593,11],[12687,0],[12704,11],[12736,0],[12784,17],[12800,0],[13008,17],[13055,0],[13056,17],[13144,0],[40960,11],[42125,0],[42192,11],[42238,0],[42240,11],[42509,0],[42512,11],[42528,9],[42538,11],[42540,0],[42560,11],[42607,14],[42611,0],[42612,14],[42622,0],[42623,11],[42654,14],[42656,11],[42736,14],[42738,0],[42760,11],[42944,0],[42946,11],[42955,0],[42997,11],[43010,14],[43011,11],[43014,14],[43015,11],[43019,14],[43020,11],[43043,14],[43048,0],[43052,14],[43053,0],[43072,11],[43124,0],[43136,14],[43138,11],[43188,14],[43206,0],[43216,9],[43226,0],[43232,14],[43250,11],[43256,0],[43259,11],[43260,0],[43261,11],[43263,14],[43264,9],[43274,11],[43302,14],[43310,0],[43312,11],[43335,14],[43348,0],[43360,11],[43389,0],[43392,14],[43396,11],[43443,14],[43457,0],[43471,11],[43472,9],[43482,0],[43493,14],[43494,0],[43504,9],[43514,0],[43520,11],[43561,14],[43575,0],[43584,11],[43587,14],[43588,11],[43596,14],[43598,0],[43600,9],[43610,0],[43643,14],[43646,0],[43696,14],[43697,0],[43698,14],[43701,0],[43703,14],[43705,0],[43710,14],[43712,0],[43713,14],[43714,0],[43744,11],[43755,14],[43760,0],[43762,11],[43765,14],[43767,0],[43777,11],[43783,0],[43785,11],[43791,0],[43793,11],[43799,0],[43808,11],[43815,0],[43816,11],[43823,0],[43824,11],[43882,0],[43888,11],[44003,14],[44011,0],[44012,14],[44014,0],[44016,9],[44026,0],[44032,11],[55204,0],[55216,11],[55239,0],[55243,11],[55292,0],[64256,11],[64263,0],[64275,11],[64280,0],[64285,15],[64286,14],[64287,15],[64297,0],[64298,15],[64311,0],[64312,15],[64317,0],[64318,15],[64319,0],[64320,15],[64322,0],[64323,15],[64325,0],[64326,15],[64336,11],[64434,0],[64467,11],[64830,0],[64848,11],[64912,0],[64914,11],[64968,0],[65008,11],[65020,0],[65024,14],[65040,7],[65041,0],[65043,10],[65044,7],[65045,0],[65056,14],[65072,0],[65075,12],[65077,0],[65101,12],[65104,7],[65105,0],[65106,8],[65107,0],[65108,7],[65109,10],[65110,0],[65136,11],[65141,0],[65142,11],[65277,0],[65279,13],[65280,0],[65287,8],[65288,0],[65292,7],[65293,0],[65294,8],[65295,0],[65296,9],[65306,10],[65307,7],[65308,0],[65313,11],[65339,0],[65343,12],[65344,0],[65345,11],[65371,0],[65382,17],[65438,14],[65440,11],[65471,0],[65474,11],[65480,0],[65482,11],[65488,0],[65490,11],[65496,0],[65498,11],[65501,0],[65529,13],[65532,0],[65536,11],[65548,0],[65549,11],[65575,0],[65576,11],[65595,0],[65596,11],[65598,0],[65599,11],[65614,0],[65616,11],[65630,0],[65664,11],[65787,0],[65856,11],[65909,0],[66045,14],[66046,0],[66176,11],[66205,0],[66208,11],[66257,0],[66272,14],[66273,0],[66304,11],[66336,0],[66349,11],[66379,0],[66384,11],[66422,14],[66427,0],[66432,11],[66462,0],[66464,11],[66500,0],[66504,11],[66512,0],[66513,11],[66518,0],[66560,11],[66718,0],[66720,9],[66730,0],[66736,11],[66772,0],[66776,11],[66812,0],[66816,11],[66856,0],[66864,11],[66916,0],[67072,11],[67383,0],[67392,11],[67414,0],[67424,11],[67432,0],[67584,11],[67590,0],[67592,11],[67593,0],[67594,11],[67638,0],[67639,11],[67641,0],[67644,11],[67645,0],[67647,11],[67670,0],[67680,11],[67703,0],[67712,11],[67743,0],[67808,11],[67827,0],[67828,11],[67830,0],[67840,11],[67862,0],[67872,11],[67898,0],[67968,11],[68024,0],[68030,11],[68032,0],[68096,11],[68097,14],[68100,0],[68101,14],[68103,0],[68108,14],[68112,11],[68116,0],[68117,11],[68120,0],[68121,11],[68150,0],[68152,14],[68155,0],[68159,14],[68160,0],[68192,11],[68221,0],[68224,11],[68253,0],[68288,11],[68296,0],[68297,11],[68325,14],[68327,0],[68352,11],[68406,0],[68416,11],[68438,0],[68448,11],[68467,0],[68480,11],[68498,0],[68608,11],[68681,0],[68736,11],[68787,0],[68800,11],[68851,0],[68864,11],[68900,14],[68904,0],[68912,9],[68922,0],[69248,11],[69290,0],[69291,14],[69293,0],[69296,11],[69298,0],[69376,11],[69405,0],[69415,11],[69416,0],[69424,11],[69446,14],[69457,0],[69552,11],[69573,0],[69600,11],[69623,0],[69632,14],[69635,11],[69688,14],[69703,0],[69734,9],[69744,0],[69759,14],[69763,11],[69808,14],[69819,0],[69821,13],[69822,0],[69837,13],[69838,0],[69840,11],[69865,0],[69872,9],[69882,0],[69888,14],[69891,11],[69927,14],[69941,0],[69942,9],[69952,0],[69956,11],[69957,14],[69959,11],[69960,0],[69968,11],[70003,14],[70004,0],[70006,11],[70007,0],[70016,14],[70019,11],[70067,14],[70081,11],[70085,0],[70089,14],[70093,0],[70094,14],[70096,9],[70106,11],[70107,0],[70108,11],[70109,0],[70144,11],[70162,0],[70163,11],[70188,14],[70200,0],[70206,14],[70207,0],[70272,11],[70279,0],[70280,11],[70281,0],[70282,11],[70286,0],[70287,11],[70302,0],[70303,11],[70313,0],[70320,11],[70367,14],[70379,0],[70384,9],[70394,0],[70400,14],[70404,0],[70405,11],[70413,0],[70415,11],[70417,0],[70419,11],[70441,0],[70442,11],[70449,0],[70450,11],[70452,0],[70453,11],[70458,0],[70459,14],[70461,11],[70462,14],[70469,0],[70471,14],[70473,0],[70475,14],[70478,0],[70480,11],[70481,0],[70487,14],[70488,0],[70493,11],[70498,14],[70500,0],[70502,14],[70509,0],[70512,14],[70517,0],[70656,11],[70709,14],[70727,11],[70731,0],[70736,9],[70746,0],[70750,14],[70751,11],[70754,0],[70784,11],[70832,14],[70852,11],[70854,0],[70855,11],[70856,0],[70864,9],[70874,0],[71040,11],[71087,14],[71094,0],[71096,14],[71105,0],[71128,11],[71132,14],[71134,0],[71168,11],[71216,14],[71233,0],[71236,11],[71237,0],[71248,9],[71258,0],[71296,11],[71339,14],[71352,11],[71353,0],[71360,9],[71370,0],[71453,14],[71468,0],[71472,9],[71482,0],[71680,11],[71724,14],[71739,0],[71840,11],[71904,9],[71914,0],[71935,11],[71943,0],[71945,11],[71946,0],[71948,11],[71956,0],[71957,11],[71959,0],[71960,11],[71984,14],[71990,0],[71991,14],[71993,0],[71995,14],[71999,11],[72e3,14],[72001,11],[72002,14],[72004,0],[72016,9],[72026,0],[72096,11],[72104,0],[72106,11],[72145,14],[72152,0],[72154,14],[72161,11],[72162,0],[72163,11],[72164,14],[72165,0],[72192,11],[72193,14],[72203,11],[72243,14],[72250,11],[72251,14],[72255,0],[72263,14],[72264,0],[72272,11],[72273,14],[72284,11],[72330,14],[72346,0],[72349,11],[72350,0],[72384,11],[72441,0],[72704,11],[72713,0],[72714,11],[72751,14],[72759,0],[72760,14],[72768,11],[72769,0],[72784,9],[72794,0],[72818,11],[72848,0],[72850,14],[72872,0],[72873,14],[72887,0],[72960,11],[72967,0],[72968,11],[72970,0],[72971,11],[73009,14],[73015,0],[73018,14],[73019,0],[73020,14],[73022,0],[73023,14],[73030,11],[73031,14],[73032,0],[73040,9],[73050,0],[73056,11],[73062,0],[73063,11],[73065,0],[73066,11],[73098,14],[73103,0],[73104,14],[73106,0],[73107,14],[73112,11],[73113,0],[73120,9],[73130,0],[73440,11],[73459,14],[73463,0],[73648,11],[73649,0],[73728,11],[74650,0],[74752,11],[74863,0],[74880,11],[75076,0],[77824,11],[78895,0],[78896,13],[78905,0],[82944,11],[83527,0],[92160,11],[92729,0],[92736,11],[92767,0],[92768,9],[92778,0],[92880,11],[92910,0],[92912,14],[92917,0],[92928,11],[92976,14],[92983,0],[92992,11],[92996,0],[93008,9],[93018,0],[93027,11],[93048,0],[93053,11],[93072,0],[93760,11],[93824,0],[93952,11],[94027,0],[94031,14],[94032,11],[94033,14],[94088,0],[94095,14],[94099,11],[94112,0],[94176,11],[94178,0],[94179,11],[94180,14],[94181,0],[94192,14],[94194,0],[110592,17],[110593,0],[110948,17],[110952,0],[113664,11],[113771,0],[113776,11],[113789,0],[113792,11],[113801,0],[113808,11],[113818,0],[113821,14],[113823,0],[113824,13],[113828,0],[119141,14],[119146,0],[119149,14],[119155,13],[119163,14],[119171,0],[119173,14],[119180,0],[119210,14],[119214,0],[119362,14],[119365,0],[119808,11],[119893,0],[119894,11],[119965,0],[119966,11],[119968,0],[119970,11],[119971,0],[119973,11],[119975,0],[119977,11],[119981,0],[119982,11],[119994,0],[119995,11],[119996,0],[119997,11],[120004,0],[120005,11],[120070,0],[120071,11],[120075,0],[120077,11],[120085,0],[120086,11],[120093,0],[120094,11],[120122,0],[120123,11],[120127,0],[120128,11],[120133,0],[120134,11],[120135,0],[120138,11],[120145,0],[120146,11],[120486,0],[120488,11],[120513,0],[120514,11],[120539,0],[120540,11],[120571,0],[120572,11],[120597,0],[120598,11],[120629,0],[120630,11],[120655,0],[120656,11],[120687,0],[120688,11],[120713,0],[120714,11],[120745,0],[120746,11],[120771,0],[120772,11],[120780,0],[120782,9],[120832,0],[121344,14],[121399,0],[121403,14],[121453,0],[121461,14],[121462,0],[121476,14],[121477,0],[121499,14],[121504,0],[121505,14],[121520,0],[122880,14],[122887,0],[122888,14],[122905,0],[122907,14],[122914,0],[122915,14],[122917,0],[122918,14],[122923,0],[123136,11],[123181,0],[123184,14],[123191,11],[123198,0],[123200,9],[123210,0],[123214,11],[123215,0],[123584,11],[123628,14],[123632,9],[123642,0],[124928,11],[125125,0],[125136,14],[125143,0],[125184,11],[125252,14],[125259,11],[125260,0],[125264,9],[125274,0],[126464,11],[126468,0],[126469,11],[126496,0],[126497,11],[126499,0],[126500,11],[126501,0],[126503,11],[126504,0],[126505,11],[126515,0],[126516,11],[126520,0],[126521,11],[126522,0],[126523,11],[126524,0],[126530,11],[126531,0],[126535,11],[126536,0],[126537,11],[126538,0],[126539,11],[126540,0],[126541,11],[126544,0],[126545,11],[126547,0],[126548,11],[126549,0],[126551,11],[126552,0],[126553,11],[126554,0],[126555,11],[126556,0],[126557,11],[126558,0],[126559,11],[126560,0],[126561,11],[126563,0],[126564,11],[126565,0],[126567,11],[126571,0],[126572,11],[126579,0],[126580,11],[126584,0],[126585,11],[126589,0],[126590,11],[126591,0],[126592,11],[126602,0],[126603,11],[126620,0],[126625,11],[126628,0],[126629,11],[126634,0],[126635,11],[126652,0],[127280,11],[127306,0],[127312,11],[127338,0],[127344,11],[127370,0],[127462,18],[127488,0],[127995,14],[128e3,0],[130032,9],[130042,0],[917505,13],[917506,0],[917536,14],[917632,0],[917760,14],[918e3,0]];function default_(r,e){var t=findBoundaries(r,e);if(t.length==0)return[];for(var n=[],i=0;i=this.text.length?20:isStartOfSurrogatePair(this.text[e])?property(this.text[e]+this.text[e+1]):property(this.text[e],this.options)},r.prototype.match=function(e,t,n,i){var a,u,o,l,s=(a=e==null?void 0:e.includes(this.lookbehind))!==null&&a!==void 0?a:!0;return s=s&&((u=t==null?void 0:t.includes(this.left))!==null&&u!==void 0?u:!0),s=s&&((o=n==null?void 0:n.includes(this.right))!==null&&o!==void 0?o:!0),s&&((l=i==null?void 0:i.includes(this.lookahead))!==null&&l!==void 0?l:!0)},r.prototype.propertyMatch=function(e,t,n,i){var a=this,u=v(function(o){return propertyVal(o,a.options)},"propMapper");return this.match(e==null?void 0:e.map(u),t==null?void 0:t.map(u),n==null?void 0:n.map(u),i==null?void 0:i.map(u))},r}();function isNonSpace(r,e){return!Array.from(r).map(function(t){return property(t,e)}).every(function(t){return t===3||t===1||t===2||t===4})}v(isNonSpace,"isNonSpace");function findBoundaries(r,e){var t,n,i;if(r.length===0)return[];e&&!e.rules&&(e.rules=[]);var a=[],u,o=0,l=new BreakerContext(r,e,o),s=0;do{if(u=o,o=C(o),l=l.next(o),l.match(null,[19],null,null)){a.push(u);continue}if(l.match(null,null,[20],null)){a.push(u);break}if(!l.match(null,[3],[1],null)){var f=[2,3,1];if(l.match(null,f,null,null)){a.push(u);continue}if(l.match(null,null,f,null)){a.push(u);continue}if(!l.match(null,[4],[4],null)){for(var c=[13,14,16];l.match(null,null,c,null);)t=(0,build_exports.__read)([o,C(o)],2),u=t[0],o=t[1],l=l.ignoringRight(o);if(l.right===20){a.push(u);break}for(;l.match(null,null,null,c);)o=C(o),l=l.ignoringLookahead(o);var d=[11,15],p=[8,6];if(e!=null&&e.rules){var g=!1;try{for(var w=(n=void 0,(0,build_exports.__values)(e.rules)),b=w.next();!b.done;b=w.next()){var T=b.value;if(g=T.match(l),g){T.breakIfMatch&&a.push(u);break}}}catch(h){n={error:h}}finally{try{b&&!b.done&&(i=w.return)&&i.call(w)}finally{if(n)throw n.error}}if(g)continue}if(!l.match(null,d,d,null)){var S=[10].concat(p);if(!l.match(null,d,S,d)&&!l.match(d,S,d,null)&&!l.match(null,[15],[6],null)&&!l.match(null,[15],[5],[15])&&!l.match([15],[5],[15],null)&&!l.match(null,[9],[9],null)&&!l.match(null,d,[9],null)&&!l.match(null,[9],d,null)){var _=[7].concat(p);if(!l.match([9],_,[9],null)&&!l.match(null,[9],_,[9])&&!l.match(null,[17],[17],null)){var y=[17,9].concat(d);if(!l.match(null,y,[12],null)&&!l.match(null,[12],[12],null)&&!l.match(null,[12],y,null)){if(l.right===18){if(s+=1,s%2==1)continue}else s=0;a.push(u)}}}}}}}while(u=r.length?r.length:isStartOfSurrogatePair(r[h])?h+2:h+1}}v(findBoundaries,"findBoundaries");function isStartOfSurrogatePair(r){var e=r.charCodeAt(0);return e>=55296&&e<=56319}v(isStartOfSurrogatePair,"isStartOfSurrogatePair");function property(r,e){if(e!=null&&e.propertyMapping){var t=e.propertyMapping(r);if(t)return propertyVal(t,e)}var n=r.codePointAt(0);return searchForProperty(n,0,WORD_BREAK_PROPERTY.length-1)}v(property,"property");function propertyVal(r,e){var t,n,i=v(function(u){return u.toLowerCase()==r.toLowerCase()},"matcher"),a=(n=(t=e==null?void 0:e.customProperties)===null||t===void 0?void 0:t.findIndex(i))!==null&&n!==void 0?n:-1;return a!=-1?-a-1:propertyMap.findIndex(i)}v(propertyVal,"propertyVal");function searchForProperty(r,e,t){if(t=u?searchForProperty(r,n+1,t):i[1]}v(searchForProperty,"searchForProperty");var MAX_SUGGESTIONS=12,TrieModel=function(){function r(e,t){t===void 0&&(t={}),this.languageUsesCasing=t.languageUsesCasing,this.applyCasing=t.applyCasing,this._trie=new Trie(e.root,e.totalWeight,t.searchTermToKey||defaultSearchTermToKey),this.breakWords=t.wordBreaker||default_,this.punctuation=t.punctuation}return v(r,"TrieModel2"),r.prototype.configure=function(e){var t;return this.configuration={leftContextCodePoints:e.maxLeftContextCodePoints,rightContextCodePoints:(t=e.maxRightContextCodePoints)!==null&&t!==void 0?t:0}},r.prototype.toKey=function(e){return this._trie.toKey(e)},r.prototype.predict=function(e,t){if(!e.insert&&!t.left&&!t.right&&t.startOfBuffer&&t.endOfBuffer)return u(this._trie.firstN(MAX_SUGGESTIONS).map(function(o){var l=o.text,s=o.p;return{transform:{insert:l,deleteLeft:0},displayAs:l,p:s}}));var n=applyTransform(e,t),i=e.deleteLeft-e.insert.kmwLength(),a=getLastPreCaretToken(this.breakWords,n);return u(this._trie.lookup(a).map(function(o){var l=o.text,s=o.p;return transformToSuggestion({insert:l,deleteLeft:i+a.kmwLength()},s)}));function u(o){var l,s,f=[];try{for(var c=(0,build_exports.__values)(o),d=c.next();!d.done;d=c.next()){var p=d.value;f.push({sample:p,p:p.p})}}catch(g){l={error:g}}finally{try{d&&!d.done&&(s=c.return)&&s.call(c)}finally{if(l)throw l.error}}return f}},Object.defineProperty(r.prototype,"wordbreaker",{get:function(){return this.breakWords},enumerable:!1,configurable:!0}),r.prototype.traverseFromRoot=function(){return new r.Traversal(this._trie.root,"")},r.Traversal=function(){function e(t,n){this.root=t,this.prefix=n}return v(e,"class_1"),e.prototype.children=function(){var t,n,i,a,u,o,l,s,f,c,d,p,g,w,b,T,S,_;return(0,build_exports.__generator)(this,function(y){switch(y.label){case 0:if(t=this.root,t.type!="internal")return[3,9];n=v(function(C){var h,m,O,k,A,I,E,D,F,q,R,U;return(0,build_exports.__generator)(this,function(M){switch(M.label){case 0:if(h=t.children[C],!isHighSurrogate(C))return[3,12];if(h.type!="internal")return[3,9];m=h,O=v(function(N){var j;return(0,build_exports.__generator)(this,function(V){switch(V.label){case 0:return j=i.prefix+C+N,[4,{char:C+N,traversal:function(){return new r.Traversal(m.children[N],j)}}];case 1:return V.sent(),[2]}})},"_loop_3"),M.label=1;case 1:M.trys.push([1,6,7,8]),k=(R=void 0,(0,build_exports.__values)(m.values)),A=k.next(),M.label=2;case 2:return A.done?[3,5]:(I=A.value,[5,O(I)]);case 3:M.sent(),M.label=4;case 4:return A=k.next(),[3,2];case 5:return[3,8];case 6:return E=M.sent(),R={error:E},[3,8];case 7:try{A&&!A.done&&(U=k.return)&&U.call(k)}finally{if(R)throw R.error}return[7];case 8:return[3,11];case 9:return D=h.entries[0].key,C=C+D[i.prefix.length+1],F=i.prefix+C,[4,{char:C,traversal:function(){return new r.Traversal(h,F)}}];case 10:M.sent(),M.label=11;case 11:return[3,16];case 12:return isSentinel(C)?[2,"continue"]:[3,13];case 13:return C?[3,14]:[2,"continue"];case 14:return q=i.prefix+C,[4,{char:C,traversal:function(){return new r.Traversal(h,q)}}];case 15:M.sent(),M.label=16;case 16:return[2]}})},"_loop_1"),i=this,y.label=1;case 1:y.trys.push([1,6,7,8]),a=(0,build_exports.__values)(t.values),u=a.next(),y.label=2;case 2:return u.done?[3,5]:(o=u.value,[5,n(o)]);case 3:y.sent(),y.label=4;case 4:return u=a.next(),[3,2];case 5:return[3,8];case 6:return l=y.sent(),b={error:l},[3,8];case 7:try{u&&!u.done&&(T=a.return)&&T.call(a)}finally{if(b)throw b.error}return[7];case 8:return[2];case 9:s=this.prefix,f=t.entries.filter(function(C){return C.key!=s&&s.length=n)return o}}}catch(b){i={error:b}}finally{try{s&&!s.done&&(a=l.return)&&a.call(l)}finally{if(i)throw i.error}}else{u.enqueue(r);for(var p=void 0,g=v(function(){if(isNode(p))if(p.type==="leaf")u.enqueueAll(p.entries);else{var b=p;u.enqueueAll(p.values.map(function(T){return b.children[T]}))}else if(o.push({text:p.content,p:p.weight/t}),o.length>=n)return{value:o}},"_loop_4");p=u.dequeue();){var w=g();if(typeof w=="object")return w.value}}return o}v(getSortedResults,"getSortedResults");function isNode(r){return"type"in r}v(isNode,"isNode");function defaultSearchTermToKey(r){return r.normalize("NFD").replace(/[\\u0300-\\u036f]/g,"").toLowerCase()}v(defaultSearchTermToKey,"defaultSearchTermToKey"),extendString();var DummyModel=function(){function r(e){e=e||{},this._futureSuggestions=e.futureSuggestions?e.futureSuggestions.slice():[],e.punctuation&&(this.punctuation=e.punctuation)}return v(r,"DummyModel2"),r.prototype.configure=function(e){return this.configuration={leftContextCodePoints:e.maxLeftContextCodePoints,rightContextCodePoints:e.maxRightContextCodePoints},this.configuration},r.prototype.predict=function(e,t,n){var i=v(function(u){var o,l,s=[],f=u.length;try{for(var c=(0,build_exports.__values)(u),d=c.next();!d.done;d=c.next()){var p=d.value;s.push({sample:p,p:1})}}catch(g){o={error:g}}finally{try{d&&!d.done&&(l=c.return)&&l.call(c)}finally{if(o)throw o.error}}return s},"makeUniformDistribution");if(n)return i(n);var a=this._futureSuggestions.shift();return a?i(a):[]},r}(),dummy_model_default=DummyModel,correction_exports={};__export(correction_exports,{ClassicalDistanceCalculation:function(){return ClassicalDistanceCalculation},ContextTracker:function(){return ContextTracker},QUEUE_NODE_COMPARATOR:function(){return QUEUE_NODE_COMPARATOR},SearchNode:function(){return SearchNode},SearchResult:function(){return SearchResult},SearchSpace:function(){return SearchSpace},TrackedContextState:function(){return TrackedContextState},TrackedContextSuggestion:function(){return TrackedContextSuggestion},TrackedContextToken:function(){return TrackedContextToken}});var ClassicalDistanceCalculation=function(){function r(e){if(this.diagonalWidth=2,this.inputSequence=[],this.matchSequence=[],e){var t=e.resolvedDistances.length;this.resolvedDistances=Array(t);for(var n=0;n2*n)&&(i.sparse=!0),i},r.prototype.getCostAt=function(e,t,n){if(n===void 0&&(n=this.diagonalWidth),e<0||t<0)return e==-1&&t>=-1?t+1:t==-1&&e>=-1?e+1:Number.MAX_VALUE;var i=this.getTrueIndex(e,t,n);return i.sparse?Number.MAX_VALUE:this.resolvedDistances[i.row][i.col]},r.prototype.getFinalCost=function(){for(var e=this,t=e.getHeuristicFinalCost();t>e.diagonalWidth;)e=e.increaseMaxDistance(),t=e.getHeuristicFinalCost();return t},r.prototype.getHeuristicFinalCost=function(){return this.getCostAt(this.inputSequence.length-1,this.matchSequence.length-1)},r.prototype.hasFinalCostWithin=function(e){var t=this,n=t.getHeuristicFinalCost(),i=this.diagonalWidth;do{if(n<=e)return!0;if(i=0&&c>=0){var d=1;if(i=["transpose-start"],f!=e-1){var p=e-f-1;i=i.concat(Array(p).fill("transpose-delete")),d+=p}else{var p=t-c-1;i=i.concat(Array(p).fill("transpose-insert")),d+=p}i.push("transpose-end"),this.getCostAt(f-1,c-1)!=n-d&&(i=null),a=[f-1,c-1]}return i||(l==n-1?(i=["substitute"],a=[e-1,t-1]):u==n-1?(i=["insert"],a=[e,t-1]):o==n-1?(i=["delete"],a=[e-1,t]):(i=["match"],a=[e-1,t-1])),a[0]>=0&&a[1]>=0?this.editPath(a[0],a[1]).concat(i):a[0]>-1?Array(a[0]+1).fill("delete").concat(i):a[1]>-1?Array(a[1]+1).fill("insert").concat(i):i},r.getTransposeParent=function(e,t,n){if(t<0||n<0||e.inputSequence[t].key==e.matchSequence[n].key)return[-1,-1];for(var i=-1,a=t-1;a>=0;a--)if(e.inputSequence[a].key==e.matchSequence[n].key){i=a;break}for(var u=-1,a=n-1;a>=0;a--)if(e.matchSequence[a].key==e.inputSequence[t].key){u=a;break}return[i,u]},r.initialCostAt=function(e,t,n,i,a){var u=e.inputSequence[t].key==e.matchSequence[n].key?0:1,o=e.getCostAt(t-1,n-1)+u,l=i||e.getCostAt(t,n-1)+1,s=a||e.getCostAt(t-1,n)+1,f=Number.MAX_VALUE;if(t>0&&n>0){var c=(0,build_exports.__read)(r.getTransposeParent(e,t,n),2),d=c[0],p=c[1];f=e.getCostAt(d-1,p-1)+(t-d-1)+1+(n-p-1)}return Math.min(o,s,l,f)},r.prototype.getSubset=function(e,t){var n=new r(this);if(e>this.inputSequence.length||t>this.matchSequence.length)throw"Invalid dimensions specified for trim operation";n.inputSequence.splice(e),n.matchSequence.splice(t),n.resolvedDistances.splice(e);for(var i=this.getTrueIndex(e-1,t-1,this.diagonalWidth),a=i.col;a<=2*this.diagonalWidth;a++){var u=i.row-(a-i.col);if(u<0)break;if(a<0)n.resolvedDistances[u]=Array(2*n.diagonalWidth+1).fill(Number.MAX_VALUE);else{var o=2*this.diagonalWidth-a,l=n.resolvedDistances[u].splice(0,a+1),s=Array(o).fill(Number.MAX_VALUE);n.resolvedDistances[u]=l.concat(s)}}return n},r.forDiagonalOfAxis=function(e,t,n,i){for(var a=n-t=0){var f=s==0?o+2:Number.MAX_VALUE;l=r.initialCostAt(e,o,s,f,void 0);var c=l;if(s0&&u){var l=i+1;this.propagateUpdateFrom(e,t+1,n,l,a-1)}if(u&&o){var l=i+(e.inputSequence[t+1].key==e.matchSequence[n+1].key?0:1);this.propagateUpdateFrom(e,t+1,n+1,l,a);for(var s=-1,f=t+2;f0&&c>0){var d=i+(s-t-2)+1+(c-n-2);this.propagateUpdateFrom(e,s,c,d,e.diagonalWidth-1+c-s)}}},Object.defineProperty(r.prototype,"mapKey",{get:function(){var e=this.inputSequence.map(function(n){return n.key}).join(""),t=this.matchSequence.map(function(n){return n.key}).join("");return e+SENTINEL_CODE_UNIT+t+SENTINEL_CODE_UNIT+this.diagonalWidth},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"lastInputEntry",{get:function(){return this.inputSequence[this.inputSequence.length-1]},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"lastMatchEntry",{get:function(){return this.matchSequence[this.matchSequence.length-1]},enumerable:!1,configurable:!0}),r.computeDistance=function(e,t,n){n===void 0&&(n=1);var i=new r;n=n||1,i.diagonalWidth=n;for(var a=0;ae?t.p:e}).reduce(function(t,n){return t-Math.log(n)},0),this._inputCost},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"currentCost",{get:function(){return SearchSpace.EDIT_DISTANCE_COST_SCALE*this.knownCost+this.inputSamplingCost},enumerable:!1,configurable:!0}),r.prototype.buildInsertionEdges=function(){var e,t,n=[];try{for(var i=(0,build_exports.__values)(this.currentTraversal.children()),a=i.next();!a.done;a=i.next()){var u=a.value,o=u.traversal(),l={key:u.char,traversal:o},s=this.calculation.addMatchChar(l),f=new r(this);f.calculation=s,f.priorInput=this.priorInput,f.currentTraversal=o,n.push(f)}}catch(c){e={error:c}}finally{try{a&&!a.done&&(t=i.return)&&t.call(i)}finally{if(e)throw e.error}}return n},r.prototype.buildDeletionEdges=function(e){var t,n,i=[];try{for(var a=(0,build_exports.__values)(e),u=a.next();!u.done;u=a.next()){var o=u.value;if(o.p0:!1},r.prototype.handleNextNode=function(){if(!this.hasNextMatchEntry())return{type:"none"};var e=this.selectionQueue.dequeue(),t=e.correctionQueue.dequeue(),n={type:"intermediate",cost:t.currentCost};if(this.processedEdgeSet[t.mapKey])return this.selectionQueue.enqueue(e),n;this.processedEdgeSet[t.mapKey]=!0;var i=!1;if(t.knownCost>2)return n;t.knownCost==2&&(i=!0);for(var a=0,u=0;u<=e.index;u++)a+=this.minInputCost[u];if(t.currentCost>a+2.5*r.EDIT_DISTANCE_COST_SCALE)return n;if(!i){var o=t.buildInsertionEdges();e.correctionQueue.enqueueAll(o)}if(e.index==this.tierOrdering.length-1)return this.completedPaths.push(t),this.selectionQueue.enqueue(e),{type:"complete",cost:t.currentCost,finalNode:t};var l=this.tierOrdering[e.index+1],s=l.index,f=[];i||(f=t.buildDeletionEdges(this.inputSequence[s-1]));var c=t.buildSubstitutionEdges(this.inputSequence[s-1]);return l.correctionQueue.enqueueAll(f.concat(c)),this.selectionQueue=new priority_queue_default(this.QUEUE_SPACE_COMPARATOR,this.tierOrdering),n},r.prototype.getBestMatches=function(e){var t,n,i,a,u,o,l,s,f,c,d,p,g,w,b,T;return(0,build_exports.__generator)(this,function(S){switch(S.label){case 0:if(t=this,n={},e==0?i=1/0:e==null||Number.isNaN(e)?i=r.DEFAULT_ALLOTTED_CORRECTION_TIME_INTERVAL:i=e,a=function(){function _(y,C){this.largestIntervals=[0],this.loopStart=this.start=Date.now(),this.maxExecutionTime=y,this.maxTrueTime=C}return v(_,"ExecutionTimer2"),_.prototype.startLoop=function(){this.loopStart=Date.now()},_.prototype.markIteration=function(){var y=Date.now(),C=y-this.loopStart;this.executionTime+=C,C&&(this.largestIntervals.length>2&&C>this.largestIntervals[0]?this.largestIntervals[0]=C:this.largestIntervals.push(C),this.largestIntervals.sort(),this.updateOutliers())},_.prototype.updateOutliers=function(){this.largestIntervals.length>2&&this.largestIntervals[2]>=2*(this.largestIntervals[0]+this.largestIntervals[1])&&(this.executionTime-=this.largestIntervals[2],this.largestIntervals.pop())},_.prototype.shouldTimeout=function(){var y=Date.now();return y-this.start>this.maxTrueTime?!0:this.executionTime>this.maxExecutionTime},_.prototype.resetOutlierCheck=function(){this.largestIntervals=[]},_}(),u=function(){function _(){this.currentCost=Number.MIN_SAFE_INTEGER,this.entries=[]}return v(_,"BatchingAssistant2"),_.prototype.checkAndAdd=function(y){var C=null;y.currentCost>this.currentCost&&(C=this.tryFinalize(),this.currentCost=y.currentCost);var h=y.calculation.matchSequence.map(function(m){return m.key}).join("");return t.returnedValues[h]||(t.returnedValues[h]=y),n[h]||(this.entries.push(new SearchResult(y)),n[h]=y),C},_.prototype.tryFinalize=function(){var y=null;return this.entries.length>0&&(y=this.entries,this.entries=[]),y},_}(),o=new u,l=new a(i*1.5,i),s=Object.values(this.returnedValues),!(s.length>0))return[3,6];f=new priority_queue_default(QUEUE_NODE_COMPARATOR,s),l.startLoop(),S.label=1;case 1:return f.count>0?(c=f.dequeue(),c.isFullReplacement?[3,1]:(d=o.checkAndAdd(c),l.markIteration(),d?[4,d]:[3,3])):[3,4];case 2:S.sent(),S.label=3;case 3:return[3,1];case 4:return p=o.tryFinalize(),p?[4,p]:[3,6];case 5:S.sent(),S.label=6;case 6:l.resetOutlierCheck(),l.startLoop(),g=!1,S.label=7;case 7:w=void 0;do w=this.handleNextNode(),l.markIteration(),l.shouldTimeout()&&(g=!0);while(!g&&w.type=="intermediate");if(b=void 0,w.type=="none")return[3,10];if(w.type=="complete"){if(w.finalNode.isFullReplacement)return[3,10];b=o.checkAndAdd(w.finalNode)}return b?[4,b]:[3,9];case 8:S.sent(),S.label=9;case 9:if(!g&&this.hasNextMatchEntry())return[3,7];S.label=10;case 10:return T=o.tryFinalize(),T?[4,T]:[3,12];case 11:S.sent(),S.label=12;case 12:return[2,null]}})},r.EDIT_DISTANCE_COST_SCALE=5,r.MIN_KEYSTROKE_PROBABILITY=1e-4,r.DEFAULT_ALLOTTED_CORRECTION_TIME_INTERVAL=33,r}(),TransformUtils=function(){function r(){}return v(r,"TransformUtils2"),r.isWhitespace=function(e){var t=/^[\\u0009\\u000A\\u000D\\u0020\\u00a0\\u1680\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u200b\\u2028\\u2029\\u202f\\u205f\\u3000]+$/i;return e.insert.match(t)!=null},r.isBackspace=function(e){return e.insert==""&&e.deleteLeft>0&&!e.deleteRight},r.isEmpty=function(e){return e.insert==""&&e.deleteLeft==0&&!e.deleteRight},r}(),transformUtils_default=TransformUtils;function textToCharTransforms(r,e){for(var t=[],n=0;n0&&e.transformDistributions.forEach(function(n){return t.searchSpace[0].addInput(n)})},r.prototype.pushWhitespaceToTail=function(e){e===void 0&&(e=null);var t=new TrackedContextToken;t.transformDistributions=e?[e]:[],t.raw=null,this.tokens.push(t)},r.prototype.replaceTailForBackspace=function(e,t){this.tokens.pop();var n=textToCharTransforms(e,t).map(function(a){return[{sample:a,p:1}]}),i=new TrackedContextToken;i.raw=e,i.transformDistributions=n,this.pushTail(i)},r.prototype.updateTail=function(e,t){var n=this.tail;t=t||(t===""?"":n.raw),e&&e.length>0&&(n.transformDistributions.push(e),this.searchSpace&&this.searchSpace.forEach(function(i){return i.addInput(e)})),n.raw=t},r.prototype.toRawTokenization=function(){var e,t,n=[];try{for(var i=(0,build_exports.__values)(this.tokens),a=i.next();!a.done;a=i.next()){var u=a.value;u.currentText!==null&&n.push(u.currentText)}}catch(o){e={error:o}}finally{try{a&&!a.done&&(t=i.return)&&t.call(i)}finally{if(e)throw e.error}}return n},r}(),CircularArray=function(){function r(e){e===void 0&&(e=r.DEFAULT_ARRAY_SIZE),this.currentHead=0,this.currentTail=0,this.circle=Array(e)}return v(r,"CircularArray2"),Object.defineProperty(r.prototype,"count",{get:function(){var e=this.currentHead-this.currentTail;return e<0&&(e=e+this.circle.length),e},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"maxCount",{get:function(){return this.circle.length},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"oldest",{get:function(){if(this.count!=0)return this.item(0)},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"newest",{get:function(){if(this.count!=0)return this.item(this.count-1)},enumerable:!1,configurable:!0}),r.prototype.enqueue=function(e){var t=null,n=(this.currentHead+1)%this.maxCount;return n==this.currentTail&&(t=this.circle[this.currentTail],this.currentTail=(this.currentTail+1)%this.maxCount),this.circle[this.currentHead]=e,this.currentHead=n,t},r.prototype.dequeue=function(){if(this.currentTail==this.currentHead)return null;var e=this.circle[this.currentTail];return this.currentTail=(this.currentTail+1)%this.maxCount,e},r.prototype.popNewest=function(){if(this.currentTail==this.currentHead)return null;var e=this.circle[this.currentHead];return this.currentHead=(this.currentHead-1+this.maxCount)%this.maxCount,e},r.prototype.item=function(e){if(e>=this.count)throw"Invalid array index";var t=(this.currentTail+e)%this.maxCount;return this.circle[t]},r.DEFAULT_ARRAY_SIZE=5,r}(),ContextTracker=function(r){(0,build_exports.__extends)(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return v(e,"ContextTracker2"),e.attemptMatchContext=function(t,n,i){var a=n.toRawTokenization(),u=ClassicalDistanceCalculation.computeDistance(a.map(function(h){return{key:h}}),t.map(function(h){return{key:h}}),1),o=u.editPath(),l=!1,s=!1;if(o.length>1){if(o[0]=="insert"&&!(o[1]=="substitute"&&o.length==2)||o[0].indexOf("transpose")>=0)return null;o[0]=="delete"&&(l=!0)}var f=o.length-1,c=!1;if(o[f]=="delete"||o[0].indexOf("transpose")>=0||(o[f]=="insert"?s=!0:f>0&&o[f-1]=="insert"&&o[f]=="substitute"&&(s=!0,c=!0),f>0&&o[f-1]=="delete"&&o[f]=="substitute"))return null;for(var d=1;d1)if(l&&p.popHead(),s){var _=t[t.length-1],y=new TrackedContextToken;y.raw=_,b||!w?(p.pushWhitespaceToTail(i!=null?i:[]),y.transformDistributions=[]):(p.pushWhitespaceToTail(),y.transformDistributions=i?[i]:[]),p.pushTail(y)}else T?p.replaceTailForBackspace(S,w.id):p.updateTail(w?i:null,S);else if(o[f]=="insert"){var C=new TrackedContextToken;C.raw=t[0],C.transformDistributions=[i],p.pushTail(C)}else T?p.replaceTailForBackspace(S,w.id):p.updateTail(w?i:null,S);return p},e.modelContextState=function(t,n,i){var a=t.map(function(l){var s=new TrackedContextToken;return s.raw=l,s.raw?s.transformDistributions=textToCharTransforms(s.raw).map(function(f){return[{sample:f,p:1}]}):s.transformDistributions=[],s}),u=new TrackedContextState(i);for(a.length>0&&u.pushTail(a.splice(0,1)[0]);a.length>0;)u.pushWhitespaceToTail(),u.pushTail(a.splice(0,1)[0]);if(u.tokens.length==0){var o=new TrackedContextToken;o.raw="",u.pushTail(o)}return u},e.prototype.analyzeState=function(t,n,i){if(!t.traverseFromRoot)throw"This lexical model does not provide adequate data for correction algorithms and context reuse";var a=tokenize(t.wordbreaker||default_,n);if(a.left.length>0)for(var u=this.count-1;u>=0;u--){var o=e.attemptMatchContext(a.left,this.item(u),i);if(o)return o.taggedContext=n,o!=this.item(u)&&this.enqueue(o),o}var l=e.modelContextState(a.left,i,t);return l.taggedContext=n,this.enqueue(l),l},e}(CircularArray),ModelCompositor=function(){function r(e,t){this.SUGGESTION_ID_SEED=0,this.testMode=!1,this.lexicalModel=e,e.traverseFromRoot&&(this.contextTracker=new ContextTracker),this.punctuation=r.determinePunctuationFromModel(e),this.testMode=!!t}return v(r,"ModelCompositor2"),r.prototype.predictFromCorrections=function(e,t){var n,i,a=[],u=v(function(c){var d=o.lexicalModel.predict(c.sample,t),p=d.map(function(g){var w=c.sample,b=c.p;w.id!==void 0&&(g.sample.transformId=w.id);var T={sample:g.sample,p:g.p*b};return T},o);a=a.concat(p)},"_loop_1"),o=this;try{for(var l=(0,build_exports.__values)(e),s=l.next();!s.done;s=l.next()){var f=s.value;u(f)}}catch(c){n={error:c}}finally{try{s&&!s.done&&(i=l.return)&&i.call(l)}finally{if(n)throw n.error}}return a},r.prototype.predict=function(e,t){var n,i,a,u,o=[],l=this.lexicalModel,s=this.punctuation;e instanceof Array?e.length==0&&e.push({sample:{insert:"",deleteLeft:0},p:1}):e=[{sample:e,p:1}];var f=e.sort(function(x,P){return P.p-x.p})[0].sample,c=transformUtils_default.isWhitespace(f),d=transformUtils_default.isBackspace(f),p=applyTransform(f,t),g=this.wordbreak(p),w=null,b=[],T,S=null;if(this.contextTracker){var y=this.contextTracker.analyzeState(this.lexicalModel,t,null);S=this.contextTracker.analyzeState(this.lexicalModel,p,transformUtils_default.isEmpty(f)?null:e);var C=S.searchSpace[0],h=0,m=S.tokens,O=m.length,k=m.length-y.tokens.length;O==0||k>0?(h=0,transformUtils_default.isWhitespace(f)&&(T=f,t=p,y=S)):k<0?h=this.wordbreak(p).kmwLength()+f.deleteLeft:h=this.wordbreak(t).kmwLength();var A=m[m.length-1],I=A.transformDistributions.length<=1,E=void 0,D=this.testMode?0:SearchSpace.DEFAULT_ALLOTTED_CORRECTION_TIME_INTERVAL;try{for(var F=(0,build_exports.__values)(C.getBestMatches(D)),q=F.next();!q.done;q=F.next()){var R=q.value,_=R.map(function(P){var W=P.matchString,H;P.inputSequence.length>0?H=P.inputSequence[P.inputSequence.length-1].sample:H=f;var ie={insert:W,deleteLeft:h,id:f.id},ee=P.totalCost;return I&&(ee*=r.SINGLE_CHAR_KEY_PROB_EXPONENT),{sample:ie,p:Math.exp(-ee)}},this),U=this.predictFromCorrections(_,t);U.length>0&&E===void 0&&(E=-Math.log(_[0].p)),b=b.concat(U);var M=R[0].totalCost;if(M>=E+8)break;if(b.length>=r.MAX_SUGGESTIONS){if(M>=E+4)break;if(b.sort(function(P,W){return W.p-P.p}),b[r.MAX_SUGGESTIONS-1].p>Math.exp(-M))break}}}catch(x){n={error:x}}finally{try{q&&!q.done&&(i=F.return)&&i.call(F)}finally{if(n)throw n.error}}}else{var _=void 0;c?(_=[{sample:f,p:1}],T=f):_=e.map(function(x){var P=x.sample;return transformUtils_default.isWhitespace(P)&&!c||transformUtils_default.isBackspace(P)&&!d?null:x}),_=_.filter(function(x){return!!x}),b=this.predictFromCorrections(_,t)}var N={},j=null;l.languageUsesCasing&&(j=this.detectCurrentCasing(p));var V=this.wordbreak(t);try{for(var G=(0,build_exports.__values)(b),B=G.next();!B.done;B=G.next()){var L=B.value,Q=L.sample.displayAs,X=Q==g;if(this.lexicalModel.languageUsesCasing&&(X=X||Q==this.lexicalModel.applyCasing("lower",g)),X)if(w)w.p&&L.p&&(w.p+=L.p);else{var z=L.sample.transform,Y={insert:g,deleteLeft:z.deleteLeft,deleteRight:z.deleteRight,id:z.id},te=transformToSuggestion(Y,L.p);w=this.toAnnotatedSuggestion(te,"keep",quote_behavior_default.noQuotes),w.matchesModel=!0,w.transformId=L.sample.transformId}else{j&&j!="lower"&&(this.applySuggestionCasing(L.sample,V,j),Q=L.sample.displayAs);var J=N[Q];J?J.p+=L.p:N[Q]=L}}}catch(x){a={error:x}}finally{try{B&&!B.done&&(u=G.return)&&u.call(G)}finally{if(a)throw a.error}}if(!w&&g!=""){var Y=(0,build_exports.__assign)({},f),$=transformToSuggestion(Y,1);$.displayAs=g,w=this.toAnnotatedSuggestion($,"keep"),w.matchesModel=!1}for(var re in N){var ne=N[re];o.push(ne)}o=o.sort(function(x,P){return P.p-x.p});var K=o.splice(0,r.MAX_SUGGESTIONS).map(function(x){return x.sample.p&&(x.sample["lexical-p"]=x.sample.p,x.sample["correction-p"]=x.p/x.sample.p,x.sample.p=x.p),x.sample});w&&(K=[w].concat(K));var Z=this;return K.forEach(function(x){if(!t.right)x.transform.insert+=s.insertAfterWord;else{var P=Z.tokenize(t);P&&P.caretSplitsToken&&(x.transform.insert+=s.insertAfterWord)}if(T){var W=buildMergedTransform(T,x.transform);W.id=x.transformId;var H=x;H.transform=W}x.id=Z.SUGGESTION_ID_SEED,Z.SUGGESTION_ID_SEED++}),S&&(S.tail.replacements=K.map(function(x){return{suggestion:x,tokenWidth:1}})),K},r.prototype.applySuggestionCasing=function(e,t,n){var i=t.kmwLength()-e.transform.deleteLeft;i>0&&(e.transform.deleteLeft+=i,e.transform.insert=t.kmwSubstr(0,i)+e.transform.insert),e.transform.insert=this.lexicalModel.applyCasing(n,e.transform.insert),e.displayAs=this.lexicalModel.applyCasing(n,e.displayAs)},r.prototype.toAnnotatedSuggestion=function(e,t,n){n===void 0&&(n=quote_behavior_default.default);var i=quote_behavior_default,a=i.noQuotes;return(t=="keep"||t=="revert")&&(a=i.useQuotes),{transform:e.transform,transformId:e.transformId,displayAs:i.apply(n,e.displayAs,this.punctuation,a),tag:t,p:e.p}},r.determinePunctuationFromModel=function(e){var t=DEFAULT_PUNCTUATION;if(!e.punctuation)return t;var n=e.punctuation,i=n.insertAfterWord;i!==""&&!i&&(i=t.insertAfterWord);var a=n.quotesForKeepSuggestion;a||(a=t.quotesForKeepSuggestion);var u=n.isRTL;return{insertAfterWord:i,quotesForKeepSuggestion:a,isRTL:u}},r.prototype.acceptSuggestion=function(e,t,n){var i=e.transform,a=t.left.kmwSubstr(-i.deleteLeft,i.deleteLeft),u=i.insert.kmwLength(),o={insert:a,deleteLeft:u},l=t;n&&(o=buildMergedTransform(o,n),l=applyTransform(n,l));var s,f=this.tokenize(l);f?(f.left.length>0?s=f.left[f.left.length-1]:s="",s+=f.caretSplitsToken?f.right[0]:""):s=this.wordbreak(l);var c=transformToSuggestion(o);c.displayAs=s;var d=this.toAnnotatedSuggestion(c,"revert");if(e.transformId!=null&&(d.transformId=-e.transformId),e.id!=null?d.id=-e.id:(d.id=-this.SUGGESTION_ID_SEED,this.SUGGESTION_ID_SEED++),this.contextTracker){var p=this.contextTracker.newest;p||(p=this.contextTracker.analyzeState(this.lexicalModel,t)),p.tail.activeReplacementId=e.id;var g=applyTransform(e.transform,t);this.contextTracker.analyzeState(this.lexicalModel,g)}return d},r.prototype.applyReversion=function(e,t){var n=this,i=v(function(){var s=applyTransform(e.transform,t),f=n.predict({insert:"",deleteLeft:0},s);return f.forEach(function(c){c.transformId=-e.transformId}),f},"fallbackSuggestions");if(!this.contextTracker)return i();for(var a=!1,u=this.contextTracker.count-1;u>=0;u--){var o=this.contextTracker.item(u);if(o.tail.activeReplacementId==-e.id){a=!0;break}}if(!a)return i();for(;this.contextTracker.newest.tail.activeReplacementId!=-e.id;)this.contextTracker.popNewest();this.contextTracker.newest.tail.revert();var l=this.contextTracker.newest.tail.replacements.map(function(s){return s.suggestion});return l.forEach(function(s){s.transformId=-e.transformId}),l},r.prototype.wordbreak=function(e){var t=this.lexicalModel;if(t.wordbreaker||!t.wordbreak){var n=t.wordbreaker||default_;return wordbreak(n,e)}else return t.wordbreak(e)},r.prototype.tokenize=function(e){var t=this.lexicalModel;return t.wordbreaker?tokenize(t.wordbreaker,e):null},r.prototype.resetContext=function(e){if(this.contextTracker){var t=tokenize(this.lexicalModel.wordbreaker||default_,e),n=ContextTracker.modelContextState(t.left,null,this.lexicalModel);this.contextTracker.enqueue(n)}},r.prototype.detectCurrentCasing=function(e){var t,n=this.lexicalModel,i=this.wordbreak(e);if(!n.languageUsesCasing)throw"Invalid attempt to detect casing: languageUsesCasing is set to false";if(!n.applyCasing)throw"Invalid LMLayer state: languageUsesCasing is set to true, but no applyCasing function exists";return e.casingForm=="upper"||e.casingForm=="initial"?e.casingForm:n.applyCasing("lower",i)==i?"lower":n.applyCasing("upper",i)==i?i.kmwLength()>1?"upper":"initial":n.applyCasing("initial",i)==i?"initial":(t=e.casingForm)!==null&&t!==void 0?t:null},r.MAX_SUGGESTIONS=12,r.SINGLE_CHAR_KEY_PROB_EXPONENT=16,r}(),model_compositor_default=ModelCompositor,DEFAULT_PUNCTUATION={quotesForKeepSuggestion:{open:"\\u201C",close:"\\u201D"},insertAfterWord:" "};extendString();var LMLayerWorker=function(){function r(e){e===void 0&&(e={importScripts:null,postMessage:null}),this._testMode=!1,this._postMessage=e.postMessage||postMessage,this._importScripts=e.importScripts||importScripts,this.setupConfigState()}return v(r,"LMLayerWorker2"),r.prototype.error=function(e,t){this.cast("error",{log:e,error:t&&t.stack?t.stack:void 0})},r.prototype.onMessage=function(e){var t=e.data.message;if(!t)throw new Error("Missing required \'message\' property: ".concat(e.data));var n=e.data;if(n.message=="load"){var i=n,a=!1;if(this._currentModelSource&&i.source.type==this._currentModelSource.type&&(i.source.type=="file"&&i.source.file==this._currentModelSource.file||i.source.type=="raw"&&i.source.code==this._currentModelSource.code)&&(a=!0),a){typeof console!="undefined"&&console.warn("Duplicate model load message detected - squashing!");return}else this._currentModelSource=i.source}else n.message=="unload"&&(this._currentModelSource=null);this.state.handleMessage(n)},r.prototype.cast=function(e,t){var n=this._postMessage;n((0,build_exports.__assign)({message:e},t))},r.prototype.loadModel=function(e){try{var t=e.configure(this._platformCapabilities);t.leftContextCodePoints||(t.leftContextCodePoints=t.leftContextCodeUnits),t.rightContextCodePoints||(t.rightContextCodePoints=t.rightContextCodeUnits),t.leftContextCodePoints||(t.leftContextCodePoints=this._platformCapabilities.maxLeftContextCodePoints),t.rightContextCodePoints||(t.rightContextCodePoints=this._platformCapabilities.maxRightContextCodePoints||0),e.languageUsesCasing&&!e.applyCasing&&(e.applyCasing=defaultApplyCasing);var n=this.transitionToReadyState(e);t.wordbreaksAfterSuggestions===void 0&&(t.wordbreaksAfterSuggestions=n.punctuation.insertAfterWord!=""),this.cast("ready",{configuration:t})}catch(i){this.error("loadModel failed!",i)}},r.prototype.loadModelFile=function(e){try{this._importScripts(e)}catch(t){this.error("Error occurred when attempting to load dictionary",t)}},r.prototype.unloadModel=function(){this.transitionToLoadingState()},r.prototype.setupConfigState=function(){var e=this;this.state={name:"unconfigured",handleMessage:function(t){if(t.message!=="config")throw new Error("invalid message; expected \'config\' but got ".concat(t.message));e._platformCapabilities=t.capabilities,e._testMode=!!t.testMode,e.transitionToLoadingState()}}},r.prototype.transitionToLoadingState=function(){var e=this;this.state={name:"modelless",handleMessage:function(t){if(t.message!=="load")throw new Error("invalid message; expected \'load\' but got ".concat(t.message));if(t.source.type=="file")e.loadModelFile(t.source.file);else{var n=t.source.code,i=new Function("LMLayerWorker","models","correction","wordBreakers",n);i(e,models_exports,correction_exports,obj_exports)}}}},r.prototype.transitionToReadyState=function(e){var t=this,n=new model_compositor_default(e,this._testMode);return this.state={name:"ready",handleMessage:function(i){switch(i.message){case"predict":var a=i.transform,c=i.context,f=n.predict(a,c);t.cast("suggestions",{token:i.token,suggestions:f});break;case"wordbreak":var u=wordbreak(e.wordbreaker||default_,i.context);t.cast("currentword",{token:i.token,word:u});break;case"unload":t.unloadModel();break;case"accept":var o=i.suggestion,c=i.context,l=i.postTransform,s=n.acceptSuggestion(o,c,l);t.cast("postaccept",{token:i.token,reversion:s});break;case"revert":var s=i.reversion,c=i.context,f=n.applyReversion(s,c);t.cast("postrevert",{token:i.token,suggestions:f});break;case"reset-context":var c=i.context;n.resetContext(c);break;default:throw new Error("invalid message; expected one of {\'predict\', \'wordbreak\', \'accept\', \'revert\', \'reset-context\', \'unload\'} but got ".concat(i.message))}},compositor:n},n},r.install=function(e){var t=new r({postMessage:e.postMessage,importScripts:e.importScripts.bind(e)});return e.onmessage=t.onMessage.bind(t),t.self=e,e.LMLayerWorker=t,e.models=models_exports,e.correction=correction_exports,e.wordBreakers=obj_exports,t},r}(),obj_default=LMLayerWorker;typeof self!="undefined"&&"postMessage"in self&&"importScripts"in self?obj_default.install(self):window.LMLayerWorker=obj_default;\n',bc="";var Bl=function(){function i(){}return i.constructInstance=function(){return new Worker(this.asBlobURI(Cc))},i.asBlobURI=function(t){var e=Ft(t);e+="\n"+bc;var n=new Blob([e],{type:"text/javascript"});return URL.createObjectURL(n)},i}(),on=Bl;var hc=function(){function i(t,e){this.suggestions=t,this.transcriptionID=e}return i}();var ol=function(i){(0,d.__extends)(t,i);function t(e,n){n===void 0&&(n=!1);var c=i.call(this)||this;c.recentTranscriptions=[],c._mayPredict=!0,c._mayCorrect=!0,c._state="inactive";var l={maxLeftContextCodePoints:64,maxRightContextCodePoints:n?0:64};return e&&(c.lmEngine=new Bn(l,e)),c}return Object.defineProperty(t.prototype,"activeModel",{get:function(){return this.currentModel},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"isConfigured",{get:function(){return!!this.configuration},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"state",{get:function(){return this._state},enumerable:!1,configurable:!0}),t.prototype.unloadModel=function(){this.lmEngine.unloadModel(),delete this.currentModel,delete this.configuration,this._state="inactive",this.emit("statechange","inactive")},t.prototype.loadModel=function(e){var n=this;if(!e)throw new Error("Null reference not allowed.");var c=e.path?"file":"raw",l=c=="file"?e.path:e.code;return this.currentModel=e,this.mayPredict&&(this._state="active",this.emit("statechange","active")),this.lmEngine.loadModel(l,c).then(function(r){n.configuration=r,n._state="configured",n.emit("statechange","configured")}).catch(function(r){var s;r instanceof Error?s=r.message:s=String(r),console.error("Could not load model '"+e.id+"': "+s),n.currentModel=null,n._state="inactive",n.emit("statechange","inactive")})},t.prototype.invalidateContext=function(e,n){if(this.emit("invalidatesuggestions","context"),!this.currentModel||!this.configuration)return Promise.resolve([]);if(this.isActive){if(e){var c=e.buildTranscriptionFrom(e,null,!1);return this.predict_internal(c,!0,n)}}else return Promise.resolve([])},t.prototype.wordbreak=function(e,n){if(!this.isActive)return null;var c=new ue(H.from(e,!1),this.configuration,n);return this.lmEngine.wordbreak(c)},t.prototype.predict=function(e,n){return!this.isActive||!this.currentModel||!this.configuration?null:(this.emit("invalidatesuggestions","new"),this.predict_internal(e,!1,n))},t.prototype.applySuggestion=function(e,n,c){var l=this;if(!n)throw"Accepting suggestions requires a destination OutputTarget instance.";var r=this.getPredictionState(e.transformId);if(r){var s=H.from(r.preInput,!1);s.apply(e.transform);var B=s.buildTransformFrom(n);n.apply(B),this.emit("suggestionapplied",n);var o=H.from(r.preInput,!1);o.apply(r.transform);var g=new ue(r.preInput,this.configuration,c()),a=this.lmEngine.acceptSuggestion(e,g,r.transform);return a=a.then(function(F){var y={transform:r.transform,transformId:-r.token,displayAs:F.displayAs,id:F.id,tag:F.tag};return l.predictFromTarget(n,c()),y}),a}else return console.warn("Could not apply the Suggestion!"),null},t.prototype.applyReversion=function(e,n){var c=this;if(!n)throw"Accepting suggestions requires a destination OutputTarget instance.";var l=this.getPredictionState(-e.transformId);if(!l){console.warn("Could not apply the Suggestion!");return}var r=H.from(l.preInput,!1);r.apply(e.transform);var s=r.buildTransformFrom(n);n.apply(s);var B=this.lmEngine.revertSuggestion(e,new ue(l.preInput,this.configuration,null));return B.then(function(o){var g=new hc(o,s.id);return c.emit("suggestionsready",g),c.currentPromise=null,o})},t.prototype.predictFromTarget=function(e,n){if(!e)return null;var c=e.buildTranscriptionFrom(e,null,!1);return this.predict(c,n)},t.prototype.predict_internal=function(e,n,c){var l=this;if(!e)return null;var r=new ue(e.preInput,this.configuration,c);this.recordTranscription(e),n&&this.lmEngine.resetContext(r);var s=e.alternates;(!s||s.length==0)&&(s=[{sample:e.transform,p:1}]);var B=e.transform,o=this.currentPromise=this.lmEngine.predict(s,r);return o.then(function(g){if(o==l.currentPromise){var a=new hc(g,B.id);l.emit("suggestionsready",a),l.currentPromise=null}return g})},t.prototype.recordTranscription=function(e){this.recentTranscriptions.push(e),this.recentTranscriptions.length>t.TRANSCRIPTION_BUFFER&&this.recentTranscriptions.splice(0,1)},t.prototype.getPredictionState=function(e){var n=this.recentTranscriptions.filter(function(c){return c.token==e});return n.length==0?null:n[0]},t.prototype.shutdown=function(){this.lmEngine.shutdown()},Object.defineProperty(t.prototype,"isActive",{get:function(){return this.canEnable()?(this.activeModel||!1)&&this._mayPredict:(this._mayPredict=!1,!1)},enumerable:!1,configurable:!0}),t.prototype.canEnable=function(){return!!this.lmEngine},Object.defineProperty(t.prototype,"mayPredict",{get:function(){return this._mayPredict},set:function(e){if(!!this.canEnable()){var n=this._mayPredict;if(this._mayPredict=e,n!=e&&this.activeModel){var c=e?"active":"inactive";this._state=c,this.emit("statechange",c),this.isConfigured&&(this._state="configured",this.emit("statechange","configured"))}}},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"mayCorrect",{get:function(){return this._mayCorrect},set:function(e){this._mayCorrect=e},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"wordbreaksAfterSuggestions",{get:function(){return this.configuration.wordbreaksAfterSuggestions},enumerable:!1,configurable:!0}),t.prototype.tryAcceptSuggestion=function(e){return this.emit("tryaccept",e),!1},t.prototype.tryRevertSuggestion=function(){return this.emit("tryrevert"),!1},t.TRANSCRIPTION_BUFFER=10,t}(Uc.default),an=ol;var al=function(){function i(t,e,n){if(!t)throw new Error("device must be defined");n||(n=i.DEFAULT_OPTIONS),this.contextDevice=t,this.kbdProcessor=new Ot(t,n),this.lngProcessor=new an(e)}return Object.defineProperty(i.prototype,"languageProcessor",{get:function(){return this.lngProcessor},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"keyboardProcessor",{get:function(){return this.kbdProcessor},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"keyboardInterface",{get:function(){return this.keyboardProcessor.keyboardInterface},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"activeKeyboard",{get:function(){return this.keyboardInterface.activeKeyboard},set:function(t){this.keyboardInterface.activeKeyboard=t,this.resetContext()},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"activeModel",{get:function(){return this.languageProcessor.activeModel},enumerable:!1,configurable:!0}),i.prototype.processKeyEvent=function(t,e){var n=t.srcKeyboard&&this.activeKeyboard!=t.srcKeyboard,c=this.activeKeyboard;try{return n&&(this.keyboardInterface.activeKeyboard=t.srcKeyboard),this._processKeyEvent(t,e)}finally{n&&(this.keyboardInterface.activeKeyboard=c)}},i.prototype._processKeyEvent=function(t,e){var n,c=t.device.formFactor,l=t.isSynthetic;if((c==x.FormFactor.Desktop||!this.activeKeyboard||this.activeKeyboard.usesDesktopLayoutOnDevice(t.device))&&l&&this.keyboardProcessor.selectLayer(t))return new le;if(this.keyboardProcessor.doModifierPress(t,e,!l)&&!l)return new le;if(this.languageProcessor.isActive){if((t.kName=="K_BKSP"||t.Lcode==I.keyCodes.K_BKSP)&&this.languageProcessor.tryRevertSuggestion())return new le;if((t.kName=="K_SPACE"||t.Lcode==I.keyCodes.K_SPACE)&&this.languageProcessor.tryAcceptSuggestion("space"))return new le}var r=H.from(e,!0),s=this.keyboardProcessor.layerId,B=this.keyboardProcessor.processKeystroke(t,e);t.kNextLayer&&this.keyboardProcessor.selectLayer(t);var o=I.isKnownOSKModifierKey(t.kName);ot((n=B==null?void 0:B.transcription)===null||n===void 0?void 0:n.transform)&&t.kNextLayer&&(o=!0);var g=B!=null;if(g){var a=o?null:this.buildAlternates(B,t,r);B.finalize(this.keyboardProcessor,e,!1),a&&a.length>0&&(B.transcription.alternates=a)}else B=new le,B.transcription=e.buildTranscriptionFrom(e,null,!1),B.triggersDefaultCommand=!0;var F=B.setStore[f.TSS_LAYER]||t.kNextLayer;this.keyboardProcessor.newLayerStore.set(F?this.keyboardProcessor.layerId:""),this.keyboardProcessor.oldLayerStore.set(F?s:"");var y=this.keyboardProcessor.processPostKeystroke(this.contextDevice,e);return y&&y.finalize(this.keyboardProcessor,e,!0),B.predictionPromise=this.languageProcessor.predict(B.transcription,this.keyboardProcessor.layerId),B.triggersDefaultCommand||e.doInputEvent(),g?B:null},i.prototype.buildAlternates=function(t,e,n){var c;if(this.languageProcessor.isActive&&!t.triggersDefaultCommand){var l=e.keyDistribution,r=new ue(n,ue.ENGINE_RULE_WINDOW,this.keyboardProcessor.layerId),s=r.toMock();if(this.languageProcessor.isActive&&l&&e.kbdLayer){var B=Number.MAX_VALUE,o=Ae(),g=void 0;o.performance&&o.performance.now&&(g=function(){return o.performance.now()},B=g()+16);var a=Math.exp(-5);l.sort(function(L,P){return P.p-L.p});var F=this.activeKeyboard.layout(e.device.formFactor);c=[];for(var y=0,u=0,Q=l;u=B)break;var h=H.from(s,!1),b=F.getLayer(e.kbdLayer).getKey(C.keyId);if(!b){console.warn("Potential fat-finger key could not be found in layer!");continue}var U=this.keyboardProcessor.activeKeyboard.constructKeyEvent(b,e.device,this.keyboardProcessor.stateKeys),V=this.keyboardProcessor.processKeystroke(U,h);if(V&&!V.beep&&C.p>0){var X=V.transcription.transform;X.id=t.transcription.token,c.push({sample:X,p:C.p}),y+=C.p}}c.forEach(function(L){L.p/=y})}}return c},i.prototype.resetContext=function(t){this.keyboardProcessor.resetContext(t),this.languageProcessor.invalidateContext(t,this.keyboardProcessor.layerId)},i.DEFAULT_OPTIONS={baseLayout:"us"},i}(),gn=al;var xc=J(R(),1),gl=function(i){(0,d.__extends)(t,i);function t(e,n){var c=i.call(this)||this;c.initNewContext=!0,c._currentSuggestions=[],c.recentAccept=!1,c.swallowPrediction=!1,c.doRevert=!1,c.recentRevert=!1,c.doTryAccept=function(r){!c.recentAccept&&c.selected?c.accept(c.selected):c.recentAccept&&r=="space"&&(c.recentAccept=!1)},c.doTryRevert=function(){c.doRevert?(c.doRevert=!1,c.recentAccept=!1):c.recentAccept&&(c.showRevert(),c.swallowPrediction=!0)},c.invalidateSuggestions=function(r){c.initNewContext=!1,(!c.swallowPrediction||r=="context")&&(c.recentAccept=!1,c.doRevert=!1,c.recentRevert=!1,r=="context"&&(c.swallowPrediction=!1,c.initNewContext=!0)),r!="new"&&c.clearSuggestions()},c.updateSuggestions=function(r){var s=r.suggestions;c._currentSuggestions=s,c.keepSuggestion=null;for(var B=0,o=s;B0?1:0},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"target",{get:function(){var t;return(t=this.source)===null||t===void 0?void 0:t.target},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"isFromTouch",{get:function(){return!this.isFromMouse},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"isFromMouse",{get:function(){return this.source instanceof MouseEvent},enumerable:!1,configurable:!0}),i}(),K=Il;var Ql=function(i){(0,d.__extends)(t,i);function t(e){var n=i.call(this,e)||this;return n.hasActiveClick=!1,n.ignoreSequence=!1,n._mouseStart=n.onMouseStart.bind(n),n._mouseMove=n.onMouseMove.bind(n),n._mouseEnd=n.onMouseEnd.bind(n),n}return t.prototype.registerEventHandlers=function(){this.config.eventRoot.addEventListener("mousedown",this._mouseStart,!0),this.config.eventRoot.addEventListener("mousemove",this._mouseMove,!1),this.config.eventRoot.addEventListener("mouseup",this._mouseEnd,!0)},t.prototype.unregisterEventHandlers=function(){this.config.eventRoot.removeEventListener("mousedown",this._mouseStart,!0),this.config.eventRoot.removeEventListener("mousemove",this._mouseMove,!1),this.config.eventRoot.removeEventListener("mouseup",this._mouseEnd,!0)},t.prototype.preventPropagation=function(e){e.preventDefault(),e.cancelBubble=!0,e.returnValue=!1,typeof e.stopImmediatePropagation=="function"?e.stopImmediatePropagation():typeof e.stopPropagation=="function"&&e.stopPropagation()},t.prototype.onMouseStart=function(e){if(!this.config.targetRoot.contains(e.target)){this.ignoreSequence=!0;return}this.preventPropagation(e),this.onInputStart(K.fromEvent(e)),this.hasActiveClick=!0},t.prototype.onMouseMove=function(e){if(!this.ignoreSequence){var n=K.fromEvent(e);if(e.buttons){if(!this.hasActiveClick)return}else{this.hasActiveClick&&(this.hasActiveClick=!1,this.onInputMoveCancel(n));return}this.preventPropagation(e),this.config.coordConstrainedWithinInteractiveBounds(n)?this.onInputMove(n):this.onInputMoveCancel(n)}},t.prototype.onMouseEnd=function(e){if(this.ignoreSequence){this.ignoreSequence=!1;return}e.buttons||(this.hasActiveClick=!1),this.onInputEnd(K.fromEvent(e))},t}(It),Qt=Ql;var Cl=function(i){(0,d.__extends)(t,i);function t(e){var n=i.call(this,e)||this;return n._touchStart=n.onTouchStart.bind(n),n._touchMove=n.onTouchMove.bind(n),n._touchEnd=n.onTouchEnd.bind(n),n}return t.prototype.registerEventHandlers=function(){this.config.eventRoot.addEventListener("touchstart",this._touchStart,!0),this.config.eventRoot.addEventListener("touchmove",this._touchMove,!1),this.config.eventRoot.addEventListener("touchend",this._touchEnd,!0)},t.prototype.unregisterEventHandlers=function(){this.config.eventRoot.removeEventListener("touchstart",this._touchStart,!0),this.config.eventRoot.removeEventListener("touchmove",this._touchMove,!1),this.config.eventRoot.removeEventListener("touchend",this._touchEnd,!0)},t.prototype.preventPropagation=function(e){e.preventDefault(),e.cancelBubble=!0,typeof e.stopImmediatePropagation=="function"?e.stopImmediatePropagation():typeof e.stopPropagation=="function"&&e.stopPropagation()},t.prototype.onTouchStart=function(e){this.onInputStart(K.fromEvent(e))},t.prototype.onTouchMove=function(e){this.preventPropagation(e);var n=K.fromEvent(e);this.config.coordConstrainedWithinInteractiveBounds(n)?this.onInputMove(n):this.onInputMoveCancel(n)},t.prototype.onTouchEnd=function(e){this.onInputEnd(K.fromEvent(e))},t}(It),Ct=Cl;var bl=function(){function i(t){this.totalLength=0,this.x=t.x,this.totalLength=0}return i.prototype.updateTo=function(t){var e=this.x;this.x=t.x;var n={deltaX:this.x-e};return this.totalLength+=Math.abs(n.deltaX),n},Object.defineProperty(i.prototype,"hasScrolled",{get:function(){return this.totalLength>i.HAS_SCROLLED_FUDGE_FACTOR},enumerable:!1,configurable:!0}),i.HAS_SCROLLED_FUDGE_FACTOR=10,i}(),hl=function(){function i(t,e,n){this.baseElement=t,this.rowClassMatch=e,this.selectedTargetMatch=n}return i.prototype.findTargetFromTouch=function(t,e,n){for(var c=t.x;e&&e.className!==void 0&&e.className.indexOf(this.rowClassMatch)<0;)e=e.parentNode;if(!e)return null;var l,r=0,s=24,B=1e5,o,g;for(l=0;l=0&&F=0&&y40&&(s=.6*e.offsetWidth),o-c>=0&&o-c=0&&c-g0)return;(e==null||e.id.indexOf("popup")<0)&&(this.pendingTarget&&this.highlight(this.pendingTarget,!1),this.clearHolds(),this.pendingTarget=null)}var n=t.x,c=n<2&&this.touchX>5||n>window.innerWidth-2&&this.touchX0&&this.touchCount--,this.pendingTarget?(this.highlight(this.pendingTarget,!1),this.pendingTarget.className.indexOf("hidden")<0&&l>0&&!c&&this.select(this.pendingTarget),this.clearHolds(),this.pendingTarget=null):(e=this.findBestTarget(t),e&&this.highlight(e,!1))},i.prototype.touchMove=function(t){if(!(t.activeInputCount>1||this.touchCount==0)){if(this.currentTarget&&this.scrollTouchState!=null){var e=this.scrollTouchState.updateTo(t).deltaX;this.currentTarget.scrollLeft-=window.devicePixelRatio*e;return}var n=t.y,c=this.pendingTarget,l=this.findBestTarget(t,!0);if(this.hasModalPopup()){c&&this.highlight(c,!1),this.pendingTarget=null;return}if(l=this.dealiasSubTarget(l),this.currentTarget=l,c&&l&&l!==c&&this.highlight(c,!1),l&&this.hasSubmenu(l)){if(l&&l.id.indexOf("popup")<0)return;l&&l.className.indexOf(this.selectedTargetMatch)<0&&this.highlight(l,!0)}else{var r=this.baseElement,s=Z(r),B=r.offsetHeight,o=Math.max(5,s-.25*B),g=s+B+.25*B;c&&(t.yg)&&(this.highlight(c,!1),this.clearHolds(),this.pendingTarget=null)}l&&this.pendingTarget&&(this.pendingTarget=l),this.pendingTarget&&l&&(c!=l||l.className.indexOf(this.selectedTargetMatch)<0)&&this.highlight(l,!0),c&&l&&l!=c&&l.id!=""&&this.hold(l)}},i}(),Xc=hl;var Ze=function(){function i(t){var e=m("div");e.id=i.BANNER_ID,e.className=i.BANNER_CLASS,this.div=e,this.height=t,this.update()}return Object.defineProperty(i.prototype,"height",{get:function(){return this._height},set:function(t){this._height=t>0?t:0,this.update()},enumerable:!1,configurable:!0}),i.prototype.update=function(){var t=this.div.style,e=t.height,n=t.display;return this._height>0?(t.height=this._height+"px",t.display="block"):(t.height="0px",t.display="none"),e!==t.height||n!==t.display},i.prototype.appendStyleSheet=function(){},i.prototype.getDiv=function(){return this.div},i.prototype.configureForKeyboard=function(t,e){},i.DEFAULT_HEIGHT=37,i.BANNER_CLASS="kmw-banner-bar",i.BANNER_ID="kmw-banner-bar",i}();var bt=function(i){(0,d.__extends)(t,i);function t(){return i.call(this,0)||this}return t}(Ze);var mc=function(i){(0,d.__extends)(t,i);function t(e,n){var c=this;e.length>0?(c=i.call(this)||this,n&&(c.height=n)):c=i.call(this,0)||this,e.indexOf("base64")>=0?console.log("Loading img from base64 data"):console.log("Loading img with src '"+e+"'"),c.img=document.createElement("img"),c.img.setAttribute("src",e);var l=c.img.style;return l.width="100%",l.height="100%",c.getDiv().appendChild(c.img),console.log("Image loaded."),c}return t.prototype.setImagePath=function(e){this.img&&this.img.setAttribute("src",e)},t}(Ze);var Ul=function(){function i(t,e){this.rtl=!1,this.index=t,this.rtl=e,this.constructRoot();var n=this.display=m("span");this.div.appendChild(n)}return i.prototype.constructRoot=function(){var t=this.div=m("div"),e=t.style;t.className="kmw-suggest-option",t.id=i.BASE_ID+this.index;var n=100-me.MARGIN*(me.SUGGESTION_LIMIT-1),c=n/me.SUGGESTION_LIMIT;e.width=c+"%",this.div.suggestion=this},i.prototype.matchKeyboardProperties=function(t){var e=this.div;if(t){t.KLC&&(e.lang=t.KLC);var n=t.KFont;n&&n.family&&n.family!=""&&(e.style.fontFamily=this.fontFamily=n.family)}},Object.defineProperty(i.prototype,"suggestion",{get:function(){return this._suggestion},enumerable:!1,configurable:!0}),i.prototype.update=function(t){this._suggestion=t,this.updateText()},i.prototype.updateText=function(){var t=this.generateSuggestionText(this.rtl);this.div.replaceChild(t,this.display),this.display=t},i.prototype.isEmpty=function(){return!this._suggestion},i.prototype.generateSuggestionText=function(t){var e=this._suggestion,n,c=m("span");if(c.className="kmw-suggestion-text",e==null)return c;if(e.displayAs==null||e.displayAs=="")n="\xA0";else{var l=t?8238:8237;n=String.fromCharCode(l)+e.displayAs}return c.innerHTML=n,c},i.BASE_ID="kmw-suggestion-",i}();var me=function(i){(0,d.__extends)(t,i);function t(e,n){var c=i.call(this,n||Ze.DEFAULT_HEIGHT)||this;return c.currentSuggestions=[],c.options=[],c.onSuggestionUpdate=function(l){c.currentSuggestions=l,c.options.forEach(function(r,s){s0&&this.options.splice(0,this.options.length);for(var n=0;n10)&&(c=0),n.className="kmw-key kmw-key-"+Le[c]},i.prototype.setToggleState=function(t){var e;switch(e=this.spec.sp,Le[e]){case"shift":case"shift-on":t===void 0&&(t=Le[e]=="shift"),this.spec.sp=1+(t?1:0);break;case"special":case"special-on":t===void 0&&(t=Le[e]=="special"),this.spec.sp=3+(t?1:0);break;default:return}this.setButtonClass()},i.prototype.isFrameKey=function(){var t=this.spec.sp||0;switch(Le[t]){case"default":case"deadkey":return!1;default:return!0}},i.prototype.allowsKeyTip=function(){return this.isFrameKey()?!1:!this.btn.classList.contains("kmw-spacebar")},i.prototype.highlight=function(t){var e=this.btn.classList;t?e.contains(i.HIGHLIGHT_CLASS)||e.add(i.HIGHLIGHT_CLASS):e.remove(i.HIGHLIGHT_CLASS)},i.getTextMetrics=function(t,e,n){n={fontFamily:n.fontFamily,fontSize:n.fontSize},n.fontFamily||(n.fontFamily=getComputedStyle(document.body).fontFamily),(!n.fontSize||n.fontSize=="")&&(n.fontSize="1em");var c=n.fontFamily,l=Se(n.fontSize),r;l.absolute?r=l.val+"px":r=l.val*e+"px";var s=i.getTextMetrics.canvas||(i.getTextMetrics.canvas=document.createElement("canvas")),B=s.getContext("2d");B.font=r+" "+c;var o=B.measureText(t);return o},i.prototype.getIdealFontSize=function(t,e,n,c){var l=getComputedStyle(this.btn),r=parseFloat(l.width),s=1,B=Se(n.fontSize||"1em");l.fontSize?c||(n=l):(s=t.getKeyEmFontSize(),r=this.getKeyWidth(t));var o=Se(n.fontSize||"1em"),g=i.getTextMetrics(e,s,n),a=.9,F=.9,y=2,u=2,Q,C;g.fontBoundingBoxAscent&&(Q=g.fontBoundingBoxAscent+g.fontBoundingBoxDescent);var h=Q?Q+u:0;n.height&&n.height.indexOf("px")!=-1&&(C=Number.parseFloat(n.height.substring(0,n.height.indexOf("px"))));var b=r*a/(g.width+y),U=h&&C?C*F/h:void 0,V=b;return U&&Ul&&er&&n90)&&(e=0)}var n=document.createElement("div");return n.className="kmw-key-label",e>0&&(n.innerText=String.fromCharCode(e)),n},t.prototype.processSubkeys=function(e,n){var c,l=e.subKeys=this.spec.sk;for(c=0;c0)return this.keys[0].displaysKeyCap},set:function(t){for(var e=0,n=this.keys;e4&&t.device.formFactor=="phone"&&(c.className=c.className+" kmw-5rows"),l.fontFamily="font"in e?e.font:"",this.nextlayer=c.layer=n.id,typeof n.nextlayer=="string"&&(c.nextLayer=this.nextlayer=n.nextlayer);var s=n.row;this.rows=[];for(var B=0;B=0&&t.layerGroup.layers.caps&&!e.subKeys&&t.touchCount==1},i.prototype.cleanup=function(){this.timerId&&window.clearTimeout(this.timerId),this.timerId=null},i.prototype.cancel=function(){this._state=te.Cancelled,this.cleanup()},i.prototype.incrementTouch=function(t){var e;return this._state==te.Waiting&&(!((e=t==null?void 0:t.keyId)===null||e===void 0)&&e.includes("K_SHIFT")?++this._touches==this.count&&this.realize():this.cancel()),this._state},i.prototype.realize=function(){if(this._state==te.Waiting){this._state=te.Realized,this.cleanup();var t=$.constructNullKeyEvent(this.vkbd.device);t.kNextLayer=this._destinationLayerId,t.Lstates=I.stateBitmasks.CAPS,t.LmodifierChange=!0,this.vkbd.raiseKeyEvent(t,null)}},i}(),In=fl;var Sl=function(i){(0,d.__extends)(t,i);function t(e,n){if(typeof n!="string"||n=="")throw"The 'layer' parameter for subkey construction must be properly defined.";return i.call(this,e,n)||this}return t.prototype.getId=function(){return"popup-"+this.layer+"-"+this.spec.id},t.prototype.construct=function(e,n,c){var l=this.spec,r=document.createElement("div"),s=e.getDefaultKeyObject(),B=r.style;for(var o in s)typeof l[o]!="string"&&(l[o]=s[o]);r.className="kmw-key-square-ex",c&&(B.marginTop="5px"),typeof l.width!="undefined"?B.width=l.width*n.offsetWidth/100+"px":B.width=n.offsetWidth+"px",B.height=n.offsetHeight+"px";var g=document.createElement("div"),a=this.btn=Vt(g,new xt(this,l.id));this.setButtonClass(),a.id=this.getId();var F=a.style;return F.height=B.height,F.lineHeight=n.style.lineHeight,F.width=B.width,F.position="absolute",a.appendChild(this.label=this.generateKeyText(e)),r.appendChild(a),this.square=r},t.prototype.allowsKeyTip=function(){return!1},t}(Qe),Wc=Sl;var Hl=function(){function i(t,e){var n=this;this.promise=new Promise(function(h){n.resolver=h}),this.vkbd=t,this.baseKey=e,this.currentSelection=e,e.key.highlight(!0);var c=e.subKeys,l=this.element=document.createElement("div"),r;l.id="kmw-popup-keys";var s=l.style;s.fontFamily=t.fontFamily;var B=getComputedStyle(e);s.fontSize=B.fontSize,s.visibility="hidden";var o=c.length,g,a;for(g=Math.min(Math.ceil(o/9),2),a=Math.ceil(o/g),s.width=a*e.offsetWidth+a*5+"px",r=0;r1&&y>0&&(F=!0);var u=e.key.layer;(typeof u!="string"||u=="")&&(u=t.layerId);var Q=new Wc(c[r],u),C=Q.construct(t,e,F);l.appendChild(C)}this.shim=document.createElement("div"),this.shim.id="kmw-popup-shim",t.device.formFactor==x.FormFactor.Phone&&this.selectDefaultSubkey(t,e,l)}return i.prototype.finalize=function(t){if(this.resolver){var e=null;this.currentSelection&&(e=this.vkbd.initKeyEvent(this.currentSelection,t),this.currentSelection.key.highlight(!1)),this.resolver(e)}this.resolver=null},i.prototype.reposition=function(t){var e=this.element,n=this.baseKey,c=t.topContainer,l=n.key.row.element,r=e.style,s=n.offsetLeft+n.offsetParent.offsetLeft+.5*(n.offsetWidth-e.offsetWidth),B=t.width-e.offsetWidth;s>B&&(s=B),s<0&&(s=0),r.left=s+"px";var o=c.getBoundingClientRect(),g=l.getBoundingClientRect();r.top=g.top-o.top-e.offsetHeight-3+"px",r.visibility="visible";var a=t.isEmbedded,F=getComputedStyle(e),y=parseFloat(F.top),u=0,Q=0;y0){var l=document.createElement("div"),r=l.style;l.id="kmw-popup-callout",n.appendChild(l);var s=t.getBoundingClientRect(),B=n.getBoundingClientRect(),o=Math.floor(s.top-B.top-9+e);return r.top=o+"px",r.left=s.left-B.left+"px",r.width=s.width+"px",r.height=s.bottom-B.top-o-1+"px",l}else return null},i.prototype.selectDefaultSubkey=function(t,e,n){for(var c,l=e.subKeys,r=0;rwindow.innerWidth-B-p?(this.cap.style.left=b-B-1+"px",s-=p-1):this.cap.style.left=p+"px",F.left=s-p+"px";var E=getComputedStyle(this.element),oe=u.height,S=parseFloat(E.bottom),Ue=parseFloat(E.height),k=Math.ceil(U/2);if(this.cap.style.width=B+"px",this.tip.style.height=k+"px",this.cap.style.top=k-3+"px",this.cap.style.height=Q.bottom-u.top-Math.floor(C-U)-k+"px",this.constrain&&Ue+S>oe){var j=Ue+S-oe;F.height=U-j+"px";var ae=Math.max(0,U-j-U/2+2);this.cap.style.height=ae+"px"}F.display="block"}else this.element.style.display="none";this.key=t,this.state=e},i}(),Nc=Nl;function He(){try{if(this.device.formFactor=="desktop")return 1;var i=document.documentElement.clientWidth;if(screen.width>i)return 1;var t=screen.width;return D()?screen.widthscreen.height&&(t=screen.height),Math.round(100*t/i)/100}catch(e){return 1}}var Tl=function(i){(0,d.__extends)(t,i);function t(e){var n=this,c,l;n=i.call(this)||this,n._layerId="default",n.layerIndex=0,n.isStatic=!1,n._fixedWidthScaling=!1,n._fixedHeightScaling=!0,n.stateKeys={K_CAPS:!1,K_NUMLOCK:!1,K_SCROLL:!1},n.repeatDelete=function(){this.deleting&&(this.modelKeyClick(this.deleteKey),this.deleting=window.setTimeout(this.repeatDelete,100))}.bind(n),n.config=e,n.config.device=e.device||e.hostDevice,n.config.isEmbedded=e.isEmbedded||!1,e.isStatic&&(n.isStatic=e.isStatic),n._fixedWidthScaling=n.device.touchable&&!n.isStatic,n._fixedHeightScaling=n.device.touchable&&!n.isStatic;var r=document.createElement("div");n.config.styleSheetManager=e.styleSheetManager||new Fe(r);var s;if(e.keyboard)s=n.kbdLayout=e.keyboard.layout(e.device.formFactor),n.layoutKeyboardProperties=e.keyboardMetadata,n.isRTL=e.keyboard.isRTL;else{var B=ee.buildDefaultLayout(null,null,e.device.formFactor);s=n.kbdLayout=st.polyfill(B,null,e.device.formFactor),n.layoutKeyboardProperties=null,n.isRTL=!1}"font"in s?n.fontFamily=s.font:n.fontFamily="";var o=e.device.formFactor;n.layoutKeyboard=e.keyboard,n.layoutKeyboard||(n.layoutKeyboard=new w(null)),n.layerGroup=new Hc(n,n.layoutKeyboard,o),n.layoutKeyboard.markLayoutCalibrated(o),r.appendChild(n.layerGroup.element),n.kbdDiv=r,n.isStatic||(n.hostDevice.touchable?n.inputEngine=n.touchInputConfiguration:n.inputEngine=n.mouseInputConfiguration,n.inputEngine.registerEventHandlers()),r.classList.add(e.device.formFactor,"kmw-osk-inner-frame");var g=(l=(c=n.layoutKeyboard)===null||c===void 0?void 0:c.id.replace("Keyboard_",""))!==null&&l!==void 0?l:"",a=g.indexOf("::");a!=-1&&(g=g.substring(a+2));var F="kmw-keyboard-"+g;return n.element.classList.add(F),n}return Object.defineProperty(t.prototype,"layerId",{get:function(){return this._layerId},set:function(e){var n=e!=this._layerId;if(this.layerGroup.layers[e])this._layerId=e;else throw new Error("Keyboard ".concat(this.layoutKeyboard.id," does not have a layer with id ").concat(e));n&&(this.updateState(),this.refreshLayout())},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"currentLayer",{get:function(){var e;return this.layerId?(e=this.layerGroup)===null||e===void 0?void 0:e.layers[this.layerId]:null},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"lgKey",{get:function(){var e,n;return(n=(e=this.currentLayer)===null||e===void 0?void 0:e.globeKey)===null||n===void 0?void 0:n.btn},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"hkKey",{get:function(){var e,n;return(n=(e=this.currentLayer)===null||e===void 0?void 0:e.hideKey)===null||n===void 0?void 0:n.btn},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"spaceBar",{get:function(){var e,n;return(n=(e=this.currentLayer)===null||e===void 0?void 0:e.spaceBarKey)===null||n===void 0?void 0:n.btn},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"mouseInputConfiguration",{get:function(){var e={targetRoot:this.element,eventRoot:document.body,inputStartHandler:this.touch.bind(this),inputMoveHandler:this.moveOver.bind(this),inputMoveCancelHandler:this.moveCancel.bind(this),inputEndHandler:this.release.bind(this),coordConstrainedWithinInteractiveBounds:this.detectWithinInteractiveBounds.bind(this)};return new Qt(e)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"touchInputConfiguration",{get:function(){var e={targetRoot:this.element,eventRoot:this.element,inputStartHandler:this.touch.bind(this),inputMoveHandler:this.moveOver.bind(this),inputMoveCancelHandler:this.moveCancel.bind(this),inputEndHandler:this.release.bind(this),coordConstrainedWithinInteractiveBounds:this.detectWithinInteractiveBounds.bind(this)};return new Ct(e)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"element",{get:function(){return this.kbdDiv},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"device",{get:function(){return this.config.device},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"hostDevice",{get:function(){return this.config.hostDevice},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"fontRootPath",{get:function(){return this.config.pathConfig.fonts},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"styleSheetManager",{get:function(){return this.config.styleSheetManager},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"topContainer",{get:function(){return this.config.topContainer},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"isEmbedded",{get:function(){return this.config.isEmbedded},enumerable:!1,configurable:!0}),t.prototype.postInsert=function(){},Object.defineProperty(t.prototype,"width",{get:function(){return this._width},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"height",{get:function(){return this._height},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"layoutWidth",{get:function(){if(this.usesFixedWidthScaling){var e=this.width,n=getComputedStyle(this.element);if(n.border){var c=new G(n.borderWidth).val;e-=c*2}return G.inPixels(e)}else return G.forScalar(1)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"layoutHeight",{get:function(){if(this.usesFixedHeightScaling){var e=this.height,n=getComputedStyle(this.element);if(n.border){var c=new G(n.borderWidth).val;e-=c*2}return G.inPixels(e)}else return G.forScalar(1)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"internalHeight",{get:function(){return this.usesFixedHeightScaling?G.inPixels(this.layoutHeight.val-this.getVerticalLayerGroupPadding()):G.forScalar(1)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"fontSize",{get:function(){return this._fontSize||(this._fontSize=new G("1em")),this._fontSize},set:function(e){this._fontSize=e,this.kbdDiv.style.fontSize=e.styleString},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"usesFixedWidthScaling",{get:function(){return this._fixedWidthScaling},set:function(e){this._fixedWidthScaling=e},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"usesFixedHeightScaling",{get:function(){return this._fixedHeightScaling},set:function(e){this._fixedHeightScaling=e},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"usesFixedPositioning",{get:function(){for(var e=this.element;e;){if(getComputedStyle(e).position=="fixed")return!0;e=e.offsetParent}return!1},enumerable:!1,configurable:!0}),t.prototype.setSize=function(e,n,c){this._width=e,this._height=n,this.kbdDiv&&(this.kbdDiv.style.width=e?this._width+"px":"",this.kbdDiv.style.height=n?this._height+"px":"",!this.device.touchable&&n&&(this.fontSize=new G(this._height/8+"px")),c||this.refreshLayout())},t.prototype.getDefaultKeyObject=function(){return new Rc(void 0,"",M.DEFAULT_KEY.width,M.DEFAULT_KEY.sp,null,M.DEFAULT_KEY.pad)},t.prototype.getTouchCoordinatesOnKeyboard=function(e){var n={x:v(this.kbdDiv),y:Z(this.kbdDiv)},c={x:e.x-n.x,y:e.y-n.y};return c.x/=this.layerGroup.element.offsetWidth,c.y/=this.kbdDiv.offsetHeight,c},t.prototype.getTouchProbabilities=function(e,n){var c=this.getTouchCoordinatesOnKeyboard(e),l=this.layerGroup.element,r=l.offsetWidth,s=this.kbdDiv.offsetHeight;if(!r||!s)return null;var B=l.offsetWidth/this.kbdDiv.offsetHeight,o=this.kbdLayout.getLayer(this.layerId).getTouchProbabilities(c,B);if(!n||!this.subkeyGesture||!this.subkeyGesture.baseKey.key)return o;var g=1,a=1,F=this.subkeyGesture.baseKey.key.spec.coreID,y=0,u=null;y=3,u=n.coreID,u==F?(a+=y,y=0):u="".concat(F,"::").concat(u);for(var Q=g+a+y,C=1/Q,h=0;h0&&U.push({keyId:u,p:y*C}),U.push({keyId:F,p:a*C}),U=U.concat(o),U},t.prototype.getInteractiveBoundingRect=function(){var e=v(this.element),n=Z(this.element),c=this.currentLayer.rows.length,l=.333*this.height/c,r={left:e-l,right:e+this.width+l,top:n-l,bottom:n+this.height+l};return r},t.prototype.applyScreenMarginBoundsThresholding=function(e,n){var c=window.screenLeft-window.pageXOffset,l=window.screenTop-window.pageYOffset,r=(0,d.__assign)({},e),s=new K(n.x+c,n.y+l);return s.x>=5&&e.left+c<=2&&(r.left=2-c),s.x<=screen.width-5&&e.right+c>=screen.width-2&&(r.right=screen.width-2-c),s.y>=5&&e.top+l<=2&&(r.top=2-l),s.y<=screen.height-5&&e.bottom+l>=screen.height-2&&(r.bottom=screen.height-2-l),r},t.prototype.detectWithinInteractiveBounds=function(e){if(e.x===null&&e.y===null)return!0;var n=this.getInteractiveBoundingRect(),c=n;return this.initTouchCoord&&this.applyScreenMarginBoundsThresholding(n,this.initTouchCoord),e.xc.right?!1:!(e.yc.bottom)},t.prototype.touch=function(e){var n=e.target,c=this.keyTarget(n);if(this.initTouchCoord=e,this.currentTarget=c,this.cancelDelete(),!(this.subkeyGesture&&this.subkeyGesture.isVisible())&&(this.touchCount=e.activeInputCount,(c&&(c.className.indexOf("key-hidden")>=0||c.className.indexOf("key-blank")>=0)||n.className.indexOf("kmw-key-row")>=0)&&(e.isFromMouse||(c=this.findNearestKey(e,n))),c!=null)){var l=c.keyId;if(this.highlightKey(c,!0),l=="K_LOPT"||l=="K_ROPT")window.setTimeout(function(){this.modelKeyClick(c),this.highlightKey(c,!0)}.bind(this),0),this.keyPending=null,this.touchPending=null;else if(l=="K_BKSP")this.modelKeyClick(c,e),this.deleteKey=c,this.deleting=window.setTimeout(this.repeatDelete,500),this.keyPending=null,this.touchPending=null;else{if(this.keyPending){if(this.highlightKey(this.keyPending,!1),this.subkeyGesture&&this.subkeyGesture instanceof je){var r=this.subkeyGesture;r.updateTouch(e),r.finalize(e)}else this.modelKeyClick(this.keyPending,this.touchPending);this.touchCount--}else this.initGestures(c,e);this.keyPending=c,this.touchPending=e}}},t.prototype.release=function(e){var n=this.currentTarget;if(this.cancelDelete(),this.pendingMultiTap&&this.pendingMultiTap.realized){this.pendingMultiTap=null,this.highlightKey(this.keyPending,!1),this.keyPending=null,this.touchPending=null;return}if(this.pendingMultiTap&&this.pendingMultiTap.cancelled&&(this.pendingMultiTap=null),this.subkeyGesture&&this.subkeyGesture.isVisible()){if(e.activeInputCount>0)return;if(this.subkeyGesture instanceof je){var c=this.subkeyGesture;c.finalize(e)}this.highlightKey(this.keyPending,!1),this.keyPending=null,this.touchPending=null;return}if(n&&n.id&&this.optionKey(n,n.id,!1),!this.detectWithinInteractiveBounds(e)){this.moveCancel(e),this.touchCount--;return}var l=this.touchCount;if(this.touchCount>0&&this.touchCount--,this.keyPending)this.highlightKey(this.keyPending,!1),this.keyPending.className.indexOf("hidden")<0&&l>0&&this.modelKeyClick(this.keyPending,e),this.clearPopup(),this.keyPending=null,this.touchPending=null;else{var r=e;if(n=this.keyTarget(r.target),!n){var s=document.elementFromPoint(e.x-window.pageXOffset,e.y-window.pageYOffset);n=this.findNearestKey(e,s)}this.highlightKey(n,!1)}},t.prototype.moveCancel=function(e){e.activeInputCount>1||this.updateGestures(null,this.keyPending,e)||(this.cancelDelete(),this.highlightKey(this.keyPending,!1),this.showKeyTip(null,!1),this.clearPopup(),this.keyPending=null,this.touchPending=null)},t.prototype.moveOver=function(e){if(this.touchCount==0){this.cancelDelete();return}var n=e.x-window.pageXOffset,c=e.y-window.pageYOffset;this.touchPending=e;var l=document.elementFromPoint(n,c),r=this.keyPending,s=this.keyTarget(l);(s&&s.className.indexOf("key-hidden")>=0||l&&!s&&l.className.indexOf("key-row")>=0)&&(s=this.findNearestKey(e,l)),s&&typeof s.id=="string"&&s.id.indexOf("-K_BKSP")<0&&this.cancelDelete(),!(e.activeInputCount>1)&&(this.updateGestures(s,r,e)||(this.currentTarget=s,s&&this.keyPending&&(this.highlightKey(r,!1),this.keyPending=s,this.touchPending=e),r&&s&&s!=r&&s.id!=""&&(this.clearPopup(),this.initGestures(s,e)),this.keyPending&&(r!=s||s.className.indexOf(Qe.HIGHLIGHT_CLASS)<0)&&this.highlightKey(s,!0)))},t.prototype.keyTarget=function(e){var n=e;try{if(n){if(n.classList.contains("kmw-key"))return Gt(n);if(n.parentNode&&n.parentNode.classList.contains("kmw-key"))return Gt(n.parentNode);if(n.firstChild&&n.firstChild.classList.contains("kmw-key"))return Gt(n.firstChild)}}catch(c){}return null},t.prototype.findNearestKey=function(e,n){if(!e)return null;for(var c=e.x;n&&n.className!==void 0&&n.className.indexOf("key-row")<0;)n=n.parentNode;if(!n)return null;var l,r=0,s,B=24,o=1e5,g,a;for(l=0;l=0||y.className.indexOf("key-blank")>=0))){if(g=F.offsetLeft,a=g+F.offsetWidth,c>=g&&c<=a)return y;s=g-c,s>=0&&s=0&&s40&&(B=.6*n.offsetWidth),g-c>=0&&g-c=0&&c-a=0)){var c=this.keytip!=null&&e.key.allowsKeyTip();c?this.showKeyTip(e,n):(this.showKeyTip(null,!1),e.key.highlight(n))}},t.prototype.getKeyEmFontSize=function(){if(!this.fontSize)return 0;if(this.device.formFactor=="desktop"){var e=.8;return this.fontSize.scaledBy(e).val}else{var n=getComputedStyle(document.body).fontSize,c=Se(n).val,l=1;if(!this.isStatic){if(this.fontSize.absolute)return this.fontSize.val;l=this.fontSize.val}return c*l}},t.prototype.updateState=function(){if(!!this.currentLayer){var e,n=this.kbdDiv.childNodes[0].childNodes;for(this.nextLayer=this.layerId,this.currentLayer.nextlayer&&(this.nextLayer=this.currentLayer.nextlayer),e=0;ec&&(c=o)}var g=0,a=c+g;return a},t.prototype.appendStyleSheet=function(){var e=this,n=this.layoutKeyboard,c=this.layoutKeyboardProperties;if(c!=null){this.styleSheet&&this.styleSheet.parentNode&&this.styleSheet.parentNode.removeChild(this.styleSheet);var l=c.textFont,r=c.oskFont;this.styleSheetManager.addStyleSheetForFont(l,this.fontRootPath,this.device.OS),this.styleSheetManager.addStyleSheetForFont(r,this.fontRootPath,this.device.OS);var s=this.addFontStyle(l,r);n!=null&&typeof n.oskStyling=="string"&&(s=s+n.oskStyling),this.styleSheet=Ve(s),this.styleSheet.addEventListener("load",function(){e.refreshLayout()}),this.styleSheetManager.linkStylesheet(this.styleSheet)}},t.prototype.addFontStyle=function(e,n){var c="",l=function(r){return r.family.replace(/\u0022/g,"").replace(/,/g,'","')};return(e||n)&&(c='\n.kmw-key-text {\n font-family: "'.concat(l(n||e),'";\n}\n\n.kmw-suggestion-text {\n font-family: "').concat(l(e||n),'";\n}\n')),c},t.buildDocumentationKeyboard=function(e,n,c,l,r,s){if(!e)return null;var B=typeof l=="undefined"?"desktop":l,o=typeof r=="undefined"?"default":r,g={};g.formFactor=B,B!="desktop"?(g.OS=x.OperatingSystem.iOS,g.touchable=!0):(g.OS=x.OperatingSystem.Windows,g.touchable=!1);var a=e.layout(B),F=new x("other",g.formFactor,g.OS,g.touchable),y=new t({keyboard:e,keyboardMetadata:n,hostDevice:F,isStatic:!0,topContainer:null,pathConfig:{fonts:c,resources:""},styleSheetManager:null});y.layerGroup.element.className=y.kbdDiv.className,y.layerGroup.element.classList.add(g.formFactor+"-static");var u=y.kbdDiv.childNodes[0],Q=document.createElement("div");Q.classList.add(g.OS.toLowerCase(),g.formFactor),a!=null?(y.layerId=o,y.setSize(800,s),y.fontSize=vc(F,s,!1),y.refreshLayout(),u.style.fontSize=y.kbdDiv.style.fontSize,u.style.height=y.kbdDiv.style.height,u.style.maxHeight=y.kbdDiv.style.maxHeight):u.innerHTML="

No "+B+" layout is defined for "+e.name+".

",u.style.border="1px solid #ccc";var C=function(){if(document.contains(u))try{getComputedStyle(u)&&u.style.fontSize&&(y.fontSize=new G(u.style.fontSize)),y.appendStyleSheet();var b=y.styleSheet,U=b.parentElement;y.refreshLayout(),u.style.fontSize=y.kbdDiv.style.fontSize,y.shutdown(),u.appendChild(b)}finally{h.disconnect()}},h=new MutationObserver(C);return h.observe(document.body,{childList:!0,subtree:!0}),Q.append(u),Q},t.prototype.onHide=function(){this.hkKey&&this.highlightKey(this.hkKey,!1)},t.prototype.startLongpress=function(e){var n=this;if(this.config.embeddedGestureConfig.startLongpress)return this.config.embeddedGestureConfig.startLongpress(this,e);var c=new Qn(this,e);return c.promise.then(function(l){l&&(n.topContainer.appendChild(l.element),n.topContainer.appendChild(l.shim),l.reposition(n))}),c},t.prototype.initGestures=function(e,n){var c=this;if(this.pendingMultiTap)switch(this.pendingMultiTap.incrementTouch(e)){case te.Cancelled:this.pendingMultiTap=null;break;case te.Realized:return}if(!this.pendingMultiTap&&In.isValidTarget(this,e)&&(this.pendingMultiTap=new In(this,e,2),this.pendingMultiTap.timeout.then(function(){c.pendingMultiTap=null})),e.subKeys){var l=this,r=this.startLongpress(e);if(r==null)return;this.pendingSubkey=r,r.promise.then(function(s){l.pendingSubkey==r&&(l.pendingSubkey=null),s&&(l.showKeyTip(null,!1),l.subkeyGesture=s,s.promise.then(function(B){B&&l.raiseKeyEvent(B,null),l.clearPopup()}))})}},t.prototype.updateGestures=function(e,n,c){var l=n,r=e;return!e&&this.pendingMultiTap&&(this.pendingMultiTap.cancel(),this.pendingMultiTap=null),this.subkeyGesture?(l&&l.key.highlight(!1),this.subkeyGesture.updateTouch(c),this.keyPending=null,this.touchPending=null,!0):(this.currentTarget=null,r&&r.subKeys!=null&&this.initTouchCoord&&this.pendingSubkey&&this.pendingSubkey instanceof Qn&&this.initTouchCoord.y-c.y>this.getLongpressFlickThreshold()&&this.pendingSubkey.resolve(),!!(this.subkeyGesture||this.pendingSubkey))},t.prototype.getLongpressFlickThreshold=function(){var e=this.currentLayer.rowHeight,n=e/4;return Math.max(n,5)},t.prototype.optionKey=function(e,n,c){n.indexOf("K_LOPT")>=0?this.emit("globekey",e,c):n.indexOf("K_ROPT")>=0&&c&&this.emit("hiderequested",e)},t.prototype.showKeyTip=function(e,n){var c=this.keytip;if(c!=null){var l=this.subkeyGesture,r=l&&l.isVisible();n=r?!1:n,c.show(e,n,this)}},t.prototype.createKeyTip=function(){if(this.config.embeddedGestureConfig.createKeyTip)this.keytip=this.config.embeddedGestureConfig.createKeyTip(this);else if(this.device.formFactor=="phone"&&this.keytip==null){var e=this.isEmbedded;this.keytip=new Nc(e)}this.keytip&&this.keytip.element&&this.topContainer.appendChild(this.keytip.element)},t.prototype.createGlobeHint=function(){return this.config.embeddedGestureConfig.createGlobeHint?this.config.embeddedGestureConfig.createGlobeHint(this):null},t.prototype.shutdown=function(){var e,n,c,l;this.styleSheet&&this.styleSheet.parentNode&&this.styleSheet.parentNode.removeChild(this.styleSheet),this.inputEngine&&this.inputEngine.unregisterEventHandlers(),this.deleting&&window.clearTimeout(this.deleting),this.keyPending=null,this.touchPending=null,(e=this.keytip)===null||e===void 0||e.show(null,!1,this),(n=this.subkeyGesture)===null||n===void 0||n.clear(),(c=this.pendingMultiTap)===null||c===void 0||c.cancel(),(l=this.pendingSubkey)===null||l===void 0||l.cancel()},t.prototype.raiseKeyEvent=function(e,n){if(e.kName=="K_LOPT"||e.kName=="K_ROPT")return this.optionKey(n,e.kName,!0),!0;this.emit("keyevent",e)},t.specialCharacters=Qe.specialCharacters,t}(Tc.default),Ce=Tl;var Ec=J(R(),1),kc=function(i){(0,d.__extends)(t,i);function t(){return i!==null&&i.apply(this,arguments)||this}return t}(Ec.default),qe=kc,$e=function(i){(0,d.__extends)(t,i);function t(){return i!==null&&i.apply(this,arguments)||this}return Object.defineProperty(t.prototype,"enabled",{get:function(){return!0},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"activate",{get:function(){return!0},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"conditionsMet",{get:function(){return!0},enumerable:!1,configurable:!0}),t}(kc);var El=function(){function i(){this.map={}}return i.prototype.promiseForTouchpoint=function(t){return this.map[t]||(this.map[t]=new A),this.map[t]},i.prototype.maintainTouches=function(t){for(var e=Object.keys(this.map),n=0;n0&&(g-=this.bannerView.height+5),this.vkbd.setSize(this.computedWidth,g,e);var a=this._Box.style;a.width=a.maxWidth=this.computedWidth+"px",a.height=a.maxHeight=this.computedHeight+"px",this.vkbd.showLanguage()}else{var a=this._Box.style;a.width="auto",a.height="auto",a.maxWidth=a.maxHeight=""}}}},t.prototype.refreshLayoutIfNeeded=function(e){this.needsLayout&&this.refreshLayout(e)},t.prototype.postKeyboardLoad=function(){this._Visible=!1,this.postKeyboardAdjustments(),this.displayIfActive&&this.present()},t.prototype.loadActiveKeyboard=function(){var e,n,c,l;this.setBoxStyling(),this.vkbd&&this.vkbd.shutdown(),this.keyboardView=null,this.needsLayout=!0,this._Box.innerHTML="";var r="osk/";this.config.isEmbedded&&(r="");for(var s=0,B=t.STYLESHEET_FILES;s=0)){if(this._Box){var e=this._Box.style;e.display="none",e.transition="",e.opacity="1",this._Visible=!1}this.vkbd&&this.vkbd.onHide()}},t.prototype.mayShow=function(){return!(!this.activationModel.conditionsMet||!this.keyboardView||this.keyboardView instanceof ht||!this.activationModel.enabled||!this._Box)},t.prototype.mayHide=function(e){return!(this.activationModel.conditionsMet&&!this.mayDisable||this.activationModel instanceof $e||!e&&this.hostDevice.formFactor=="desktop"&&document.body.className.indexOf("osk-always-visible")>=0)},t.prototype.useHideAnimation=function(){var e=this._Box.style,n=this;return new Promise(function(c){var l=function(){return n._Box.removeEventListener("transitionend",l,!1),n._Box.removeEventListener("webkitTransitionEnd",l,!1),n._Box.removeEventListener("transitioncancel",l,!1),n._Box.removeEventListener("webkitTransitionCancel",l,!1),n._animatedHideTimeout!=0&&window.clearTimeout(n._animatedHideTimeout),n._animatedHideTimeout=0,n._Visible&&n.activationModel.conditionsMet?(e.transition="",e.opacity="1",c(!1),!1):(c(!0),!0)},r=function(){n._Box.removeEventListener("transitionrun",r,!1),n._Box.removeEventListener("webkitTransitionRun",r,!1),n._Box.addEventListener("transitionend",l,!1),n._Box.addEventListener("webkitTransitionEnd",l,!1),n._Box.addEventListener("transitioncancel",l,!1),n._Box.addEventListener("webkitTransitionCancel",l,!1)};n._Box.addEventListener("transitionrun",r,!1),n._Box.addEventListener("webkitTransitionRun",r,!1),e.transition="opacity 0.5s linear 0",e.opacity="0",n._animatedHideTimeout=window.setTimeout(l,200)})},t.prototype.hideNow=function(){if(!(!this.mayHide(!1)||!this._Box)){this._animatedHideTimeout&&(window.clearTimeout(this._animatedHideTimeout),this._animatedHideTimeout=0);var e=this._Box.style;e.transition="",e.opacity="0",this.finalizeHide()}},t.prototype.shutdown=function(){this.removeBaseMouseEventListeners(),this.removeBaseTouchEventListeners();var e=this._Box;e.parentElement&&e.parentElement.removeChild(e),this.kbdStyleSheetManager.unlinkAll(),this.uiStyleSheetManager.unlinkAll()},t.prototype.getRect=function(){var e={};return e.left=e.left=v(this._Box),e.top=e.top=Z(this._Box),e.width=this.computedWidth,e.height=this.computedHeight,e},t.prototype.isEnabled=function(){return this.displayIfActive},t.prototype.isVisible=function(){return this._Visible},t.prototype.hide=function(){this.activationModel.enabled=!1,this.startHide(!0)},t.prototype.show=function(e){arguments.length>0?this.activationModel.enabled=e:this.activationModel.conditionsMet&&(this.activationModel.enabled=!this.activationModel.enabled)},t.prototype.doShow=function(e){this.legacyEvents.callEvent("show",e)},t.prototype.doHide=function(e){var n={HiddenByUser:e};this.legacyEvents.callEvent("hide",n)},t.prototype.addEventListener=function(e,n){this.legacyEvents.addEventListener(e,n)},t.prototype.removeEventListener=function(e,n){this.legacyEvents.removeEventListener(e,n)},t.STYLESHEET_FILES=["kmwosk.css","globe-hint.css"],t}(wc.default),Je=kl;var Oc=J(R(),1);var Yl=function(i){(0,d.__extends)(t,i);function t(e){var n=i.call(this)||this;return n.mouseCancellingHandler=function(c){return c.preventDefault(),c.cancelBubble=!0,!1},n._element=n.buildTitleBar(),n.helpEnabled=!1,n.configEnabled=!1,e&&(n.element.onmousedown=e.mouseDownHandler),n}return Object.defineProperty(t.prototype,"helpEnabled",{get:function(){return this._helpEnabled},set:function(e){this._helpEnabled=e,this._helpButton.style.display=e?"inline":"none"},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"configEnabled",{get:function(){return this._configEnabled},set:function(e){this._configEnabled=e,this._configButton.style.display=e?"inline":"none"},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"layoutHeight",{get:function(){return t.DISPLAY_HEIGHT},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"element",{get:function(){return this._element},enumerable:!1,configurable:!0}),t.prototype.setPinCJKOffset=function(){this._unpinButton.style.left="15px"},t.prototype.showPin=function(e){this._unpinButton.style.display=e?"block":"none"},t.prototype.setTitle=function(e){this._caption.innerHTML=e},t.prototype.setTitleFromKeyboard=function(e){var n=""+(e==null?void 0:e.name)+"";this._caption.innerHTML=n},t.prototype.buildTitleBar=function(){var e=this,n=m("div");n.id="keymanweb_title_bar",n.className="kmw-title-bar";var c=this._caption=m("span");c.className="kmw-title-bar-caption",c.style.color="#fff",n.appendChild(c);var l=this._closeButton=this.buildCloseButton();return this._closeButton.onclick=function(){return e.emit("close"),!1},n.appendChild(l),l=this._helpButton=this.buildHelpButton(),this._helpButton.onclick=function(){return e.emit("help"),!1},n.appendChild(l),l=this._configButton=this.buildConfigButton(),this._configButton.onclick=function(){return e.emit("config"),!1},n.appendChild(l),l=this._unpinButton=this.buildUnpinButton(),this._unpinButton.onclick=function(){return e.emit("unpin"),!1},n.appendChild(l),n},t.prototype.buildCloseButton=function(){var e=m("div");return e.id="kmw-close-button",e.className="kmw-title-bar-image",e.onmousedown=this.mouseCancellingHandler,e},t.prototype.buildHelpButton=function(){var e=m("div");return e.id="kmw-help-image",e.className="kmw-title-bar-image",e.title="KeymanWeb Help",e.onmousedown=this.mouseCancellingHandler,e},t.prototype.buildConfigButton=function(){var e=m("div");return e.id="kmw-config-image",e.className="kmw-title-bar-image",e.title="KeymanWeb Configuration Options",e.onmousedown=this.mouseCancellingHandler,e},t.prototype.buildUnpinButton=function(){var e=m("div");return e.id="kmw-pin-image",e.className="kmw-title-bar-image",e.title="Pin the On Screen Keyboard to its default location on the active text box",e.onmousedown=this.mouseCancellingHandler,e},t.prototype.refreshLayout=function(){},t.DISPLAY_HEIGHT=G.inPixels(20),t}(Oc.default),Cn=Yl;var Dc=J(R(),1);var wl=function(i){(0,d.__extends)(t,i);function t(e){var n=i.call(this)||this;return n.mouseCancellingHandler=function(c){return c.preventDefault(),c.cancelBubble=!0,!1},n._element=n.buildResizeBar(),e&&(n._resizeHandle.onmousedown=e.mouseDownHandler),n}return Object.defineProperty(t.prototype,"layoutHeight",{get:function(){return t.DISPLAY_HEIGHT},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"element",{get:function(){return this._element},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"handle",{get:function(){return this._resizeHandle},enumerable:!1,configurable:!0}),t.prototype.allowResizing=function(e){this._resizeHandle.style.display=e?"block":"none"},t.prototype.buildResizeBar=function(){var e=this,n=m("div");n.className="kmw-footer",n.onmousedown=this.mouseCancellingHandler;var c=m("div");c.className="kmw-footer-caption",c.innerHTML='KeymanWeb',c.id="keymanweb-osk-footer-caption",c.addEventListener("dblclick",function(r){return e.emit("showbuild"),!1},!1),n.appendChild(c);var l=m("div");return l.className="kmw-footer-resize",n.appendChild(l),this._resizeHandle=l,n},t.prototype.refreshLayout=function(){},t.DISPLAY_HEIGHT=G.inPixels(16),t}(Dc.default),Mc=wl;var Ol=function(){function i(t){this._VPreviousMouseMove=document.onmousemove,this._VPreviousMouseUp=document.onmouseup,this._VPreviousCursor=document.body.style.cursor,this._VPreviousMouseButton=typeof t.which=="undefined"?t.button:t.which}return i.prototype.restore=function(){document.onmousemove=this._VPreviousMouseMove,document.onmouseup=this._VPreviousMouseUp,document.body.style.cursor&&(document.body.style.cursor=this._VPreviousCursor)},i.prototype.matchesCausingClick=function(t){return this._VPreviousMouseButton==(typeof t.which=="undefined"?t.button:t.which)},i}(),Dl=function(){function i(t){this.startHandler=this._VMoveMouseDown.bind(this),this.cursorType=t}return Object.defineProperty(i.prototype,"enabled",{get:function(){return this._enabled},set:function(t){this._enabled=t},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"isActive",{get:function(){return!!this._mouseStartSnapshot},enumerable:!1,configurable:!0}),Object.defineProperty(i.prototype,"mouseDownHandler",{get:function(){return this.startHandler},enumerable:!1,configurable:!0}),i.prototype._VMoveMouseDown=function(t){return!t||!this._enabled?!0:(this._mouseStartSnapshot||(this._mouseStartSnapshot=new Ol(t)),this._startCoord=K.fromEvent(t),document.onmousemove=this._VMoveMouseMove.bind(this),document.onmouseup=this._VMoveMouseUp.bind(this),document.body.style.cursor&&(document.body.style.cursor=this.cursorType),t.preventDefault(),t.cancelBubble=!0,this.onDragStart(),!1)},i.prototype._VMoveMouseMove=function(t){if(!t||!this.enabled)return!0;if(t.preventDefault(),t.cancelBubble=!0,this._mouseStartSnapshot.matchesCausingClick(t)){var e=K.fromEvent(t),n=e.x-this._startCoord.x,c=e.y-this._startCoord.y;return this.onDragMove(n,c),!1}else return this._VMoveMouseUp(t)},i.prototype._VMoveMouseUp=function(t){return t?(this._mouseStartSnapshot.restore(),this._mouseStartSnapshot=null,t.preventDefault(),t.cancelBubble=!0,this.onDragRelease(),!1):!0},i}(),bn=Dl;var Ml=function(i){(0,d.__extends)(t,i);function t(){var e=i!==null&&i.apply(this,arguments)||this;return e._enabled=!0,e.actValue=null,e}return Object.defineProperty(t.prototype,"activate",{get:function(){return this._enabled&&!!this.actValue},enumerable:!1,configurable:!0}),t.prototype.checkState=function(e){this.activate!=e&&this.emit("activate",this.activate)},Object.defineProperty(t.prototype,"enabled",{get:function(){return this._enabled},set:function(e){var n=this.activate;this._enabled=e,this.checkState(n)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"activationTrigger",{get:function(){return this.actValue},set:function(e){var n=this.activate,c=this.actValue;this.actValue=e,this.checkState(n),c!=e&&this.emit("triggerchange",e)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"conditionsMet",{get:function(){return!!this.activationTrigger},enumerable:!1,configurable:!0}),t}(qe),et=Ml;var zc=function(i){(0,d.__extends)(t,i);function t(){return i.call(this,"KeymanWeb_OnScreenKeyboard")||this}return t.prototype.loadWithDefaults=function(e){return(0,d.__assign)((0,d.__assign)({},e),this.load())},t.prototype.load=function(){var e=i.prototype.load.call(this,function(n,c){switch(c){case"version":return n;default:return Number.parseInt(n,10)}});return e.width||delete e.width,e.height||delete e.height,e},t.prototype.save=function(e){i.prototype.save.call(this,e)},t}(se);var zl=function(i){(0,d.__extends)(t,i);function t(e){var n=this;e.activator=e.activator||new et,n=i.call(this,e)||this,n.userPositioned=!1,n.specifiedPosition=!1,n.noDrag=!1,n.layoutSerializer=new zc,n.restorePosition=function(g){var a=this._Visible,F=new A;this.emit("dragmove",F.corePromise),this.loadPersistedLayout(),this.userPositioned=!1,g||(delete this.dfltX,delete this.dfltY),this.savePersistedLayout(),a&&this.present(),this.titleBar.showPin(!1),F.resolve(),this.doResizeMove()}.bind(n),n.typedActivationModel.on("triggerchange",function(){return n.setDisplayPositioning()}),document.body.appendChild(n._Box),n.titleBar=new Cn(n.titleDragHandler),n.titleBar.on("help",function(){n.legacyEvents.callEvent("helpclick",{})}),n.titleBar.on("config",function(){n.legacyEvents.callEvent("configclick",{})}),n.titleBar.on("close",function(){return n.startHide(!0)}),n.titleBar.on("unpin",function(){return n.restorePosition(!0)}),n.resizeBar=new Mc(n.resizeDragHandler),n.resizeBar.on("showbuild",function(){return n.emit("showbuild")}),n.headerView=n.titleBar;for(var c=function(g){var a=n.headerView;if(a&&a instanceof Cn)switch(g){case"configclick":a.configEnabled=n.legacyEvents.listenerCount("configclick")>0;break;case"helpclick":a.helpEnabled=n.legacyEvents.listenerCount("helpclick")>0;break;default:return}},l=new yt(n),r=new yt(n.legacyEvents),s=0,B=[l,r];s.9*screen.width&&(l=.9*screen.width),r>.5*screen.height&&(r=.5*screen.height),(c||!n)&&(this.headerView&&this.headerView.layoutHeight.absolute&&(r+=this.headerView.layoutHeight.val),this.footerView&&this.footerView.layoutHeight.absolute&&(r+=this.footerView.layoutHeight.val)),this.setSize(l,r),(this.x==-1||this.y==-1||!this._Box)&&(this.userPositioned=!1),this.x.9*screen.width&&(s=.9*screen.width),r.width=s+"px",this.setSize(s,this.computedHeight,!0)}if("height"in e){var B=e.height-(n.offsetHeight-l.offsetHeight);B<.1*screen.height&&(B=.1*screen.height),B>.5*screen.height&&(B=.5*screen.height),r.height=B+"px",r.fontSize=B/8+"px",this.setSize(this.computedWidth,B,!0)}"nosize"in e&&(this.resizingEnabled=!e.nosize)}"nomove"in e&&(this.noDrag=e.nomove,this.movementEnabled=!this.noDrag),this.savePersistedLayout()}},t.prototype.getPos=function(){var e=this._Box,n={left:this._Visible?e.offsetLeft:this.x,top:this._Visible?e.offsetTop:this.y};return n},t.prototype.setPos=function(e){if(typeof this._Box!="undefined"){if(this.userPositioned){var n=e.left,c=e.top;typeof n!="undefined"&&(n<-.8*this._Box.offsetWidth&&(n=-.8*this._Box.offsetWidth),this.userPositioned&&(this._Box.style.left=n+"px",this.x=n)),typeof c!="undefined"&&(c<0&&(c=0),this.userPositioned&&(this._Box.style.top=c+"px",this.y=c))}this.titleBar.showPin(this.userPositioned)}},t.prototype.setDisplayPositioning=function(){var e=this._Box.style;if(e.position="absolute",this.activationModel.activate&&(e.display="block"),e.left="0px",this.specifiedPosition||this.userPositioned)e.left=this.x+"px",e.top=this.y+"px";else{var n=this.typedActivationModel.activationTrigger||null;this.dfltX?e.left=this.dfltX:typeof n!="undefined"&&n!=null&&(e.left=v(n)+"px"),this.dfltY?e.top=this.dfltY:typeof n!="undefined"&&n!=null&&(e.top=Z(n)+n.offsetHeight+"px")}this.specifiedPosition=!1},t.prototype.presentAtPosition=function(e,n){!this.mayShow()||(this.specifiedPosition=e>=0||n>=0,this.specifiedPosition&&(this.x=e,this.y=n),this.specifiedPosition=this.specifiedPosition||this.userPositioned,this.present())},t.prototype.present=function(){if(!!this.mayShow()){this.titleBar.showPin(this.userPositioned),i.prototype.present.call(this);var e={};e.x=this._Box.offsetLeft,e.y=this._Box.offsetTop,e.userLocated=this.userPositioned,this.doShow(e)}},t.prototype.startHide=function(e){i.prototype.startHide.call(this,e),e&&this.savePersistedLayout()},t.prototype.show=function(e){e!==void 0?i.prototype.show.call(this,e):i.prototype.show.call(this),this.savePersistedLayout()},t.prototype.userLocated=function(){return this.userPositioned},Object.defineProperty(t.prototype,"movementEnabled",{get:function(){return this.titleDragHandler.enabled},set:function(e){this.titleDragHandler.enabled=e,this.titleBar.showPin(e&&this.userPositioned)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"resizingEnabled",{get:function(){return this.resizeDragHandler.enabled},set:function(e){this.resizeDragHandler.enabled=e,this.resizeBar.allowResizing(e)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"isBeingMoved",{get:function(){return this.titleDragHandler.isActive},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"isBeingResized",{get:function(){return this.resizeDragHandler.isActive},enumerable:!1,configurable:!0}),t.prototype.enableMoveResizeHandlers=function(){this.titleDragHandler.enabled=!this.noDrag,this.resizeDragHandler.enabled=!0},Object.defineProperty(t.prototype,"titleDragHandler",{get:function(){var e=this;return this._moveHandler?this._moveHandler:(this._moveHandler=new(function(n){(0,d.__extends)(c,n);function c(){return n.call(this,"move")||this}return c.prototype.onDragStart=function(){this.startX=e._Box.offsetLeft,this.startY=e._Box.offsetTop,e.activeKeyboard.keyboard.isCJK&&e.titleBar.setPinCJKOffset(),this.dragPromise&&this.dragPromise.resolve(),this.dragPromise=new A,e.emit("dragmove",this.dragPromise.corePromise)},c.prototype.onDragMove=function(l,r){e.titleBar.showPin(!0),e.userPositioned=!0,e._Box.style.left=this.startX+l+"px",e._Box.style.top=this.startY+r+"px";var s=e.getRect();e.setSize(s.width,s.height,!0),e.x=s.left,e.y=s.top},c.prototype.onDragRelease=function(){e.vkbd&&(e.vkbd.currentKey=null),this.dragPromise.resolve(),this.dragPromise.then(function(){e.userPositioned=!0,e.doResizeMove(),e.savePersistedLayout()}),this.dragPromise=null},c}(bn)),this._moveHandler)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"resizeDragHandler",{get:function(){var e=this;return this._resizeHandler?this._resizeHandler:(this._resizeHandler=new(function(n){(0,d.__extends)(c,n);function c(){return n.call(this,"se-resize")||this}return c.prototype.onDragStart=function(){this.startWidth=e.computedWidth,this.startHeight=e.computedHeight,this.dragPromise&&this.dragPromise.resolve(),this.dragPromise=new A,e.emit("resizemove",this.dragPromise.corePromise)},c.prototype.onDragMove=function(l,r){var s=this.startWidth+l,B=this.startHeight+r;s<.2*screen.width&&(s=.2*screen.width),B<.1*screen.height&&(B=.1*screen.height),s>.9*screen.width&&(s=.9*screen.width),B>.5*screen.height&&(B=.5*screen.height),e.setSize(s,B,!0)},c.prototype.onDragRelease=function(){e.vkbd&&(e.vkbd.currentKey=null),e.vkbd&&(this.startWidth=e.computedWidth,this.startHeight=e.computedHeight),e.refreshLayout(),this.dragPromise.resolve(),this.dragPromise.then(function(){e.doResizeMove(),e.savePersistedLayout()}),this.dragPromise=null},c}(bn)),this._resizeHandler)},enumerable:!1,configurable:!0}),t}(Je),We=zl;var Kl=function(i){(0,d.__extends)(t,i);function t(e){var n=this;return e.isEmbedded?e.activator=e.activator||new $e:e.activator=e.activator||new et,n=i.call(this,e)||this,n.isResizing=!1,n.restorePosition=function(c){}.bind(n),document.body.appendChild(n._Box),n}return t.prototype._Unload=function(){this.keyboardView=null,this.bannerView=null,this._Box=null},t.prototype.setBoxStyling=function(){var e=this._Box.style;e.zIndex="9999",e.display="none",e.width="100%",e.position="fixed"},t.prototype.refreshLayout=function(e){if(!this.isResizing){try{this.isResizing=!0,this.doResize()}finally{this.isResizing=!1}i.prototype.refreshLayout.call(this,e)}},t.prototype.doResize=function(){if(this.vkbd){var e=this.getDefaultKeyboardHeight();this.setSize(this.getDefaultWidth(),e+this.banner.height)}},t.prototype.postKeyboardAdjustments=function(){this.doResize()},t.prototype.getDefaultKeyboardHeight=function(){var e,n,c=this.targetDevice;if(this.configuration.heightOverride)return this.configuration.heightOverride();var l=(e=document==null?void 0:document.documentElement)===null||e===void 0?void 0:e.clientWidth,r=(n=document==null?void 0:document.documentElement)===null||n===void 0?void 0:n.clientHeight;if(typeof l=="undefined"&&(l=Math.min(screen.height,screen.width),r=Math.max(screen.height,screen.width),D())){var s=l;l=r,r=s}var B=Math.floor(Math.min(r,l)/2),o=B;return c.formFactor=="phone"&&(D()?o=Math.floor(r/1.6):o=Math.floor(r/2.4)),this.targetDevice.OS==x.OperatingSystem.iOS&&(o=o/He()),o},t.prototype.getDefaultWidth=function(){var e,n=this.targetDevice;if(this.configuration.widthOverride)return this.configuration.widthOverride();var c;return c=(e=document==null?void 0:document.documentElement)===null||e===void 0?void 0:e.clientWidth,typeof c=="undefined"&&(this.targetDevice.OS==x.OperatingSystem.iOS?c=window.innerWidth:n.OS==x.OperatingSystem.Android?c=screen.availWidth:c=screen.width),c},t.prototype.setRect=function(e){},t.prototype.getPos=function(){var e=this._Box,n={left:this._Visible?e.offsetLeft:this.x,top:this._Visible?e.offsetTop:this.y};return n},t.prototype.setPos=function(e){},t.prototype.setDisplayPositioning=function(){var e=this._Box.style;this.vkbd&&(e.position="fixed",e.left=e.bottom="0px",e.border="none",e.borderTop="1px solid gray")},t.prototype.present=function(){i.prototype.present.call(this),this.legacyEvents.callEvent("show",{})},t}(Je),hn=Kl;var _l=function(i){(0,d.__extends)(t,i);function t(){var e=i!==null&&i.apply(this,arguments)||this;return e.flag=!0,e}return Object.defineProperty(t.prototype,"enabled",{get:function(){return this.flag},set:function(e){this.activate=e},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"activate",{get:function(){return this.flag},set:function(e){this.flag!=e&&(this.flag=e,this.emit("activate",e))},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"conditionsMet",{get:function(){return!0},enumerable:!1,configurable:!0}),t}(qe),Un=_l;var Pl=function(i){(0,d.__extends)(t,i);function t(e){var n=this;return e.activator=e.activator||new Un,n=i.call(this,e)||this,n.restorePosition=function(c){}.bind(n),n}return Object.defineProperty(t.prototype,"element",{get:function(){return this._Box},enumerable:!1,configurable:!0}),t.prototype._Unload=function(){this.keyboardView=null,this.bannerView=null,this._Box=null},t.prototype.setBoxStyling=function(){var e=this._Box.style;e.display="none",e.position="relative"},t.prototype.postKeyboardAdjustments=function(){},t.prototype.getDefaultKeyboardHeight=function(){return this.keyboardView instanceof Ce?this.keyboardView.height:this.computedHeight},t.prototype.getDefaultWidth=function(){return this.computedWidth},t.prototype.setRect=function(e){},t.prototype.getPos=function(){var e=this._Box,n={left:this._Visible?e.offsetLeft:void 0,top:this._Visible?e.offsetTop:void 0};return n},t.prototype.setPos=function(e){},t.prototype.present=function(){i.prototype.present.call(this),this.legacyEvents.callEvent("show",{})},t.prototype.setDisplayPositioning=function(){},t.prototype.allowsDeviceChange=function(e){return!0},t}(Je),xn=Pl;var pn={};kn(pn,{AnchoredOSKView:function(){return Gn},FloatingOSKView:function(){return Xn},InlinedOSKView:function(){return jl}});function Vn(i){return{hostDevice:i.config.hostDevice,pathConfig:i.config.paths,predictionContextManager:i.contextManager.predictionContext,isEmbedded:!1}}var Gn=function(i){(0,d.__extends)(t,i);function t(e,n){var c=(0,d.__assign)((0,d.__assign)({},Vn(e)),n||{});return i.call(this,c)||this}return t}(hn),Xn=function(i){(0,d.__extends)(t,i);function t(e,n){var c=(0,d.__assign)((0,d.__assign)({},Vn(e)),n||{});return i.call(this,c)||this}return t}(We),jl=function(i){(0,d.__extends)(t,i);function t(e,n){var c=(0,d.__assign)((0,d.__assign)({},Vn(e)),n||{});return i.call(this,c)||this}return t}(xn);var Kc=J(R(),1),ql=function(i){(0,d.__extends)(t,i);function t(){var e=i!==null&&i.apply(this,arguments)||this;return e.events=new Kc.default,e.changed=!1,e}return t.prototype.focus=function(){var e=this.getElement();e.focus&&e.focus()},t.prototype.isForcingScroll=function(){return!1},t.prototype.dispatchInputEventOn=function(e){var n;window.InputEvent&&(n=new InputEvent("input",{bubbles:!0,cancelable:!1})),e&&n&&e.dispatchEvent(n)},t}(wt),_=ql;var $l=function(i){(0,d.__extends)(t,i);function t(e){var n=i.call(this)||this;return n.root=e,n._cachedSelectionStart=-1,n}return Object.defineProperty(t.prototype,"isSynthetic",{get:function(){return!1},enumerable:!1,configurable:!0}),t.prototype.getElement=function(){return this.root},t.prototype.clearSelection=function(){this.getCaret(),this.root.value=this.root.value._kmwSubstring(0,this.processedSelectionStart)+this.root.value._kmwSubstring(this.processedSelectionEnd),this.setCaret(this.processedSelectionStart)},t.prototype.isSelectionEmpty=function(){return this.root.selectionStart==this.root.selectionEnd},t.prototype.hasSelection=function(){return!0},t.prototype.invalidateSelection=function(){this._cachedSelectionStart=-1},t.prototype.getCaret=function(){return this.root.selectionStart!=this._cachedSelectionStart&&(this._cachedSelectionStart=this.root.selectionStart,this.processedSelectionStart=this.root.value._kmwCodeUnitToCodePoint(this.root.selectionStart),this.processedSelectionEnd=this.root.value._kmwCodeUnitToCodePoint(this.root.selectionEnd)),this.root.selectionDirection=="forward"?this.processedSelectionEnd:this.processedSelectionStart},t.prototype.getDeadkeyCaret=function(){return this.getCaret()},t.prototype.setCaret=function(e){this.setSelection(e,e,"none")},t.prototype.setSelection=function(e,n,c){var l=this.root.value._kmwCodePointToCodeUnit(e),r=this.root.value._kmwCodePointToCodeUnit(n);this.root.setSelectionRange(l,r,c),this.processedSelectionStart=e,this.processedSelectionEnd=n,this.forceScroll(),this.root.setSelectionRange(l,r,c)},t.prototype.forceScroll=function(){var e=this.getElement(),n=e.selectionStart,c=e.selectionEnd;this._activeForcedScroll=!0;try{e.blur(),e.focus()}finally{e.selectionStart=n,e.selectionEnd=c,this._activeForcedScroll=!1}},t.prototype.isForcingScroll=function(){return this._activeForcedScroll},t.prototype.getSelectionDirection=function(){return this.root.selectionDirection},t.prototype.getTextBeforeCaret=function(){return this.getCaret(),this.getText()._kmwSubstring(0,this.processedSelectionStart)},t.prototype.getSelectedText=function(){return this.getCaret(),this.getText()._kmwSubstring(this.processedSelectionStart,this.processedSelectionEnd)},t.prototype.setTextBeforeCaret=function(e){this.getCaret();var n=this.processedSelectionEnd-this.processedSelectionStart,c=this.getSelectionDirection(),l=e._kmwLength();this.root.value=e+this.getText()._kmwSubstring(this.processedSelectionStart),this.setSelection(l,l+n,c)},t.prototype.setTextAfterCaret=function(e){var n=this.getCaret(),c=this.getSelectionDirection();this.root.value=this.getTextBeforeCaret()+e,this.setSelection(this.processedSelectionStart,this.processedSelectionEnd,c)},t.prototype.getTextAfterCaret=function(){return this.getCaret(),this.getText()._kmwSubstring(this.processedSelectionEnd)},t.prototype.getText=function(){return this.root.value},t.prototype.deleteCharsBeforeCaret=function(e){if(e>0){var n=this.getTextBeforeCaret(),c=this.processedSelectionStart;e>c&&(e=c),this.adjustDeadkeys(-e),this.setTextBeforeCaret(n.kmwSubstring(0,c-e)),this.setCaret(c-e)}},t.prototype.insertTextBeforeCaret=function(e){if(!!e){var n=this.getCaret(),c=this.getTextBeforeCaret(),l=this.getText()._kmwSubstring(this.processedSelectionStart);this.adjustDeadkeys(e._kmwLength()),this.root.value=c+e+l,this.setCaret(n+e._kmwLength())}},t.prototype.handleNewlineAtCaret=function(){var e=this.root;e&&(e.type=="search"||e.type=="submit")?(e.disabled=!1,e.form.submit()):this.events.emit("unhandlednewline",e)},t.prototype.doInputEvent=function(){this.dispatchInputEventOn(this.root)},t}(_),mn=$l;var er=function(i){(0,d.__extends)(t,i);function t(e){var n=i.call(this)||this;return n.root=e,n._cachedSelectionStart=-1,n}return Object.defineProperty(t.prototype,"isSynthetic",{get:function(){return!1},enumerable:!1,configurable:!0}),t.prototype.getElement=function(){return this.root},t.prototype.clearSelection=function(){this.getCaret(),this.root.value=this.root.value._kmwSubstring(0,this.processedSelectionStart)+this.root.value._kmwSubstring(this.processedSelectionEnd),this.setCaret(this.processedSelectionStart)},t.prototype.isSelectionEmpty=function(){return this.root.selectionStart==this.root.selectionEnd},t.prototype.hasSelection=function(){return!0},t.prototype.invalidateSelection=function(){this._cachedSelectionStart=-1},t.prototype.getCaret=function(){return this.root.selectionStart!=this._cachedSelectionStart&&(this._cachedSelectionStart=this.root.selectionStart,this.processedSelectionStart=this.root.value._kmwCodeUnitToCodePoint(this.root.selectionStart),this.processedSelectionEnd=this.root.value._kmwCodeUnitToCodePoint(this.root.selectionEnd)),this.root.selectionDirection=="forward"?this.processedSelectionEnd:this.processedSelectionStart},t.prototype.getDeadkeyCaret=function(){return this.getCaret()},t.prototype.setCaret=function(e){this.setSelection(e,e,"none")},t.prototype.setSelection=function(e,n,c){var l=this.root.value._kmwCodePointToCodeUnit(e),r=this.root.value._kmwCodePointToCodeUnit(n);this.root.setSelectionRange(l,r,c),this.processedSelectionStart=e,this.processedSelectionEnd=n,this.forceScroll(),this.root.setSelectionRange(l,r,c)},t.prototype.forceScroll=function(){var e=this.getElement(),n=e.selectionStart,c=e.selectionEnd;this._activeForcedScroll=!0;try{e.blur(),e.focus()}finally{e.selectionStart=n,e.selectionEnd=c,this._activeForcedScroll=!1}},t.prototype.isForcingScroll=function(){return this._activeForcedScroll},t.prototype.getSelectionDirection=function(){return this.root.selectionDirection},t.prototype.getTextBeforeCaret=function(){return this.getCaret(),this.getText()._kmwSubstring(0,this.processedSelectionStart)},t.prototype.setTextBeforeCaret=function(e){this.getCaret();var n=this.processedSelectionEnd-this.processedSelectionStart,c=this.getSelectionDirection(),l=e._kmwLength();this.root.value=e+this.getText()._kmwSubstring(this.processedSelectionStart),this.setSelection(l,l+n,c)},t.prototype.setTextAfterCaret=function(e){var n=this.getCaret(),c=this.getSelectionDirection();this.root.value=this.getTextBeforeCaret()+e,this.setSelection(this.processedSelectionStart,this.processedSelectionEnd,c)},t.prototype.getTextAfterCaret=function(){return this.getCaret(),this.getText()._kmwSubstring(this.processedSelectionEnd)},t.prototype.getSelectedText=function(){return this.getCaret(),this.getText()._kmwSubstring(this.processedSelectionStart,this.processedSelectionEnd)},t.prototype.getText=function(){return this.root.value},t.prototype.deleteCharsBeforeCaret=function(e){if(e>0){var n=this.getTextBeforeCaret(),c=this.processedSelectionStart;e>c&&(e=c),this.adjustDeadkeys(-e),this.setTextBeforeCaret(n.kmwSubstring(0,c-e)),this.setCaret(c-e)}},t.prototype.insertTextBeforeCaret=function(e){if(!!e){var n=this.getCaret(),c=this.getTextBeforeCaret(),l=this.getText()._kmwSubstring(this.processedSelectionStart);this.adjustDeadkeys(e._kmwLength()),this.root.value=c+e+l,this.setCaret(n+e._kmwLength())}},t.prototype.handleNewlineAtCaret=function(){this.insertTextBeforeCaret("\n")},t.prototype.doInputEvent=function(){this.dispatchInputEventOn(this.root)},t}(_),Zn=er;var Ln=function(){function i(t,e){this.node=t,this.offset=e}return i}(),Jn=function(){function i(t,e){this.start=t,this.end=e}return i}(),Be=function(){function i(t,e){this.cmd=t,this.stateType=e}return i}(),tr=function(i){(0,d.__extends)(t,i);function t(e){var n=i.call(this)||this;if(n.root=e,e.contentWindow&&e.contentWindow.document&&e.contentWindow.document.designMode=="on")n.doc=e.contentWindow.document,n.docRoot=e.contentWindow.document.documentElement;else throw"Specified IFrame is not in design-mode!";return n}return Object.defineProperty(t.prototype,"isSynthetic",{get:function(){return!1},enumerable:!1,configurable:!0}),t.prototype.getElement=function(){return this.root},t.prototype.focus=function(){this.doc.defaultView.focus()},t.prototype.isSelectionEmpty=function(){return this.hasSelection()?this.doc.getSelection().isCollapsed:!0},t.prototype.hasSelection=function(){var e=this.doc.getSelection(),n=document.getSelection();return n.anchorNode==e.anchorNode&&n.focusNode==e.focusNode,!0},t.prototype.clearSelection=function(){if(this.hasSelection()){var e=this.doc.getSelection();e.isCollapsed||e.deleteFromDocument()}else console.warn("Attempted to clear an unowned Selection!")},t.prototype.invalidateSelection=function(){},t.prototype.getCarets=function(){var e=this.doc.getSelection(),n=e.anchorNode.compareDocumentPosition(e.focusNode);if(e.isCollapsed){var c=new Ln(e.anchorNode,e.anchorOffset);return new Jn(c,c)}else{var l=new Ln(e.anchorNode,e.anchorOffset),r=new Ln(e.focusNode,e.focusOffset);return l.node==r.node&&(n=r.offset-l.offset>0?2:4),n&2?new Jn(l,r):new Jn(r,l)}},t.prototype.getDeadkeyCaret=function(){return this.getTextBeforeCaret().kmwLength()},t.prototype.getTextBeforeCaret=function(){if(!this.hasSelection())return this.getText();var e=this.getCarets().start;return e.node.nodeType!=3?"":e.node.textContent.substr(0,e.offset)},t.prototype.getSelectedText=function(){return""},t.prototype.getTextAfterCaret=function(){if(!this.hasSelection())return"";var e=this.getCarets().end;return e.node.nodeType!=3?"":e.node.textContent.substr(e.offset)},t.prototype.getText=function(){return this.docRoot.innerText},t.prototype.deleteCharsBeforeCaret=function(e){if(!(!this.hasSelection()||e<=0)){var n=this.getCarets().start;if(e>n.offset&&(e=n.offset),n.node.nodeType!=3){console.warn("Deletion of characters requested without available context!");return}var c=this.doc.createRange(),l=n.offset-n.node.nodeValue.substr(0,n.offset)._kmwSubstr(-e).length;c.setStart(n.node,l),c.setEnd(n.node,n.offset),this.adjustDeadkeys(-e),c.deleteContents()}},t.prototype.insertTextBeforeCaret=function(e){if(!!this.hasSelection()){var n=this.getCarets().start,c=e._kmwLength(),l=this.doc.getSelection();if(c!=0){this.adjustDeadkeys(c);var r=this.root.ownerDocument.createRange();if(n.node.nodeType==3){var s=n.node;s.insertData(n.offset,e),r.setStart(s,n.offset+e.length)}else{var B=this.doc.createTextNode(e),o=this.doc.createRange();o.setStart(n.node,n.offset),o.collapse(!0),o.insertNode(B),r.setStart(B,e.length)}r.collapse(!0),l.removeAllRanges();try{l.addRange(r)}catch(g){n.node.parentElement.scrollIntoView(),l.addRange(r)}l.collapseToEnd()}}},t.prototype.handleNewlineAtCaret=function(){},t.prototype.setTextAfterCaret=function(e){if(!!this.hasSelection()){var n=this.getCarets().end,c=e._kmwLength(),l=this.doc.getSelection();if(c!=0)if(n.node.nodeType==3){var r=n.node;r.replaceData(n.offset,r.length,e)}else{var s=n.node.ownerDocument.createTextNode(e),B=this.root.ownerDocument.createRange();B.setStart(n.node,n.offset),B.collapse(!0),B.insertNode(s)}}},t.prototype.saveProperties=function(){var e=[new Be("backcolor",1),new Be("fontname",1),new Be("fontsize",1),new Be("forecolor",1),new Be("bold",0),new Be("italic",0),new Be("strikethrough",0),new Be("subscript",0),new Be("superscript",0),new Be("underline",0)];this.doc.defaultView&&e.push(new Be("hilitecolor",1));for(var n=0;n0?2:4),n&2?new Rn(l,r):new Rn(r,l)}},t.prototype.getDeadkeyCaret=function(){return this.getTextBeforeCaret().kmwLength()},t.prototype.getTextBeforeCaret=function(){if(!this.hasSelection())return this.getText();var e=this.getCarets().start;return e.node.nodeType!=3?"":e.node.textContent.substr(0,e.offset)},t.prototype.getSelectedText=function(){return""},t.prototype.getTextAfterCaret=function(){if(!this.hasSelection())return"";var e=this.getCarets().end;return e.node.nodeType!=3?"":e.node.textContent.substr(e.offset)},t.prototype.getText=function(){return this.root.innerText},t.prototype.deleteCharsBeforeCaret=function(e){if(!(!this.hasSelection()||e<=0)){var n=this.getCarets().start;if(e>n.offset&&(e=n.offset),n.node.nodeType!=3){console.warn("Deletion of characters requested without available context!");return}var c=this.root.ownerDocument.createRange(),l=n.offset-n.node.nodeValue.substr(0,n.offset)._kmwSubstr(-e).length;c.setStart(n.node,l),c.setEnd(n.node,n.offset),this.adjustDeadkeys(-e),c.deleteContents()}},t.prototype.insertTextBeforeCaret=function(e){if(!!this.hasSelection()){var n=this.getCarets().start,c=e._kmwLength(),l=this.root.ownerDocument.getSelection();if(c!=0){this.adjustDeadkeys(c);var r=this.root.ownerDocument.createRange();if(n.node.nodeType==3){var s=n.node;s.insertData(n.offset,e),r.setStart(s,n.offset+e.length)}else{var B=n.node.ownerDocument.createTextNode(e),o=this.root.ownerDocument.createRange();o.setStart(n.node,n.offset),o.collapse(!0),o.insertNode(B),r.setStart(B,e.length)}r.collapse(!0),l.removeAllRanges();try{l.addRange(r)}catch(g){n.node.parentElement.scrollIntoView(),l.addRange(r)}l.collapseToEnd()}}},t.prototype.handleNewlineAtCaret=function(){},t.prototype.setTextAfterCaret=function(e){if(!!this.hasSelection()){var n=this.getCarets().end,c=e._kmwLength(),l=this.root.ownerDocument.getSelection();if(c!=0)if(n.node.nodeType==3){var r=n.node;r.replaceData(n.offset,r.length,e)}else{var s=n.node.ownerDocument.createTextNode(e),B=this.root.ownerDocument.createRange();B.setStart(n.node,n.offset),B.collapse(!0),B.insertNode(s)}}},t.prototype.doInputEvent=function(){this.dispatchInputEventOn(this.root)},t}(_),Xt=nr;function N(i,t){var e;if(!i)return!1;if(i.Window)return t=="Window";if(i.defaultView)e=i.defaultView[t];else if(i.ownerDocument)e=i.ownerDocument.defaultView[t];else if(i.target){var n=i;this.instanceof(n.target,"Window")?e=n.target[t]:this.instanceof(n.target,"Document")?e=n.target.defaultView[t]:this.instanceof(n.target,"HTMLElement")&&(e=n.target.ownerDocument.defaultView[t])}return e?i instanceof e:!1}function pt(i){if(N(i,"HTMLInputElement"))return new mn(i);if(N(i,"HTMLTextAreaElement"))return new Zn(i);if(N(i,"HTMLIFrameElement")){var t=i;return t.contentWindow&&t.contentWindow.document&&t.contentWindow.document.designMode=="on"?new ne(t):i.isContentEditable?new Xt(i):null}else if(i.isContentEditable)return new Xt(i);return null}var Ne=function(){function i(){var t=this;this.pending=!1;var e=this.bg=document.createElement("div"),n=this.lb=document.createElement("div"),c=this.lt=document.createElement("div"),l=this.gr=document.createElement("div"),r=this.bx=document.createElement("div");e.className="kmw-wait-background",n.className="kmw-wait-box",this.dismiss=null,c.className="kmw-wait-text",l.className="kmw-wait-graphic",r.className="kmw-alert-close",n.onmousedown=n.onclick=function(s){r.style.display=="block"&&(e.style.display="none",t.dismiss&&t.dismiss())},n.addEventListener("touchstart",n.onclick,!1),e.onmousedown=e.onclick=function(s){s.preventDefault(),s.stopPropagation()},e.addEventListener("touchstart",e.onclick,!1),n.appendChild(r),n.appendChild(c),n.appendChild(l),e.appendChild(n),document.body.appendChild(e)}return Object.defineProperty(i.prototype,"rootElement",{get:function(){return this.bg},enumerable:!1,configurable:!0}),i.prototype.wait=function(t){var e=this,n=this.bg;typeof n=="undefined"||n==null||(t?(this.pending=!0,window.setTimeout(function(){e.pending&&(window.scrollTo(0,0),e.bx.style.display="none",e.lt.className="kmw-wait-text",e.lt.innerHTML=t,e.gr.style.display="block",n.style.display="block")},1e3)):this.pending&&(this.lt.innerHTML="",this.pending=!1,n.style.display="none"))},i.prototype.alert=function(t,e){var n=this.bg;this.bx.style.display="block",this.lt.className="kmw-alert-text",this.lt.innerHTML=t,this.gr.style.display="none",n.style.display="block",this.dismiss=arguments.length>1?e:null},i.prototype.shutdown=function(){this.bg.parentNode.removeChild(this.bg)},i}();function tt(){return document.readyState==="complete"?Promise.resolve():new Promise(function(i,t){var e=function(){window.removeEventListener("load",e),i()};window.addEventListener("load",e)})}var _c=function(i){(0,d.__extends)(t,i);function t(){return i!==null&&i.apply(this,arguments)||this}return t.prototype.initialize=function(e){var n=this;this._options?this._options=(0,d.__assign)((0,d.__assign)({},this._options),e):this._options=(0,d.__assign)({},e),i.prototype.initialize.call(this,e),this._options=e,this._ui=e.ui,this._attachType=e.attachType,tt().then(function(){var c;e.useAlerts&&!n.alertHost?n._alertHost=new Ne:!e.useAlerts&&n.alertHost&&((c=n._alertHost)===null||c===void 0||c.shutdown(),n._alertHost=null)})},Object.defineProperty(t.prototype,"options",{get:function(){return this._options},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"attachType",{get:function(){return this._attachType},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"alertHost",{get:function(){return this._alertHost},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"signalUser",{set:function(e){(!e||e!=this.alertHost)&&this.alertHost.shutdown(),this._alertHost=e},enumerable:!1,configurable:!0}),t.prototype.debugReport=function(){var e=i.prototype.debugReport.call(this);return e.attachType=this.attachType,e.ui=this._ui,e.keymanEngine="app/browser",e},t.prototype.onRuleFinalization=function(e,n){var c=e.transcription.transform;ot(c)||n instanceof _&&(n.changed=!0)},t}(zt);var Pc=(0,d.__assign)({ui:"",attachType:"",useAlerts:!0},Kt);var An=function(){function i(t,e,n){this.interface=t,this.keyboard=e}return i}();function be(i){var t=i==null?void 0:i.target;return he(t)}function he(i){if(i==null)return null;if(i.body&&(i=i.body),i.nodeType==3&&(i=i.parentNode),N(i,"HTMLInputElement")){var t=i.type.toLowerCase();if(!(t=="text"||t=="search"))return null}return i._kmwAttachment.interface}var jc=J(R(),1);var fn=function(i){(0,d.__extends)(t,i);function t(e,n){var c=this;if(!e)throw new Error("Cannot attach to a null/undefined document");return c=i.call(this)||this,c.baseFont="",c.appliedFont="",c.embeddedPageContexts=[],c._inputList=[],c._sortedInputs=[],c._InputModeObserverCore=function(l){c.disableInputModeObserver();try{for(var r=0,s=l;r=0:!1,o=s.target.className.indexOf("kmw-disabled")>=0;if(B&&!o?c._EnableControl(s.target):!B&&o&&c._DisableControl(s.target),!o&&s.attributeName=="readonly"){var g=s.oldValue?s.oldValue!=null:!1,a=s.target;if(a instanceof a.ownerDocument.defaultView.HTMLInputElement||a instanceof a.ownerDocument.defaultView.HTMLTextAreaElement){var F=a.readOnly;g&&!F?c._EnableControl(s.target):!g&&F&&c._DisableControl(s.target)}}}},c._AutoAttachObserverCore=function(l){for(var r=[],s=[],B=0;B=0)},t.prototype.enableInputElement=function(e){var n;this.isKMWDisabled(e)||(e instanceof e.ownerDocument.defaultView.HTMLIFrameElement?this._AttachToIframe(e):(this.setupElementAttachment(e),e._kmwAttachment.inputMode=(n=e.inputMode)!==null&&n!==void 0?n:"text",this.disableInputModeObserver(),e.inputMode="none",this.enableInputModeObserver(),e.classList.add("keymanweb-font"),this._inputList.push(e),this.emit("enabled",e)))},t.prototype.disableInputElement=function(e){var n;if(!!e)if(e.ownerDocument.defaultView&&e instanceof e.ownerDocument.defaultView.HTMLIFrameElement||e instanceof HTMLIFrameElement)this._DetachFromIframe(e);else{if(this.isAttached(e)){var c=(n=e._kmwAttachment)===null||n===void 0?void 0:n.inputMode;this.disableInputModeObserver(),e.inputMode=c,this.enableInputModeObserver()}var l=e.className.indexOf("keymanweb-font");l>=0&&(e.className=e.className.replace("keymanweb-font","").trim());var r=this.inputList.indexOf(e);r>-1&&this._inputList.splice(r,1),this.emit("disabled",e)}},t.prototype.enableTouchElement=function(e){return this.isKMWDisabled(e)?(this.emit("disabled",e),!1):(this.isAttached(e)||this.setupElementAttachment(e),this.enableInputElement(e),!0)},t.prototype.disableTouchElement=function(e){if(this.isAttached(e)){var n=e._kmwAttachment.inputMode;this.disableInputModeObserver(),e.inputMode=n,this.enableInputModeObserver()}},t.prototype._AttachToIframe=function(e){var n=this;try{var c=e.contentWindow.document;if(c){if(c.designMode.toLowerCase()=="on")this.setupElementAttachment(e),c.body._kmwAttachment=e._kmwAttachment,this._inputList.push(e),this.emit("enabled",e);else if(this.embeddedPageContexts.filter(function(r){return r.document==c}).length==0){var l=new t(c,(0,d.__assign)((0,d.__assign)({},this.options),{owner:e}));this.embeddedPageContexts.push(l),l.on("enabled",function(r){return n.emit("enabled",r)}),l.on("disabled",function(r){return n.emit("disabled",r)}),l.install(this.manualAttach)}}}catch(r){}},t.prototype._DetachFromIframe=function(e){var n=this,c=function(){n.clearElementAttachment(e);var B=n._inputList.indexOf(e);B!=-1&&n._inputList.splice(B,1),n.emit("disabled",e)};try{var l=e.contentWindow.document;if(l){if(l.designMode.toLowerCase()=="on")l.body._kmwAttachment=null,c();else for(var r=0;r=0&&(e.className=n.replace("kmw-disabled","").trim())},t.prototype.listInputs=function(){for(var e=[],n=document.getElementsByTagName("input"),c=document.getElementsByTagName("textarea"),l=0;l=l.length?c-l.length:c,c=c<0?c+l.length:c,l[c]},t.prototype._GetDocumentEditables=function(e){var n=[];if(e.ownerDocument&&e instanceof e.ownerDocument.defaultView.HTMLElement){var c=e.ownerDocument.defaultView;(e instanceof c.HTMLInputElement||e instanceof c.HTMLTextAreaElement||e instanceof c.HTMLIFrameElement)&&n.push(e)}if(e.getElementsByTagName){var l=function(r){return dt(e.getElementsByTagName(r))};n=n.concat(l("INPUT"),l("TEXTAREA"),l("IFRAME"))}return e.querySelectorAll&&(n=n.concat(dt(e.querySelectorAll("[contenteditable]")))),e.ownerDocument&&e instanceof e.ownerDocument.defaultView.HTMLElement&&e.isContentEditable&&n.push(e),n},t.prototype._SetupDocument=function(e){for(var n=this._GetDocumentEditables(e),c=0;c0&&n.length==0)c=1;else if(e.length==0&&n.length>0)c=2;else{var s=e[0],B=n[0];s.offsetTopB.offsetTop?c=2:s.offsetLeftB.offsetLeft&&(c=2)}switch(c){case 0:l=r;break;case 1:l=getComputedStyle(e[0]).fontFamily||"";break;case 2:l=getComputedStyle(n[0]).fontFamily||"";break}return(typeof l=="undefined"||l=="monospace")&&(l=r),l},t.prototype.buildAttachmentFontStyle=function(e){var n=e,c=this.baseFont;n&&typeof n.family!="undefined"&&(c=n.family),c=c.replace(/\u0022/g,"");var l=new RegExp("\\s?"+c+",?"),r=this.appliedFont.replace(/\u0022/g,"");r=r.replace(l,""),r=r.replace(/,$/,""),r==""?r=c:r=c+","+r,r='"'+r.replace(/\,\s?/g,'","')+'"';var s=".keymanweb-font{\nfont-family:"+r+" !important;\n}\n";return this.appliedFont=r,s},t.prototype.setAttachmentFont=function(e,n,c){this.stylesheetManager.unlinkAll(),this.stylesheetManager.addStyleSheetForFont(e,n,c),this.stylesheetManager.linkStylesheet(Ve(this.buildAttachmentFontStyle(e)))},t.prototype.shutdown=function(){var e,n,c,l;try{(e=this.enablementObserver)===null||e===void 0||e.disconnect(),(n=this.attachmentObserver)===null||n===void 0||n.disconnect(),(c=this.inputModeObserver)===null||c===void 0||c.disconnect(),(l=this.stylesheetManager)===null||l===void 0||l.unlinkAll(),this.inputModeObserver=null,this.embeddedPageContexts.forEach(function(o){try{o.shutdown()}catch(g){}});for(var r=0,s=this.inputList;r"+n.name),c.then(function(){var r;(r=e.engineConfig.alertHost)===null||r===void 0||r.wait()})}),this.engineConfig.deferForInitialization.then(function(){var n=e.engineConfig.hostDevice,c=function(l){return l.stopPropagation()};e.page.on("enabled",function(l){if(!(l._kmwAttachment.interface instanceof ne))n.touchable&&(e.domEventTracker.detachDOMEvent(l,"touchstart",e.nonKMWTouchHandler),e.domEventTracker.attachDOMEvent(l,"touchmove",c,!1),e.domEventTracker.attachDOMEvent(l,"touchend",c,!1)),e.domEventTracker.attachDOMEvent(l,"focus",e._ControlFocus),e.domEventTracker.attachDOMEvent(l,"blur",e._ControlBlur),e.domEventTracker.attachDOMEvent(l,"click",e._Click);else{var r=l.contentWindow.document;n.browser=="firefox"?(e.domEventTracker.attachDOMEvent(r,"focus",e._ControlFocus),e.domEventTracker.attachDOMEvent(r,"blur",e._ControlBlur)):(e.domEventTracker.attachDOMEvent(r.body,"focus",e._ControlFocus),e.domEventTracker.attachDOMEvent(r.body,"blur",e._ControlBlur))}}),e.page.on("disabled",function(l){var r;if(!N(l,"HTMLIFrameElement"))n.touchable&&e.domEventTracker.attachDOMEvent(l,"touchstart",e.nonKMWTouchHandler,!1),e.domEventTracker.detachDOMEvent(l,"focus",e._ControlFocus),e.domEventTracker.detachDOMEvent(l,"blur",e._ControlBlur),e.domEventTracker.detachDOMEvent(l,"click",e._Click);else{var s=l.contentWindow.document;n.browser=="firefox"?(e.domEventTracker.detachDOMEvent(s,"focus",e._ControlFocus),e.domEventTracker.detachDOMEvent(s,"blur",e._ControlBlur)):(e.domEventTracker.detachDOMEvent(s.body,"focus",e._ControlFocus),e.domEventTracker.detachDOMEvent(s.body,"blur",e._ControlBlur))}var B=(r=e.mostRecentTarget)===null||r===void 0?void 0:r.getElement();B&&B==l&&e.forgetActiveTarget()}),e.page.install(e.engineConfig.attachType=="manual")})},Object.defineProperty(t.prototype,"activeTarget",{get:function(){var e=this.focusAssistant.maintainingFocus;return this.currentTarget||(e?this.mostRecentTarget:null)},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"lastActiveTarget",{get:function(){return this.mostRecentTarget},enumerable:!1,configurable:!0}),t.prototype.deactivateCurrentTarget=function(){var e=this.activeTarget||this.lastActiveTarget;e&&this._BlurKeyboardSettings(e.getElement()),this.activeTarget||this.setActiveTarget(null,!0)},t.prototype.forgetActiveTarget=function(){this.focusAssistant.maintainingFocus=!1,this.focusAssistant.restoringFocus=!1;var e=this.activeTarget||this.lastActiveTarget;e&&this._BlurKeyboardSettings(e.getElement()),this.setActiveTarget(null,!0)},t.prototype.setActiveTarget=function(e,n){var c,l=this.mostRecentTarget,r=this.activeTarget;if(e==r){r&&(this.currentTarget=r);return}var s=!!l;if(this.currentTarget=this.mostRecentTarget=e,this.predictionContext.setCurrentTarget(e),this.focusAssistant.restoringFocus?this._BlurKeyboardSettings(e.getElement()):e&&this._FocusKeyboardSettings(e.getElement(),!s),this._CommonFocusHelper(e))return!0;var B=e==null?void 0:e.getElement();e instanceof ne&&(B=e.docRoot),B&&B.ownerDocument&&B instanceof B.ownerDocument.defaultView.HTMLElement&&ei(B,(c=this.activeKeyboard)===null||c===void 0?void 0:c.keyboard),e!=r&&this.emit("targetchange",e),n&&this.apiEvents.callEvent("controlfocused",{target:(e==null?void 0:e.getElement())||null,activeControl:l==null?void 0:l.getElement()})},Object.defineProperty(t.prototype,"activeKeyboard",{get:function(){return this._activeKeyboard},enumerable:!1,configurable:!0}),t.prototype.restoreLastActiveTarget=function(){!this.mostRecentTarget||(this.focusAssistant.restoringFocus=!0,this.mostRecentTarget.focus(),this.focusAssistant.restoringFocus=!1)},t.prototype.insertText=function(e,n,c){this.restoreLastActiveTarget();var l=this.activeTarget;return l==null&&this.mostRecentTarget&&(l=this.activeTarget),l!=null?i.prototype.insertText.call(this,e,n,c):!1},t.prototype.currentKeyboardSrcTarget=function(){var e=this.currentTarget||this.mostRecentTarget;return this.isTargetKeyboardIndependent(e)?e:null},t.prototype.isTargetKeyboardIndependent=function(e){var n=e==null?void 0:e.getElement()._kmwAttachment;return!!((n==null?void 0:n.keyboard)||(n==null?void 0:n.keyboard)==="")},t.prototype.activateKeyboardForTarget=function(e,n){var c,l,r=n==null?void 0:n.getElement()._kmwAttachment;if(r?(r.keyboard=(c=e==null?void 0:e.metadata.id)!==null&&c!==void 0?c:"",r.languageCode=(l=e==null?void 0:e.metadata.langId)!==null&&l!==void 0?l:""):this.globalKeyboard=e,this.currentKeyboardSrcTarget()==n){this._activeKeyboard=e;var s=e==null?void 0:e.metadata;this.page.setAttachmentFont(s==null?void 0:s.KFont,this.engineConfig.paths.fonts,this.engineConfig.hostDevice.OS)}},t.prototype.setKeyboardForTarget=function(e,n,c){if(e instanceof ne){console.warn("'keymanweb.setKeyboardForControl' cannot set keyboard on iframes.");return}var l=e.getElement()._kmwAttachment,r=this.currentKeyboardSrcTarget()==e;if(l){if(l.keyboard=n||null,l.languageCode=c||null,r||this.currentKeyboardSrcTarget()==e){var s=this.globalKeyboard.metadata;this.activateKeyboard(l.keyboard||s.id,l.languageCode||s.langId,!0)}}else return},t.prototype.getKeyboardStubForTarget=function(e){if(this.isTargetKeyboardIndependent(e)){var n=e.getElement()._kmwAttachment;return this.keyboardCache.getStub(n.keyboard,n.languageCode)}else return this.globalKeyboard.metadata},t.prototype.getFallbackStubKey=function(){var e={id:"",langId:""};return this.engineConfig.hostDevice.touchable&&this.keyboardCache.defaultStub||e},t.prototype.activateKeyboard=function(e,n,c){var l,r,s,B,o,g;return(0,d.__awaiter)(this,void 0,void 0,function(){var a,F,y,u,Q,C=this;return(0,d.__generator)(this,function(h){switch(h.label){case 0:c||(c=!1),a=this.currentKeyboardSrcTarget(),e||(e=this.getFallbackStubKey().id,n=this.getFallbackStubKey().langId),h.label=1;case 1:return h.trys.push([1,3,,7]),[4,i.prototype.activateKeyboard.call(this,e,n,c)];case 2:return F=h.sent(),(l=this.engineConfig.alertHost)===null||l===void 0||l.wait(),c&&!a&&this.cookieManager.save({current:"".concat(e,":").concat(n)}),a==this.currentKeyboardSrcTarget()&&(ei((r=this.currentTarget)===null||r===void 0?void 0:r.getElement(),this.keyboardCache.getKeyboard(e)),this.page.setAttachmentFont((B=(s=this.activeKeyboard)===null||s===void 0?void 0:s.metadata)===null||B===void 0?void 0:B.KFont,this.engineConfig.paths.fonts,this.engineConfig.hostDevice.OS),this.restoreLastActiveTarget()),[2,F];case 3:return y=h.sent(),u=function(){return(0,d.__awaiter)(C,void 0,void 0,function(){var b;return(0,d.__generator)(this,function(U){switch(U.label){case 0:return b=this.getFallbackStubKey(),b.id==e?[3,2]:[4,this.activateKeyboard(b.id,b.langId,!0).catch(function(){})];case 1:U.sent(),U.label=2;case 2:return[2]}})})},(o=this.engineConfig.alertHost)===null||o===void 0||o.wait(),Q=(y==null?void 0:y.message)||"Sorry, the "+e+" keyboard for "+n+" is not currently available.",y instanceof ze?console.error(y||Q):console.warn(y||Q),this.engineConfig.alertHost?((g=this.engineConfig.alertHost)===null||g===void 0||g.alert(Q,u),[3,6]):[3,4];case 4:return[4,u()];case 5:h.sent(),h.label=6;case 6:throw y;case 7:return[2]}})})},t.prototype._BlurKeyboardSettings=function(e,n,c){var l,r=this.activeKeyboard?this.activeKeyboard.keyboard.id:"",s=(l=this.activeKeyboard)===null||l===void 0?void 0:l.metadata.langId;n!==void 0&&c!==void 0&&(r=n,s=c),e&&e._kmwAttachment.keyboard!=null?(e._kmwAttachment.keyboard=r,e._kmwAttachment.languageCode=s):this.globalKeyboard=this.activeKeyboard},t.prototype._FocusKeyboardSettings=function(e,n){var c,l=e._kmwAttachment,r=this.globalKeyboard;l.keyboard!=null?this.activateKeyboard(l.keyboard,l.languageCode,!0):!n&&(r==null?void 0:r.metadata)!=((c=this._activeKeyboard)===null||c===void 0?void 0:c.metadata)&&this.activateKeyboard(r==null?void 0:r.metadata.id,r==null?void 0:r.metadata.langId,!0)},t.prototype._CommonFocusHelper=function(e){var n,c=this.focusAssistant,l=(n=this.activeKeyboard)===null||n===void 0?void 0:n.keyboard;return c.restoringFocus||(e==null||e.deadkeys().clear(),l==null||l.notify(0,e,1)),!c.restoringFocus&&this.mostRecentTarget!=e&&(c.maintainingFocus=!1),c.restoringFocus=!1,this.resetContext(),!1},t.prototype.doChangeEvent=function(e){if(e.changed){var n=new Event("change",{bubbles:!0,cancelable:!1});e.getElement().dispatchEvent(n)}e.changed=!1},t.prototype.getSavedKeyboardRaw=function(){var e=new se("KeymanWeb_Keyboard"),n=e.load(decodeURIComponent);return typeof n.current!="string"||n.current=="Keyboard_us:eng"?"Keyboard_us:en":n.current},t.prototype.getSavedKeyboard=function(){for(var e=this.getSavedKeyboardRaw(),n=this.keyboardCache.getStubList(),c,l=0;l0?n[0].KI+":"+n[0].KLC:"Keyboard_us:en"},t.prototype.restoreSavedKeyboard=function(e){var n=e,c=n.split(":");c.length<2&&(c[1]="");var l=this.keyboardCache.getStub(c[0],c[1])||this.keyboardCache.defaultStub;l&&this.activateKeyboard(c[0],c[1])},t.prototype.shutdown=function(){this.page.shutdown(),this.domEventTracker.shutdown()},t}(_t),ti=ir;var lr=function(i){(0,d.__extends)(t,i);function t(e){var n=i.call(this)||this;return n.contextManager=e,n}return t.prototype.isCommand=function(e){var n=this.codeForEvent(e);switch(n){case I.keyCodes.K_TAB:case I.keyCodes.K_TABBACK:case I.keyCodes.K_TABFWD:return!0;default:return i.prototype.isCommand.call(this,e)}},t.prototype.applyCommand=function(e,n){var c=this,l=this.codeForEvent(e),r=function(s){var B,o=c.contextManager,g=(B=o.activeTarget)===null||B===void 0?void 0:B.getElement(),a=o.page.findNeighboringInput(g,s);a.focus()};switch(l){case I.keyCodes.K_TAB:r((e.Lmodifiers&I.modifierCodes.SHIFT)!=0);break;case I.keyCodes.K_TABBACK:r(!0);break;case I.keyCodes.K_TABFWD:r(!1);break}i.prototype.applyCommand.call(this,e,n)},t}(Ge),ni=lr;function Sn(i){return i.keyCode?i.keyCode:i.which?i.which:null}function mt(i,t,e){if(i.cancelBubble===!0)return null;var n=Sn(i);if(n==null)return null;var c=t.modStateFlags,l=0,r=!1,s=!1,B=I.keyCodes;switch(n){case B.K_CTRL:case B.K_LCTRL:case B.K_RCTRL:case B.K_CONTROL:case B.K_LCONTROL:case B.K_RCONTROL:r=!0;break;case B.K_LMENU:case B.K_RMENU:case B.K_ALT:case B.K_LALT:case B.K_RALT:s=!0;break}l|=i.getModifierState("Shift")?16:0;var o=I.modifierCodes;i.getModifierState("Control")&&(l|=i.location!=0&&r?i.location==1?o.LCTRL:o.RCTRL:c&3),i.getModifierState("Alt")&&(l|=i.location!=0&&s?i.location==1?o.LALT:o.RALT:c&12);var g=0;g|=i.getModifierState("CapsLock")?o.CAPS:o.NO_CAPS,g|=i.getModifierState("NumLock")?o.NUM_LOCK:o.NO_NUM_LOCK,g|=i.getModifierState("ScrollLock")?o.SCROLL_LOCK:o.NO_SCROLL_LOCK,l|=g;var a=t.modStateFlags!=l;t.modStateFlags=l;var F=o.RALT|o.LCTRL;(c&F)==F&&(l&F)!=F&&(l&=~F),l&o.RALT&&(l&=~o.LCTRL);var y=I.modifierBitmasks,u=t.activeKeyboard,Q;u&&u.isChiral?(Q=l&y.CHIRAL,u.emulatesAltGr&&(Q&y.ALT_GR_SIM)==y.ALT_GR_SIM&&(Q^=y.ALT_GR_SIM,Q|=o.RALT)):Q=l&16|(l&(o.LCTRL|o.RCTRL)?32:0)|(l&(o.LALT|o.RALT)?64:0),Q|=i.metaKey?o.META:0,e.browser==x.Browser.Firefox&&de.browserMap.FF["k"+n]&&(n=de.browserMap.FF["k"+n]);var C=new $({device:e,kName:"",Lcode:n,Lmodifiers:Q,Lstates:g,LmodifierChange:a,isSynthetic:!1});u&&u.isMnemonic&&C.setMnemonicCode(i.getModifierState("Shift"),i.getModifierState("CapsLock"));var h=typeof i.charCode!="undefined"&&i.charCode!=null&&(i.charCode==0||(Q&111)!=0);if(C.LisVirtualKey=h||i.type!="keypress",u&&!u.isMnemonic){var b=de.languageMap[t.baseLayout];b&&b["k"+C.Lcode]&&(C.Lcode=b["k"+C.Lcode]),!u.definesPositionalOrMnemonic&&!(C.Lmodifiers&96)&&(C=new $({Lcode:de._USKeyCodeToCharCode(C),Lmodifiers:0,LisVirtualKey:!1,vkCode:C.Lcode,Lstates:C.Lstates,kName:"",device:e,isSynthetic:!1}))}var U=new $(C);return U.source=i,U}var rr=function(i){(0,d.__extends)(t,i);function t(e,n,c){var l=i.call(this)||this;l.domEventTracker=new Ie,l.swallowKeypress=!1,l._KeyDown=function(B){var o,g=l.contextManager.activeKeyboard,a=be(B);if(!a||g==null)return!0;var F=a.getElement();return((o=F==null?void 0:F.className)===null||o===void 0?void 0:o.indexOf("kmw-disabled"))>=0?!0:l.keyDown(B)},l._KeyPress=function(B){var o=be(B);return!o||l.activeKeyboard==null?!0:l.keyPress(B)},l._KeyUp=function(B){var o=be(B),g=mt(B,l.processor,l.hardDevice);if(g==null)return!0;var a=o.getElement();if(g.Lcode==13){var F=!1;if(N(a,"HTMLTextAreaElement")&&(F=!0),!F){if(a instanceof a.ownerDocument.defaultView.HTMLInputElement)if(a.form&&(a.type=="search"||a.type=="submit"))a.form.submit();else{var y=l.contextManager.page.findNeighboringInput(a,!1);y.focus()}return!0}}return l.keyUp(B)},l.hardDevice=e,l.contextManager=c,l.processor=n;var r=c.page,s=l.domEventTracker;return r.on("enabled",function(B){var o=he(B);if(!(o instanceof ne))s.attachDOMEvent(B,"keypress",l._KeyPress),s.attachDOMEvent(B,"keydown",l._KeyDown),s.attachDOMEvent(B,"keyup",l._KeyUp);else{var g=o.getElement().contentDocument;s.attachDOMEvent(g.body,"keydown",l._KeyDown),s.attachDOMEvent(g.body,"keypress",l._KeyPress),s.attachDOMEvent(g.body,"keyup",l._KeyUp)}}),r.off("disabled",function(B){var o=he(B);if(!(o instanceof ne))s.detachDOMEvent(B,"keypress",l._KeyPress),s.detachDOMEvent(B,"keydown",l._KeyDown),s.detachDOMEvent(B,"keyup",l._KeyUp);else{var g=o.getElement().contentDocument;s.detachDOMEvent(g.body,"keydown",l._KeyDown),s.detachDOMEvent(g.body,"keypress",l._KeyPress),s.detachDOMEvent(g.body,"keyup",l._KeyUp)}}),l}return Object.defineProperty(t.prototype,"activeKeyboard",{get:function(){return this.contextManager.activeKeyboard.keyboard},enumerable:!1,configurable:!0}),t.prototype.keyDown=function(e){var n=this;this.swallowKeypress=!1;var c=mt(e,this.processor,this.hardDevice);if(c==null)return!0;var l={LeventMatched:!1};return this.emit("keyevent",c,function(r,s){l.LeventMatched=r&&!r.triggerKeyDefault,l.LeventMatched?(e&&e.preventDefault&&(e.preventDefault(),e.stopPropagation()),n.swallowKeypress=!!c.Lcode,c.Lcode==8&&(n.swallowKeypress=!1)):n.swallowKeypress=!1}),!l.LeventMatched},t.prototype.keyUp=function(e){var n=mt(e,this.processor,this.hardDevice);if(n==null)return!0;var c=be(e);return this.processor.doModifierPress(n,c,!1)},t.prototype.keyPress=function(e){var n=mt(e,this.processor,this.hardDevice);if(n==null||n.LisVirtualKey)return!0;if(!this.activeKeyboard.isMnemonic)return!this.swallowKeypress||n.Lcode<32||this.hardDevice.browser==x.Browser.Safari&&n.Lcode>63232&&n.Lcode<63744;var c={};return this.swallowKeypress||this.emit("keyevent",n,function(l,r){c.preventDefaultKeystroke=!!l}),this.swallowKeypress||c.preventDefaultKeystroke?(this.swallowKeypress=!1,e&&e.preventDefault&&(e.preventDefault(),e.stopPropagation()),!1):(this.swallowKeypress=!1,!0)},t.prototype.shutdown=function(){this.domEventTracker.shutdown()},t}(Pt),ci=rr;var ii=function(){function i(){this.innerWidth=window.innerWidth,this.innerHeight=window.innerHeight}return i.prototype.equals=function(t){return this.innerWidth==t.innerWidth&&this.innerHeight==t.innerHeight},i}(),li=function(){function i(t){this.idlePermutationCounter=i.IDLE_PERMUTATION_CAP,this.keyman=t}return i.prototype.resolve=function(){var t,e=this.keyman.osk;(t=this.keyman.touchLanguageMenu)===null||t===void 0||t.hide(),this.keyman.touchLanguageMenu=null,e.setNeedsLayout(),this.oskVisible&&e.present(),this.isActive=!1,this.updateTimer&&(window.clearInterval(this.updateTimer),this.rotState=null)},i.prototype.initNewRotation=function(){this.oskVisible=this.keyman.osk.isVisible(),this.keyman.osk.hideNow(),this.isActive=!0},i.prototype.init=function(){var t=this,e=this.keyman.config.hostDevice.OS,n=this.keyman.util;e=="ios"?(n.attachDOMEvent(window,"orientationchange",function(){return t.iOSEventHandler(),!1}),n.attachDOMEvent(window,"resize",function(){return t.iOSEventHandler(),!1})):e=="android"&&("onmozorientationchange"in screen?n.attachDOMEvent(screen,"mozorientationchange",function(){return t.initNewRotation(),!1}):n.attachDOMEvent(window,"orientationchange",function(){return t.initNewRotation(),!1}),n.attachDOMEvent(window,"resize",function(){return t.resolve(),!1}))},i.prototype.iOSEventHandler=function(){this.isActive||(this.initNewRotation(),this.rotState=new ii,this.updateTimer=window.setInterval(this.iOSEventUpdate.bind(this),i.UPDATE_INTERVAL)),this.idlePermutationCounter=0},i.prototype.iOSEventUpdate=function(){var t=new ii;this.rotState.equals(t)?++this.idlePermutationCounter==i.IDLE_PERMUTATION_CAP&&this.resolve():(this.rotState=t,this.idlePermutationCounter=0)},i.IDLE_PERMUTATION_CAP=15,i.UPDATE_INTERVAL=20,i}();var ri=function(){function i(t,e){var n=this;this.domEventTracker=new Ie,this.suppressFocusCheck=function(c){return n.focusAssistant.isTargetForcingScroll()&&(c.stopPropagation(),c.cancelBubble=!0),!0},this.pageFocusHandler=function(){var c;return!n.focusAssistant.maintainingFocus&&((c=n.engine.osk)===null||c===void 0?void 0:c.vkbd)&&(n.engine.contextManager.deactivateCurrentTarget(),n.engine.contextManager.resetContext()),!1},this.touchStartActivationHandler=function(c){var l=n.engine.osk;if(!l)return!1;var r=n.engine.config.hostDevice;if(n.deactivateOnRelease=!0,n.touchY=c.touches[0].screenY,n.deactivateOnScroll=!1,r.OS=="android"&&r.browser=="chrome"){if(typeof l._Box=="undefined"||typeof l._Box.style=="undefined")return!1;var s=c.target.parentElement;if(typeof s!="undefined"&&s!=null&&(s.className.indexOf("kmw-key-")>=0||typeof s.parentElement!="undefined"&&s.parentElement!=null&&(s=s.parentElement,s.className.indexOf("kmw-key-")>=0)))return!1;n.deactivateOnScroll=!0}return!1},this.touchMoveActivationHandler=function(c){n.deactivateOnScroll&&(n.focusAssistant.focusing=!1,n.engine.contextManager.deactivateCurrentTarget());var l=c.touches[0].screenY,r=n.touchY;return(l-r>5||r-l<5)&&(n.deactivateOnRelease=!1),!1},this.touchEndActivationHandler=function(c){return n.deactivateOnRelease&&!n.engine.touchLanguageMenu&&!n.focusAssistant.focusing&&n.engine.contextManager.deactivateCurrentTarget(),n.deactivateOnRelease=!1,!1},this._WindowLoad=function(){document.body.scrollTop=0,typeof document.documentElement!="undefined"&&(document.documentElement.scrollTop=0)},this._WindowUnload=function(){n.engine.shutdown()},this.window=t,this.engine=e,this.attachHandlers(),e.config.hostDevice.touchable&&(this.buildPageTrailer(),this.rotationProcessor=new li(this.engine),this.rotationProcessor.init())}return i.prototype.buildPageTrailer=function(){var t=this.mobilePageTrailer=document.createElement("div"),e=t.style;e.width="100%",e.height=screen.width/2+"px",document.body.appendChild(t)},Object.defineProperty(i.prototype,"focusAssistant",{get:function(){return this.engine.contextManager.focusAssistant},enumerable:!1,configurable:!0}),i.prototype.attachHandlers=function(){var t=this.domEventTracker,e=this.engine.config.hostDevice,n=this.window.document.body;t.attachDOMEvent(this.window,"focus",this.pageFocusHandler,!1),t.attachDOMEvent(this.window,"blur",this.pageFocusHandler,!1),t.attachDOMEvent(n,"focus",this.suppressFocusCheck,!0),t.attachDOMEvent(n,"blur",this.suppressFocusCheck,!0),e.touchable&&(t.attachDOMEvent(n,"touchstart",this.touchStartActivationHandler,!1),t.attachDOMEvent(n,"touchmove",this.touchMoveActivationHandler,!1),t.attachDOMEvent(n,"touchend",this.touchEndActivationHandler,!1)),t.attachDOMEvent(window,"load",this._WindowLoad,!1),t.attachDOMEvent(window,"unload",this._WindowUnload,!1),t.attachDOMEvent(document,"keyup",this.engine.hotkeyManager._Process,!1)},i.prototype.shutdown=function(){var t,e=this.domEventTracker,n=this.engine.config.hostDevice,c=this.window.document.body;e.detachDOMEvent(this.window,"focus",this.pageFocusHandler,!1),e.detachDOMEvent(this.window,"blur",this.pageFocusHandler,!1),e.detachDOMEvent(c,"focus",this.suppressFocusCheck,!0),e.detachDOMEvent(c,"blur",this.suppressFocusCheck,!0),n.touchable&&(e.detachDOMEvent(c,"touchstart",this.touchStartActivationHandler,!1),e.detachDOMEvent(c,"touchmove",this.touchMoveActivationHandler,!1),e.detachDOMEvent(c,"touchend",this.touchEndActivationHandler,!1),(t=this.mobilePageTrailer)===null||t===void 0||t.parentElement.removeChild(this.mobilePageTrailer)),e.detachDOMEvent(window,"load",this._WindowLoad,!1),e.detachDOMEvent(window,"unload",this._WindowUnload,!1),e.detachDOMEvent(document,"keyup",this.engine.hotkeyManager._Process,!1)},i}();function ce(i){var t=document.createElement(i);return t.style.userSelect="none",t.style.MozUserSelect="none",t.style.KhtmlUserSelect="none",t.style.UserSelect="none",t.style.WebkitUserSelect="none",t}function ve(i,t){try{if(i&&typeof window.getComputedStyle!="undefined")return window.getComputedStyle(i,"").getPropertyValue(t)}catch(e){}return""}function Zt(){try{if(this.device.formFactor=="desktop")return 1;var i=document.documentElement.clientWidth;if(screen.width>i)return 1;var t=screen.width;return this.landscapeView()?screen.widthscreen.height&&(t=screen.height),Math.round(100*t/i)/100}catch(e){return 1}}var si=function(){function i(t){this.keyman=t,this.scrolling=!1,this.shim=this.constructShim()}return i.prototype.constructShim=function(){var t=this,e=this,n=ce("div"),c=this.keyman.osk;return n.id="kmw-language-menu-background",n.addEventListener("touchstart",function(l){if(l.preventDefault(),e.hide(),l.touches.length>2){var r=l.touches[1].pageX,s=l.touches[1].pageY,B=c.vkbd.spaceBar;r>B.offsetLeft&&rB.offsetTop&&sr.scrollHeight-r.offsetHeight-1&&(r.scrollTop=r.scrollHeight-r.offsetHeight-1)},!1),this.activeLgNo=this.addLanguages(B,e);var y=B.childNodes.length-1;if(this.lgList.style.visibility="hidden",document.body.appendChild(this.lgList),t.OS=="android"&&"devicePixelRatio"in window&&(this.lgList.style.fontSize=2/window.devicePixelRatio+"em"),t.OS=="android"&&t.formFactor=="tablet"&&"devicePixelRatio"in window){var u=parseInt(ve(c,"width"),10),Q=c.style;isNaN(u)||(Q.width=Q.maxWidth=2*u/window.devicePixelRatio+"px"),u=parseInt(ve(r,"width"),10),Q=r.style,isNaN(u)||(Q.width=Q.maxWidth=2*u/window.devicePixelRatio+"px"),u=parseInt(ve(B,"width"),10),Q=B.style,isNaN(u)||(Q.width=Q.maxWidth=2*u/window.devicePixelRatio+"px")}this.adjust(0);var C=F.childNodes[1].offsetTop-F.childNodes[0].offsetTop,h=Math.floor(c.offsetHeight/26),b=Math.round(100*h/C)/100,U=b>.6?1:2;for(b>1.25&&(b=1.25),g=0;g<26;g++){var V=F.childNodes[g].style;U==2&&g%2==1?V.display="none":(V.fontSize=b*U+"em",V.lineHeight=h*U+"px")}var X=r.offsetWidth;r.scrollHeight>r.offsetHeight+3?X=X+F.offsetWidth:F.style.display="none",c.style.width=X+"px",this.lgList.style.visibility="",this.scrollToIndex(this.activeLgNo,r,B)}},i.prototype.adjust=function(t){var e=this.keyman.osk,n=this.keyman.config.hostDevice,c=this.lgList,l=c.firstChild,r=l.firstChild,s=0,B=c.style,o=c.childNodes[1],g=window.innerHeight-e.vkbd.lgKey.offsetHeight-16,a=r.childNodes.length+t-1,F=r.firstChild.firstChild.offsetHeight,y=a*F;n.OS=="ios"&&(n.formFactor=="phone"?(s=D()?36:0,g=(window.innerHeight-s-16)*Zt()):n.formFactor=="tablet"&&(s=D()?16:0,g=g-s)),B.left=v(e.vkbd.lgKey)+"px",y>g&&(y=g),B.height=y+"px",B.bottom="0px",o.style.height=l.style.height=B.height},i.prototype.scrollToLanguage=function(t,e,n){t.stopImmediatePropagation(),t.stopPropagation(),t.preventDefault();var c=t.touches[0].target;if(c.nodeName=="P"){var l,r,s=c.innerHTML.charCodeAt(0),B=n.childNodes;try{for(l=0;l=s));l++);}catch(o){}this.scrollToIndex(l,e,n)}},i.prototype.scrollToIndex=function(t,e,n){var c,l=.5;try{c=n.firstChild.getBoundingClientRect().height*(t-l)+1,e.scrollTop=c}catch(r){c=0}try{e.scrollTop<0&&(e.scrollTop=0),e.scrollTop>e.scrollHeight-e.offsetHeight-1&&(e.scrollTop=e.scrollHeight-e.offsetHeight-1)}catch(r){}},i.prototype.addLanguages=function(t,e){var n,c=e.length,l=this.keyman.config.hostDevice,r,s,B,o=[];for(s=0;s1)for(F.className="kbd-list",F.innerHTML=o[r]+"...",F.scrolled=!1,F.ontouchend=function(X){X.stopPropagation(),X.target.scrolled?X.target.scrolled=!1:this.parentNode.className=this.parentNode.className=="kbd-list-closed"?"kbd-list-open":"kbd-list-closed",V.adjust(this.parentNode.className=="kbd-list-closed"?0:this.kList.length)},F.addEventListener("touchstart",function(X){X.stopPropagation()},!1),F.addEventListener("touchmove",function(X){X.target.scrolled=!0,X.stopPropagation()},!1),y=0;y=u-1&&(l.y0=Q);else if(C>0)y.scrollTop<2&&(l.y0=Q);else return;return(C<-5||C>5)&&(l.scrolling=!0,this.className=this.className.replace(/\s*selected/,""),l.y0=Q),!0},g=function(F){return typeof F.stopImmediatePropagation!="undefined"?F.stopImmediatePropagation():F.stopPropagation(),l.scrolling?this.className=this.className.replace(/\s*selected/,""):(l.keyman.contextManager.focusAssistant.setFocusTimer(),l.lgList.style.display="none",l.keyman.contextManager.activateKeyboard(this.kn,this.kc,!0),l.keyman.contextManager.restoreLastActiveTarget(),l.hide()),s(),!0},a=function(F){s()};e.onmspointerdown=B,e.addEventListener("touchstart",B,!1),e.onmspointermove=o,e.addEventListener("touchmove",o,!1),e.onmspointerout=g,e.addEventListener("touchend",g,!1),e.addEventListener("touchcancel",a,!1)},i.prototype.hide=function(){var t=this,e=this.keyman.osk;this.lgList&&(e.vkbd.highlightKey(e.vkbd.lgKey,!1),this.lgList.style.visibility="hidden",window.setTimeout(function(){t.shim.parentElement&&(document.body.removeChild(t.shim),document.body.removeChild(t.lgList))},500)),this.keyman.touchLanguageMenu=null},i}();function Bi(i,t,e){var n=this,c=e.focusAssistant;t.on("globekey",function(l,r){r&&t.hostDevice.touchable&&(i.touchLanguageMenu=new si(i),i.touchLanguageMenu.show()),t.vkbd&&t.vkbd.highlightKey(l,!1)}),t.on("hiderequested",function(l){t&&(t.startHide(!0),e.forgetActiveTarget())}),t.addEventListener("hide",function(l){var r;l!=null&&l.HiddenByUser&&((r=e.activeTarget)===null||r===void 0||r.focus())}),t.on("showbuild",function(){var l;(l=i.config.alertHost)===null||l===void 0||l.alert("KeymanWeb Version "+Wt.VERSION+'

Copyright © 2007-2023 SIL International')}),t.on("dragmove",function(l){return(0,d.__awaiter)(n,void 0,void 0,function(){return(0,d.__generator)(this,function(r){switch(r.label){case 0:return c.restoringFocus=!0,[4,l];case 1:return r.sent(),e.restoreLastActiveTarget(),c.restoringFocus=!1,c.setMaintainingFocus(!1),[2]}})})}),t.on("resizemove",function(l){return(0,d.__awaiter)(n,void 0,void 0,function(){return(0,d.__generator)(this,function(r){switch(r.label){case 0:return c.restoringFocus=!0,[4,l];case 1:return r.sent(),e.restoreLastActiveTarget(),c.restoringFocus=!1,c.setMaintainingFocus(!1),[2]}})})}),t.on("pointerinteraction",function(l){return(0,d.__awaiter)(n,void 0,void 0,function(){return(0,d.__generator)(this,function(r){switch(r.label){case 0:return c.setMaintainingFocus(!0),[4,l];case 1:return r.sent(),c.setMaintainingFocus(!1),[2]}})})})}function Br(i){var t=document.createElement(i);return t.style.userSelect="none",t}var oi=function(){function i(t){this.getAbsoluteX=v,this.getAbsoluteY=Z,this._GetAbsoluteX=v,this._GetAbsoluteY=Z,this._GetAbsolute=this.getAbsolute,this.toNzString=this.nzString,this.createElement=Br,this.getStyleValue=ve,this.config=t,this.stylesheetManager=new Fe(document.body,t.applyCacheBusting),this.domEventTracker=new Ie}return i.prototype.isTouchDevice=function(){return this.config.hostDevice.touchable},i.prototype.getAbsolute=function(t){return{x:v(t),y:Z(t)}},i.prototype.getOption=function(t,e){return t in this.config.paths?this.config.paths[t]:t in this.config.options?this.config.options[t]:arguments.length>1?e:""},i.prototype.setOption=function(t,e){switch(t){case"attachType":break;case"ui":break;case"useAlerts":this.config.signalUser=e?new Ne:null;break;case"setActiveOnRegister":this.config.activateFirstKeyboard=!!e;break;case"spacebarText":this.config.spacebarText=e;break;default:throw new Error("Path-related options may not be changed after the engine has initialized.")}},i.prototype.loadCookie=function(t){var e=new se(t);return e.load(decodeURIComponent)},i.prototype.saveCookie=function(t,e){var n=new se(t);n.save(e,encodeURIComponent)},i.prototype.addStyleSheet=function(t){var e=Ve(t);return this.stylesheetManager.linkStylesheet(e),e},i.prototype.removeStyleSheet=function(t){return this.stylesheetManager.unlink(t)},i.prototype.linkStyleSheet=function(t){this.stylesheetManager.linkExternalSheet(t)},i.prototype.getLanguageCodes=function(t){return t.indexOf("-")==-1?[t]:t.split("-")},i.prototype.attachDOMEvent=function(t,e,n,c){this.domEventTracker.attachDOMEvent(t,e,n,c)},i.prototype.detachDOMEvent=function(t,e,n,c){this.domEventTracker.detachDOMEvent(t,e,n,c)},Object.defineProperty(i.prototype,"alertHost",{get:function(){return this.config.alertHost?this.config.alertHost:(this._alertHost||(this._alertHost=new Ne),this._alertHost)},enumerable:!1,configurable:!0}),i.prototype.alert=function(t,e){this.alertHost.alert(t,e)},i.prototype.nzString=function(t,e){var n="";return arguments.length>1&&(n=e),typeof t=="undefined"||t==null||t==0||t==""?n:""+t},i.prototype.toNumber=function(t,e){var n=parseInt(t,10);return isNaN(n)?e:n},i.prototype.toFloat=function(t,e){var n=parseFloat(t);return isNaN(n)?e:n},i.prototype.rgba=function(t,e,n,c,l){var r="transparent";try{r="rgba("+e+","+n+","+c+","+l+")"}catch(s){r="rgb("+e+","+n+","+c+")"}return r},i.prototype.shutdown=function(){var t,e,n;(t=this.stylesheetManager)===null||t===void 0||t.unlinkAll(),(e=this.domEventTracker)===null||e===void 0||e.shutdown(),(n=this._alertHost)===null||n===void 0||n.shutdown()},i}();var or=function(){function i(t,e,n){this.code=t,this.shift=e,this.handler=n}return i.prototype.matches=function(t,e){return this.code==t&&this.shift==e},i}(),ai=function(){function i(){var t=this;this.hotkeys=[],this._Process=function(e){e||(e=window.event);var n=Sn(e);if(n==null)return!1;for(var c=(e.shiftKey?16:0)|(e.ctrlKey?32:0)|(e.altKey?64:0),l=0;l=o&&(g-=window.innerHeight-c.osk._Box.offsetHeight-s.offsetHeight-2,g<0&&(g=0)),g!=0&&window.scrollTo(0,g+o)}}),c}return Object.defineProperty(t.prototype,"util",{get:function(){return this._util},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"views",{get:function(){return pn},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"initialized",{get:function(){return this._initialized},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"ui",{get:function(){return this._ui},set:function(e){this._ui&&this._ui.shutdown(),this._ui=e,this.config.deferForInitialization.hasResolved&&e.initialize()},enumerable:!1,configurable:!0}),t.prototype.init=function(e){return(0,d.__awaiter)(this,void 0,void 0,function(){var n,c,l,r;return(0,d.__generator)(this,function(s){switch(s.label){case 0:return n=new at,c=n.detect(),l=(0,d.__assign)((0,d.__assign)({},Pc),e),this.config.hostDevice=c,this.config.initialize(l),this._initialized=1,[4,tt()];case 1:return s.sent(),this.config.deferForInitialization.hasFinalized?[2,Promise.resolve()]:[4,i.prototype.init.call(this,l)];case 2:return s.sent(),this.contextManager.initialize(),r=this.contextManager.getSavedKeyboardRaw(),c.touchable?this.osk=new Gn(this):this.osk=new Xn(this),Bi(this,this.osk,this.contextManager),this.pageIntegration=new ri(window,this),String.kmwEnableSupplementaryPlane(!0),this.config.finalizeInit(),this.ui&&(this.ui.initialize(),this.legacyAPIEvents.callEvent("loaduserinterface",{})),this._initialized=2,[4,Promise.resolve()];case 3:return s.sent(),this.contextManager.restoreSavedKeyboard(r),[2]}})})},Object.defineProperty(t.prototype,"register",{get:function(){return this.keyboardRequisitioner.cloudQueryEngine.registerFromCloud},enumerable:!1,configurable:!0}),t.prototype.getUIState=function(){return this.contextManager.focusAssistant.getUIState()},t.prototype.activatingUI=function(e){this.contextManager.focusAssistant.setMaintainingFocus(!!e)},t.prototype.setKeyboardForControl=function(e,n,c){if(e instanceof e.ownerDocument.defaultView.HTMLIFrameElement){console.warn("'keymanweb.setKeyboardForControl' cannot set keyboard on iframes.");return}if(!this.isAttached(e)){console.error("KeymanWeb is not attached to element "+e);return}var l=null;if(n&&(l=this.keyboardRequisitioner.cache.getStub(n,c),!l))throw new Error("No keyboard has been registered with id ".concat(n," and language code ").concat(c,"."));this.contextManager.setKeyboardForTarget(e._kmwAttachment.interface,n,c)},t.prototype.getKeyboardForControl=function(e){var n=he(e);return this.contextManager.getKeyboardStubForTarget(n).id},t.prototype.getLanguageForControl=function(e){var n=he(e);return this.contextManager.getKeyboardStubForTarget(n).langId},t.prototype.isAttached=function(e){return this.contextManager.page.isAttached(e)},t.prototype.addKeyboards=function(){for(var e=this,n=[],c=0;c= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n };\r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n __awaiter = function (thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n };\r\n\r\n __generator = function (thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n };\r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n /** @deprecated */\r\n \r\n\r\n /** @deprecated */\r\n \r\n\r\n \r\n\r\n __await = function (v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n };\r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n exporter(\"__extends\", __extends);\r\n exporter(\"__assign\", __assign);\r\n \r\n exporter(\"__decorate\", __decorate);\r\n \r\n \r\n \r\n \r\n \r\n \r\n exporter(\"__awaiter\", __awaiter);\r\n exporter(\"__generator\", __generator);\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n exporter(\"__await\", __await);\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n});\r\n", + "'use strict';\n\nvar has = Object.prototype.hasOwnProperty\n , prefix = '~';\n\n/**\n * Constructor to create a storage for our `EE` objects.\n * An `Events` instance is a plain object whose properties are event names.\n *\n * @constructor\n * @private\n */\nfunction Events() {}\n\n//\n// We try to not inherit from `Object.prototype`. In some engines creating an\n// instance in this way is faster than calling `Object.create(null)` directly.\n// If `Object.create(null)` is not supported we prefix the event names with a\n// character to make sure that the built-in object properties are not\n// overridden or used as an attack vector.\n//\nif (Object.create) {\n Events.prototype = Object.create(null);\n\n //\n // This hack is needed because the `__proto__` property is still inherited in\n // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5.\n //\n if (!new Events().__proto__) prefix = false;\n}\n\n/**\n * Representation of a single event listener.\n *\n * @param {Function} fn The listener function.\n * @param {*} context The context to invoke the listener with.\n * @param {Boolean} [once=false] Specify if the listener is a one-time listener.\n * @constructor\n * @private\n */\nfunction EE(fn, context, once) {\n this.fn = fn;\n this.context = context;\n this.once = once || false;\n}\n\n/**\n * Add a listener for a given event.\n *\n * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.\n * @param {(String|Symbol)} event The event name.\n * @param {Function} fn The listener function.\n * @param {*} context The context to invoke the listener with.\n * @param {Boolean} once Specify if the listener is a one-time listener.\n * @returns {EventEmitter}\n * @private\n */\nfunction addListener(emitter, event, fn, context, once) {\n if (typeof fn !== 'function') {\n throw new TypeError('The listener must be a function');\n }\n\n var listener = new EE(fn, context || emitter, once)\n , evt = prefix ? prefix + event : event;\n\n if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;\n else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);\n else emitter._events[evt] = [emitter._events[evt], listener];\n\n return emitter;\n}\n\n/**\n * Clear event by name.\n *\n * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.\n * @param {(String|Symbol)} evt The Event name.\n * @private\n */\nfunction clearEvent(emitter, evt) {\n if (--emitter._eventsCount === 0) emitter._events = new Events();\n else delete emitter._events[evt];\n}\n\n/**\n * Minimal `EventEmitter` interface that is molded against the Node.js\n * `EventEmitter` interface.\n *\n * @constructor\n * @public\n */\nfunction EventEmitter() {\n this._events = new Events();\n this._eventsCount = 0;\n}\n\n/**\n * Return an array listing the events for which the emitter has registered\n * listeners.\n *\n * @returns {Array}\n * @public\n */\nEventEmitter.prototype.eventNames = function eventNames() {\n var names = []\n , events\n , name;\n\n if (this._eventsCount === 0) return names;\n\n for (name in (events = this._events)) {\n if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);\n }\n\n if (Object.getOwnPropertySymbols) {\n return names.concat(Object.getOwnPropertySymbols(events));\n }\n\n return names;\n};\n\n/**\n * Return the listeners registered for a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @returns {Array} The registered listeners.\n * @public\n */\nEventEmitter.prototype.listeners = function listeners(event) {\n var evt = prefix ? prefix + event : event\n , handlers = this._events[evt];\n\n if (!handlers) return [];\n if (handlers.fn) return [handlers.fn];\n\n for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {\n ee[i] = handlers[i].fn;\n }\n\n return ee;\n};\n\n/**\n * Return the number of listeners listening to a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @returns {Number} The number of listeners.\n * @public\n */\nEventEmitter.prototype.listenerCount = function listenerCount(event) {\n var evt = prefix ? prefix + event : event\n , listeners = this._events[evt];\n\n if (!listeners) return 0;\n if (listeners.fn) return 1;\n return listeners.length;\n};\n\n/**\n * Calls each of the listeners registered for a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @returns {Boolean} `true` if the event had listeners, else `false`.\n * @public\n */\nEventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {\n var evt = prefix ? prefix + event : event;\n\n if (!this._events[evt]) return false;\n\n var listeners = this._events[evt]\n , len = arguments.length\n , args\n , i;\n\n if (listeners.fn) {\n if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);\n\n switch (len) {\n case 1: return listeners.fn.call(listeners.context), true;\n case 2: return listeners.fn.call(listeners.context, a1), true;\n case 3: return listeners.fn.call(listeners.context, a1, a2), true;\n case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;\n case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;\n case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;\n }\n\n for (i = 1, args = new Array(len -1); i < len; i++) {\n args[i - 1] = arguments[i];\n }\n\n listeners.fn.apply(listeners.context, args);\n } else {\n var length = listeners.length\n , j;\n\n for (i = 0; i < length; i++) {\n if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);\n\n switch (len) {\n case 1: listeners[i].fn.call(listeners[i].context); break;\n case 2: listeners[i].fn.call(listeners[i].context, a1); break;\n case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;\n case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break;\n default:\n if (!args) for (j = 1, args = new Array(len -1); j < len; j++) {\n args[j - 1] = arguments[j];\n }\n\n listeners[i].fn.apply(listeners[i].context, args);\n }\n }\n }\n\n return true;\n};\n\n/**\n * Add a listener for a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @param {Function} fn The listener function.\n * @param {*} [context=this] The context to invoke the listener with.\n * @returns {EventEmitter} `this`.\n * @public\n */\nEventEmitter.prototype.on = function on(event, fn, context) {\n return addListener(this, event, fn, context, false);\n};\n\n/**\n * Add a one-time listener for a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @param {Function} fn The listener function.\n * @param {*} [context=this] The context to invoke the listener with.\n * @returns {EventEmitter} `this`.\n * @public\n */\nEventEmitter.prototype.once = function once(event, fn, context) {\n return addListener(this, event, fn, context, true);\n};\n\n/**\n * Remove the listeners of a given event.\n *\n * @param {(String|Symbol)} event The event name.\n * @param {Function} fn Only remove the listeners that match this function.\n * @param {*} context Only remove the listeners that have this context.\n * @param {Boolean} once Only remove one-time listeners.\n * @returns {EventEmitter} `this`.\n * @public\n */\nEventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {\n var evt = prefix ? prefix + event : event;\n\n if (!this._events[evt]) return this;\n if (!fn) {\n clearEvent(this, evt);\n return this;\n }\n\n var listeners = this._events[evt];\n\n if (listeners.fn) {\n if (\n listeners.fn === fn &&\n (!once || listeners.once) &&\n (!context || listeners.context === context)\n ) {\n clearEvent(this, evt);\n }\n } else {\n for (var i = 0, events = [], length = listeners.length; i < length; i++) {\n if (\n listeners[i].fn !== fn ||\n (once && !listeners[i].once) ||\n (context && listeners[i].context !== context)\n ) {\n events.push(listeners[i]);\n }\n }\n\n //\n // Reset the array, or remove it completely if we have no more listeners.\n //\n if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;\n else clearEvent(this, evt);\n }\n\n return this;\n};\n\n/**\n * Remove all listeners, or those of the specified event.\n *\n * @param {(String|Symbol)} [event] The event name.\n * @returns {EventEmitter} `this`.\n * @public\n */\nEventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {\n var evt;\n\n if (event) {\n evt = prefix ? prefix + event : event;\n if (this._events[evt]) clearEvent(this, evt);\n } else {\n this._events = new Events();\n this._eventsCount = 0;\n }\n\n return this;\n};\n\n//\n// Alias methods names because people roll like that.\n//\nEventEmitter.prototype.off = EventEmitter.prototype.removeListener;\nEventEmitter.prototype.addListener = EventEmitter.prototype.on;\n\n//\n// Expose the prefix.\n//\nEventEmitter.prefixed = prefix;\n\n//\n// Allow `EventEmitter` to be imported as module namespace.\n//\nEventEmitter.EventEmitter = EventEmitter;\n\n//\n// Expose the module.\n//\nif ('undefined' !== typeof module) {\n module.exports = EventEmitter;\n}\n", + "export * from '../../../../node_modules/tslib/tslib.js';\r\n\r\nexport * as tslib from '../../../../node_modules/tslib/tslib.js';", + "import EventEmitter from \"eventemitter3\";\r\n\r\nimport { DeviceSpec, KeyboardProperties, ManagedPromise, OutputTarget, physicalKeyDeviceAlias, RuleBehavior, SpacebarText } from \"@keymanapp/keyboard-processor\";\r\nimport { PathConfiguration, PathOptionDefaults, PathOptionSpec } from \"keyman/engine/paths\";\r\nimport { Device } from \"keyman/engine/device-detect\";\r\nimport { KeyboardStub } from \"keyman/engine/package-cache\";\r\n\r\ninterface EventMap {\r\n 'spacebartext': (mode: SpacebarText) => void;\r\n}\r\n\r\nexport class EngineConfiguration extends EventEmitter {\r\n // The app/webview path replaces this during init, but we expect to have something set for this\r\n // during engine construction, which occurs earlier. So no `readonly`, sadly.\r\n //\r\n // May also be manipulated by Developer's debug-host?\r\n public hostDevice: DeviceSpec;\r\n readonly sourcePath: string;\r\n readonly deferForInitialization: ManagedPromise;\r\n\r\n private _paths: PathConfiguration;\r\n public activateFirstKeyboard: boolean;\r\n private _spacebarText: SpacebarText;\r\n private _stubNamespacer?: (KeyboardStub) => void;\r\n\r\n public applyCacheBusting: boolean = false;\r\n\r\n // sourcePath: see `var sPath =` in kmwbase.ts. It is not obtainable headlessly.\r\n constructor(sourcePath: string, device?: DeviceSpec) {\r\n super();\r\n\r\n if(!device) {\r\n const deviceDetector = new Device();\r\n deviceDetector.detect();\r\n\r\n device = deviceDetector.coreSpec;\r\n }\r\n\r\n this.sourcePath = sourcePath;\r\n this.hostDevice = device;\r\n this.deferForInitialization = new ManagedPromise();\r\n }\r\n\r\n initialize(options: Required) {\r\n if(!this._paths) {\r\n this._paths = new PathConfiguration(options, this.sourcePath);\r\n } else {\r\n this._paths.updateFromOptions(options);\r\n }\r\n\r\n if(typeof options.setActiveOnRegister == 'boolean') {\r\n this.activateFirstKeyboard = options.setActiveOnRegister;\r\n } else {\r\n this.activateFirstKeyboard = true;\r\n }\r\n\r\n this._spacebarText = options.spacebarText;\r\n\r\n // Make sure this is accessible to stubs for use in generating display names!\r\n KeyboardProperties.spacebarTextMode = () => this.spacebarText;\r\n }\r\n\r\n finalizeInit() {\r\n this.deferForInitialization.resolve();\r\n }\r\n\r\n get paths() {\r\n return this._paths;\r\n }\r\n\r\n get spacebarText() {\r\n return this._spacebarText;\r\n }\r\n\r\n set spacebarText(value: SpacebarText) {\r\n if(this._spacebarText != value) {\r\n this._spacebarText = value;\r\n this.emit('spacebartext', value);\r\n }\r\n }\r\n\r\n get softDevice(): DeviceSpec {\r\n return this.hostDevice;\r\n }\r\n\r\n get hardDevice(): DeviceSpec {\r\n return physicalKeyDeviceAlias(this.hostDevice);\r\n }\r\n\r\n get stubNamespacer() {\r\n return this._stubNamespacer;\r\n }\r\n\r\n set stubNamespacer(functor: (stub: KeyboardStub) => void) {\r\n this._stubNamespacer = functor;\r\n }\r\n\r\n debugReport(): Record {\r\n return {\r\n hostDevice: this.hostDevice,\r\n initialized: this.deferForInitialization.hasFinalized\r\n }\r\n }\r\n\r\n /**\r\n * Facilitates implementation of additional functionality for finalized keystroke-event rules\r\n * after postKeystroke takes effect. Any behaviors defined here should be considered 'readonly' in\r\n * terms of context and should instead facilitate integration with the engine's host platform.\r\n * @param ruleBehavior The full effects of keystroke + postkeystroke rules from a processed keystroke.\r\n * @param outputTarget The engine's current source for context\r\n */\r\n onRuleFinalization(ruleBehavior: RuleBehavior, outputTarget: OutputTarget) {};\r\n}\r\n\r\nexport interface InitOptionSpec extends PathOptionSpec {\r\n /**\r\n * If set to true || \"true\" or if left undefined, the engine will automatically select the first available\r\n * keyboard for activation.\r\n *\r\n * Note that keyboards specified locally are synchronously loaded while cloud keyboards are async; as a\r\n * result, a locally-specified keyboard will generally be available \"sooner\", even if added \"later\".\r\n */\r\n setActiveOnRegister?: boolean;\r\n\r\n /**\r\n * Determines the default text shown on the spacebar. If undefined, uses `LANGUAGE_KEYBOARD`\r\n */\r\n spacebarText?: SpacebarText;\r\n}\r\n\r\nexport const InitOptionDefaults: Required = {\r\n setActiveOnRegister: true, // only needed for browser?\r\n spacebarText: SpacebarText.LANGUAGE_KEYBOARD, // useful in both, for OSK config.\r\n ...PathOptionDefaults\r\n}", + "// TODO: Move to separate folder: 'codes'\r\n// We should start splitting off code needed by keyboards even without a KeyboardProcessor active.\r\n// There's an upcoming `/common/web/types` package that 'codes' and 'keyboards' may fit well within.\r\n// In fact, there's a file there (on its branch) that should be merged with this one!\r\n\r\nconst Codes = {\r\n // Define Keyman Developer modifier bit-flags (exposed for use by other modules)\r\n // Compare against /common/include/kmx_file.h. CTRL+F \"#define LCTRLFLAG\" to find the secton.\r\n modifierCodes: {\r\n \"LCTRL\":0x0001, // LCTRLFLAG\r\n \"RCTRL\":0x0002, // RCTRLFLAG\r\n \"LALT\":0x0004, // LALTFLAG\r\n \"RALT\":0x0008, // RALTFLAG\r\n \"SHIFT\":0x0010, // K_SHIFTFLAG\r\n \"CTRL\":0x0020, // K_CTRLFLAG\r\n \"ALT\":0x0040, // K_ALTFLAG\r\n // TENTATIVE: Represents command keys, which some OSes use for shortcuts we don't\r\n // want to block. No rule will ever target a modifier set with this bit set to 1.\r\n \"META\":0x0080, // K_METAFLAG\r\n \"CAPS\":0x0100, // CAPITALFLAG\r\n \"NO_CAPS\":0x0200, // NOTCAPITALFLAG\r\n \"NUM_LOCK\":0x0400, // NUMLOCKFLAG\r\n \"NO_NUM_LOCK\":0x0800, // NOTNUMLOCKFLAG\r\n \"SCROLL_LOCK\":0x1000, // SCROLLFLAG\r\n \"NO_SCROLL_LOCK\":0x2000, // NOTSCROLLFLAG\r\n \"VIRTUAL_KEY\":0x4000, // ISVIRTUALKEY\r\n \"VIRTUAL_CHAR_KEY\":0x8000 // VIRTUALCHARKEY // Unused by KMW, but reserved for use by other Keyman engines.\r\n },\r\n\r\n modifierBitmasks: {\r\n \"ALL\":0x007F,\r\n \"ALT_GR_SIM\": (0x0001 | 0x0004),\r\n \"CHIRAL\":0x001F, // The base bitmask for chiral keyboards. Includes SHIFT, which is non-chiral.\r\n \"IS_CHIRAL\":0x000F, // Used to test if a bitmask uses a chiral modifier.\r\n \"NON_CHIRAL\":0x0070 // The default bitmask, for non-chiral keyboards\r\n },\r\n\r\n stateBitmasks: {\r\n \"ALL\":0x3F00,\r\n \"CAPS\":0x0300,\r\n \"NUM_LOCK\":0x0C00,\r\n \"SCROLL_LOCK\":0x3000\r\n },\r\n\r\n // Define standard keycode numbers (exposed for use by other modules)\r\n keyCodes: {\r\n \"K_BKSP\":8,\"K_TAB\":9,\"K_ENTER\":13,\r\n \"K_SHIFT\":16,\"K_CONTROL\":17,\"K_ALT\":18,\"K_PAUSE\":19,\"K_CAPS\":20,\r\n \"K_ESC\":27,\"K_SPACE\":32,\"K_PGUP\":33,\r\n \"K_PGDN\":34,\"K_END\":35,\"K_HOME\":36,\"K_LEFT\":37,\"K_UP\":38,\r\n \"K_RIGHT\":39,\"K_DOWN\":40,\"K_SEL\":41,\"K_PRINT\":42,\"K_EXEC\":43,\r\n \"K_INS\":45,\"K_DEL\":46,\"K_HELP\":47,\"K_0\":48,\r\n \"K_1\":49,\"K_2\":50,\"K_3\":51,\"K_4\":52,\"K_5\":53,\"K_6\":54,\"K_7\":55,\r\n \"K_8\":56,\"K_9\":57,\"K_A\":65,\"K_B\":66,\"K_C\":67,\"K_D\":68,\"K_E\":69,\r\n \"K_F\":70,\"K_G\":71,\"K_H\":72,\"K_I\":73,\"K_J\":74,\"K_K\":75,\"K_L\":76,\r\n \"K_M\":77,\"K_N\":78,\"K_O\":79,\"K_P\":80,\"K_Q\":81,\"K_R\":82,\"K_S\":83,\r\n \"K_T\":84,\"K_U\":85,\"K_V\":86,\"K_W\":87,\"K_X\":88,\"K_Y\":89,\"K_Z\":90,\r\n \"K_NP0\":96,\"K_NP1\":97,\"K_NP2\":98,\r\n \"K_NP3\":99,\"K_NP4\":100,\"K_NP5\":101,\"K_NP6\":102,\r\n \"K_NP7\":103,\"K_NP8\":104,\"K_NP9\":105,\"K_NPSTAR\":106,\r\n \"K_NPPLUS\":107,\"K_SEPARATOR\":108,\"K_NPMINUS\":109,\"K_NPDOT\":110,\r\n \"K_NPSLASH\":111,\"K_F1\":112,\"K_F2\":113,\"K_F3\":114,\"K_F4\":115,\r\n \"K_F5\":116,\"K_F6\":117,\"K_F7\":118,\"K_F8\":119,\"K_F9\":120,\r\n \"K_F10\":121,\"K_F11\":122,\"K_F12\":123,\"K_NUMLOCK\":144,\"K_SCROLL\":145,\r\n \"K_LSHIFT\":160,\"K_RSHIFT\":161,\"K_LCONTROL\":162,\"K_RCONTROL\":163,\r\n \"K_LALT\":164,\"K_RALT\":165,\r\n \"K_COLON\":186,\"K_EQUAL\":187,\"K_COMMA\":188,\"K_HYPHEN\":189,\r\n \"K_PERIOD\":190,\"K_SLASH\":191,\"K_BKQUOTE\":192,\r\n \"K_LBRKT\":219,\"K_BKSLASH\":220,\"K_RBRKT\":221,\r\n \"K_QUOTE\":222,\"K_oE2\":226,\"K_OE2\":226,\r\n \"K_LOPT\":50001,\"K_ROPT\":50002,\r\n \"K_NUMERALS\":50003,\"K_SYMBOLS\":50004,\"K_CURRENCIES\":50005,\r\n \"K_UPPER\":50006,\"K_LOWER\":50007,\"K_ALPHA\":50008,\r\n \"K_SHIFTED\":50009,\"K_ALTGR\":50010,\r\n \"K_TABBACK\":50011,\"K_TABFWD\":50012\r\n },\r\n\r\n codesUS: [\r\n ['0123456789',';=,-./`', '[\\\\]\\''],\r\n [')!@#$%^&*(',':+<_>?~', '{|}\"']\r\n ],\r\n\r\n isKnownOSKModifierKey(keyID: string): boolean {\r\n switch(keyID) {\r\n case 'K_SHIFT':\r\n case 'K_LOPT':\r\n case 'K_ROPT':\r\n case 'K_NUMLOCK': // Often used for numeric layers.\r\n case 'K_CAPS':\r\n return true;\r\n default:\r\n if(Codes.keyCodes[keyID] >= 50000) { // A few are used by `sil_euro_latin`.\r\n return true; // is a 'K_' key defined for layer shifting or 'control' use.\r\n }\r\n // Refer to text/codes.ts - these are Keyman-custom \"keycodes\" used for\r\n // layer shifting keys. To be safe, we currently let K_TABBACK and\r\n // K_TABFWD through, though we might be able to drop them too.\r\n const code = Codes[keyID];\r\n if(code > 50000 && code < 50011) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n },\r\n\r\n\r\n /**\r\n * Get modifier key state from layer id\r\n *\r\n * @param {string} layerId layer id (e.g. ctrlshift)\r\n * @return {number} modifier key state (desktop keyboards)\r\n */\r\n getModifierState(layerId: string): number {\r\n var modifier=0;\r\n if(layerId.indexOf('shift') >= 0) {\r\n modifier |= Codes.modifierCodes['SHIFT'];\r\n }\r\n\r\n // The chiral checks must not be directly exclusive due each other to visual OSK feedback.\r\n var ctrlMatched=false;\r\n if(layerId.indexOf('leftctrl') >= 0) {\r\n modifier |= Codes.modifierCodes['LCTRL'];\r\n ctrlMatched=true;\r\n }\r\n if(layerId.indexOf('rightctrl') >= 0) {\r\n modifier |= Codes.modifierCodes['RCTRL'];\r\n ctrlMatched=true;\r\n }\r\n if(layerId.indexOf('ctrl') >= 0 && !ctrlMatched) {\r\n modifier |= Codes.modifierCodes['CTRL'];\r\n }\r\n\r\n var altMatched=false;\r\n if(layerId.indexOf('leftalt') >= 0) {\r\n modifier |= Codes.modifierCodes['LALT'];\r\n altMatched=true;\r\n }\r\n if(layerId.indexOf('rightalt') >= 0) {\r\n modifier |= Codes.modifierCodes['RALT'];\r\n altMatched=true;\r\n }\r\n if(layerId.indexOf('alt') >= 0 && !altMatched) {\r\n modifier |= Codes.modifierCodes['ALT'];\r\n }\r\n\r\n return modifier;\r\n },\r\n\r\n /**\r\n * Get state key state from layer id\r\n *\r\n * @param {string} layerId layer id (e.g. caps)\r\n * @return {number} modifier key state (desktop keyboards)\r\n */\r\n getStateFromLayer(layerId: string): number {\r\n var modifier=0;\r\n\r\n if(layerId.indexOf('caps') >= 0) {\r\n modifier |= Codes.modifierCodes['CAPS'];\r\n } else {\r\n modifier |= Codes.modifierCodes['NO_CAPS'];\r\n }\r\n\r\n return modifier;\r\n }\r\n}\r\n\r\nexport default Codes;", + "// TODO: Move to separate folder: 'codes'\r\n// We should start splitting off code needed by keyboards even without a KeyboardProcessor active.\r\n// There's an upcoming `/common/web/types` package that 'codes' and 'keyboards' may fit well within.\r\n\r\nimport Codes from \"./codes.js\";\r\nimport type KeyEvent from \"./keyEvent.js\";\r\nimport type OutputTarget from \"./outputTarget.js\";\r\n\r\n// The only members referenced are to produce warning and error logs. A little abstraction\r\n// via an optional 'logger' interface can maintain it while facilitating a the split alluded\r\n// to above.\r\n//\r\n// Alternatively, we could just... not take in the parameter at all, which'd also facilitate\r\n// the future modularization effort.\r\nimport RuleBehavior from \"./ruleBehavior.js\";\r\n\r\nexport enum EmulationKeystrokes {\r\n Enter = '\\n',\r\n Backspace = '\\b'\r\n}\r\n\r\n/**\r\n * Defines a collection of static library functions that define KeymanWeb's default (implied) keyboard rule behaviors.\r\n */\r\nexport default class DefaultRules {\r\n public constructor() {\r\n }\r\n\r\n codeForEvent(Lkc: KeyEvent) {\r\n return Codes.keyCodes[Lkc.kName] || Lkc.Lcode;;\r\n }\r\n\r\n /**\r\n * Serves as a default keycode lookup table. This may be referenced safely by mnemonic handling without fear of side-effects.\r\n * Also used by Processor.defaultRuleBehavior to generate output after filtering for special cases.\r\n */\r\n public forAny(Lkc: KeyEvent, isMnemonic: boolean, ruleBehavior?: RuleBehavior) {\r\n var char = '';\r\n\r\n // A pretty simple table of lookups, corresponding VERY closely to the original defaultKeyOutput.\r\n if((char = this.forSpecialEmulation(Lkc)) != null) {\r\n return char;\r\n } else if(!isMnemonic && ((char = this.forNumpadKeys(Lkc)) != null)) {\r\n return char;\r\n } else if((char = this.forUnicodeKeynames(Lkc, ruleBehavior)) != null) {\r\n return char;\r\n } else if((char = this.forBaseKeys(Lkc, ruleBehavior)) != null) {\r\n return char;\r\n } else {\r\n // // For headless and embeddded, we may well allow '\\t'. It's DOM mode that has other uses.\r\n // // Not originally defined for text output within defaultKeyOutput.\r\n // // We can't enable it yet, as it'll cause hardware keystrokes in the DOM to output '\\t' rather\r\n // // than rely on the browser-default handling.\r\n let code = this.codeForEvent(Lkc);\r\n switch(code) {\r\n // case Codes.keyCodes['K_TAB']:\r\n // case Codes.keyCodes['K_TABBACK']:\r\n // case Codes.keyCodes['K_TABFWD']:\r\n // return '\\t';\r\n default:\r\n return null;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * isCommand - returns a boolean indicating if a non-text event should be triggered by the keystroke.\r\n */\r\n public isCommand(Lkc: KeyEvent): boolean {\r\n let code = this.codeForEvent(Lkc);\r\n\r\n switch(code) {\r\n // Should we ever implement them:\r\n // case Codes.keyCodes['K_LEFT']: // would not output text, but would alter the caret's position in the context.\r\n // case Codes.keyCodes['K_RIGHT']:\r\n // return true;\r\n default:\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Used when a RuleBehavior represents a non-text \"command\" within the Engine. This will generally\r\n * trigger events that require context reset - often by moving the caret or by moving what OutputTarget\r\n * the caret is in. However, we let those events perform the actual context reset.\r\n *\r\n * Note: is extended by DOM-aware KeymanWeb code.\r\n */\r\n public applyCommand(Lkc: KeyEvent, outputTarget: OutputTarget): void {\r\n // Notes for potential default-handling extensions:\r\n //\r\n // switch(code) {\r\n // // Problem: clusters, and doing them right.\r\n // // The commented-out code below should be a decent starting point, but clusters make it complex.\r\n // // Mostly based on pre-12.0 code, but the general idea should be relatively clear.\r\n //\r\n // case Codes.keyCodes['K_LEFT']:\r\n // if(touchAlias) {\r\n // var caretPos = keymanweb.getTextCaret(Lelem);\r\n // keymanweb.setTextCaret(Lelem, caretPos - 1 >= 0 ? caretPos - 1 : 0);\r\n // }\r\n // break;\r\n // case Codes.keyCodes['K_RIGHT']:\r\n // if(touchAlias) {\r\n // var caretPos = keymanweb.getTextCaret(Lelem);\r\n // keymanweb.setTextCaret(Lelem, caretPos + 1);\r\n // }\r\n // if(code == VisualKeyboard.keyCodes['K_RIGHT']) {\r\n // break;\r\n // }\r\n // }\r\n //\r\n // Note that these would be useful even outside of a DOM context.\r\n }\r\n\r\n /**\r\n * Codes matched here generally have default implementations when in a browser but require emulation\r\n * for 'synthetic' `OutputTarget`s like `Mock`s, which have no default text handling.\r\n */\r\n public forSpecialEmulation(Lkc: KeyEvent): EmulationKeystrokes {\r\n let code = this.codeForEvent(Lkc);\r\n\r\n switch(code) {\r\n case Codes.keyCodes['K_BKSP']:\r\n return EmulationKeystrokes.Backspace;\r\n case Codes.keyCodes['K_ENTER']:\r\n return EmulationKeystrokes.Enter;\r\n // case Codes.keyCodes['K_DEL']:\r\n // return '\\u007f'; // 127, ASCII / Unicode control code for DEL.\r\n default:\r\n return null;\r\n }\r\n }\r\n\r\n // Should not be used for mnenomic keyboards. forAny()'s use of this method checks first.\r\n public forNumpadKeys(Lkc: KeyEvent) {\r\n // Translate numpad keystrokes into their non-numpad equivalents\r\n if(Lkc.Lcode >= Codes.keyCodes[\"K_NP0\"] && Lkc.Lcode <= Codes.keyCodes[\"K_NPSLASH\"]) {\r\n // Number pad, numlock on\r\n if(Lkc.Lcode < 106) {\r\n var Lch = Lkc.Lcode-48;\r\n } else {\r\n Lch = Lkc.Lcode-64;\r\n }\r\n let ch = String._kmwFromCharCode(Lch); //I3319\r\n return ch;\r\n } else {\r\n return null;\r\n }\r\n }\r\n\r\n // Test for fall back to U_xxxxxx key id\r\n // For this first test, we ignore the keyCode and use the keyName\r\n public forUnicodeKeynames(Lkc: KeyEvent, ruleBehavior?: RuleBehavior) {\r\n const keyName = Lkc.kName;\r\n\r\n // Test for fall back to U_xxxxxx key id\r\n // For this first test, we ignore the keyCode and use the keyName\r\n if(!keyName || keyName.substr(0,2) != 'U_') {\r\n return null;\r\n }\r\n\r\n let result = '';\r\n const codePoints = keyName.substr(2).split('_');\r\n for(let codePoint of codePoints) {\r\n const codePointValue = parseInt(codePoint, 16);\r\n if (((0x0 <= codePointValue) && (codePointValue <= 0x1F)) || ((0x80 <= codePointValue) && (codePointValue <= 0x9F)) || isNaN(codePointValue)) {\r\n // Code points [U_0000 - U_001F] and [U_0080 - U_009F] refer to Unicode C0 and C1 control codes.\r\n // Check the codePoint number and do not allow output of these codes via U_xxxxxx shortcuts.\r\n // Also handles invalid identifiers (e.g. `U_ghij`) for which parseInt returns NaN\r\n if(ruleBehavior) {\r\n ruleBehavior.errorLog = (\"Suppressing Unicode control code in \" + keyName);\r\n }\r\n // We'll attempt to add valid chars\r\n continue;\r\n } else {\r\n // String.fromCharCode() is inadequate to handle the entire range of Unicode\r\n // Someday after upgrading to ES2015, can use String.fromCodePoint()\r\n result += String.kmwFromCharCode(codePointValue);\r\n }\r\n }\r\n return result ? result : null;\r\n }\r\n\r\n // Test for otherwise unimplemented keys on the the base default & shift layers.\r\n // Those keys must be blocked by keyboard rules if intentionally unimplemented; otherwise, this function will trigger.\r\n public forBaseKeys(Lkc: KeyEvent, ruleBehavior?: RuleBehavior) {\r\n let n = Lkc.Lcode;\r\n let keyShiftState = Lkc.Lmodifiers;\r\n\r\n // check if exact match to SHIFT's code. Only the 'default' and 'shift' layers should have default key outputs.\r\n // TODO: Extend to allow AltGr as well - better mnemonic support.\r\n if(keyShiftState == Codes.modifierCodes['SHIFT']) {\r\n keyShiftState = 1;\r\n } else if(keyShiftState != 0) {\r\n if(ruleBehavior) {\r\n ruleBehavior.warningLog = \"KMW only defines default key output for the 'default' and 'shift' layers!\";\r\n }\r\n return null;\r\n }\r\n\r\n // Now that keyShiftState is either 0 or 1, we can use the following structure to determine the default output.\r\n try {\r\n if(n == Codes.keyCodes['K_SPACE']) {\r\n return ' ';\r\n } else if(n >= Codes.keyCodes['K_0'] && n <= Codes.keyCodes['K_9']) { // The number keys.\r\n return Codes.codesUS[keyShiftState][0][n-Codes.keyCodes['K_0']];\r\n } else if(n >= Codes.keyCodes['K_A'] && n <= Codes.keyCodes['K_Z']) { // The base letter keys\r\n return String.fromCharCode(n+(keyShiftState?0:32)); // 32 is the offset from uppercase to lowercase.\r\n } else if(n >= Codes.keyCodes['K_COLON'] && n <= Codes.keyCodes['K_BKQUOTE']) {\r\n return Codes.codesUS[keyShiftState][1][n-Codes.keyCodes['K_COLON']];\r\n } else if(n >= Codes.keyCodes['K_LBRKT'] && n <= Codes.keyCodes['K_QUOTE']) {\r\n return Codes.codesUS[keyShiftState][2][n-Codes.keyCodes['K_LBRKT']];\r\n } else if(n == Codes.keyCodes['K_oE2']) {\r\n return keyShiftState ? '|' : '\\\\';\r\n }\r\n } catch (e) {\r\n if(ruleBehavior) {\r\n ruleBehavior.errorLog = \"Error detected with default mapping for key: code = \" + n + \", shift state = \" + (keyShiftState == 1 ? 'shift' : 'default');\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n}\r\n", + "// TODO: Move to separate folder: 'codes'\r\n// We should start splitting off code needed by keyboards even without a KeyboardProcessor active.\r\n// There's an upcoming `/common/web/types` package that 'codes' and 'keyboards' may fit well within.\r\n\r\n// KeyEvent may be a _little_ bit of pollution, but this IS what the Web OSK currently generates to signal\r\n// a key event. The most straightforward way to integrate Web OSK events on other platforms is to have\r\n// other platforms recognize and utilize this type.\r\n\r\nimport type Keyboard from \"../keyboards/keyboard.js\";\r\nimport { type DeviceSpec } from \"@keymanapp/web-utils\";\r\n\r\nimport Codes from './codes.js';\r\nimport DefaultRules from './defaultRules.js';\r\n\r\n// Represents a probability distribution over a keyboard's keys.\r\n// Defined here to avoid compilation issues.\r\nexport type KeyDistribution = {keyId: string, p: number}[];\r\n\r\n/**\r\n * A simple instance of the standard 'default rules' for keystroke processing from the\r\n * DefaultRules base class.\r\n */\r\nconst BASE_DEFAULT_RULES = new DefaultRules();\r\n\r\nexport interface KeyEventSpec {\r\n\r\n Lcode: number;\r\n Lstates: number;\r\n LmodifierChange?: boolean;\r\n Lmodifiers: number;\r\n LisVirtualKey?: boolean;\r\n vkCode?: number;\r\n kName: string;\r\n kLayer?: string; // The key's layer property\r\n kbdLayer?: string; // The virtual keyboard's active layer\r\n kNextLayer?: string;\r\n\r\n /**\r\n * Marks the active keyboard at the time that this KeyEvent was generated by the user.\r\n *\r\n * Note: this is NOT equivalent to the active keyboard at the time that the event handler begins\r\n * processing! It should be set via closure (or similar) on the event handler that can 100%\r\n * guarantee that the keyboard instance known to the handler has not changed during JS execution\r\n * since the user's interaction that raised the event.\r\n */\r\n srcKeyboard?: Keyboard;\r\n\r\n // Holds relevant event properties leading to construction of this KeyEvent.\r\n source?: any; // Technically, KeyEvent|MouseEvent|Touch - but those are DOM types that must be kept out of headless mode.\r\n // Holds a generated fat-finger distribution (when appropriate)\r\n keyDistribution?: KeyDistribution;\r\n\r\n /**\r\n * The device model for web-core to follow when processing the keystroke.\r\n */\r\n device: DeviceSpec;\r\n\r\n /**\r\n * `true` if this event was produced by sources other than a DOM-based KeyboardEvent.\r\n */\r\n isSynthetic?: boolean;\r\n}\r\n\r\n/**\r\n * This class is defined within its own file so that it can be loaded by code outside of KMW without\r\n * having to actually load the entirety of KMW.\r\n */\r\nexport default class KeyEvent implements KeyEventSpec {\r\n Lcode: number;\r\n Lstates: number;\r\n LmodifierChange?: boolean;\r\n Lmodifiers: number;\r\n LisVirtualKey?: boolean;\r\n vkCode?: number;\r\n kName: string;\r\n kLayer?: string; // The key's layer property\r\n kbdLayer?: string; // The virtual keyboard's active layer\r\n kNextLayer?: string;\r\n\r\n /**\r\n * Marks the active keyboard at the time that this KeyEvent was generated by the user.\r\n *\r\n * Note: this is NOT equivalent to the active keyboard at the time that the event handler begins\r\n * processing! It should be set via closure (or similar) on the event handler that can 100%\r\n * guarantee that the keyboard instance known to the handler has not changed during JS execution\r\n * since the user's interaction that raised the event.\r\n */\r\n srcKeyboard?: Keyboard;\r\n\r\n // Holds relevant event properties leading to construction of this KeyEvent.\r\n source?: any; // Technically, KeyEvent|MouseEvent|Touch - but those are DOM types that must be kept out of headless mode.\r\n // Holds a generated fat-finger distribution (when appropriate)\r\n keyDistribution?: KeyDistribution;\r\n\r\n /**\r\n * The device model for web-core to follow when processing the keystroke.\r\n */\r\n device: DeviceSpec;\r\n\r\n /**\r\n * `true` if this event was produced by sources other than a DOM-based KeyboardEvent.\r\n */\r\n isSynthetic: boolean = true;\r\n\r\n public constructor(keyEventSpec: KeyEventSpec) {\r\n for(let key in keyEventSpec) {\r\n if(keyEventSpec[key] !== undefined) {\r\n this[key] = keyEventSpec[key];\r\n }\r\n }\r\n }\r\n\r\n public static constructNullKeyEvent(device: DeviceSpec): KeyEvent {\r\n const keyEvent = new KeyEvent({\r\n Lcode: 0,\r\n kName: '',\r\n device: device,\r\n Lstates: undefined,\r\n Lmodifiers: undefined,\r\n vkCode: undefined,\r\n LisVirtualKey: undefined\r\n });\r\n return keyEvent;\r\n }\r\n\r\n get isModifier(): boolean {\r\n switch(this.Lcode) {\r\n case 16: //\"K_SHIFT\":16,\"K_CONTROL\":17,\"K_ALT\":18\r\n case 17:\r\n case 18:\r\n case 20: //\"K_CAPS\":20, \"K_NUMLOCK\":144,\"K_SCROLL\":145\r\n case 144:\r\n case 145:\r\n return true;\r\n default:\r\n return false;\r\n }\r\n }\r\n\r\n // FIXME: makes some bad assumptions.\r\n setMnemonicCode(shifted: boolean, capsActive: boolean) {\r\n // K_SPACE is not handled by defaultKeyOutput for physical keystrokes unless using touch-aliased elements.\r\n // It's also a \"exception required, March 2013\" for clickKey, so at least they both have this requirement.\r\n if(this.Lcode != Codes.keyCodes['K_SPACE']) {\r\n // So long as the key name isn't prefixed with 'U_', we'll get a default mapping based on the Lcode value.\r\n // We need to determine the mnemonic base character - for example, SHIFT + K_PERIOD needs to map to '>'.\r\n let mappingEvent: KeyEvent = new KeyEvent(this);\r\n for(let key in (this as KeyEvent)) {\r\n mappingEvent[key] = this[key];\r\n }\r\n\r\n // To facilitate storing relevant commands, we should probably reverse-lookup\r\n // the actual keyname instead.\r\n mappingEvent.kName = 'K_xxxx';\r\n mappingEvent.Lmodifiers = (shifted ? 0x10 : 0); // mnemonic lookups only exist for default & shift layers.\r\n var mappedChar: string = BASE_DEFAULT_RULES.forAny(mappingEvent, true);\r\n\r\n /* First, save a backup of the original code. This one won't needlessly trigger keyboard\r\n * rules, but allows us to replicate/emulate commands after rule processing if needed.\r\n * (Like backspaces)\r\n */\r\n this.vkCode = this.Lcode;\r\n if(mappedChar) {\r\n // Will return 96 for 'a', which is a keycode corresponding to Codes.keyCodes('K_NP1') - a numpad key.\r\n // That stated, we're in mnemonic mode - this keyboard's rules are based on the char codes.\r\n this.Lcode = mappedChar.charCodeAt(0);\r\n } else {\r\n // Don't let command-type keys (like K_DEL, which will output '.' otherwise!)\r\n // trigger keyboard rules.\r\n //\r\n // However, DO make sure modifier keys pass through safely.\r\n // (https://github.com/keymanapp/keyman/issues/3744)\r\n if(!this.isModifier) {\r\n delete this.Lcode;\r\n }\r\n }\r\n }\r\n\r\n if(capsActive) {\r\n // TODO: Needs fixing - does not properly mirror physical keystrokes, as Lcode range 96-111 corresponds\r\n // to numpad keys! (Physical keyboard section has its own issues here.)\r\n if((this.Lcode >= 65 && this.Lcode <= 90) /* 'A' - 'Z' */ || (this.Lcode >= 97 && this.Lcode <= 122) /* 'a' - 'z' */) {\r\n this.Lmodifiers ^= 0x10; // Flip the 'shifted' bit, so it'll act as the opposite key.\r\n this.Lcode ^= 0x20; // Flips the 'upper' vs 'lower' bit for the base 'a'-'z' ASCII alphabetics.\r\n }\r\n }\r\n }\r\n};", + "/***\r\n KeymanWeb 11.0\r\n Copyright 2019 SIL International\r\n***/\r\n\r\nimport type KeyEvent from \"./keyEvent.js\";\r\nimport { KeyEventSpec } from \"./keyEvent.js\";\r\n\r\nclass KeyMap {\r\n [keycode: string]: number;\r\n}\r\n\r\nclass BrowserKeyMaps {\r\n FF: KeyMap = new KeyMap();\r\n Safari: KeyMap = new KeyMap();\r\n Opera: KeyMap = new KeyMap();\r\n\r\n constructor() {\r\n // All three have been around since at least May 2014 / FF 29.\r\n // It'd hard to find precise history, but at least that much has been confirmed.\r\n // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode, on Feb 26 2021.\r\n this.FF['k61'] = 187; // = // FF 2.0\r\n this.FF['k59'] = 186; // ;\r\n this.FF['k173'] = 189; // -/_\r\n }\r\n}\r\n\r\nclass LanguageKeyMaps {\r\n [languageCode: string]: KeyMap;\r\n\r\n // // Here are some old legacy definitions that were no longer referenced but are likely related:\r\n // static _BaseLayoutEuro: {[code: string]: string} = {\r\n // 'se': '\\u00a71234567890+´~~~QWERTYUIOP\\u00c5\\u00a8\\'~~~ASDFGHJKL\\u00d6\\u00c4~~~~~ ` ~\r\n this['uk']['k192'] = 222; // ' @ => ' \"\r\n this['uk']['k222'] = 226; // # ~ => K_oE2 // I1504 - UK keyboard mixup #, \\\r\n this['uk']['k220'] = 220; // \\ | => \\ | // I1504 - UK keyboard mixup #, \\\r\n }\r\n}\r\n\r\nexport default class KeyMapping {\r\n static readonly browserMap: BrowserKeyMaps = new BrowserKeyMaps();\r\n static readonly languageMap: LanguageKeyMaps = new LanguageKeyMaps();\r\n\r\n private static _usCharCodes: KeyMap[];\r\n\r\n private constructor() {\r\n // Do not construct this class.\r\n }\r\n\r\n private static _usCodeInit() {\r\n var s0=new KeyMap(),s1=new KeyMap();\r\n\r\n s0['k192'] = 96;\r\n s0['k49'] = 49;\r\n s0['k50'] = 50;\r\n s0['k51'] = 51;\r\n s0['k52'] = 52;\r\n s0['k53'] = 53;\r\n s0['k54'] = 54;\r\n s0['k55'] = 55;\r\n s0['k56'] = 56;\r\n s0['k57'] = 57;\r\n s0['k48'] = 48;\r\n s0['k189'] = 45;\r\n s0['k187'] = 61;\r\n s0['k81'] = 113;\r\n s0['k87'] = 119;\r\n s0['k69'] = 101;\r\n s0['k82'] = 114;\r\n s0['k84'] = 116;\r\n s0['k89'] = 121;\r\n s0['k85'] = 117;\r\n s0['k73'] = 105;\r\n s0['k79'] = 111;\r\n s0['k80'] = 112;\r\n s0['k219'] = 91;\r\n s0['k221'] = 93;\r\n s0['k220'] = 92;\r\n s0['k65'] = 97;\r\n s0['k83'] = 115;\r\n s0['k68'] = 100;\r\n s0['k70'] = 102;\r\n s0['k71'] = 103;\r\n s0['k72'] = 104;\r\n s0['k74'] = 106;\r\n s0['k75'] = 107;\r\n s0['k76'] = 108;\r\n s0['k186'] = 59;\r\n s0['k222'] = 39;\r\n s0['k90'] = 122;\r\n s0['k88'] = 120;\r\n s0['k67'] = 99;\r\n s0['k86'] = 118;\r\n s0['k66'] = 98;\r\n s0['k78'] = 110;\r\n s0['k77'] = 109;\r\n s0['k188'] = 44;\r\n s0['k190'] = 46;\r\n s0['k191'] = 47;\r\n\r\n s1['k192'] = 126;\r\n s1['k49'] = 33;\r\n s1['k50'] = 64;\r\n s1['k51'] = 35;\r\n s1['k52'] = 36;\r\n s1['k53'] = 37;\r\n s1['k54'] = 94;\r\n s1['k55'] = 38;\r\n s1['k56'] = 42;\r\n s1['k57'] = 40;\r\n s1['k48'] = 41;\r\n s1['k189'] = 95;\r\n s1['k187'] = 43;\r\n s1['k81'] = 81;\r\n s1['k87'] = 87;\r\n s1['k69'] = 69;\r\n s1['k82'] = 82;\r\n s1['k84'] = 84;\r\n s1['k89'] = 89;\r\n s1['k85'] = 85;\r\n s1['k73'] = 73;\r\n s1['k79'] = 79;\r\n s1['k80'] = 80;\r\n s1['k219'] = 123;\r\n s1['k221'] = 125;\r\n s1['k220'] = 124;\r\n s1['k65'] = 65;\r\n s1['k83'] = 83;\r\n s1['k68'] = 68;\r\n s1['k70'] = 70;\r\n s1['k71'] = 71;\r\n s1['k72'] = 72;\r\n s1['k74'] = 74;\r\n s1['k75'] = 75;\r\n s1['k76'] = 76;\r\n s1['k186'] = 58;\r\n s1['k222'] = 34;\r\n s1['k90'] = 90;\r\n s1['k88'] = 88;\r\n s1['k67'] = 67;\r\n s1['k86'] = 86;\r\n s1['k66'] = 66;\r\n s1['k78'] = 78;\r\n s1['k77'] = 77;\r\n s1['k188'] = 60;\r\n s1['k190'] = 62;\r\n s1['k191'] = 63;\r\n\r\n KeyMapping._usCharCodes = [s0,s1];\r\n }\r\n\r\n /**\r\n * Function _USKeyCodeToCharCode\r\n * Scope Private\r\n * @param {Event} Levent KMW event object\r\n * @return {number} Character code\r\n * Description Translate keyboard codes to standard US layout codes\r\n */\r\n static _USKeyCodeToCharCode(Levent: KeyEvent | KeyEventSpec) {\r\n return KeyMapping.usCharCodes[Levent.Lmodifiers & 0x10 ? 1 : 0]['k'+Levent.Lcode];\r\n };\r\n\r\n public static get usCharCodes() {\r\n if(!KeyMapping._usCharCodes) {\r\n KeyMapping._usCodeInit();\r\n }\r\n\r\n return KeyMapping._usCharCodes;\r\n }\r\n}", + "import Codes from \"../text/codes.js\";\r\nimport KeyEvent, { KeyEventSpec } from \"../text/keyEvent.js\";\r\nimport KeyMapping from \"../text/keyMapping.js\";\r\nimport type { KeyDistribution } from \"../text/keyEvent.js\";\r\nimport type { LayoutKey, LayoutRow, LayoutLayer, LayoutFormFactor, ButtonClass } from \"./defaultLayouts.js\";\r\nimport type Keyboard from \"./keyboard.js\";\r\n\r\nimport { type DeviceSpec } from \"@keymanapp/web-utils\";\r\n\r\n// TS 3.9 changed behavior of getters to make them\r\n// non-enumerable by default. This broke our 'polyfill'\r\n// functions which depended on enumeration to copy the\r\n// relevant props over.\r\n// https://github.com/microsoft/TypeScript/pull/32264#issuecomment-677718191\r\nfunction Enumerable(\r\n target: unknown,\r\n propertyKey: string,\r\n descriptor: PropertyDescriptor\r\n) {\r\n descriptor.enumerable = true;\r\n};\r\n\r\nexport class ActiveKey implements LayoutKey {\r\n static readonly DEFAULT_PAD=15; // Padding to left of key, in virtual units\r\n static readonly DEFAULT_RIGHT_MARGIN=15; // Padding to right of right-most key, in virtual units\r\n static readonly DEFAULT_KEY_WIDTH=100; // Width of a key, if not specified, in virtual units\r\n\r\n // Defines key defaults\r\n static readonly DEFAULT_KEY = {\r\n text: '',\r\n width: ActiveKey.DEFAULT_KEY_WIDTH,\r\n sp: 0,\r\n pad: ActiveKey.DEFAULT_PAD\r\n };\r\n\r\n /** WARNING - DO NOT USE DIRECTLY outside of @keymanapp/keyboard-processor! */\r\n id?: string;\r\n\r\n // These are fine.\r\n width?: number;\r\n pad?: number;\r\n\r\n layer: string;\r\n displayLayer: string;\r\n nextlayer: string;\r\n sp?: ButtonClass;\r\n\r\n _baseKeyEvent: KeyEvent;\r\n isMnemonic: boolean = false;\r\n\r\n proportionalPad: number;\r\n proportionalX: number;\r\n proportionalWidth: number;\r\n\r\n sk?: ActiveKey[];\r\n\r\n // Keeping things simple here, as this was added LATE in 14.0 beta.\r\n // Could definitely extend in the future to instead return an object\r\n // that denotes the 'nature' of the key.\r\n // - isUnicode\r\n // - isHardwareKey\r\n // - etc.\r\n\r\n // Reference for the terminology in the comments below:\r\n // https://help.keyman.com/developer/current-version/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty\r\n\r\n /**\r\n * Matches the key code as set within Keyman Developer for the layout.\r\n * For example, K_R or U_0020. Denotes either physical keys or virtual keys with custom output,\r\n * with no additional metadata like layer or active modifiers.\r\n *\r\n * Is used to determine the keycode for input events, rule-matching, and keystroke processing.\r\n */\r\n @Enumerable\r\n public get baseKeyID(): string {\r\n if(typeof this.id === 'undefined') {\r\n return undefined;\r\n }\r\n\r\n return this.id;\r\n }\r\n\r\n @Enumerable\r\n public get isPadding(): boolean {\r\n // Does not include 9 (class: blank) as that may be an intentional 'catch' for misplaced\r\n // keystrokes.\r\n return this['sp'] == 10; // Button class: hidden.\r\n }\r\n\r\n /**\r\n * A unique identifier based on both the key ID & the 'desktop layer' to be used for the key.\r\n *\r\n * Allows diambiguation of scenarios where the same key ID is used twice within a layer, but\r\n * with different innate modifiers. (Refer to https://github.com/keymanapp/keyman/issues/4617)\r\n * The 'desktop layer' may be omitted if it matches the key's display layer.\r\n *\r\n * Examples, given a 'default' display layer, matching keys to Keyman keyboard language:\r\n *\r\n * ```\r\n * \"K_Q\"\r\n * + [K_Q]\r\n * \"K_Q+shift\"\r\n * + [K_Q SHIFT]\r\n * ```\r\n *\r\n * Useful when the active layer of an input-event is already known.\r\n */\r\n @Enumerable\r\n public get coreID(): string {\r\n if(typeof this.id === 'undefined') {\r\n return undefined;\r\n }\r\n\r\n let baseID = this.id || '';\r\n\r\n if(this.displayLayer != this.layer) {\r\n baseID = baseID + '+' + this.layer;\r\n }\r\n\r\n return baseID;\r\n }\r\n\r\n /**\r\n * A keyboard-unique identifier to be used for any display elements representing this key\r\n * in user interfaces and/or on-screen keyboards.\r\n *\r\n * Distinguishes between otherwise-identical keys on different layers of an OSK.\r\n * Includes identifying information about the key's display layer.\r\n *\r\n * Examples, given a 'default' display layer, matching keys to Keyman keyboard language:\r\n *\r\n * ```\r\n * \"default-K_Q\"\r\n * + [K_Q]\r\n * \"default-K_Q+shift\"\r\n * + [K_Q SHIFT]\r\n * ```\r\n *\r\n * Useful when only the active keyboard is known about an input event.\r\n */\r\n @Enumerable\r\n public get elementID(): string {\r\n if(typeof this.id === 'undefined') {\r\n return undefined;\r\n }\r\n\r\n return this.displayLayer + '-' + this.coreID;\r\n }\r\n\r\n @Enumerable\r\n public get baseKeyEvent(): KeyEvent {\r\n return new KeyEvent(this._baseKeyEvent);\r\n }\r\n\r\n /**\r\n * Converts key IDs of the U_* form to their corresponding UTF-16 text.\r\n * If an ID not matching the pattern is received, returns null.\r\n * @param id\r\n * @returns\r\n */\r\n static unicodeIDToText(id: string, errorCallback?: (codeAsString: string) => void) {\r\n if(!id || id.substring(0,2) != 'U_') {\r\n return null;\r\n }\r\n\r\n let result = '';\r\n const codePoints = id.substring(2).split('_');\r\n for(let codePoint of codePoints) {\r\n const codePointValue = parseInt(codePoint, 16);\r\n if (((0x0 <= codePointValue) && (codePointValue <= 0x1F)) ||\r\n ((0x80 <= codePointValue) && (codePointValue <= 0x9F)) ||\r\n isNaN(codePointValue)) {\r\n if(errorCallback) {\r\n errorCallback(codePoint);\r\n }\r\n continue;\r\n } else {\r\n // String.fromCharCode() is inadequate to handle the entire range of Unicode\r\n // Someday after upgrading to ES2015, can use String.fromCodePoint()\r\n result += String.kmwFromCharCode(codePointValue);\r\n }\r\n }\r\n return result ? result : null;\r\n }\r\n\r\n static sanitize(rawKey: LayoutKey) {\r\n if(typeof rawKey.width == 'string') {\r\n rawKey.width = parseInt(rawKey.width, 10);\r\n }\r\n // Handles NaN cases as well as 'set to 0' cases; both are intentional here.\r\n rawKey.width ||= ActiveKey.DEFAULT_KEY_WIDTH;\r\n\r\n if(typeof rawKey.pad == 'string') {\r\n rawKey.pad = parseInt(rawKey.pad, 10);\r\n }\r\n rawKey.pad ||= ActiveKey.DEFAULT_PAD;\r\n\r\n if(typeof rawKey.sp == 'string') {\r\n rawKey.sp = Number.parseInt(rawKey.sp, 10) as ButtonClass;\r\n }\r\n rawKey.sp ||= 0; // The default button class.\r\n }\r\n\r\n static polyfill(key: LayoutKey, keyboard: Keyboard, layout: ActiveLayout, displayLayer: string) {\r\n // Add class functions to the existing layout object, allowing it to act as an ActiveLayout.\r\n let dummy = new ActiveKey();\r\n let proto = Object.getPrototypeOf(dummy);\r\n\r\n for(let prop in dummy) {\r\n if(!key.hasOwnProperty(prop)) {\r\n let descriptor = Object.getOwnPropertyDescriptor(proto, prop);\r\n if(descriptor) {\r\n // It's a computed property! Copy the descriptor onto the key's object.\r\n Object.defineProperty(key, prop, descriptor);\r\n } else {\r\n key[prop] = dummy[prop];\r\n }\r\n }\r\n }\r\n\r\n // Ensure subkeys are also properly extended.\r\n if(key.sk) {\r\n for(let subkey of key.sk) {\r\n ActiveKey.polyfill(subkey, keyboard, layout, displayLayer);\r\n }\r\n }\r\n\r\n let aKey = key as ActiveKey;\r\n aKey.displayLayer = displayLayer;\r\n aKey.layer = aKey.layer || displayLayer;\r\n\r\n // Compute the key's base KeyEvent properties for use in future event generation\r\n aKey.constructBaseKeyEvent(keyboard, layout, displayLayer);\r\n }\r\n\r\n private constructBaseKeyEvent(keyboard: Keyboard, layout: ActiveLayout, displayLayer: string) {\r\n // Get key name and keyboard shift state (needed only for default layouts and physical keyboard handling)\r\n // Note - virtual keys should be treated case-insensitive, so we force uppercasing here.\r\n let layer = this.layer || displayLayer || '';\r\n let keyName= this.id ? this.id.toUpperCase() : null;\r\n\r\n // Start: mirrors _GetKeyEventProperties\r\n\r\n // First check the virtual key, and process shift, control, alt or function keys\r\n let props: KeyEventSpec = {\r\n // Override key shift state if specified for key in layout (corrected for popup keys KMEW-93)\r\n Lmodifiers: Codes.getModifierState(layer),\r\n Lstates: Codes.getStateFromLayer(layer),\r\n Lcode: keyName ? Codes.keyCodes[keyName] : 0,\r\n LisVirtualKey: true,\r\n vkCode: 0,\r\n kName: keyName,\r\n kLayer: layer,\r\n kbdLayer: displayLayer,\r\n kNextLayer: this.nextlayer,\r\n device: null,\r\n isSynthetic: true\r\n };\r\n\r\n let Lkc: KeyEvent = new KeyEvent(props);\r\n\r\n if(layout.keyboard) {\r\n let keyboard = layout.keyboard;\r\n\r\n // Include *limited* support for mnemonic keyboards (Sept 2012)\r\n // If a touch layout has been defined for a mnemonic keyout, do not perform mnemonic mapping for rules on touch devices.\r\n if(keyboard.isMnemonic && !(layout.isDefault && layout.formFactor != 'desktop')) {\r\n if(Lkc.Lcode != Codes.keyCodes['K_SPACE']) { // exception required, March 2013\r\n // Jan 2019 - interesting that 'K_SPACE' also affects the caps-state check...\r\n Lkc.vkCode = Lkc.Lcode;\r\n this.isMnemonic = true;\r\n }\r\n } else {\r\n Lkc.vkCode=Lkc.Lcode;\r\n }\r\n\r\n // Support version 1.0 KeymanWeb keyboards that do not define positional vs mnemonic\r\n if(!keyboard.definesPositionalOrMnemonic) {\r\n // Not the best pattern, but currently safe - we don't look up any properties of any of the\r\n // arguments in this use case, and the object's scope is extremely limited.\r\n Lkc.Lcode = KeyMapping._USKeyCodeToCharCode(keyboard.constructKeyEvent(null, null, {\r\n K_CAPS: false,\r\n K_NUMLOCK: false,\r\n K_SCROLL: false\r\n }));\r\n Lkc.LisVirtualKey=false;\r\n }\r\n }\r\n\r\n this._baseKeyEvent = Lkc;\r\n }\r\n\r\n public getSubkey(coreID: string): ActiveKey {\r\n if(this.sk) {\r\n for(let key of this.sk) {\r\n if(key.coreID == coreID) {\r\n return key;\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n}\r\n\r\nexport class ActiveRow implements LayoutRow {\r\n // Identify key labels (e.g. *Shift*) that require the special OSK font\r\n static readonly SPECIAL_LABEL=/\\*\\w+\\*/;\r\n\r\n id: number;\r\n key: ActiveKey[];\r\n\r\n /**\r\n * Used for calculating fat-fingering offsets.\r\n */\r\n proportionalY: number;\r\n\r\n private constructor() {\r\n\r\n }\r\n\r\n static sanitize(rawRow: LayoutRow) {\r\n for(const key of rawRow.key) {\r\n // Test for a trailing comma included in spec, added as null object by IE\r\n // It has only ever appeared at the end of a row's spec.\r\n if(key == null) {\r\n rawRow.key.length = rawRow.key.length-1;\r\n } else {\r\n ActiveKey.sanitize(key);\r\n }\r\n }\r\n\r\n if(typeof rawRow.id == 'string') {\r\n rawRow.id = Number.parseInt(rawRow.id, 10);\r\n }\r\n }\r\n\r\n static polyfill(row: LayoutRow, keyboard: Keyboard, layout: ActiveLayout, displayLayer: string, totalWidth: number, proportionalY: number) {\r\n // Apply defaults, setting the width and other undefined properties for each key\r\n let keys=row['key'];\r\n for(let j=0; j 0) {\r\n const finalKey = keys[keys.length-1] as ActiveKey;\r\n\r\n // If a single key, and padding is negative, add padding to right align the key\r\n if(keys.length == 1 && finalKey.pad < 0) {\r\n const keyPercent = finalKey.width/totalWidth;\r\n const padPercent = 1-(totalPercent + keyPercent + rightMargin);\r\n\r\n // compute center's default x-coord (used in headless modes)\r\n setProportions(finalKey, padPercent, keyPercent, totalPercent);\r\n } else {\r\n const padPercent = finalKey.pad/totalWidth;\r\n const keyPercent = 1-(totalPercent + padPercent + rightMargin);\r\n\r\n // compute center's default x-coord (used in headless modes)\r\n setProportions(finalKey, padPercent, keyPercent, totalPercent);\r\n }\r\n }\r\n\r\n // Add class functions to the existing layout object, allowing it to act as an ActiveLayout.\r\n let dummy = new ActiveRow();\r\n for(let key in dummy) {\r\n if(!row.hasOwnProperty(key)) {\r\n row[key] = dummy[key];\r\n }\r\n }\r\n\r\n let aRow = row as ActiveRow;\r\n aRow.proportionalY = proportionalY;\r\n }\r\n\r\n populateKeyMap(map: {[keyId: string]: ActiveKey}) {\r\n this.key.forEach(function(key: ActiveKey) {\r\n if(key.coreID) {\r\n map[key.coreID] = key;\r\n }\r\n });\r\n }\r\n}\r\n\r\nexport class ActiveLayer implements LayoutLayer {\r\n row: ActiveRow[];\r\n id: string;\r\n\r\n // These already exist on the objects, pre-polyfill...\r\n // but they still need to be proactively declared on this type.\r\n capsKey?: ActiveKey;\r\n numKey?: ActiveKey;\r\n scrollKey?: ActiveKey;\r\n\r\n totalWidth: number;\r\n\r\n defaultKeyProportionalWidth: number;\r\n rowProportionalHeight: number;\r\n\r\n /**\r\n * Facilitates mapping key id strings to their specification objects.\r\n */\r\n keyMap: {[keyId: string]: ActiveKey};\r\n\r\n constructor() {\r\n\r\n }\r\n\r\n static sanitize(rawLayer: LayoutLayer) {\r\n for(const row of rawLayer.row) {\r\n ActiveRow.sanitize(row);\r\n }\r\n }\r\n\r\n static polyfill(layer: LayoutLayer, keyboard: Keyboard, layout: ActiveLayout) {\r\n layer.aligned=false;\r\n\r\n // Create a DIV for each row of the group\r\n let rows=layer['row'];\r\n\r\n // Calculate the maximum row width (in layout units)\r\n let totalWidth=0;\r\n for(const row of rows) {\r\n let width=0;\r\n const keys=row['key'];\r\n\r\n for(const key of keys) {\r\n // So long as `sanitize` is called first, these coercions are safe.\r\n width += (key.width as number) + (key.pad as number);\r\n }\r\n\r\n if(width > totalWidth) {\r\n totalWidth = width;\r\n }\r\n }\r\n\r\n // Add default right margin\r\n if(layout.formFactor == 'desktop') {\r\n totalWidth += 5; // TODO: resolve difference between touch and desktop; why don't we use ActiveKey.DEFAULT_RIGHT_MARGIN?\r\n } else {\r\n totalWidth += ActiveKey.DEFAULT_RIGHT_MARGIN;\r\n }\r\n\r\n let rowCount = layer.row.length;\r\n for(let i=0; i probability, use a function parameter in place\r\n // of the formula in the loop below.\r\n for(let key in keyDists) {\r\n totalMass += keyProbs[key] = 1 / (Math.pow(keyDists[key], 2) + 1e-6); // Prevent div-by-0 errors.\r\n }\r\n\r\n for(let key in keyProbs) {\r\n keyProbs[key] /= totalMass;\r\n }\r\n\r\n return keyProbs;\r\n }\r\n\r\n /**\r\n * Computes a squared 'pseudo-distance' for the touch from each key. (Not a proper metric.)\r\n * Intended for use in generating a probability distribution over the keys based on the touch input.\r\n * @param touchCoords A proportional (x, y) coordinate of the touch within the keyboard's geometry.\r\n * Should be within [0, 0] to [1, 1].\r\n * @param kbdScaleRatio The ratio of the keyboard's horizontal scale to its vertical scale.\r\n * For a 400 x 200 keyboard, should be 2.\r\n */\r\n private keyTouchDistances(touchCoords: {x: number, y: number}, kbdScaleRatio: number): {[keyId: string]: number} {\r\n let layer = this;\r\n\r\n let keyDists: {[keyId: string]: number} = {};\r\n\r\n // This double-nested loop computes a pseudo-distance for the touch from each key. Quite useful for\r\n // generating a probability distribution.\r\n this.row.forEach(function(row: ActiveRow): void {\r\n row.key.forEach(function(key: ActiveKey): void {\r\n // If the key lacks an ID, just skip it. Sometimes used for padding.\r\n if(!key.baseKeyID) {\r\n return;\r\n } else {\r\n // Attempt to filter out known non-output keys.\r\n // Results in a more optimized distribution.\r\n if(Codes.isKnownOSKModifierKey(key.baseKeyID)) {\r\n return;\r\n } else if(key.isPadding) { // to the user, blank / padding keys do not exist.\r\n return;\r\n }\r\n }\r\n // These represent the within-key distance of the touch from the key's center.\r\n // Both should be on the interval [0, 0.5].\r\n let dx = Math.abs(touchCoords.x - key.proportionalX);\r\n let dy = Math.abs(touchCoords.y - row.proportionalY);\r\n\r\n // If the touch isn't within the key, these store the out-of-key distance\r\n // from the closest point on the key being checked.\r\n let distX: number, distY: number;\r\n\r\n if(dx > 0.5 * key.proportionalWidth) {\r\n distX = (dx - 0.5 * key.proportionalWidth);\r\n dx = 0.5;\r\n } else {\r\n distX = 0;\r\n dx /= key.proportionalWidth;\r\n }\r\n\r\n if(dy > 0.5 * layer.rowProportionalHeight) {\r\n distY = (dy - 0.5 * layer.rowProportionalHeight);\r\n dy = 0.5;\r\n } else {\r\n distY = 0;\r\n dy /= layer.rowProportionalHeight;\r\n }\r\n\r\n // Now that the differentials are computed, it's time to do distance scaling.\r\n //\r\n // For out-of-key distance, we scale the X component by the keyboard's aspect ratio\r\n // to get the actual out-of-key distance rather than proportional.\r\n distX *= kbdScaleRatio;\r\n\r\n // While the keys are rarely perfect squares, we map all within-key distance\r\n // to a square shape. (ALT/CMD should seem as close to SPACE as a 'B'.)\r\n //\r\n // For that square, we take the rowHeight as its edge lengths.\r\n distX += dx * layer.rowProportionalHeight;\r\n distY += dy * layer.rowProportionalHeight;\r\n\r\n let distance = distX * distX + distY * distY;\r\n keyDists[key.coreID] = distance;\r\n });\r\n });\r\n\r\n return keyDists;\r\n }\r\n\r\n getKey(keyId: string) {\r\n // Keys usually are specified in a \"long form\" prefixed with their layer's ID.\r\n if(keyId.indexOf(this.id + '-') == 0) {\r\n keyId = keyId.replace(this.id + '-', '');\r\n }\r\n\r\n let idComponents = keyId.split('::');\r\n if(idComponents.length > 1) {\r\n let baseKey = this.keyMap[idComponents[0]];\r\n return baseKey.getSubkey(idComponents[1]);\r\n } else {\r\n return this.keyMap[keyId];\r\n }\r\n }\r\n}\r\n\r\nexport class ActiveLayout implements LayoutFormFactor{\r\n layer: ActiveLayer[];\r\n font: string;\r\n keyLabels: boolean;\r\n isDefault?: boolean;\r\n keyboard: Keyboard;\r\n formFactor: DeviceSpec.FormFactor;\r\n\r\n /**\r\n * Facilitates mapping layer id strings to their specification objects.\r\n */\r\n layerMap: {[layerId: string]: ActiveLayer};\r\n\r\n private constructor() {\r\n\r\n }\r\n\r\n getLayer(layerId: string): ActiveLayer {\r\n return this.layerMap[layerId];\r\n }\r\n\r\n /**\r\n * Refer to https://github.com/keymanapp/keyman/issues/254, which mentions\r\n * KD-11 from a prior issue-tracking system from the closed-source days that\r\n * resulted in an unintended extra empty row.\r\n *\r\n * It'll be pretty rare to see a keyboard affected by the bug, but we don't\r\n * 100% control all keyboards out there, so it's best we make sure the edge\r\n * case is covered.\r\n *\r\n * @param layers The layer group to be loaded for the form factor. Will be\r\n * mutated by this operation.\r\n */\r\n static correctLayerEmptyRowBug(layers: LayoutLayer[]) {\r\n for(let n=0; n=0; i--) {\r\n if(!Array.isArray(rows[i]['key']) || rows[i]['key'].length == 0) {\r\n rows.splice(i, 1)\r\n }\r\n }\r\n }\r\n }\r\n\r\n static sanitize(rawLayout: LayoutFormFactor) {\r\n ActiveLayout.correctLayerEmptyRowBug(rawLayout.layer);\r\n\r\n for(const layer of rawLayout.layer) {\r\n ActiveLayer.sanitize(layer);\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param layout\r\n * @param formFactor\r\n */\r\n static polyfill(layout: LayoutFormFactor, keyboard: Keyboard, formFactor: DeviceSpec.FormFactor): ActiveLayout {\r\n if(layout == null) {\r\n throw new Error(\"Cannot build an ActiveLayout for a null specification.\");\r\n }\r\n\r\n /* Standardize the layout object's data types.\r\n *\r\n * In older versions of KMW, some numeric properties were long represented as strings instead,\r\n * and that lives on within a _lot_ of keyboards. The data should be sanitized before it\r\n * is processed by this method.\r\n */\r\n this.sanitize(layout);\r\n\r\n // Create a separate OSK div for each OSK layer, only one of which will ever be visible\r\n var n: number;\r\n let layerMap: {[layerId: string]: ActiveLayer} = {};\r\n\r\n let layers=layout.layer;\r\n\r\n // Add class functions to the existing layout object, allowing it to act as an ActiveLayout.\r\n let dummy = new ActiveLayout();\r\n for(let key in dummy) {\r\n if(!layout.hasOwnProperty(key)) {\r\n layout[key] = dummy[key];\r\n }\r\n }\r\n\r\n let aLayout = layout as ActiveLayout;\r\n aLayout.keyboard = keyboard;\r\n aLayout.formFactor = formFactor;\r\n\r\n for(n=0; n(p:T, c0?): T {\r\n var c = c0 || {};\r\n for (var i in p) {\r\n if(typeof p[i] === 'object' && p[i] != null) {\r\n c[i] = (p[i].constructor === Array ) ? [] : {};\r\n deepCopy(p[i],c[i]);\r\n }\r\n else {\r\n c[i] = p[i];\r\n }\r\n }\r\n\r\n return c;\r\n}", + "/**\r\n * This class provides an abstract version of com.keyman.Device that is core-friendly,\r\n * containing only the information needed by web-core for text processing use, devoid\r\n * of any direct references to the DOM.\r\n */\r\nexport class DeviceSpec {\r\n readonly browser: DeviceSpec.Browser;\r\n readonly formFactor: DeviceSpec.FormFactor;\r\n readonly OS: DeviceSpec.OperatingSystem;\r\n readonly touchable: boolean;\r\n\r\n constructor(browser: string, formFactor: string, OS: string, touchable: boolean) {\r\n switch(browser.toLowerCase() as DeviceSpec.Browser) {\r\n case DeviceSpec.Browser.Chrome:\r\n case DeviceSpec.Browser.Edge:\r\n case DeviceSpec.Browser.Firefox:\r\n case DeviceSpec.Browser.Native:\r\n case DeviceSpec.Browser.Opera:\r\n case DeviceSpec.Browser.Safari:\r\n this.browser = browser.toLowerCase() as DeviceSpec.Browser;\r\n break;\r\n default:\r\n this.browser = DeviceSpec.Browser.Other;\r\n }\r\n\r\n switch(formFactor.toLowerCase() as DeviceSpec.FormFactor) {\r\n case DeviceSpec.FormFactor.Desktop:\r\n case DeviceSpec.FormFactor.Phone:\r\n case DeviceSpec.FormFactor.Tablet:\r\n this.formFactor = formFactor.toLowerCase() as DeviceSpec.FormFactor;\r\n break;\r\n default:\r\n throw (\"Invalid form factor specified for device: \" + formFactor);\r\n }\r\n\r\n switch(OS.toLowerCase() as DeviceSpec.OperatingSystem) {\r\n case DeviceSpec.OperatingSystem.Windows.toLowerCase():\r\n case DeviceSpec.OperatingSystem.macOS.toLowerCase():\r\n case DeviceSpec.OperatingSystem.Linux.toLowerCase():\r\n case DeviceSpec.OperatingSystem.Android.toLowerCase():\r\n case DeviceSpec.OperatingSystem.iOS.toLowerCase():\r\n this.OS = OS.toLowerCase() as DeviceSpec.OperatingSystem;\r\n break;\r\n default:\r\n this.OS = DeviceSpec.OperatingSystem.Other;\r\n }\r\n\r\n this.touchable = touchable;\r\n }\r\n}\r\n\r\n// Namespaces these under DeviceSpec, as each is primarily used with it.\r\nexport namespace DeviceSpec {\r\n export enum Browser {\r\n Chrome = 'chrome',\r\n Edge = 'edge',\r\n Firefox = 'firefox',\r\n Native = 'native', // Used by embedded mode\r\n Opera = 'opera',\r\n Safari = 'safari',\r\n Other = 'other'\r\n }\r\n\r\n export enum OperatingSystem {\r\n Windows = 'windows',\r\n macOS = 'macosx',\r\n Linux = 'linux',\r\n Android = 'android',\r\n iOS = 'ios',\r\n Other = 'other'\r\n }\r\n\r\n export enum FormFactor {\r\n Desktop = 'desktop',\r\n Phone = 'phone',\r\n Tablet = 'tablet'\r\n }\r\n}\r\n\r\nexport function physicalKeyDeviceAlias(device: DeviceSpec) {\r\n return new DeviceSpec(device.browser, DeviceSpec.FormFactor.Desktop, device.OS, false);\r\n}\r\n\r\nexport default DeviceSpec;", + "\n// Generated by common/web/keyman-version/build.sh\n//\n// Note: does not use the 'default' keyword so that the export name is\n// correct when converted to a CommonJS module with `esbuild`.\nexport class KEYMAN_VERSION {\n static readonly VERSION = \"17.0.168\";\n static readonly VERSION_RELEASE =\"17.0\";\n static readonly VERSION_MAJOR = \"17\";\n static readonly VERSION_MINOR = \"0\";\n static readonly VERSION_PATCH = \"168\";\n static readonly TIER =\"alpha\";\n static readonly VERSION_TAG = \"-alpha\";\n static readonly VERSION_WITH_TAG = \"17.0.168-alpha\";\n static readonly VERSION_ENVIRONMENT = \"alpha\";\n static readonly VERSION_GIT_TAG = \"release@17.0.168-alpha\";\n}\n\n// Also provides it as a 'default' export.\nexport default KEYMAN_VERSION;\n \n", + "import KEYMAN_VERSION from \"@keymanapp/keyman-version\";\r\n\r\n// Dotted-decimal version\r\nexport default class Version {\r\n public static readonly CURRENT = new Version(KEYMAN_VERSION.VERSION_RELEASE);\r\n\r\n // Represents a default version value for keyboards compiled before this was compiled into keyboards.\r\n // The exact version is unknown at this point, but the value is \"good enough\" for what we need.\r\n public static readonly DEVELOPER_VERSION_FALLBACK = new Version([9, 0, 0]);\r\n\r\n // For 12.0, the old default behavior of adding missing keycaps to the default layers was removed,\r\n // as it results in unexpected, bug-like behavior for keyboard designers when it is unwanted.\r\n public static readonly NO_DEFAULT_KEYCAPS = new Version([12, 0]);\r\n\r\n public static readonly MAC_POSSIBLE_IPAD_ALIAS = new Version([10, 15]);\r\n\r\n private readonly components: number[]\r\n\r\n /**\r\n * Parses version information, preparing it for use in comparisons.\r\n * @param text Either a string representing a version number (ex: \"9.0.0\") or an array representing\r\n * its components (ex: [9, 0, 0]).\r\n */\r\n constructor(text: String | number[]) {\r\n // If a keyboard doesn't specify a version, use the DEVELOPER_VERSION_FALLBACK values.\r\n if(text === undefined || text === null) {\r\n this.components = [].concat(Version.DEVELOPER_VERSION_FALLBACK.components);\r\n return;\r\n }\r\n\r\n if(Array.isArray(text)) {\r\n let components = text as number[];\r\n if(components.length < 2) {\r\n throw new Error(\"Version string must have at least a major and minor component!\");\r\n } else {\r\n this.components = [].concat(components);\r\n return;\r\n }\r\n }\r\n\r\n // else, standard constructor path.\r\n let parts = text.split('.');\r\n let componentArray: number[] = [];\r\n\r\n if(parts.length < 2) {\r\n throw new Error(\"Version string must have at least a major and minor component!\");\r\n }\r\n\r\n for(let i=0; i < parts.length; i++) {\r\n let value = parseInt(parts[i], 10);\r\n if(isNaN(value)) {\r\n throw new Error(\"Version string components must be numerical!\");\r\n }\r\n\r\n componentArray.push(value);\r\n }\r\n\r\n this.components = componentArray;\r\n }\r\n\r\n get major(): number {\r\n return this.components[0];\r\n }\r\n\r\n get minor(): number {\r\n return this.components[1];\r\n }\r\n\r\n toString(): string {\r\n return this.components.join('.');\r\n }\r\n\r\n toJSON(): string {\r\n return this.toString();\r\n }\r\n\r\n equals(other: Version): boolean {\r\n return this.compareTo(other) == 0;\r\n }\r\n\r\n precedes(other: Version): boolean {\r\n return this.compareTo(other) < 0;\r\n }\r\n\r\n compareTo(other: Version): number {\r\n // If the version info depth differs, we need a flag to indicate which instance is shorter.\r\n var isShorter: boolean = this.components.length < other.components.length;\r\n var maxDepth: number = (this.components.length < other.components.length) ? this.components.length : other.components.length;\r\n\r\n var i: number;\r\n for(i = 0; i < maxDepth; i++) {\r\n let delta = this.components[i] - other.components[i];\r\n if(delta != 0) {\r\n return delta;\r\n }\r\n }\r\n\r\n var longList = isShorter ? other.components : this.components;\r\n do {\r\n if(longList[i] > 0) {\r\n return isShorter ? -1 : 1;\r\n }\r\n i++;\r\n } while (i < longList.length);\r\n\r\n // Equal.\r\n return 0;\r\n }\r\n}", + "/**\r\n * Returns the base global object available to the current JS platform.\r\n * - In browsers, returns `window`.\r\n * - In WebWorkers, returns `self`.\r\n * - In Node, returns `global`.\r\n */\r\nexport default function getGlobalObject(): typeof globalThis {\r\n // Evergreen browsers have started defining 'globalThis'.\r\n // Refer to https://devblogs.microsoft.com/typescript/announcing-typescript-3-4/#type-checking-for-globalthis\r\n // and its referenced polyfill. Said polyfill is very complex, so we opt for this far leaner variant.\r\n if(typeof globalThis != 'undefined') {\r\n return globalThis; // Not available in IE or older Edge versions\r\n // @ts-ignore (TS will throw errors for whatever platform we're not compiling for.)\r\n } else if(typeof window != 'undefined') {\r\n // @ts-ignore\r\n return window; // The browser-based classic\r\n // @ts-ignore\r\n } else if(typeof self != 'undefined') {\r\n // @ts-ignore\r\n return self; // WebWorker global\r\n } else {\r\n // Assumption - if neither of the above exist, we're in Node, for unit-testing.\r\n // Node doesn't have as many methods and properties as the other two, but what\r\n // matters for us is that it's the base global.\r\n //\r\n // Some other headless JS solutions use 'this' instead, but Node's enough for our needs.\r\n // @ts-ignore\r\n return (global as any) as typeof globalThis;\r\n }\r\n}", + "/***\r\n KeymanWeb 14.0\r\n Copyright 2020 SIL International\r\n***/\r\n\r\n\r\n/*\r\n * TODO: Remove this file as part of addressing https://github.com/keymanapp/keyman/issues/2492.\r\n */\r\n\r\ndeclare global {\r\n interface StringConstructor {\r\n kmwFromCharCode(cp0: number): string,\r\n _kmwFromCharCode(cp0: number): string,\r\n kmwEnableSupplementaryPlane(bEnable: boolean)\r\n }\r\n\r\n interface String {\r\n kmwCharCodeAt(codePointIndex: number): number,\r\n kmwCharAt(codePointIndex: number) : string,\r\n kmwIndexOf(searchValue: string, fromIndex?: number) : number,\r\n kmwLastIndexOf(searchValue: string, fromIndex?: number) : number,\r\n kmwSlice(beginSlice: number, endSlice: number) : string,\r\n kmwSubstring(start: number, length: number) : string,\r\n kmwSubstr(start: number, length?: number) : string,\r\n kmwBMPSubstr(start: number, length?: number) : string,\r\n kmwLength(): number,\r\n kmwBMPLength(): number,\r\n kmwNextChar(codeUnitIndex: number): number,\r\n kmwBMPNextChar(codeUnitIndex: number): number,\r\n kmwPrevChar(codeUnitIndex: number): number,\r\n kmwBMPPrevChar(codeUnitIndex: number): number,\r\n kmwCodePointToCodeUnit(codePointIndex: number) : number,\r\n kmwBMPCodePointToCodeUnit(codePointIndex: number) : number,\r\n kmwCodeUnitToCodePoint(codeUnitIndex: number) : number,\r\n kmwBMPCodeUnitToCodePoint(codeUnitIndex: number) : number,\r\n _kmwCharCodeAt(codePointIndex: number): number,\r\n _kmwCharAt(codePointIndex: number) : string,\r\n _kmwIndexOf(searchValue: string, fromIndex?: number) : number,\r\n _kmwLastIndexOf(searchValue: string, fromIndex?: number) : number,\r\n _kmwSlice(beginSlice: number, endSlice: number) : string,\r\n _kmwSubstring(start: number, length?: number) : string,\r\n _kmwSubstr(start: number, length?: number) : string,\r\n _kmwLength(): number,\r\n _kmwNextChar(codeUnitIndex: number): number,\r\n _kmwPrevChar(codeUnitIndex: number): number,\r\n _kmwCodePointToCodeUnit(codePointIndex: number) : number,\r\n _kmwCodeUnitToCodePoint(codeUnitIndex: number) : number\r\n }\r\n}\r\n\r\nexport default function extendString() {\r\n /**\r\n * Constructs a string from one or more Unicode character codepoint values\r\n * passed as integer parameters.\r\n *\r\n * @param {number} cp0,... 1 or more Unicode codepoints, e.g. 0x0065, 0x10000\r\n * @return {string|null} The new String object.\r\n */\r\n String.kmwFromCharCode = function(cp0) {\r\n var chars = [], i;\r\n for (i = 0; i < arguments.length; i++) {\r\n var c = Number(arguments[i]);\r\n if (!isFinite(c) || c < 0 || c > 0x10FFFF || Math.floor(c) !== c) {\r\n throw new RangeError(\"Invalid code point \" + c);\r\n }\r\n if (c < 0x10000) {\r\n chars.push(c);\r\n } else {\r\n c -= 0x10000;\r\n chars.push((c >> 10) + 0xD800);\r\n chars.push((c % 0x400) + 0xDC00);\r\n }\r\n }\r\n return String.fromCharCode.apply(undefined, chars);\r\n }\r\n\r\n /**\r\n * Returns a number indicating the Unicode value of the character at the given\r\n * code point index, with support for supplementary plane characters.\r\n *\r\n * @param {number} codePointIndex The code point index into the string (not\r\n the code unit index) to return\r\n * @return {number} The Unicode character value\r\n */\r\n String.prototype.kmwCharCodeAt = function(codePointIndex) {\r\n var str = String(this);\r\n var codeUnitIndex = 0;\r\n\r\n if (codePointIndex < 0 || codePointIndex >= str.length) {\r\n return NaN;\r\n }\r\n\r\n for(var i = 0; i < codePointIndex; i++) {\r\n codeUnitIndex = str.kmwNextChar(codeUnitIndex);\r\n if(codeUnitIndex === null) return NaN;\r\n }\r\n\r\n var first = str.charCodeAt(codeUnitIndex);\r\n if (first >= 0xD800 && first <= 0xDBFF && str.length > codeUnitIndex + 1) {\r\n var second = str.charCodeAt(codeUnitIndex + 1);\r\n if (second >= 0xDC00 && second <= 0xDFFF) {\r\n return ((first - 0xD800) << 10) + (second - 0xDC00) + 0x10000;\r\n }\r\n }\r\n return first;\r\n }\r\n\r\n /**\r\n * Returns the code point index within the calling String object of the first occurrence\r\n * of the specified value, or -1 if not found.\r\n *\r\n * @param {string} searchValue The value to search for\r\n * @param {number} [fromIndex] Optional code point index to start searching from\r\n * @return {number} The code point index of the specified search value\r\n */\r\n String.prototype.kmwIndexOf = function(searchValue, fromIndex) {\r\n var str = String(this);\r\n var codeUnitIndex = str.indexOf(searchValue, fromIndex);\r\n\r\n if(codeUnitIndex < 0) {\r\n return codeUnitIndex;\r\n }\r\n\r\n var codePointIndex = 0;\r\n for(var i = 0; i !== null && i < codeUnitIndex; i = str.kmwNextChar(i)) codePointIndex++;\r\n return codePointIndex;\r\n }\r\n\r\n /**\r\n * Returns the code point index within the calling String object of the last occurrence\r\n * of the specified value, or -1 if not found.\r\n *\r\n * @param {string} searchValue The value to search for\r\n * @param {number} fromIndex Optional code point index to start searching from\r\n * @return {number} The code point index of the specified search value\r\n */\r\n String.prototype.kmwLastIndexOf = function(searchValue, fromIndex)\r\n {\r\n var str = String(this);\r\n var codeUnitIndex = str.lastIndexOf(searchValue, fromIndex);\r\n\r\n if(codeUnitIndex < 0) {\r\n return codeUnitIndex;\r\n }\r\n\r\n var codePointIndex = 0;\r\n for(var i = 0; i !== null && i < codeUnitIndex; i = str.kmwNextChar(i)) codePointIndex++;\r\n return codePointIndex;\r\n }\r\n\r\n /**\r\n * Returns the length of the string in code points, as opposed to code units.\r\n *\r\n * @return {number} The length of the string in code points\r\n */\r\n String.prototype.kmwLength = function() {\r\n var str = String(this);\r\n\r\n if(str.length == 0) return 0;\r\n\r\n for(var i = 0, codeUnitIndex = 0; codeUnitIndex !== null; i++)\r\n codeUnitIndex = str.kmwNextChar(codeUnitIndex);\r\n return i;\r\n }\r\n\r\n /**\r\n * Extracts a section of a string and returns a new string.\r\n *\r\n * @param {number} beginSlice The start code point index in the string to\r\n * extract from\r\n * @param {number} endSlice Optional end code point index in the string\r\n * to extract to\r\n * @return {string} The substring as selected by beginSlice and\r\n * endSlice\r\n */\r\n String.prototype.kmwSlice = function(beginSlice, endSlice) {\r\n var str = String(this);\r\n var beginSliceCodeUnit = str.kmwCodePointToCodeUnit(beginSlice);\r\n var endSliceCodeUnit = str.kmwCodePointToCodeUnit(endSlice);\r\n if(beginSliceCodeUnit === null || endSliceCodeUnit === null)\r\n return '';\r\n else\r\n return str.slice(beginSliceCodeUnit, endSliceCodeUnit);\r\n }\r\n\r\n /**\r\n * Returns the characters in a string beginning at the specified location through\r\n * the specified number of characters.\r\n *\r\n * @param {number} start The start code point index in the string to\r\n * extract from\r\n * @param {number=} length Optional length to extract\r\n * @return {string} The substring as selected by start and length\r\n */\r\n String.prototype.kmwSubstr = function(start, length?)\r\n {\r\n var str = String(this);\r\n if(start < 0)\r\n {\r\n start = str.kmwLength() + start;\r\n }\r\n if(start < 0) start = 0;\r\n var startCodeUnit = str.kmwCodePointToCodeUnit(start);\r\n var endCodeUnit = startCodeUnit;\r\n\r\n if(startCodeUnit === null) return '';\r\n\r\n if(arguments.length < 2) {\r\n endCodeUnit = str.length;\r\n } else {\r\n for(var i = 0; i < length; i++) endCodeUnit = str.kmwNextChar(endCodeUnit);\r\n }\r\n if(endCodeUnit === null)\r\n return str.substring(startCodeUnit);\r\n else\r\n return str.substring(startCodeUnit, endCodeUnit);\r\n }\r\n\r\n /**\r\n * Returns the characters in a string between two indexes into the string.\r\n *\r\n * @param {number} indexA The start code point index in the string to\r\n * extract from\r\n * @param {number} indexB The end code point index in the string to\r\n * extract to\r\n * @return {string} The substring as selected by indexA and indexB\r\n */\r\n String.prototype.kmwSubstring = function(indexA, indexB)\r\n {\r\n var str = String(this),indexACodeUnit,indexBCodeUnit;\r\n\r\n if(typeof(indexB) == 'undefined')\r\n {\r\n indexACodeUnit = str.kmwCodePointToCodeUnit(indexA);\r\n indexBCodeUnit = str.length;\r\n }\r\n else\r\n {\r\n if(indexA > indexB) { var c = indexA; indexA = indexB; indexB = c; }\r\n\r\n indexACodeUnit = str.kmwCodePointToCodeUnit(indexA);\r\n indexBCodeUnit = str.kmwCodePointToCodeUnit(indexB);\r\n }\r\n if(isNaN(indexACodeUnit) || indexACodeUnit === null) indexACodeUnit = 0;\r\n if(isNaN(indexBCodeUnit) || indexBCodeUnit === null) indexBCodeUnit = str.length;\r\n\r\n return str.substring(indexACodeUnit, indexBCodeUnit);\r\n }\r\n\r\n /*\r\n Helper functions\r\n */\r\n\r\n /**\r\n * Returns the code unit index for the next code point in the string, accounting for\r\n * supplementary pairs\r\n *\r\n * @param {number|null} codeUnitIndex The code unit position to increment\r\n * @return {number|null} The index of the next code point in the string,\r\n * in code units\r\n */\r\n String.prototype.kmwNextChar = function(codeUnitIndex) {\r\n var str = String(this);\r\n\r\n if(codeUnitIndex === null || codeUnitIndex < 0 || codeUnitIndex >= str.length - 1) {\r\n return null;\r\n }\r\n\r\n var first = str.charCodeAt(codeUnitIndex);\r\n if (first >= 0xD800 && first <= 0xDBFF && str.length > codeUnitIndex + 1) {\r\n var second = str.charCodeAt(codeUnitIndex + 1);\r\n if (second >= 0xDC00 && second <= 0xDFFF) {\r\n if(codeUnitIndex == str.length - 2) {\r\n return null;\r\n }\r\n return codeUnitIndex + 2;\r\n }\r\n }\r\n return codeUnitIndex + 1;\r\n }\r\n\r\n /**\r\n * Returns the code unit index for the previous code point in the string, accounting\r\n * for supplementary pairs\r\n *\r\n * @param {number|null} codeUnitIndex The code unit position to decrement\r\n * @return {number|null} The index of the previous code point in the\r\n * string, in code units\r\n */\r\n String.prototype.kmwPrevChar = function(codeUnitIndex) {\r\n var str = String(this);\r\n\r\n if(codeUnitIndex == null || codeUnitIndex <= 0 || codeUnitIndex > str.length) {\r\n return null;\r\n }\r\n\r\n var second = str.charCodeAt(codeUnitIndex - 1);\r\n if (second >= 0xDC00 && second <= 0xDFFF && codeUnitIndex > 1) {\r\n var first = str.charCodeAt(codeUnitIndex - 2);\r\n if(first >= 0xD800 && first <= 0xDBFF) {\r\n return codeUnitIndex - 2;\r\n }\r\n }\r\n return codeUnitIndex - 1;\r\n }\r\n\r\n /**\r\n * Returns the corresponding code unit index to the code point index passed\r\n *\r\n * @param {number|null} codePointIndex A code point index in the string\r\n * @return {number|null} The corresponding code unit index\r\n */\r\n String.prototype.kmwCodePointToCodeUnit = function(codePointIndex) {\r\n\r\n if(codePointIndex === null) return null;\r\n\r\n var str = String(this);\r\n var codeUnitIndex = 0;\r\n\r\n if(codePointIndex < 0) {\r\n codeUnitIndex = str.length;\r\n for(var i = 0; i > codePointIndex; i--)\r\n codeUnitIndex = str.kmwPrevChar(codeUnitIndex);\r\n return codeUnitIndex;\r\n }\r\n\r\n if(codePointIndex == str.kmwLength()) return str.length;\r\n\r\n for(var i = 0; i < codePointIndex; i++)\r\n codeUnitIndex = str.kmwNextChar(codeUnitIndex);\r\n return codeUnitIndex;\r\n }\r\n\r\n /**\r\n * Returns the corresponding code point index to the code unit index passed\r\n *\r\n * @param {number|null} codeUnitIndex A code unit index in the string\r\n * @return {number|null} The corresponding code point index\r\n */\r\n String.prototype.kmwCodeUnitToCodePoint = function(codeUnitIndex) {\r\n var str = String(this);\r\n\r\n if(codeUnitIndex === null)\r\n return null;\r\n else if(codeUnitIndex == 0)\r\n return 0;\r\n else if(codeUnitIndex < 0)\r\n return str.substr(codeUnitIndex).kmwLength();\r\n else\r\n return str.substr(0,codeUnitIndex).kmwLength();\r\n }\r\n\r\n /**\r\n * Returns the character at a the code point index passed\r\n *\r\n * @param {number} codePointIndex A code point index in the string\r\n * @return {string} The corresponding character\r\n */\r\n String.prototype.kmwCharAt = function(codePointIndex) {\r\n var str = String(this);\r\n\r\n if(codePointIndex >= 0) return str.kmwSubstr(codePointIndex,1); else return '';\r\n }\r\n\r\n /**\r\n * String prototype library extensions for basic plane characters,\r\n * to simplify enabling or disabling supplementary plane functionality (I3319)\r\n */\r\n\r\n /**\r\n * Returns the code unit index for the next code point in the string\r\n *\r\n * @param {number} codeUnitIndex A code point index in the string\r\n * @return {number|null} The corresponding character\r\n */\r\n String.prototype.kmwBMPNextChar = function(codeUnitIndex)\r\n {\r\n var str = String(this);\r\n if(codeUnitIndex < 0 || codeUnitIndex >= str.length - 1) {\r\n return null;\r\n }\r\n return codeUnitIndex + 1;\r\n }\r\n\r\n /**\r\n * Returns the code unit index for the previous code point in the string\r\n *\r\n * @param {number} codeUnitIndex A code unit index in the string\r\n * @return {number|null} The corresponding character\r\n */\r\n String.prototype.kmwBMPPrevChar = function(codeUnitIndex)\r\n {\r\n var str = String(this);\r\n\r\n if(codeUnitIndex <= 0 || codeUnitIndex > str.length) {\r\n return null;\r\n }\r\n return codeUnitIndex - 1;\r\n }\r\n\r\n /**\r\n * Returns the code unit index for a code point index\r\n *\r\n * @param {number} codePointIndex A code point index in the string\r\n * @return {number} The corresponding character\r\n */\r\n String.prototype.kmwBMPCodePointToCodeUnit = function(codePointIndex)\r\n {\r\n return codePointIndex;\r\n }\r\n\r\n /**\r\n * Returns the code point index for a code unit index\r\n *\r\n * @param {number} codeUnitIndex A code point index in the string\r\n * @return {number} The corresponding character\r\n */\r\n String.prototype.kmwBMPCodeUnitToCodePoint = function(codeUnitIndex)\r\n {\r\n return codeUnitIndex;\r\n }\r\n\r\n /**\r\n * Returns the length of a BMP string\r\n *\r\n * @return {number} The length in code points\r\n */\r\n String.prototype.kmwBMPLength = function()\r\n {\r\n var str = String(this);\r\n return str.length;\r\n }\r\n\r\n /**\r\n * Returns a substring\r\n *\r\n * @param {number} n\r\n * @param {number=} ln\r\n * @return {string}\r\n */\r\n String.prototype.kmwBMPSubstr = function(n,ln?)\r\n {\r\n var str=String(this);\r\n if(n > -1)\r\n return str.substr(n,ln);\r\n else\r\n return str.substr(str.length+n,-n);\r\n }\r\n\r\n /**\r\n * Enable or disable supplementary plane string handling\r\n *\r\n * @param {boolean} bEnable\r\n */\r\n String.kmwEnableSupplementaryPlane = function(bEnable)\r\n {\r\n var p=String.prototype;\r\n String._kmwFromCharCode = bEnable ? String.kmwFromCharCode : String.fromCharCode;\r\n p._kmwCharAt = bEnable ? p.kmwCharAt : p.charAt;\r\n p._kmwCharCodeAt = bEnable ? p.kmwCharCodeAt : p.charCodeAt;\r\n p._kmwIndexOf = bEnable ? p.kmwIndexOf :p.indexOf;\r\n p._kmwLastIndexOf = bEnable ? p.kmwLastIndexOf : p.lastIndexOf ;\r\n p._kmwSlice = bEnable ? p.kmwSlice : p.slice;\r\n p._kmwSubstring = bEnable ? p.kmwSubstring : p.substring;\r\n p._kmwSubstr = bEnable ? p.kmwSubstr : p.kmwBMPSubstr;\r\n p._kmwLength = bEnable ? p.kmwLength : p.kmwBMPLength;\r\n p._kmwNextChar = bEnable ? p.kmwNextChar : p.kmwBMPNextChar;\r\n p._kmwPrevChar = bEnable ? p.kmwPrevChar : p.kmwBMPPrevChar;\r\n p._kmwCodePointToCodeUnit = bEnable ? p.kmwCodePointToCodeUnit : p.kmwBMPCodePointToCodeUnit;\r\n p._kmwCodeUnitToCodePoint = bEnable ? p.kmwCodeUnitToCodePoint : p.kmwBMPCodeUnitToCodePoint;\r\n }\r\n\r\n // Ensure that _all_ String extensions are established, even if disabled by default.\r\n if(!String._kmwFromCharCode) {\r\n String.kmwEnableSupplementaryPlane(false);\r\n }\r\n}\r\n\r\n// For side-effect imports:\r\nextendString();", + "type ResolveSignature = (value: Type | PromiseLike) => void;\r\ntype RejectSignature = (reason?: any) => void;\r\n\r\nexport default class ManagedPromise {\r\n public resolve: ResolveSignature;\r\n public reject: RejectSignature;\r\n\r\n private _hasResolved: boolean = false;\r\n private _hasRejected: boolean = false;\r\n\r\n public get hasResolved(): boolean {\r\n return this._hasResolved;\r\n }\r\n\r\n public get hasRejected(): boolean {\r\n return this._hasRejected;\r\n }\r\n\r\n public get hasFinalized(): boolean {\r\n return this.hasResolved || this.hasRejected;\r\n }\r\n\r\n private _promise: Promise;\r\n\r\n constructor();\r\n constructor(executor: (resolve: ResolveSignature, reject: RejectSignature) => Type);\r\n constructor(executor?: (resolve: ResolveSignature, reject: RejectSignature) => Type) {\r\n this._promise = new Promise((resolve, reject) => {\r\n this.resolve = (value) => {\r\n this._hasResolved = true;\r\n resolve(value);\r\n };\r\n\r\n this.reject = (reason) => {\r\n this._hasRejected = true;\r\n reject(reason);\r\n };\r\n\r\n if(executor) {\r\n executor(this.resolve, this.reject);\r\n }\r\n });\r\n }\r\n\r\n // Cannot actually extend the Promise class in ES5; attempt to use it will throw errors.\r\n // So, we just implement a Promise-like interface.\r\n\r\n then(onfulfilled?: ((value: Type) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise {\r\n return this._promise.then(onfulfilled, onrejected);\r\n }\r\n\r\n catch(onrejected?: (reason: any) => PromiseLike): Promise {\r\n return this._promise.catch(onrejected);\r\n }\r\n\r\n finally(onfinally?: () => void): Promise {\r\n return this._promise.finally(onfinally);\r\n }\r\n\r\n // And for things that actually need to provide something typed to Promise... well...\r\n get corePromise(): Promise {\r\n return this._promise;\r\n }\r\n}", + "/***\r\n KeymanWeb 10.0\r\n Copyright 2017 SIL International\r\n***/\r\n\r\nimport Codes from \"../text/codes.js\";\r\nimport type Keyboard from \"./keyboard.js\";\r\nimport { Version, deepCopy } from \"@keymanapp/web-utils\";\r\n\r\nexport type KLS = {[layerName: string]: string[]};\r\n\r\n// The following types provide type definitions for the full JSON format we use for visual keyboard definitions.\r\nexport type ButtonClass = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;\r\nexport type ButtonClassString = \"0\"|\"1\"|\"2\"|\"3\"|\"4\"|\"5\"|\"6\"|\"7\"|\"8\"|\"9\"|\"10\";\r\n\r\nexport type LayoutKey = {\r\n \"id\"?: string,\r\n \"text\"?: string,\r\n \"sp\"?: ButtonClass | ButtonClassString,\r\n \"width\"?: string | number,\r\n \"layer\"?: string, // Key derives any modifiers from the value set here if specified, not the actual display layer.\r\n \"nextlayer\"?: string,\r\n \"pad\"?: string | number,\r\n \"sk\"?: LayoutKey[]\r\n}\r\n\r\nexport type LayoutRow = {\r\n \"id\": string | number,\r\n \"key\": LayoutKey[]\r\n};\r\n\r\nexport type LayoutLayer = {\r\n \"id\": string,\r\n \"row\": LayoutRow[],\r\n\r\n // Post-processing elements.\r\n shiftKey?: LayoutKey,\r\n capsKey?: LayoutKey,\r\n numKey?: LayoutKey,\r\n scrollKey?: LayoutKey,\r\n aligned?: boolean\r\n}\r\n\r\nexport type LayoutFormFactor = {\r\n \"displayUnderlying\"?: boolean,\r\n \"font\": string,\r\n \"layer\": LayoutLayer[],\r\n isDefault?: boolean\r\n}\r\n\r\nexport type LayoutSpec = {\r\n \"desktop\"?: LayoutFormFactor,\r\n \"phone\"?: LayoutFormFactor,\r\n \"tablet\"?: LayoutFormFactor\r\n}\r\n\r\n// This class manages default layout construction for consumption by OSKs without a specified layout.\r\nexport class Layouts {\r\n static dfltCodes=[\r\n \"K_BKQUOTE\",\"K_1\",\"K_2\",\"K_3\",\"K_4\",\"K_5\",\"K_6\",\"K_7\",\"K_8\",\"K_9\",\"K_0\",\r\n \"K_HYPHEN\",\"K_EQUAL\",\"K_*\",\"K_*\",\"K_*\",\"K_Q\",\"K_W\",\"K_E\",\"K_R\",\"K_T\",\r\n \"K_Y\",\"K_U\",\"K_I\",\"K_O\",\"K_P\",\"K_LBRKT\",\"K_RBRKT\",\"K_BKSLASH\",\"K_*\",\r\n \"K_*\",\"K_*\",\"K_A\",\"K_S\",\"K_D\",\"K_F\",\"K_G\",\"K_H\",\"K_J\",\"K_K\",\"K_L\",\r\n \"K_COLON\",\"K_QUOTE\",\"K_*\",\"K_*\",\"K_*\",\"K_*\",\"K_*\",\"K_oE2\",\r\n \"K_Z\",\"K_X\",\"K_C\",\"K_V\",\"K_B\",\"K_N\",\"K_M\",\"K_COMMA\",\"K_PERIOD\",\r\n \"K_SLASH\",\"K_*\",\"K_*\",\"K_*\",\"K_*\",\"K_*\",\"K_SPACE\"\r\n ];\r\n\r\n static dfltText='`1234567890-=\\xA7~~qwertyuiop[]\\\\~~~asdfghjkl;\\'~~~~~?zxcvbnm,./~~~~~ '\r\n +'~!@#$%^&*()_+\\xA7~~QWERTYUIOP{}\\\\~~~ASDFGHJKL:\"~~~~~?ZXCVBNM<>?~~~~~ ';\r\n\r\n static readonly DEFAULT_RAW_SPEC = {'F':'Tahoma', 'BK': Layouts.dfltText};\r\n\r\n // Cross-reference with the ids in osk.setButtonClass.\r\n static buttonClasses: {[name: string]: ButtonClass} = {\r\n 'DEFAULT':0,\r\n 'SHIFT':1,\r\n 'SHIFT-ON':2,\r\n 'SPECIAL':3,\r\n 'SPECIAL-ON':4,\r\n 'DEADKEY':8,\r\n 'BLANK':9,\r\n 'HIDDEN':10\r\n };\r\n\r\n static modifierSpecials = {\r\n 'leftalt': '*LAlt*',\r\n 'rightalt': '*RAlt*',\r\n 'alt': '*Alt*',\r\n 'leftctrl': '*LCtrl*',\r\n 'rightctrl': '*RCtrl*',\r\n 'ctrl': '*Ctrl*',\r\n 'ctrl-alt': '*AltGr*',\r\n 'leftctrl-leftalt': '*LAltCtrl*',\r\n 'rightctrl-rightalt': '*RAltCtrl*',\r\n 'leftctrl-leftalt-shift': '*LAltCtrlShift*',\r\n 'rightctrl-rightalt-shift': '*RAltCtrlShift*',\r\n 'shift': '*Shift*',\r\n 'shift-alt': '*AltShift*',\r\n 'shift-ctrl': '*CtrlShift*',\r\n 'shift-ctrl-alt': '*AltCtrlShift*',\r\n 'leftalt-shift': '*LAltShift*',\r\n 'rightalt-shift': '*RAltShift*',\r\n 'leftctrl-shift': '*LCtrlShift*',\r\n 'rightctrl-shift': '*RCtrlShift*'\r\n };\r\n\r\n /**\r\n * Build a default layout for keyboards with no explicit layout\r\n *\r\n * @param {Object} PVK raw specifications\r\n * @param {Keyboard} keyboard keyboard object (as loaded)\r\n * @param {string} formFactor (really utils.FormFactor)\r\n * @return {LayoutFormFactor}\r\n */\r\n static buildDefaultLayout(PVK, keyboard: Keyboard, formFactor: string): LayoutFormFactor {\r\n // Build a layout using the default for the device\r\n var layoutType=formFactor;\r\n\r\n if(typeof Layouts.dfltLayout[layoutType] != 'object') {\r\n layoutType = 'desktop';\r\n }\r\n\r\n let kbdBitmask = Codes.modifierBitmasks['NON_CHIRAL'];\r\n // An unfortunate dependency there. Should probably also set a version within web-core for use.\r\n let kbdDevVersion = Version.CURRENT;\r\n if(keyboard) {\r\n kbdBitmask = keyboard.modifierBitmask;\r\n kbdDevVersion = keyboard.compilerVersion;\r\n }\r\n\r\n if(!PVK) {\r\n PVK = this.DEFAULT_RAW_SPEC;\r\n }\r\n\r\n // Clone the default layout object for this device\r\n var layout: LayoutFormFactor = deepCopy(Layouts.dfltLayout[layoutType]);\r\n\r\n var n,layers=layout['layer'], keyLabels: KLS=PVK['KLS'], key102=PVK['K102'];\r\n var i, j, k, m, row, rows: LayoutRow[], key: LayoutKey, keys: LayoutKey[];\r\n var chiral: boolean = (kbdBitmask & Codes.modifierBitmasks.IS_CHIRAL) != 0;\r\n\r\n if(PVK['F']) {\r\n // The KeymanWeb compiler generates a string of the format `[italic ][bold ] 1em \"\"`\r\n // We will ignore the bold, italic and font size spec\r\n let legacyFontSpec = /^(?:(?:italic|bold) )* *[0-9.eE-]+(?:[a-z]+) \"(.+)\"$/.exec(PVK['F']);\r\n if(legacyFontSpec) {\r\n layout.font = legacyFontSpec[1];\r\n }\r\n }\r\n\r\n var kmw10Plus = !(typeof keyLabels == 'undefined' || !keyLabels);\r\n if(!kmw10Plus) {\r\n // Save the processed key label information to the keyboard's general data.\r\n // Makes things more efficient elsewhere and for reloading after keyboard swaps.\r\n keyLabels = PVK['KLS'] = Layouts.processLegacyDefinitions(PVK['BK']);\r\n }\r\n\r\n // Identify key labels (e.g. *Shift*) that require the special OSK font\r\n var specialLabel=/\\*\\w+\\*/;\r\n\r\n // *** Step 1: instantiate the layer objects. ***\r\n\r\n // Get the list of valid layers, enforcing that the 'default' layer must be the first one processed.\r\n var validIdList = Object.getOwnPropertyNames(keyLabels), invalidIdList = [];\r\n validIdList.splice(validIdList.indexOf('default'), 1);\r\n validIdList = [ 'default' ].concat(validIdList);\r\n\r\n // Automatic AltGr emulation if the 'leftctrl-leftalt' layer is otherwise undefined.\r\n if(keyboard && keyboard.emulatesAltGr) {\r\n // We insert only the layers that need to be emulated.\r\n if((validIdList.indexOf('leftctrl-leftalt') == -1) && validIdList.indexOf('rightalt') != -1) {\r\n validIdList.push('leftctrl-leftalt');\r\n keyLabels['leftctrl-leftalt'] = keyLabels['rightalt'];\r\n }\r\n\r\n if((validIdList.indexOf('leftctrl-leftalt-shift') == -1) && validIdList.indexOf('rightalt-shift') != -1) {\r\n validIdList.push('leftctrl-leftalt-shift');\r\n keyLabels['leftctrl-leftalt-shift'] = keyLabels['rightalt-shift'];\r\n }\r\n }\r\n\r\n // If there is no predefined layout, even touch layouts will follow the desktop's\r\n // setting for the displayUnderlying flag. As the desktop layout uses a different\r\n // format for its layout spec, that's found at the field referenced below.\r\n layout[\"displayUnderlying\"] = keyboard ? !!keyboard.scriptObject['KDU'] : false;\r\n\r\n // For desktop devices, we must create all layers, even if invalid.\r\n if(formFactor == 'desktop') {\r\n invalidIdList = Layouts.generateLayerIds(chiral);\r\n\r\n // Filter out all ids considered valid. (We also don't want duplicates in the following list...)\r\n for(n=0; n 0) {\r\n layers[n]=deepCopy(layers[0]);\r\n }\r\n layers[n]['id']=idList[n];\r\n layers[n]['nextlayer']=idList[n]; // This would only be different for a dynamic keyboard\r\n\r\n // Extraced into a helper method to improve readability.\r\n Layouts.formatDefaultLayer(layers[n], chiral, formFactor, !!key102);\r\n }\r\n\r\n // *** Step 2: Layer objects now exist; time to fill them with the appropriate key labels and key styles ***\r\n for(n=0; n= 0 && kx < layerSpec.length) key['text']=layerSpec[kx];\r\n }\r\n\r\n // Legacy (pre 12.0) behavior: fall back to US English keycap text as default for the base two layers\r\n // if a key cap is not otherwise defined. (Any intentional 'ghost' keys must be explicitly defined.)\r\n if(isDefault && kbdDevVersion.precedes(Version.NO_DEFAULT_KEYCAPS)) {\r\n if(key['id'] != 'K_SPACE' && kx+65 * isShift < Layouts.dfltText.length && key['text'] !== null) {\r\n key['text'] = key['text'] || Layouts.dfltText[kx+65*isShift];\r\n }\r\n }\r\n }\r\n\r\n // Leave any unmarked key caps as null strings\r\n if(key['text'] !== null) {\r\n key['text'] = key['text'] || '';\r\n }\r\n\r\n // Detect important tracking keys.\r\n switch(key['id']) {\r\n case \"K_SHIFT\":\r\n shiftKey=key;\r\n break;\r\n case \"K_TAB\":\r\n nextKey=key;\r\n break;\r\n case \"K_CAPS\":\r\n capsKey=key;\r\n break;\r\n case \"K_NUMLOCK\":\r\n numKey=key;\r\n break;\r\n case \"K_SCROLL\":\r\n scrollKey=key;\r\n break;\r\n }\r\n\r\n // Remove pop-up shift keys referencing invalid layers (Build 349)\r\n if(key['sk'] != null) {\r\n for(k=0; k 0 && shiftKey != null) {\r\n shiftKey['sp']=Layouts.buttonClasses['SHIFT-ON'];\r\n shiftKey['sk']=null;\r\n shiftKey['text'] = Layouts.modifierSpecials[layers[n].id] ? Layouts.modifierSpecials[layers[n].id] : \"*Shift*\";\r\n }\r\n }\r\n }\r\n\r\n return layout;\r\n }\r\n\r\n /**\r\n * Function getLayerId\r\n * Scope Private\r\n * @param {number} m shift modifier code\r\n * @return {string} layer string from shift modifier code (desktop keyboards)\r\n * Description Get name of layer from code, where the modifer order is determined by ascending bit-flag value.\r\n */\r\n static getLayerId(m: number): string {\r\n let modifierCodes = Codes.modifierCodes;\r\n\r\n var s='';\r\n if(m == 0) {\r\n return 'default';\r\n } else {\r\n if(m & modifierCodes['LCTRL']) {\r\n s = (s.length > 0 ? s + '-' : '') + 'leftctrl';\r\n }\r\n if(m & modifierCodes['RCTRL']) {\r\n s = (s.length > 0 ? s + '-' : '') + 'rightctrl';\r\n }\r\n if(m & modifierCodes['LALT']) {\r\n s = (s.length > 0 ? s + '-' : '') + 'leftalt';\r\n }\r\n if(m & modifierCodes['RALT']) {\r\n s = (s.length > 0 ? s + '-' : '') + 'rightalt';\r\n }\r\n if(m & modifierCodes['SHIFT']) {\r\n s = (s.length > 0 ? s + '-' : '') + 'shift';\r\n }\r\n if(m & modifierCodes['CTRL']) {\r\n s = (s.length > 0 ? s + '-' : '') + 'ctrl';\r\n }\r\n if(m & modifierCodes['ALT']) {\r\n s = (s.length > 0 ? s + '-' : '') + 'alt';\r\n }\r\n return s;\r\n }\r\n }\r\n\r\n /**\r\n * Generates a list of potential layer ids for the specified chirality mode.\r\n *\r\n * @param {boolean} chiral // Does the keyboard use chiral modifiers or not?\r\n */\r\n static generateLayerIds(chiral: boolean): string[] {\r\n var layerCnt, offset;\r\n\r\n if(chiral) {\r\n layerCnt=32;\r\n offset=0x01;\r\n } else {\r\n layerCnt=8;\r\n offset=0x10;\r\n }\r\n\r\n var layerIds = [];\r\n\r\n for(var i=0; i < layerCnt; i++) {\r\n layerIds.push(Layouts.getLayerId(i * offset));\r\n }\r\n\r\n return layerIds;\r\n }\r\n\r\n /**\r\n * Sets a formatting property for the modifier keys when constructing a default layout for a keyboard.\r\n *\r\n * @param {Object} layer // One layer specification\r\n * @param {boolean} chiral // Whether or not the keyboard uses chiral modifier information.\r\n * @param {string} formFactor // The form factor of the device the layout is being constructed for.\r\n * @param {boolean} key102 // Whether or not the extended key 102 should be hidden.\r\n */\r\n static formatDefaultLayer(layer: LayoutLayer, chiral: boolean, formFactor: string, key102: boolean) {\r\n var layerId = layer['id'];\r\n let buttonClasses = Layouts.buttonClasses;\r\n\r\n // Correct appearance of state-dependent modifier keys according to group\r\n for(var i=0; i sil_euro_latin_js.txt)\r\n *\r\n * Reference: https://help.keyman.com/developer/language/reference/kmw_embedjs\r\n */\r\n embedScript(e: any) {\r\n // e: Expects the OSKManager's _Box element. We don't add type info here b/c it would\r\n // reference the DOM.\r\n this.scriptObject['KHF'](e);\r\n }\r\n\r\n get oskStyling(): string {\r\n return this.scriptObject['KCSS'];\r\n }\r\n\r\n /**\r\n * true if this keyboard uses a (legacy) pick list (Chinese, Japanese, Korean, etc.)\r\n *\r\n * TODO: Make a property on keyboards (say, `isPickList` / `KPL`) to signal this when we\r\n * get around to better, generalized picker-list support.\r\n */\r\n get isCJK(): boolean { // I3363 (Build 301)\r\n var lg: string;\r\n if(typeof(this.scriptObject['KLC']) != 'undefined') {\r\n lg = this.scriptObject['KLC'];\r\n } else if(typeof(this.scriptObject['LanguageCode']) != 'undefined') {\r\n lg = this.scriptObject['LanguageCode'];\r\n }\r\n\r\n // While some of these aren't proper BCP-47 language codes, the CJK keyboards predate our use of BCP-47.\r\n // So, we preserve the old ISO 639-3 codes, as that's what the keyboards are matching against.\r\n return ((lg == 'cmn') || (lg == 'jpn') || (lg == 'kor'));\r\n }\r\n\r\n get isRTL(): boolean {\r\n return !!this.scriptObject['KRTL'];\r\n }\r\n\r\n /**\r\n * Obtains the currently-active modifier bitmask for the active keyboard.\r\n */\r\n get modifierBitmask(): number {\r\n // NON_CHIRAL is the default bitmask if KMBM is not defined.\r\n // We always need a bitmask to compare against, as seen in `isChiral`.\r\n return this.scriptObject['KMBM'] || Codes.modifierBitmasks['NON_CHIRAL'];\r\n }\r\n\r\n get isChiral(): boolean {\r\n return !!(this.modifierBitmask & Codes.modifierBitmasks['IS_CHIRAL']);\r\n }\r\n\r\n get desktopFont(): string {\r\n if(this.scriptObject['KV']) {\r\n return this.scriptObject['KV']['F'];\r\n } else {\r\n return null;\r\n }\r\n }\r\n\r\n private get cacheTag(): CacheTag {\r\n let tag = this.scriptObject['_kmw'];\r\n\r\n if(!tag) {\r\n tag = new CacheTag();\r\n this.scriptObject['_kmw'] = tag;\r\n }\r\n\r\n return tag;\r\n }\r\n\r\n get explodedStores(): {[storeName: string]: ComplexKeyboardStore} {\r\n return this.cacheTag.stores;\r\n }\r\n\r\n /**\r\n * Signifies whether or not a layout or OSK should include AltGr / Right-alt emulation for this keyboard.\r\n * @param {Object=} keyLabels\r\n * @return {boolean}\r\n */\r\n get emulatesAltGr(): boolean {\r\n let modifierCodes = Codes.modifierCodes;\r\n\r\n // If we're not chiral, we're not emulating.\r\n if(!this.isChiral) {\r\n return false;\r\n }\r\n\r\n if(this._legacyLayoutSpec == null) {\r\n return false;\r\n }\r\n\r\n // Only exists in KMW 10.0+, but before that Web had no chirality support, so... return false.\r\n let layers = this._legacyLayoutSpec['KLS'];\r\n if(!layers) {\r\n return false;\r\n }\r\n\r\n var emulationMask = modifierCodes['LCTRL'] | modifierCodes['LALT'];\r\n var unshiftedEmulationLayer = layers[Layouts.getLayerId(emulationMask)];\r\n var shiftedEmulationLayer = layers[Layouts.getLayerId(modifierCodes['SHIFT'] | emulationMask)];\r\n\r\n // buildDefaultLayout ensures that these are aliased to the original modifier set being emulated.\r\n // As a result, we can directly test for reference equality.\r\n //\r\n // This allows us to still return `true` after creating the layers for emulation; during keyboard\r\n // construction, the two layers should be null for AltGr emulation to succeed.\r\n if(unshiftedEmulationLayer != null &&\r\n unshiftedEmulationLayer != layers[Layouts.getLayerId(modifierCodes['RALT'])]) {\r\n return false;\r\n }\r\n\r\n if(shiftedEmulationLayer != null &&\r\n shiftedEmulationLayer != layers[Layouts.getLayerId(modifierCodes['RALT'] | modifierCodes['SHIFT'])]) {\r\n return false;\r\n }\r\n\r\n // It's technically possible for the OSK to not specify anything while allowing chiral input. A last-ditch catch:\r\n var bitmask = this.modifierBitmask;\r\n if((bitmask & emulationMask) != emulationMask) {\r\n // At least one of the emulation modifiers is never used by the keyboard! We can confirm everything's safe.\r\n return true;\r\n }\r\n\r\n if(unshiftedEmulationLayer == null && shiftedEmulationLayer == null) {\r\n // We've run out of things to go on; we can't detect if chiral AltGr emulation is intended or not.\r\n // TODO: handle this again!\r\n // if(!osk.altGrWarning) {\r\n // console.warn(\"Could not detect if AltGr emulation is safe, but defaulting to active emulation!\")\r\n // // Avoid spamming the console with warnings on every call of the method.\r\n // osk.altGrWarning = true;\r\n // }\r\n return true;\r\n }\r\n return true;\r\n }\r\n\r\n get usesSupplementaryPlaneChars(): boolean {\r\n let kbd = this.scriptObject;\r\n // I3319 - SMP extension, I3363 (Build 301)\r\n return kbd && ((kbd['KS'] && kbd['KS'] == 1) || kbd['KN'] == 'Hieroglyphic');\r\n }\r\n\r\n get version(): string {\r\n return this.scriptObject['KBVER'] || '';\r\n }\r\n\r\n usesDesktopLayoutOnDevice(device: DeviceSpec) {\r\n if(this.scriptObject['KVKL']) {\r\n // A custom mobile layout is defined... but are we using it?\r\n return device.formFactor == DeviceSpec.FormFactor.Desktop;\r\n } else {\r\n return true;\r\n }\r\n }\r\n\r\n /**\r\n * @param {number} _PCommand event code (16,17,18) or 0\r\n * @param {Object} _PTarget target element\r\n * @param {number} _PData 1 or 0\r\n * Notifies keyboard of keystroke or other event\r\n */\r\n notify(_PCommand: number, _PTarget: OutputTarget, _PData: number) { // I2187\r\n // Good example use case - the Japanese CJK-picker keyboard\r\n if(typeof(this.scriptObject['KNS']) == 'function') {\r\n this.scriptObject['KNS'](_PCommand, _PTarget, _PData);\r\n }\r\n }\r\n\r\n private findOrConstructLayout(formFactor: DeviceSpec.FormFactor): LayoutFormFactor {\r\n if(this._layouts) {\r\n // Search for viable layouts. `null` is allowed for desktop form factors when help text is available,\r\n // so we check explicitly against `undefined`.\r\n if(this._layouts[formFactor] !== undefined) {\r\n return this._layouts[formFactor];\r\n } else if(formFactor == DeviceSpec.FormFactor.Phone && this._layouts[DeviceSpec.FormFactor.Tablet]) {\r\n return this._layouts[DeviceSpec.FormFactor.Phone] = this._layouts[DeviceSpec.FormFactor.Tablet];\r\n } else if(formFactor == DeviceSpec.FormFactor.Tablet && this._layouts[DeviceSpec.FormFactor.Phone]) {\r\n return this._layouts[DeviceSpec.FormFactor.Tablet] = this._layouts[DeviceSpec.FormFactor.Phone];\r\n }\r\n }\r\n\r\n // No pre-built layout available; time to start constructing it via defaults.\r\n // First, if we have non-default keys specified by the ['BK'] array, we've got\r\n // enough to work with to build a default layout.\r\n let rawSpecifications: any = null; // TODO: better typing, same type as this._legacyLayoutSpec.\r\n if(this._legacyLayoutSpec != null && this._legacyLayoutSpec['KLS']) { // KLS is only specified whenever there are non-default keys.\r\n rawSpecifications = this._legacyLayoutSpec;\r\n } else if(this._legacyLayoutSpec != null && this._legacyLayoutSpec['BK'] != null) {\r\n var keyCaps=this._legacyLayoutSpec['BK'];\r\n for(var i=0; i 0) {\r\n rawSpecifications = this._legacyLayoutSpec;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // If we don't have key definitions to use for a layout but also lack help text or are a touch-based layout,\r\n // we make a default layout anyway. We have to show display something usable.\r\n if(!rawSpecifications && (this.helpText == '' || formFactor != DeviceSpec.FormFactor.Desktop)) {\r\n rawSpecifications = {'F':'Tahoma', 'BK': Layouts.dfltText};\r\n }\r\n\r\n // Regardless of success, we'll want to initialize the field that backs the property;\r\n // may as well cache the default layout we just built, or a 'null' if it shouldn't exist..\r\n if(!this._layouts) {\r\n this._layouts = {};\r\n }\r\n\r\n // Final check - do we construct a layout, or is this a case where helpText / insertHelpHTML should take over?\r\n if(rawSpecifications) {\r\n // Now to generate a layout from our raw specifications.\r\n let layout = this._layouts[formFactor] = Layouts.buildDefaultLayout(rawSpecifications, this, formFactor);\r\n layout.isDefault = true;\r\n return layout;\r\n } else {\r\n // The fact that it doesn't exist will indicate that help text/HTML should be inserted instead.\r\n this._layouts[formFactor] = null; // provides a cached value for the check at the top of this method.\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Returns an ActiveLayout object representing the keyboard's layout for this form factor. May return null if a custom desktop \"help\" OSK is defined, as with sil_euro_latin.\r\n *\r\n * In such cases, please use either `helpText` or `insertHelpHTML` instead.\r\n * @param formFactor {string} The desired form factor for the layout.\r\n */\r\n public layout(formFactor: DeviceSpec.FormFactor): ActiveLayout {\r\n let rawLayout = this.findOrConstructLayout(formFactor);\r\n\r\n if(rawLayout) {\r\n // Prevents accidentally reprocessing layouts; it's a simple enough check.\r\n if(this.layoutStates[formFactor] == LayoutState.NOT_LOADED) {\r\n rawLayout = ActiveLayout.polyfill(rawLayout, this, formFactor);\r\n this.layoutStates[formFactor] = LayoutState.POLYFILLED;\r\n }\r\n\r\n return rawLayout as ActiveLayout;\r\n } else {\r\n return null;\r\n }\r\n }\r\n\r\n public refreshLayouts() {\r\n let formFactors = [ DeviceSpec.FormFactor.Desktop, DeviceSpec.FormFactor.Phone, DeviceSpec.FormFactor.Tablet ];\r\n\r\n let _this = this;\r\n\r\n formFactors.forEach(function(form) {\r\n // Currently doesn't work if we reset it to POLYFILLED, likely due to how 'calibration'\r\n // currently works.\r\n _this.layoutStates[form] = LayoutState.NOT_LOADED;\r\n });\r\n }\r\n\r\n public markLayoutCalibrated(formFactor: DeviceSpec.FormFactor) {\r\n if(this.layoutStates[formFactor] != LayoutState.NOT_LOADED) {\r\n this.layoutStates[formFactor] = LayoutState.CALIBRATED;\r\n }\r\n }\r\n\r\n public getLayoutState(formFactor: DeviceSpec.FormFactor) {\r\n return this.layoutStates[formFactor];\r\n }\r\n\r\n\r\n constructNullKeyEvent(device: DeviceSpec, stateKeys?: StateKeyMap): KeyEvent {\r\n stateKeys = stateKeys || {\r\n K_CAPS: false,\r\n K_NUMLOCK: false,\r\n K_SCROLL: false\r\n }\r\n\r\n const keyEvent = KeyEvent.constructNullKeyEvent(device);\r\n this.setSyntheticEventDefaults(keyEvent, stateKeys);\r\n return keyEvent;\r\n }\r\n\r\n constructKeyEvent(key: ActiveKey, device: DeviceSpec, stateKeys: StateKeyMap): KeyEvent {\r\n // Make a deep copy of our preconstructed key event, filling it out from there.\r\n const Lkc = key.baseKeyEvent;\r\n Lkc.device = device;\r\n\r\n if(this.isMnemonic) {\r\n Lkc.setMnemonicCode(key.layer.indexOf('shift') != -1, stateKeys['K_CAPS']);\r\n }\r\n\r\n // Performs common pre-analysis for both 'native' and 'embedded' OSK key & subkey input events.\r\n // This part depends on the keyboard processor's active state.\r\n this.setSyntheticEventDefaults(Lkc, stateKeys);\r\n\r\n // If it's a state key modifier, trigger its effects as part of the\r\n // keystroke.\r\n const bitmap = {\r\n 'K_CAPS': Codes.stateBitmasks.CAPS,\r\n 'K_NUMLOCK': Codes.stateBitmasks.NUM_LOCK,\r\n 'K_SCROLL': Codes.stateBitmasks.SCROLL_LOCK\r\n };\r\n const bitmask = bitmap[Lkc.kName];\r\n\r\n if(bitmask) {\r\n Lkc.Lstates ^= bitmask;\r\n Lkc.LmodifierChange = true;\r\n }\r\n\r\n return Lkc;\r\n }\r\n\r\n setSyntheticEventDefaults(Lkc: KeyEvent, stateKeys: StateKeyMap) {\r\n // Set the flags for the state keys - for desktop devices. For touch\r\n // devices, the only state key in use currently is Caps Lock, which is set\r\n // when the 'caps' layer is active in ActiveKey::constructBaseKeyEvent.\r\n if(!Lkc.device.touchable) {\r\n /*\r\n * For desktop-style keyboards, start from a blank slate. They have a 'default'\r\n * (implicit 'NO_CAPS') layer but not a 'caps' layer. With caps set, it just\r\n * highlights the key on the 'default' layer instead.\r\n *\r\n * We should never set both `CAPS` and `NO_CAPS` at the same time, and\r\n * same for the other modifiers.\r\n */\r\n Lkc.Lstates = 0;\r\n Lkc.Lstates |= stateKeys['K_CAPS'] ? Codes.modifierCodes['CAPS'] : Codes.modifierCodes['NO_CAPS'];\r\n Lkc.Lstates |= stateKeys['K_NUMLOCK'] ? Codes.modifierCodes['NUM_LOCK'] : Codes.modifierCodes['NO_NUM_LOCK'];\r\n Lkc.Lstates |= stateKeys['K_SCROLL'] ? Codes.modifierCodes['SCROLL_LOCK'] : Codes.modifierCodes['NO_SCROLL_LOCK'];\r\n }\r\n\r\n // Set LisVirtualKey to false to ensure that nomatch rule does fire for U_xxxx keys\r\n if(Lkc.kName && Lkc.kName.substr(0,2) == 'U_') {\r\n Lkc.LisVirtualKey=false;\r\n }\r\n\r\n // Get code for non-physical keys (T_KOKAI, U_05AB etc)\r\n if(typeof Lkc.Lcode == 'undefined') {\r\n Lkc.Lcode = this.getVKDictionaryCode(Lkc.kName);// Updated for Build 347\r\n if(!Lkc.Lcode) {\r\n // Special case for U_xxxx keys. This vk code will never be used\r\n // in a keyboard, so we use this to ensure that keystroke processing\r\n // occurs for the key.\r\n Lkc.Lcode = 1;\r\n }\r\n }\r\n\r\n // Handles modifier states when the OSK is emulating rightalt through the leftctrl-leftalt layer.\r\n if((Lkc.Lmodifiers & Codes.modifierBitmasks['ALT_GR_SIM']) == Codes.modifierBitmasks['ALT_GR_SIM'] && this.emulatesAltGr) {\r\n Lkc.Lmodifiers &= ~Codes.modifierBitmasks['ALT_GR_SIM'];\r\n Lkc.Lmodifiers |= Codes.modifierCodes['RALT'];\r\n }\r\n }\r\n\r\n /**\r\n * @summary Look up a custom virtual key code in the virtual key code dictionary KVKD.\r\n * On first run, will build the dictionary.\r\n *\r\n * `VKDictionary` is constructed from the keyboard's `KVKD` member. This list is constructed\r\n * at compile-time and is a list of 'additional' virtual key codes, starting at 256 (i.e.\r\n * outside the range of standard virtual key codes). These additional codes are both\r\n * `[T_xxx]` and `[U_xxxx]` custom key codes from the Keyman keyboard language. However,\r\n * `[U_xxxx]` keys only generate an entry in `KVKD` if there is a corresponding rule that\r\n * is associated with them in the keyboard rules. If the `[U_xxxx]` key code is only\r\n * referenced as the id of a key in the touch layout, then it does not get an entry in\r\n * the `KVKD` property.\r\n *\r\n * @private\r\n * @param {string} keyName custom virtual key code to lookup in the dictionary\r\n * @return {number} key code > 255 on success, or 0 if not found\r\n */\r\n getVKDictionaryCode(keyName: string) {\r\n if(!this.scriptObject['VKDictionary']) {\r\n const a=[];\r\n if(typeof this.scriptObject['KVKD'] == 'string') {\r\n // Build the VK dictionary\r\n // TODO: Move the dictionary build into the compiler -- so compiler generates code such as following.\r\n // Makes the VKDictionary member unnecessary.\r\n // this.KVKD={\"K_ABC\":256,\"K_DEF\":257,...};\r\n const s=this.scriptObject['KVKD'].split(' ');\r\n for(var i=0; i {\r\n this.harness.install();\r\n const promise = this.loadKeyboardInternal(uri, new UriBasedErrorBuilder(uri));\r\n\r\n return promise;\r\n }\r\n\r\n public loadKeyboardFromStub(stub: KeyboardStub) {\r\n this.harness.install();\r\n let promise = this.loadKeyboardInternal(stub.filename, new StubBasedErrorBuilder(stub), stub.id);\r\n\r\n return promise;\r\n }\r\n\r\n protected abstract loadKeyboardInternal(\r\n uri: string,\r\n errorBuilder: KeyboardLoadErrorBuilder,\r\n id?: string\r\n ): Promise;\r\n}", + "// Compiles completely out if `const enum`, making it unavailable in JS-based unit tests.\r\nenum SpacebarText {\r\n KEYBOARD = 'keyboard',\r\n LANGUAGE = 'language',\r\n LANGUAGE_KEYBOARD = 'languageKeyboard',\r\n BLANK = 'blank'\r\n};\r\n\r\nexport default SpacebarText;", + "import SpacebarText from './spacebarText.js';\r\n\r\nexport interface InternalKeyboardFont {\r\n family: string;\r\n filename?: never;\r\n files: string | string[]; // internal\r\n source?: never;\r\n path: string;\r\n}\r\n\r\ninterface CloudKeyboardFont1 {\r\n family: string;\r\n filename: string | string[];\r\n files?: never;\r\n source?: never;\r\n}\r\n\r\ninterface CloudKeyboardFont2 {\r\n family: string;\r\n filename?: never;\r\n files?: never;\r\n source: string | string[];\r\n}\r\n\r\nexport type CloudKeyboardFont = CloudKeyboardFont1 | CloudKeyboardFont2;\r\n\r\n/**\r\n * Converts one of three public-facing font-specification formats into a consistent structure\r\n * used generally among the Keyman JS/TS modules.\r\n * @param fontObj\r\n * @param fontPath\r\n * @returns\r\n */\r\nexport function internalizeFont(fontObj: CloudKeyboardFont, fontPath: string): InternalKeyboardFont {\r\n if(!fontObj) {\r\n return undefined;\r\n } else {\r\n return {\r\n family: fontObj.family,\r\n path: fontPath,\r\n files: fontObj.filename || fontObj.source\r\n }\r\n }\r\n}\r\n\r\nexport type KeyboardFont = CloudKeyboardFont | InternalKeyboardFont;\r\n\r\n// Filename properties are deliberately omitted here; we can add that at higher-levels where it matters\r\n// via 'mix-in'.\r\n//\r\n// For example, the OSK module doesn't care about the filename of a loaded keyboard. It doesn't do\r\n// keyboard loading on its own whatsoever.\r\n\r\n// Corresponds to Keyman Engine for Web's internal \"keyboard stub\" format.\r\n// Also referred to by KMW 2.0-era loaders: https://help.keyman.com/developer/8.0/docs/reference_kmw20_example\r\nexport interface KeyboardInternalPropertySpec {\r\n KI: string,\r\n KFont: InternalKeyboardFont,\r\n KOskFont: InternalKeyboardFont,\r\n displayName?: string,\r\n KN?: string,\r\n KL?: string,\r\n KLC?: string\r\n};\r\n\r\nexport type LanguageAPIPropertySpec = {\r\n id: string,\r\n name: string,\r\n font: CloudKeyboardFont,\r\n oskFont: CloudKeyboardFont,\r\n region?: number|string\r\n}\r\n\r\n/**\r\n * Corresponds to the documented API for the Web engine's `addKeyboards` function\r\n * when a single language object is specified - not an array.\r\n *\r\n * See https://help.keyman.com/DEVELOPER/ENGINE/WEB/15.0/reference/core/addKeyboards,\r\n * \"Using an `object`\".\r\n */\r\nexport type KeyboardAPIPropertySpec = {\r\n id: string,\r\n name: string,\r\n\r\n /**\r\n * @deprecated Replaced with `languages`.\r\n */\r\n language?: LanguageAPIPropertySpec;\r\n languages: LanguageAPIPropertySpec;\r\n}\r\n\r\n/**\r\n * Corresponds to the documented API for the Web engine's `addKeyboards` function\r\n * when a language array is specified for the object.\r\n *\r\n * See https://help.keyman.com/DEVELOPER/ENGINE/WEB/15.0/reference/core/addKeyboards,\r\n * \"Using an `object`\".\r\n */\r\nexport type KeyboardAPIPropertyMultilangSpec = {\r\n id: string,\r\n name: string,\r\n\r\n /**\r\n * @deprecated Replaced with `languages`.\r\n */\r\n language?: LanguageAPIPropertySpec[];\r\n languages: LanguageAPIPropertySpec[];\r\n}\r\n\r\nexport type MetadataObj = KeyboardInternalPropertySpec | KeyboardAPIPropertySpec | KeyboardAPIPropertyMultilangSpec;\r\n\r\nexport default class KeyboardProperties implements KeyboardInternalPropertySpec {\r\n KI: string;\r\n KN: string;\r\n KL: string;\r\n KLC: string;\r\n KFont: InternalKeyboardFont;\r\n KOskFont: InternalKeyboardFont;\r\n _displayName?: string;\r\n\r\n private static spacebarTextModeSrc: SpacebarText | (() => SpacebarText) = SpacebarText.KEYBOARD;\r\n\r\n public static get spacebarTextMode(): SpacebarText {\r\n if(typeof this.spacebarTextModeSrc == 'string') {\r\n return this.spacebarTextModeSrc;\r\n } else {\r\n return this.spacebarTextModeSrc();\r\n }\r\n }\r\n\r\n public static set spacebarTextMode(source: typeof KeyboardProperties.spacebarTextModeSrc) {\r\n this.spacebarTextModeSrc = source;\r\n }\r\n\r\n public constructor(metadataObj: MetadataObj, fontPath?: string);\r\n public constructor(keyboardId: string, languageCode: string);\r\n public constructor(arg1: MetadataObj | string, arg2?: string) {\r\n if(!(typeof arg1 == 'string')) {\r\n if(arg1['KI'] || arg1['KL'] || arg1['KLC'] || arg1['KFont'] || arg1['KOskFont']) {\r\n const other = arg1 as KeyboardInternalPropertySpec;\r\n this.KI = other.KI;\r\n this.KN = other.KN;\r\n this.KL = other.KL;\r\n this.KLC = other.KLC;\r\n // Do NOT apply fontPath here; the mobile apps will have font issues if you do!\r\n this.KFont = other.KFont;\r\n this.KOskFont = other.KOskFont;\r\n this._displayName = (other instanceof KeyboardProperties) ? other._displayName : other.displayName;\r\n } else {\r\n let apiStub = arg1 as KeyboardAPIPropertySpec; // TODO: could be an array, as currently specified. :(\r\n\r\n apiStub.languages ||= apiStub.language;\r\n\r\n this.KI = apiStub.id,\r\n this.KN = apiStub.name,\r\n this.KL = apiStub.languages.name,\r\n this.KLC = apiStub.languages.id,\r\n this.KFont = internalizeFont(apiStub.languages.font, arg2),\r\n this.KOskFont = internalizeFont(apiStub.languages.oskFont, arg2)\r\n }\r\n } else {\r\n this.KI = arg1;\r\n this.KLC = arg2;\r\n }\r\n }\r\n\r\n public static fromMultilanguageAPIStub(apiStub: KeyboardAPIPropertyMultilangSpec): KeyboardProperties[] {\r\n let stubs: KeyboardProperties[] = [];\r\n\r\n apiStub.languages ||= apiStub.language;\r\n\r\n for(let langSpec of apiStub.languages) {\r\n let stub: KeyboardAPIPropertySpec = {\r\n id: apiStub.id,\r\n name: apiStub.name,\r\n languages: langSpec\r\n };\r\n\r\n stubs.push(new KeyboardProperties(stub));\r\n }\r\n\r\n return stubs;\r\n }\r\n\r\n public get id(): string {\r\n return this.KI;\r\n }\r\n\r\n public get name(): string {\r\n return this.KN;\r\n }\r\n\r\n public get langId(): string {\r\n return this.KLC;\r\n }\r\n\r\n public get langName(): string {\r\n return this.KL;\r\n }\r\n\r\n public get displayName(): string {\r\n if(this._displayName) {\r\n return this._displayName;\r\n }\r\n\r\n // else, construct it.\r\n const kbdName = this.KN;\r\n const lgName = this.KL;\r\n\r\n switch (KeyboardProperties.spacebarTextMode) {\r\n case SpacebarText.KEYBOARD:\r\n return kbdName;\r\n case SpacebarText.LANGUAGE:\r\n return lgName;\r\n case SpacebarText.LANGUAGE_KEYBOARD:\r\n return (kbdName == lgName) ? lgName : lgName + ' - ' + kbdName;\r\n case SpacebarText.BLANK:\r\n return '';\r\n default:\r\n return kbdName;\r\n }\r\n }\r\n\r\n public set displayName(name: string) {\r\n this._displayName = name;\r\n }\r\n\r\n public get textFont() {\r\n return this.KFont;\r\n }\r\n\r\n public get oskFont() {\r\n return this.KOskFont;\r\n }\r\n\r\n /**\r\n * Generates an error for objects with specification levels insufficient for use in the on-screen-keyboard\r\n * module, complete with a message about one or more details in need of correction.\r\n * @returns A preconstructed `Error` instance that may be thrown by the caller.\r\n */\r\n public validateForOSK(): Error {\r\n if(!this.KLC) {\r\n if(this.KI || this.KN) {\r\n return new Error(`No language code was specified for use with the ${this.KI || this.KN} keyboard`);\r\n } else {\r\n return new Error(\"No language code was specified for use with the corresponding keyboard\")\r\n }\r\n }\r\n\r\n if(this.displayName === undefined || (KeyboardProperties.spacebarTextMode != SpacebarText.BLANK && !this.displayName)) {\r\n return new Error(\"A display name is missing for this keyboard and cannot be generated under current settings.\")\r\n }\r\n\r\n return null;\r\n }\r\n\r\n public validateForCustomKeyboard(): Error {\r\n if(!this.KI || !this.KN || !this.KL || !this.KLC) {\r\n return new Error(\"To use a custom keyboard, you must specify keyboard id, keyboard name, language and language code.\");\r\n } else {\r\n return null;\r\n }\r\n }\r\n}", + "// Defines the base Deadkey-tracking object.\r\nexport class Deadkey {\r\n p: number; // Position of deadkey\r\n d: number; // Numerical id of the deadkey\r\n o: number; // Ordinal value of the deadkey (resolves same-place conflicts)\r\n matched: number;\r\n\r\n static ordinalSeed: number = 0;\r\n\r\n constructor(pos: number, id: number) {\r\n this.p = pos;\r\n this.d = id;\r\n this.o = Deadkey.ordinalSeed++;\r\n }\r\n\r\n match(p: number, d: number): boolean {\r\n var result:boolean = (this.p == p && this.d == d);\r\n\r\n return result;\r\n }\r\n\r\n set(): void {\r\n this.matched = 1;\r\n }\r\n\r\n reset(): void {\r\n this.matched = 0;\r\n }\r\n\r\n before(other: Deadkey): boolean {\r\n return this.o < other.o;\r\n }\r\n\r\n clone(): Deadkey {\r\n let dk = new Deadkey(this.p, this.d);\r\n dk.o = this.o;\r\n\r\n return dk;\r\n }\r\n\r\n /**\r\n * Sorts the deadkeys in reverse order.\r\n */\r\n static sortFunc = function(a: Deadkey, b: Deadkey) {\r\n // We want descending order, so we want 'later' deadkeys first.\r\n if(a.p != b.p) {\r\n return b.p - a.p;\r\n } else {\r\n return b.o - a.o;\r\n }\r\n };\r\n}\r\n\r\n// Object-orients deadkey management.\r\nexport class DeadkeyTracker {\r\n dks: Deadkey[] = [];\r\n\r\n toSortedArray(): Deadkey[] {\r\n this.dks = this.dks.sort(Deadkey.sortFunc);\r\n return [].concat(this.dks);\r\n }\r\n\r\n clone(): DeadkeyTracker {\r\n let dkt = new DeadkeyTracker();\r\n let dks = this.toSortedArray();\r\n\r\n // Make sure to clone the deadkeys themselves - the Deadkey object is mutable.\r\n dkt.dks = [];\r\n dks.forEach(function(value: Deadkey) {\r\n dkt.dks.push(value.clone());\r\n });\r\n\r\n return dkt;\r\n }\r\n\r\n /**\r\n * Function isMatch\r\n * Scope Public\r\n * @param {number} caretPos current cursor position\r\n * @param {number} n expected offset of deadkey from cursor\r\n * @param {number} d deadkey\r\n * @return {boolean} True if deadkey found selected context matches val\r\n * Description Match deadkey at current cursor position\r\n */\r\n isMatch(caretPos: number, n: number, d: number): boolean {\r\n if(this.dks.length == 0) {\r\n return false; // I3318\r\n }\r\n\r\n var sp=caretPos;\r\n n = sp - n;\r\n for(var i = 0; i < this.dks.length; i++) {\r\n // Don't re-match an already-matched deadkey. It's possible to have two identical\r\n // entries, and they should be kept separately.\r\n if(this.dks[i].match(n, d) && !this.dks[i].matched) {\r\n this.dks[i].set();\r\n // Assumption: since we match the first possible entry in the array, we\r\n // match the entry with the lower ordinal - the 'first' deadkey in the position.\r\n return true; // I3318\r\n }\r\n }\r\n\r\n this.resetMatched(); // I3318\r\n\r\n return false;\r\n }\r\n\r\n add(dk: Deadkey) {\r\n this.dks = this.dks.concat(dk);\r\n }\r\n\r\n remove(dk: Deadkey) {\r\n var index = this.dks.indexOf(dk);\r\n this.dks.splice(index, 1);\r\n }\r\n\r\n clear() {\r\n this.dks = [];\r\n }\r\n\r\n resetMatched() {\r\n for(let dk of this.dks) {\r\n dk.reset();\r\n }\r\n }\r\n\r\n deleteMatched(): void {\r\n for(var Li = 0; Li < this.dks.length; Li++) {\r\n if(this.dks[Li].matched) {\r\n this.dks.splice(Li--, 1); // Don't forget to decrement!\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Function adjustPositions (formerly _DeadkeyAdjustPos)\r\n * Scope Private\r\n * @param {number} Lstart start position in context\r\n * @param {number} Ldelta characters to adjust by\r\n * Description Adjust saved positions of deadkeys in context\r\n */\r\n adjustPositions(Lstart: number, Ldelta: number): void {\r\n if(Ldelta == 0) {\r\n return;\r\n }\r\n\r\n for(let dk of this.dks) {\r\n if(dk.p > Lstart) {\r\n dk.p += Ldelta;\r\n }\r\n }\r\n }\r\n\r\n count(): number {\r\n return this.dks.length;\r\n }\r\n}", + "import type KeyboardInterface from \"./kbdInterface.js\";\r\nimport { SystemStoreIDs } from \"./kbdInterface.js\";\r\n\r\n/**\r\n * Defines common behaviors associated with system stores.\r\n */\r\nexport abstract class SystemStore {\r\n public readonly id: number;\r\n\r\n constructor(id: number) {\r\n this.id = id;\r\n }\r\n\r\n abstract matches(value: string): boolean;\r\n\r\n set(value: string): void {\r\n throw new Error(\"System store with ID \" + this.id + \" may not be directly set.\");\r\n }\r\n}\r\n\r\n/**\r\n * A handler designed to receive feedback whenever a system store's value is changed.\r\n * @param source The system store being mutated, before the value change occurs.\r\n * @param newValue The new value being set\r\n * @returns `false` / `undefined` to allow the change, `true` to block the change.\r\n */\r\nexport type SystemStoreMutationHandler = (source: MutableSystemStore, newValue: string) => boolean;\r\n\r\nexport class MutableSystemStore extends SystemStore {\r\n private _value: string;\r\n handler?: SystemStoreMutationHandler = null;\r\n\r\n constructor(id: number, defaultValue: string) {\r\n super(id);\r\n this._value = defaultValue;\r\n }\r\n\r\n get value() {\r\n return this._value;\r\n }\r\n\r\n matches(value: string) {\r\n return this._value == value;\r\n }\r\n\r\n set(value: string) {\r\n // Even if things stay the same, we should still signal this.\r\n // It's important for tracking if a rule directly set the layer\r\n // versus if it passively remained.\r\n if(this.handler) {\r\n if(this.handler(this, value)) {\r\n return;\r\n }\r\n }\r\n\r\n this._value = value;\r\n }\r\n}\r\n\r\n/**\r\n * Handles checks against the current platform.\r\n */\r\nexport class PlatformSystemStore extends SystemStore {\r\n private readonly kbdInterface: KeyboardInterface;\r\n\r\n constructor(keyboardInterface: KeyboardInterface) {\r\n super(SystemStoreIDs.TSS_PLATFORM);\r\n\r\n this.kbdInterface = keyboardInterface;\r\n }\r\n\r\n matches(value: string) {\r\n var i,constraint,constraints=value.split(' ');\r\n let device = this.kbdInterface.activeDevice;\r\n\r\n for(i=0; i\r\n\r\nimport { extendString } from \"@keymanapp/web-utils\";\r\n\r\nextendString();\r\n\r\n// Defines deadkey management in a manner attachable to each element interface.\r\nimport type KeyEvent from \"./keyEvent.js\";\r\nimport { Deadkey, DeadkeyTracker } from \"./deadkeys.js\";\r\n\r\n// Also relies on string-extensions provided by the web-utils package.\r\n\r\nexport function isEmptyTransform(transform: Transform) {\r\n if(!transform) {\r\n return false;\r\n }\r\n return transform.insert === '' && transform.deleteLeft === 0 && (transform.deleteRight ?? 0) === 0;\r\n}\r\n\r\nexport class TextTransform implements Transform {\r\n readonly insert: string;\r\n readonly deleteLeft: number;\r\n readonly deleteRight?: number;\r\n\r\n constructor(insert: string, deleteLeft: number, deleteRight?: number) {\r\n this.insert = insert;\r\n this.deleteLeft = deleteLeft;\r\n this.deleteRight = deleteRight || 0;\r\n }\r\n\r\n public static readonly nil = new TextTransform('', 0, 0);\r\n}\r\n\r\nexport class Transcription {\r\n readonly token: number;\r\n readonly keystroke: KeyEvent;\r\n readonly transform: Transform;\r\n alternates: Alternate[]; // constructed after the rest of the transcription.\r\n readonly preInput: Mock;\r\n\r\n private static tokenSeed: number = 0;\r\n\r\n constructor(keystroke: KeyEvent, transform: Transform, preInput: Mock, alternates?: Alternate[]/*, removedDks: Deadkey[], insertedDks: Deadkey[]*/) {\r\n let token = this.token = Transcription.tokenSeed++;\r\n\r\n this.keystroke = keystroke;\r\n this.transform = transform;\r\n this.alternates = alternates;\r\n this.preInput = preInput;\r\n\r\n this.transform.id = this.token;\r\n\r\n // Assign the ID to each alternate, as well.\r\n if(alternates) {\r\n alternates.forEach(function(alt) {\r\n alt.sample.id = token;\r\n });\r\n }\r\n }\r\n}\r\n\r\nexport type Alternate = ProbabilityMass;\r\n\r\nexport default abstract class OutputTarget {\r\n private _dks: DeadkeyTracker;\r\n\r\n constructor() {\r\n this._dks = new DeadkeyTracker();\r\n }\r\n\r\n /**\r\n * Signifies that this OutputTarget has no default key processing behaviors. This should be false\r\n * for OutputTargets backed by web elements like HTMLInputElement or HTMLTextAreaElement.\r\n */\r\n get isSynthetic(): boolean {\r\n return true;\r\n }\r\n\r\n resetContext(): void {\r\n this.deadkeys().clear();\r\n }\r\n\r\n deadkeys(): DeadkeyTracker {\r\n return this._dks;\r\n }\r\n\r\n hasDeadkeyMatch(n: number, d: number): boolean {\r\n return this.deadkeys().isMatch(this.getDeadkeyCaret(), n, d);\r\n }\r\n\r\n insertDeadkeyBeforeCaret(d: number) {\r\n var dk: Deadkey = new Deadkey(this.getDeadkeyCaret(), d);\r\n this.deadkeys().add(dk);\r\n }\r\n\r\n /**\r\n * Should be called by each output target immediately before text mutation operations occur.\r\n *\r\n * Maintains solutions to old issues: I3318,I3319\r\n * @param {number} delta Use negative values if characters were deleted, positive if characters were added.\r\n */\r\n protected adjustDeadkeys(delta: number) {\r\n this.deadkeys().adjustPositions(this.getDeadkeyCaret(), delta);\r\n }\r\n\r\n /**\r\n * Needed to properly clone deadkeys for use with Mock element interfaces toward predictive text purposes.\r\n * @param {object} dks An existing set of deadkeys to deep-copy for use by this element interface.\r\n */\r\n protected setDeadkeys(dks: DeadkeyTracker) {\r\n this._dks = dks.clone();\r\n }\r\n\r\n /**\r\n * Determines the basic operations needed to reconstruct the current OutputTarget's text from the prior state specified\r\n * by another OutputTarget based on their text and caret positions.\r\n *\r\n * This is designed for use as a \"before and after\" comparison to determine the effect of a single keyboard rule at a time.\r\n * As such, it assumes that the caret is immediately after any inserted text.\r\n * @param from An output target (preferably a Mock) representing the prior state of the input/output system.\r\n */\r\n buildTransformFrom(original: OutputTarget): Transform {\r\n let to = this.getText();\r\n let from = original.getText();\r\n\r\n let fromCaret = original.getDeadkeyCaret();\r\n let toCaret = this.getDeadkeyCaret();\r\n\r\n // Step 1: Determine the number of left-deletions.\r\n let maxSMPLeftMatch = fromCaret < toCaret ? fromCaret : toCaret;\r\n\r\n // We need the corresponding non-SMP caret location in order to binary-search efficiently.\r\n // (Examining code units is much more computationally efficient.)\r\n let maxLeftMatch = to._kmwCodePointToCodeUnit(maxSMPLeftMatch);\r\n\r\n // 1.1: use a non-SMP-aware binary search to determine the divergence point.\r\n let start = 0;\r\n let end = maxLeftMatch; // the index AFTER the last possible matching char.\r\n\r\n // This search is O(maxLeftMatch). 1/2 + 1/4 + 1/8 + ... converges to = 1.\r\n while(start < end) {\r\n let mid = Math.floor((end+start+1) / 2); // round up (compare more)\r\n let fromLeft = from.substr(start, mid-start);\r\n let toLeft = to.substr(start, mid-start);\r\n\r\n if(fromLeft == toLeft) {\r\n start = mid;\r\n } else {\r\n end = mid - 1;\r\n }\r\n }\r\n\r\n // At the loop's end: `end` now holds the non-SMP-aware divergence point.\r\n // The 'caret' is after the last matching code unit.\r\n\r\n // 1.2: detect a possible surrogate-pair split scenario, correcting for it\r\n // (by moving the split before the high-surrogate) if detected.\r\n\r\n // If the split location is precisely on either end of the context, we can't\r\n // have split a surrogate pair.\r\n if(end > 0 && end < maxLeftMatch) {\r\n let potentialHigh = from.charCodeAt(end-1);\r\n let potentialFromLow = from.charCodeAt(end);\r\n let potentialToLow = to.charCodeAt(end);\r\n\r\n // if potentialHigh is a possible high surrogate...\r\n if(potentialHigh >= 0xD800 && potentialHigh <= 0xDBFF) {\r\n // and at least one potential 'low' is a possible low surrogate...\r\n let flag = potentialFromLow >= 0xDC00 && potentialFromLow <= 0xDFFF;\r\n flag = flag || (potentialToLow >= 0XDC00 && potentialToLow <= 0xDFFF);\r\n\r\n // Correct the split location, moving it 'before' the high surrogate.\r\n if(flag) {\r\n end = end - 1;\r\n }\r\n }\r\n }\r\n\r\n // 1.3: take substring from start to the split point; determine SMP-aware length.\r\n // This yields the SMP-aware divergence index, which gives the number of left-deletes.\r\n let newCaret = from._kmwCodeUnitToCodePoint(end);\r\n let deletedLeft = fromCaret - newCaret;\r\n\r\n // Step 2: Determine the other properties.\r\n // Since the 'after' OutputTarget's caret indicates the end of any inserted text, we\r\n // can easily calculate the rest.\r\n let insertedLength = toCaret - newCaret;\r\n let delta = to._kmwSubstr(newCaret, insertedLength);\r\n\r\n let undeletedRight = to._kmwLength() - toCaret;\r\n let originalRight = from._kmwLength() - fromCaret;\r\n let deletedRight = originalRight - undeletedRight;\r\n\r\n // May occur when reverting a suggestion that had been applied mid-word.\r\n if(deletedRight < 0) {\r\n // Restores deleteRight characters.\r\n delta = delta + to._kmwSubstr(toCaret, -deletedRight);\r\n deletedRight = 0;\r\n }\r\n\r\n return new TextTransform(delta, deletedLeft, deletedRight);\r\n }\r\n\r\n buildTranscriptionFrom(original: OutputTarget, keyEvent: KeyEvent, readonly: boolean, alternates?: Alternate[]): Transcription {\r\n let transform = this.buildTransformFrom(original);\r\n\r\n // If we ever decide to re-add deadkey tracking, this is the place for it.\r\n\r\n return new Transcription(keyEvent, transform, Mock.from(original, readonly), alternates);\r\n }\r\n\r\n /**\r\n * Restores the `OutputTarget` to the indicated state. Designed for use with `Transcription.preInput`.\r\n * @param original An `OutputTarget` (usually a `Mock`).\r\n */\r\n restoreTo(original: OutputTarget) {\r\n //\r\n this.setTextBeforeCaret(original.getTextBeforeCaret());\r\n this.setTextAfterCaret(original.getTextAfterCaret());\r\n\r\n // Also, restore the deadkeys!\r\n this._dks = original._dks.clone();\r\n }\r\n\r\n apply(transform: Transform) {\r\n if(transform.deleteRight) {\r\n this.setTextAfterCaret(this.getTextAfterCaret()._kmwSubstr(transform.deleteRight));\r\n }\r\n\r\n if(transform.deleteLeft) {\r\n this.deleteCharsBeforeCaret(transform.deleteLeft);\r\n }\r\n\r\n if(transform.insert) {\r\n this.insertTextBeforeCaret(transform.insert);\r\n }\r\n\r\n // We assume that all deadkeys are invalidated after applying a Transform, since\r\n // prediction implies we'll be completing a word, post-deadkeys.\r\n this._dks.clear();\r\n }\r\n\r\n /**\r\n * Helper to `restoreTo` - allows directly setting the 'before' context to that of another\r\n * `OutputTarget`.\r\n * @param s\r\n */\r\n protected setTextBeforeCaret(s: string): void {\r\n // This one's easy enough to provide a default implementation for.\r\n this.deleteCharsBeforeCaret(this.getTextBeforeCaret()._kmwLength());\r\n this.insertTextBeforeCaret(s);\r\n }\r\n\r\n /**\r\n * Helper to `restoreTo` - allows directly setting the 'after' context to that of another\r\n * `OutputTarget`.\r\n * @param s\r\n */\r\n protected abstract setTextAfterCaret(s: string): void;\r\n\r\n /**\r\n * Clears any selected text within the wrapper's element(s).\r\n * Silently does nothing if no such text exists.\r\n */\r\n abstract clearSelection(): void;\r\n\r\n /**\r\n * Clears any cached selection-related state values.\r\n */\r\n abstract invalidateSelection(): void;\r\n\r\n /**\r\n * Indicates whether or not the underlying element has its own selection (input, textarea)\r\n * or is part of (or possesses) the DOM's active selection. Don't confuse with isSelectionEmpty().\r\n *\r\n * TODO: rename to supportsOwnSelection\r\n */\r\n abstract hasSelection(): boolean;\r\n\r\n /**\r\n * Returns true if there is no current selection -- that is, the selection range is empty\r\n */\r\n abstract isSelectionEmpty(): boolean;\r\n\r\n /**\r\n * Returns an index corresponding to the caret's position for use with deadkeys.\r\n */\r\n abstract getDeadkeyCaret(): number;\r\n\r\n /**\r\n * Relative to the caret, gets the current context within the wrapper's element.\r\n */\r\n abstract getTextBeforeCaret(): string;\r\n\r\n /**\r\n * Gets the element's-currently selected text.\r\n */\r\n abstract getSelectedText(): string;\r\n\r\n /**\r\n * Relative to the caret (and/or active selection), gets the element's text after the caret,\r\n * excluding any actively selected text that would be immediately replaced upon text entry.\r\n */\r\n abstract getTextAfterCaret(): string;\r\n\r\n /**\r\n * Gets the element's full text, including any text that is actively selected.\r\n */\r\n abstract getText(): string;\r\n\r\n /**\r\n * Performs context deletions (from the left of the caret) as needed by the KeymanWeb engine and\r\n * corrects the location of any affected deadkeys.\r\n *\r\n * Does not delete deadkeys (b/c KMW 1 & 2 behavior maintenance).\r\n * @param dn The number of characters to delete. If negative, context will be left unchanged.\r\n */\r\n abstract deleteCharsBeforeCaret(dn: number): void;\r\n\r\n /**\r\n * Inserts text immediately before the caret's current position, moving the caret after the\r\n * newly inserted text in the process along with any affected deadkeys.\r\n *\r\n * @param s Text to insert before the caret's current position.\r\n */\r\n abstract insertTextBeforeCaret(s: string): void;\r\n\r\n /**\r\n * Allows element-specific handling for ENTER key inputs. Conceptually, this should usually\r\n * correspond to `insertTextBeforeCaret('\\n'), but actual implementation will vary greatly among\r\n * elements.\r\n */\r\n abstract handleNewlineAtCaret(): void;\r\n\r\n /**\r\n * Saves element-specific state properties prone to mutation, enabling restoration after\r\n * text-output operations.\r\n */\r\n saveProperties() {\r\n // Most element interfaces won't need anything here.\r\n }\r\n\r\n /**\r\n * Restores previously-saved element-specific state properties. Designed for use after text-output\r\n * ops to facilitate more-seamless web-dev and user interactions.\r\n */\r\n restoreProperties(){\r\n // Most element interfaces won't need anything here.\r\n }\r\n\r\n /**\r\n * Generates a synthetic event on the underlying element, signalling that its value has changed.\r\n */\r\n abstract doInputEvent(): void;\r\n}\r\n\r\n// Due to some interesting requirements on compile ordering in TS,\r\n// this needs to be in the same file as OutputTarget now.\r\nexport class Mock extends OutputTarget {\r\n text: string;\r\n\r\n selStart: number;\r\n selEnd: number;\r\n selForward: boolean = true;\r\n\r\n constructor(text?: string, caretPos?: number);\r\n constructor(text?: string, selStart?: number, selEnd?: number);\r\n constructor(text?: string, selStart?: number, selEnd?: number) {\r\n super();\r\n\r\n this.text = text ? text : \"\";\r\n var defaultLength = this.text._kmwLength();\r\n\r\n // Ensures that `caretPos == 0` is handled correctly.\r\n this.selStart = typeof selStart == \"number\" ? selStart : defaultLength;\r\n\r\n // If no selection-end is set, selection length is implied to be 0.\r\n this.selEnd = typeof selEnd == \"number\" ? selEnd : this.selStart;\r\n\r\n this.selForward = this.selEnd >= this.selStart;\r\n }\r\n\r\n // Clones the state of an existing EditableElement, creating a Mock version of its state.\r\n static from(outputTarget: OutputTarget, readonly?: boolean) {\r\n let clone: Mock;\r\n\r\n if(outputTarget instanceof Mock) {\r\n // Avoids the need to run expensive kmwstring.ts / `_kmwLength()`\r\n // calculations when deep-copying Mock instances.\r\n let priorMock = outputTarget as Mock;\r\n clone = new Mock(priorMock.text, priorMock.selStart, priorMock.selEnd);\r\n } else {\r\n const text = outputTarget.getText();\r\n const textLen = text._kmwLength();\r\n\r\n // If !hasSelection()\r\n let selectionStart: number = textLen;\r\n let selectionEnd: number = 0;\r\n\r\n if(outputTarget.hasSelection()) {\r\n let beforeText = outputTarget.getTextBeforeCaret();\r\n let afterText = outputTarget.getTextAfterCaret();\r\n selectionStart = beforeText._kmwLength();\r\n selectionEnd = textLen - afterText._kmwLength();\r\n }\r\n\r\n // readonly group or not, the returned Mock remains the same.\r\n // New-context events should act as if the caret were at the earlier-in-context\r\n // side of the selection, same as standard keyboard rules.\r\n clone = new Mock(text, selectionStart, selectionEnd);\r\n }\r\n\r\n // Also duplicate deadkey state! (Needed for fat-finger ops.)\r\n clone.setDeadkeys(outputTarget.deadkeys());\r\n\r\n return clone;\r\n }\r\n\r\n clearSelection(): void {\r\n this.text = this.getTextBeforeCaret() + this.getTextAfterCaret();\r\n this.selEnd = this.selStart;\r\n this.selForward = true;\r\n }\r\n\r\n invalidateSelection(): void {\r\n return;\r\n }\r\n\r\n isSelectionEmpty(): boolean {\r\n return this.selStart == this.selEnd;\r\n }\r\n\r\n hasSelection(): boolean {\r\n return true;\r\n }\r\n\r\n getDeadkeyCaret(): number {\r\n return this.selStart;\r\n }\r\n\r\n setSelection(start: number, end?: number) {\r\n this.selStart = start;\r\n this.selEnd = typeof end == 'number' ? end : start;\r\n\r\n this.selForward = end >= start;\r\n if(!this.selForward) {\r\n let temp = this.selStart;\r\n this.selStart = this.selEnd;\r\n this.selEnd = temp;\r\n }\r\n }\r\n\r\n getTextBeforeCaret(): string {\r\n return this.text.kmwSubstr(0, this.selStart);\r\n }\r\n\r\n getSelectedText(): string {\r\n return this.text.kmwSubstr(this.selStart, this.selEnd - this.selStart);\r\n }\r\n\r\n getTextAfterCaret(): string {\r\n return this.text.kmwSubstr(this.selEnd);\r\n }\r\n\r\n getText(): string {\r\n return this.text;\r\n }\r\n\r\n deleteCharsBeforeCaret(dn: number): void {\r\n if(dn >= 0) {\r\n if(dn > this.selStart) {\r\n dn = this.selStart;\r\n }\r\n this.adjustDeadkeys(-dn);\r\n this.text = this.text.kmwSubstr(0, this.selStart - dn) + this.text.kmwSubstr(this.selStart);\r\n this.selStart -= dn;\r\n this.selEnd -= dn;\r\n }\r\n }\r\n\r\n insertTextBeforeCaret(s: string): void {\r\n this.adjustDeadkeys(s._kmwLength());\r\n this.text = this.getTextBeforeCaret() + s + this.text.kmwSubstr(this.selStart);\r\n this.selStart += s.kmwLength();\r\n this.selEnd += s.kmwLength();\r\n }\r\n\r\n handleNewlineAtCaret(): void {\r\n this.insertTextBeforeCaret('\\n');\r\n }\r\n\r\n protected setTextAfterCaret(s: string): void {\r\n this.text = this.getTextBeforeCaret() + s;\r\n }\r\n\r\n doInputEvent() {\r\n // Mock isn't backed by an element, so it won't have any event listeners.\r\n }\r\n}", + "///\r\n\r\nimport KeyboardProcessor from \"./keyboardProcessor.js\";\r\nimport OutputTarget, { Mock, type Transcription } from \"./outputTarget.js\";\r\nimport { VariableStoreDictionary } from \"../keyboards/keyboard.js\";\r\nimport type { VariableStore } from \"./kbdInterface.js\";\r\n\r\n/**\r\n * Represents the commands and state changes that result from a matched keyboard rule.\r\n */\r\nexport default class RuleBehavior {\r\n /**\r\n * The before-and-after Transform from matching a keyboard rule. May be `null`\r\n * if no keyboard rules were matched for the keystroke.\r\n */\r\n transcription: Transcription = null;\r\n\r\n /**\r\n * Indicates whether or not a BEEP command was issued by the matched keyboard rule.\r\n */\r\n beep?: boolean;\r\n\r\n /**\r\n * A set of changed store values triggered by the matched keyboard rule.\r\n */\r\n setStore: {[id: number]: string} = {};\r\n\r\n /**\r\n * A set of variable stores with save requests triggered by the matched keyboard rule\r\n */\r\n saveStore: {[name: string]: VariableStore} = {};\r\n\r\n /**\r\n * A set of variable stores with possible changes to be applied during finalization.\r\n */\r\n variableStores: VariableStoreDictionary = {};\r\n\r\n /**\r\n * Denotes a non-output default behavior; this should be evaluated later, against the true keystroke.\r\n */\r\n triggersDefaultCommand: boolean = false;\r\n\r\n /**\r\n * Denotes error log messages generated when attempting to generate this behavior.\r\n */\r\n errorLog?: string;\r\n\r\n /**\r\n * Denotes warning log messages generated when attempting to generate this behavior.\r\n */\r\n warningLog?: string;\r\n\r\n /**\r\n * If predictive text is active, contains a Promise returning predictive Suggestions.\r\n */\r\n predictionPromise?: Promise;\r\n\r\n /**\r\n * In reference to https://github.com/keymanapp/keyman/pull/4350#issuecomment-768753852:\r\n *\r\n * If the final group processed is a context and keystroke group (using keys),\r\n * and there is no nomatch rule, and the keystroke is not matched in the group,\r\n * the keystroke's default behavior should trigger, regardless of whether or not any\r\n * rules in prior groups matched.\r\n */\r\n triggerKeyDefault?: boolean;\r\n\r\n finalize(processor: KeyboardProcessor, outputTarget: OutputTarget, readonly: boolean) {\r\n if(!this.transcription) {\r\n throw \"Cannot finalize a RuleBehavior with no transcription.\";\r\n }\r\n\r\n if(processor.beepHandler && this.beep) {\r\n processor.beepHandler(outputTarget);\r\n }\r\n\r\n for(let storeID in this.setStore) {\r\n let sysStore = processor.keyboardInterface.systemStores[storeID];\r\n if(sysStore) {\r\n try {\r\n sysStore.set(this.setStore[storeID]);\r\n } catch (error) {\r\n if(processor.errorLogger) {\r\n processor.errorLogger(\"Rule attempted to perform illegal operation - 'platform' may not be changed.\");\r\n }\r\n }\r\n } else if(processor.warningLogger) {\r\n processor.warningLogger(\"Unknown store affected by keyboard rule: \" + storeID);\r\n }\r\n }\r\n\r\n processor.keyboardInterface.applyVariableStores(this.variableStores);\r\n\r\n if(processor.keyboardInterface.variableStoreSerializer) {\r\n for(let storeID in this.saveStore) {\r\n processor.keyboardInterface.variableStoreSerializer.saveStore(processor.activeKeyboard.id, storeID, this.saveStore[storeID]);\r\n }\r\n }\r\n\r\n if(this.triggersDefaultCommand) {\r\n let keyEvent = this.transcription.keystroke;\r\n processor.defaultRules.applyCommand(keyEvent, outputTarget);\r\n }\r\n\r\n if(processor.warningLogger && this.warningLog) {\r\n processor.warningLogger(this.warningLog);\r\n } else if(processor.errorLogger && this.errorLog) {\r\n processor.errorLogger(this.errorLog);\r\n }\r\n }\r\n\r\n /**\r\n * Merges default-related behaviors from another RuleBehavior into this one. Assumes that the current instance\r\n * \"came first\" chronologically. Both RuleBehaviors must be sourced from the same keystroke.\r\n *\r\n * Intended use: merging rule-based behavior with default key behavior during scenarios like those described\r\n * at https://github.com/keymanapp/keyman/pull/4350#issuecomment-768753852.\r\n *\r\n * This function does not attempt a \"complete\" merge for two fully-constructed RuleBehaviors! Things\r\n * WILL break for unintended uses.\r\n * @param other\r\n */\r\n mergeInDefaults(other: RuleBehavior) {\r\n let keystroke = this.transcription.keystroke;\r\n let keyFromOther = other.transcription.keystroke;\r\n if(keystroke.Lcode != keyFromOther.Lcode || keystroke.Lmodifiers != keyFromOther.Lmodifiers) {\r\n throw \"RuleBehavior default-merge not supported unless keystrokes are identical!\";\r\n }\r\n\r\n this.triggersDefaultCommand = this.triggersDefaultCommand || other.triggersDefaultCommand;\r\n\r\n let mergingMock = Mock.from(this.transcription.preInput, false);\r\n mergingMock.apply(this.transcription.transform);\r\n mergingMock.apply(other.transcription.transform);\r\n\r\n this.transcription = mergingMock.buildTranscriptionFrom(this.transcription.preInput, keystroke, false, this.transcription.alternates);\r\n }\r\n}", + "/***\r\n KeymanWeb 11.0\r\n Copyright 2019 SIL International\r\n***/\r\n\r\n//#region Imports\r\n\r\nimport { type DeviceSpec } from \"@keymanapp/web-utils\";\r\n\r\nimport Codes from \"./codes.js\";\r\nimport type KeyEvent from \"./keyEvent.js\";\r\nimport type { Deadkey } from \"./deadkeys.js\";\r\nimport KeyMapping from \"./keyMapping.js\";\r\nimport { SystemStore, MutableSystemStore, PlatformSystemStore } from \"./systemStores.js\";\r\nimport type { VariableStoreSerializer } from \"./keyboardProcessor.js\";\r\nimport type OutputTarget from \"./outputTarget.js\";\r\nimport { Mock } from \"./outputTarget.js\";\r\nimport RuleBehavior from \"./ruleBehavior.js\";\r\nimport Keyboard, { VariableStoreDictionary } from \"../keyboards/keyboard.js\";\r\nimport { KeyboardHarness, KeyboardKeymanGlobal } from \"../keyboards/keyboardHarness.js\";\r\n\r\n//#endregion\r\n\r\n//#region Helper type definitions\r\n\r\nexport class KeyInformation {\r\n vk: boolean;\r\n code: number;\r\n modifiers: number;\r\n}\r\n\r\n/*\r\n* Type alias definitions to reflect the parameters of the fullContextMatch() callback (KMW 10+).\r\n* No constructors or methods since keyboards will not utilize the same backing prototype, and\r\n* property names are shorthanded to promote minification.\r\n*/\r\ntype PlainKeyboardStore = string;\r\n\r\nexport type KeyboardStoreElement = (string|StoreNonCharEntry);\r\nexport type ComplexKeyboardStore = KeyboardStoreElement[];\r\n\r\ntype KeyboardStore = PlainKeyboardStore | ComplexKeyboardStore;\r\n\r\nexport type VariableStore = {[name: string]: string};\r\n\r\ntype RuleChar = string;\r\n\r\nclass RuleDeadkey {\r\n /** Discriminant field - 'd' for Deadkey.\r\n */\r\n ['t']: 'd';\r\n\r\n /**\r\n * Value: the deadkey's ID.\r\n */\r\n ['d']: number; // For 'd'eadkey; also reflects the Deadkey class's 'd' property.\r\n}\r\n\r\nclass ContextAny {\r\n /** Discriminant field - 'a' for `any()`.\r\n */\r\n ['t']: 'a';\r\n\r\n /**\r\n * Value: the store to search.\r\n */\r\n ['a']: KeyboardStore; // For 'a'ny statement.\r\n\r\n /**\r\n * If set to true, negates the 'any'.\r\n */\r\n ['n']: boolean|0|1;\r\n}\r\n\r\nclass RuleIndex {\r\n /** Discriminant field - 'i' for `index()`.\r\n */\r\n ['t']: 'i';\r\n\r\n /**\r\n * Value: the Store from which to output\r\n */\r\n ['i']: KeyboardStore;\r\n\r\n /**\r\n * Offset: the offset in context for the corresponding `any()`.\r\n */\r\n ['o']: number;\r\n}\r\n\r\nclass ContextEx {\r\n /** Discriminant field - 'c' for `context()`.\r\n */\r\n ['t']: 'c';\r\n\r\n /**\r\n * Value: The offset into the current rule's context to be matched.\r\n */\r\n ['c']: number; // For 'c'ontext statement.\r\n}\r\n\r\nclass ContextNul {\r\n /** Discriminant field - 'n' for `nul`\r\n */\r\n ['t']: 'n';\r\n}\r\n\r\nclass StoreBeep {\r\n /** Discriminant field - 'b' for `beep`\r\n */\r\n ['t']: 'b';\r\n}\r\n\r\ntype ContextNonCharEntry = RuleDeadkey | ContextAny | RuleIndex | ContextEx | ContextNul;\r\ntype ContextEntry = RuleChar | ContextNonCharEntry;\r\n\r\ntype StoreNonCharEntry = RuleDeadkey | StoreBeep;\r\n\r\n/**\r\n * Cache of context storing and retrieving return values from KC\r\n * Must be reset prior to each keystroke and after any text changes\r\n * MCD 3/1/14\r\n **/\r\nclass CachedContext {\r\n _cache: string[][];\r\n\r\n reset(): void {\r\n this._cache = [];\r\n }\r\n\r\n get(n: number, ln: number): string {\r\n // return null; // uncomment this line to disable context caching\r\n if(typeof this._cache[n] == 'undefined') {\r\n return null;\r\n } else if(typeof this._cache[n][ln] == 'undefined') {\r\n return null;\r\n }\r\n return this._cache[n][ln];\r\n }\r\n\r\n set(n: number, ln: number, val: string): void {\r\n if(typeof this._cache[n] == 'undefined') {\r\n this._cache[n] = [];\r\n }\r\n this._cache[n][ln] = val;\r\n }\r\n};\r\n\r\ntype CachedExEntry = {valContext: (string|number)[], deadContext: Deadkey[]};\r\n/**\r\n * An extended version of cached context storing designed to work with\r\n * `fullContextMatch` and its helper functions.\r\n */\r\nclass CachedContextEx {\r\n _cache: CachedExEntry[][];\r\n\r\n reset(): void {\r\n this._cache = [];\r\n }\r\n\r\n get(n: number, ln: number): CachedExEntry {\r\n // return null; // uncomment this line to disable context caching\r\n if(typeof this._cache[n] == 'undefined') {\r\n return null;\r\n } else if(typeof this._cache[n][ln] == 'undefined') {\r\n return null;\r\n }\r\n return this._cache[n][ln];\r\n }\r\n\r\n set(n: number, ln: number, val: CachedExEntry): void {\r\n if(typeof this._cache[n] == 'undefined') {\r\n this._cache[n] = [];\r\n }\r\n this._cache[n][ln] = val;\r\n }\r\n\r\n clone(): CachedContextEx {\r\n let r = new CachedContextEx();\r\n r._cache = this._cache;\r\n return r;\r\n }\r\n};\r\n\r\nexport enum SystemStoreIDs {\r\n TSS_LAYER = 33,\r\n TSS_PLATFORM = 31,\r\n TSS_NEWLAYER = 42,\r\n TSS_OLDLAYER = 43\r\n}\r\n\r\n//#endregion\r\n\r\nexport default class KeyboardInterface extends KeyboardHarness {\r\n static readonly GLOBAL_NAME = 'KeymanWeb';\r\n\r\n cachedContext: CachedContext = new CachedContext();\r\n cachedContextEx: CachedContextEx = new CachedContextEx();\r\n ruleContextEx: CachedContextEx;\r\n\r\n activeTargetOutput: OutputTarget;\r\n ruleBehavior: RuleBehavior;\r\n\r\n systemStores: {[storeID: number]: SystemStore};\r\n\r\n _AnyIndices: number[] = []; // AnyIndex - array of any/index match indices\r\n\r\n // Must be accessible to some of the keyboard API methods.\r\n activeKeyboard: Keyboard;\r\n activeDevice: DeviceSpec;\r\n\r\n variableStoreSerializer?: VariableStoreSerializer;\r\n\r\n // A 'reference point' that debug keyboards may use to access KMW's code constants.\r\n public get Codes(): typeof Codes {\r\n return Codes;\r\n }\r\n\r\n constructor(_jsGlobal: any, keymanGlobal: KeyboardKeymanGlobal, variableStoreSerializer: VariableStoreSerializer = null) {\r\n super(_jsGlobal, keymanGlobal);\r\n\r\n this.systemStores = {};\r\n\r\n this.systemStores[SystemStoreIDs.TSS_PLATFORM] = new PlatformSystemStore(this);\r\n this.systemStores[SystemStoreIDs.TSS_LAYER] = new MutableSystemStore(SystemStoreIDs.TSS_LAYER, 'default');\r\n this.systemStores[SystemStoreIDs.TSS_NEWLAYER] = new MutableSystemStore(SystemStoreIDs.TSS_NEWLAYER, '');\r\n this.systemStores[SystemStoreIDs.TSS_OLDLAYER] = new MutableSystemStore(SystemStoreIDs.TSS_OLDLAYER, '');\r\n\r\n this.variableStoreSerializer = variableStoreSerializer;\r\n }\r\n\r\n /**\r\n * Function KSF\r\n * Scope Public\r\n *\r\n * Saves the document's current focus settings on behalf of the keyboard. Often paired with insertText.\r\n */\r\n saveFocus(): void { }\r\n\r\n /**\r\n * A text-insertion method used by custom OSKs for helpHTML interaction, like with sil_euro_latin.\r\n *\r\n * This function currently bypasses web-core's standard text handling control path and all predictive text processing.\r\n * It also has DOM-dependencies that help ensure KMW's active OutputTarget retains focus during use.\r\n */\r\n insertText?: (Ptext: string, PdeadKey: number) => void;\r\n\r\n /**\r\n * Function registerKeyboard KR\r\n * Scope Public\r\n * @param {Object} Pk Keyboard object\r\n * Description Registers a keyboard with KeymanWeb once its script has fully loaded.\r\n *\r\n * In web-core, this also activates the keyboard; in other modules, this method\r\n * may be replaced with other implementations.\r\n */\r\n registerKeyboard(Pk): void {\r\n // NOTE: This implementation is web-core specific and is intentionally replaced, whole-sale,\r\n // by DOM-aware code.\r\n let keyboard = new Keyboard(Pk);\r\n this.loadedKeyboard = keyboard;\r\n }\r\n\r\n /**\r\n * Get *cached or uncached* keyboard context for a specified range, relative to caret\r\n *\r\n * @param {number} n Number of characters to move back from caret\r\n * @param {number} ln Number of characters to return\r\n * @param {Object} Pelem Element to work with (must be currently focused element)\r\n * @return {string} Context string\r\n *\r\n * Example [abcdef|ghi] as INPUT, with the caret position marked by |:\r\n * KC(2,1,Pelem) == \"e\"\r\n * KC(3,3,Pelem) == \"def\"\r\n * KC(10,10,Pelem) == \"abcdef\" i.e. return as much as possible of the requested string\r\n */\r\n\r\n context(n: number, ln: number, outputTarget: OutputTarget): string {\r\n var v = this.cachedContext.get(n, ln);\r\n if(v !== null) {\r\n return v;\r\n }\r\n\r\n var r = this.KC_(n, ln, outputTarget);\r\n this.cachedContext.set(n, ln, r);\r\n return r;\r\n }\r\n\r\n /**\r\n * Get (uncached) keyboard context for a specified range, relative to caret\r\n *\r\n * @param {number} n Number of characters to move back from caret\r\n * @param {number} ln Number of characters to return\r\n * @param {Object} Pelem Element to work with (must be currently focused element)\r\n * @return {string} Context string\r\n *\r\n * Example [abcdef|ghi] as INPUT, with the caret position marked by |:\r\n * KC(2,1,Pelem) == \"e\"\r\n * KC(3,3,Pelem) == \"def\"\r\n * KC(10,10,Pelem) == \"XXXXabcdef\" i.e. return as much as possible of the requested string, where X = \\uFFFE\r\n */\r\n private KC_(n: number, ln: number, outputTarget: OutputTarget): string {\r\n var tempContext = '';\r\n\r\n // If we have a selection, we have an empty context\r\n tempContext = outputTarget.isSelectionEmpty() ? outputTarget.getTextBeforeCaret() : \"\";\r\n\r\n if(tempContext._kmwLength() < n) {\r\n tempContext = Array(n-tempContext._kmwLength()+1).join(\"\\uFFFE\") + tempContext;\r\n }\r\n\r\n return tempContext._kmwSubstr(-n)._kmwSubstr(0,ln);\r\n }\r\n\r\n /**\r\n * Function nul KN\r\n * Scope Public\r\n * @param {number} n Length of context to check\r\n * @param {Object} Ptarg Element to work with (must be currently focused element)\r\n * @return {boolean} True if length of context is less than or equal to n\r\n * Description Test length of context, return true if the length of the context is less than or equal to n\r\n *\r\n * Example [abc|def] as INPUT, with the caret position marked by |:\r\n * KN(3,Pelem) == TRUE\r\n * KN(2,Pelem) == FALSE\r\n * KN(4,Pelem) == TRUE\r\n */\r\n nul(n: number, outputTarget: OutputTarget): boolean {\r\n var cx=this.context(n+1, 1, outputTarget);\r\n\r\n // With #31, the result will be a replacement character if context is empty.\r\n return cx === \"\\uFFFE\";\r\n }\r\n\r\n /**\r\n * Function contextMatch KCM\r\n * Scope Public\r\n * @param {number} n Number of characters to move back from caret\r\n * @param {Object} Ptarg Focused element\r\n * @param {string} val String to match\r\n * @param {number} ln Number of characters to return\r\n * @return {boolean} True if selected context matches val\r\n * Description Test keyboard context for match\r\n */\r\n contextMatch(n: number, outputTarget: OutputTarget, val: string, ln: number): boolean {\r\n var cx=this.context(n, ln, outputTarget);\r\n if(cx === val) {\r\n return true; // I3318\r\n }\r\n outputTarget.deadkeys().resetMatched(); // I3318\r\n return false;\r\n }\r\n\r\n /**\r\n * Builds the *cached or uncached* keyboard context for a specified range, relative to caret\r\n *\r\n * @param {number} n Number of characters to move back from caret\r\n * @param {number} ln Number of characters to return\r\n * @param {Object} Pelem Element to work with (must be currently focused element)\r\n * @return {Array} Context array (of strings and numbers)\r\n */\r\n private _BuildExtendedContext(n: number, ln: number, outputTarget: OutputTarget): CachedExEntry {\r\n var cache: CachedExEntry = this.cachedContextEx.get(n, ln);\r\n if(cache !== null) {\r\n return cache;\r\n } else {\r\n // By far the easiest way to correctly build what we want is to start from the right and work to what we need.\r\n // We may have done it for a similar cursor position before.\r\n cache = this.cachedContextEx.get(n, n);\r\n if(cache === null) {\r\n // First, let's make sure we have a cloned, sorted copy of the deadkey array.\r\n let unmatchedDeadkeys = outputTarget.deadkeys().toSortedArray(); // Is reverse-order sorted for us already.\r\n\r\n // Time to build from scratch!\r\n var index = 0;\r\n cache = { valContext: [], deadContext: []};\r\n while(cache.valContext.length < n) {\r\n // As adapted from `deadkeyMatch`.\r\n var sp = outputTarget.getDeadkeyCaret();\r\n var deadPos = sp - index;\r\n if(unmatchedDeadkeys.length > 0 && unmatchedDeadkeys[0].p > deadPos) {\r\n // We have deadkeys at the right-hand side of the caret! They don't belong in the context, so pop 'em off.\r\n unmatchedDeadkeys.splice(0, 1);\r\n continue;\r\n } else if(unmatchedDeadkeys.length > 0 && unmatchedDeadkeys[0].p == deadPos) {\r\n // Take the deadkey.\r\n cache.deadContext[n-cache.valContext.length-1] = unmatchedDeadkeys[0];\r\n cache.valContext = ([unmatchedDeadkeys[0].d] as (string|number)[]).concat(cache.valContext);\r\n unmatchedDeadkeys.splice(0, 1);\r\n } else {\r\n // Take the character. We get \"\\ufffe\" if it doesn't exist.\r\n var kc = this.context(++index, 1, outputTarget);\r\n cache.valContext = ([kc] as (string|number)[]).concat(cache.valContext);\r\n }\r\n }\r\n this.cachedContextEx.set(n, n, cache);\r\n }\r\n\r\n // Now that we have the cache...\r\n var subCache = cache;\r\n subCache.valContext = subCache.valContext.slice(0, ln);\r\n for(var i=0; i < subCache.valContext.length; i++) {\r\n if(subCache[i] == '\\ufffe') {\r\n subCache.valContext.splice(0, 1);\r\n subCache.deadContext.splice(0, 1);\r\n }\r\n }\r\n\r\n if(subCache.valContext.length == 0) {\r\n subCache.valContext = ['\\ufffe'];\r\n subCache.deadContext = [];\r\n }\r\n\r\n this.cachedContextEx.set(n, ln, subCache);\r\n\r\n return subCache;\r\n }\r\n }\r\n\r\n /**\r\n * Function fullContextMatch KFCM\r\n * Scope Private\r\n * @param {number} n Number of characters to move back from caret\r\n * @param {Object} Ptarg Focused element\r\n * @param {Array} rule An array of ContextEntries to match.\r\n * @return {boolean} True if the fully-specified rule context matches the current KMW state.\r\n *\r\n * A KMW 10+ function designed to bring KMW closer to Keyman Desktop functionality,\r\n * near-directly modeling (externally) the compiled form of Desktop rules' context section.\r\n */\r\n fullContextMatch(n: number, outputTarget: OutputTarget, rule: ContextEntry[]): boolean {\r\n // Stage one: build the context index map.\r\n var fullContext = this._BuildExtendedContext(n, rule.length, outputTarget);\r\n this.ruleContextEx = this.cachedContextEx.clone();\r\n var context = fullContext.valContext;\r\n var deadContext = fullContext.deadContext;\r\n\r\n var mismatch = false;\r\n\r\n // This symbol internally indicates lack of context in a position. (See KC_)\r\n const NUL_CONTEXT = \"\\uFFFE\";\r\n\r\n var assertNever = function(x: never): never {\r\n // Could be accessed by improperly handwritten calls to `fullContextMatch`.\r\n throw new Error(\"Unexpected object in fullContextMatch specification: \" + x);\r\n }\r\n\r\n // Stage two: time to match against the rule specified.\r\n for(var i=0; i < rule.length; i++) {\r\n if(typeof rule[i] == 'string') {\r\n var str = rule[i] as string;\r\n if(str !== context[i]) {\r\n mismatch = true;\r\n break;\r\n }\r\n } else {\r\n // TypeScript needs a cast to this intermediate type to do its discriminated union magic.\r\n var r = rule[i] as ContextNonCharEntry;\r\n switch(r.t) {\r\n case 'd':\r\n // We still need to set a flag here;\r\n if(r['d'] !== context[i]) {\r\n mismatch = true;\r\n } else {\r\n deadContext[i].set();\r\n }\r\n break;\r\n case 'a':\r\n var lookup: KeyboardStoreElement;\r\n\r\n if(typeof context[i] == 'string') {\r\n lookup = context[i] as string;\r\n } else {\r\n lookup = {'t': 'd', 'd': context[i] as number};\r\n }\r\n\r\n var result = this.any(i, lookup, r.a);\r\n\r\n if(!r.n) { // If it's a standard 'any'...\r\n if(!result) {\r\n mismatch = true;\r\n } else if(deadContext[i] !== undefined) {\r\n // It's a deadkey match, so indicate that.\r\n deadContext[i].set();\r\n }\r\n // 'n' for 'notany'.\r\n // - if `result === true`, `any` would match: this should thus fail.\r\n // - if `context[i] === NUL_CONTEXT`, `notany` should not match.\r\n } else if(r.n && (result || context[i] === NUL_CONTEXT)) {\r\n mismatch = true;\r\n }\r\n break;\r\n case 'i':\r\n // The context will never hold a 'beep.'\r\n var ch = this._Index(r.i, r.o) as string | RuleDeadkey;\r\n\r\n if(ch !== undefined && (typeof(ch) == 'string' ? ch : ch.d) !== context[i]) {\r\n mismatch = true;\r\n } else if(deadContext[i] !== undefined) {\r\n deadContext[i].set();\r\n }\r\n break;\r\n case 'c':\r\n if(context[r.c - 1] !== context[i]) {\r\n mismatch = true;\r\n } else if(deadContext[i] !== undefined) {\r\n deadContext[i].set();\r\n }\r\n break;\r\n case 'n':\r\n // \\uFFFE is the internal 'no context here sentinel'.\r\n if(context[i] != NUL_CONTEXT) {\r\n mismatch = true;\r\n }\r\n break;\r\n default:\r\n assertNever(r);\r\n }\r\n }\r\n }\r\n\r\n if(mismatch) {\r\n // Reset the matched 'any' indices, if any.\r\n outputTarget.deadkeys().resetMatched();\r\n this._AnyIndices = [];\r\n }\r\n\r\n return !mismatch;\r\n }\r\n\r\n /**\r\n * Function KIK\r\n * Scope Public\r\n * @param {Object} e keystroke event\r\n * @return {boolean} true if keypress event\r\n * Description Test if event as a keypress event\r\n */\r\n isKeypress(e: KeyEvent): boolean {\r\n if(this.activeKeyboard.isMnemonic) { // I1380 - support KIK for positional layouts\r\n return !e.LisVirtualKey; // will now return true for U_xxxx keys, but not for T_xxxx keys\r\n } else {\r\n return KeyMapping._USKeyCodeToCharCode(e) ? true : false; // I1380 - support KIK for positional layouts\r\n }\r\n }\r\n\r\n /**\r\n * Maps a KeyEvent's modifiers to their appropriate value for key-rule evaluation\r\n * based on the rule's specified target modifier set.\r\n *\r\n * Mostly used to correct chiral OSK-keys targeting non-chiral rules.\r\n * @param e The source KeyEvent\r\n * @returns\r\n */\r\n private static matchModifiersToRuleChirality(eventModifiers: number, targetModifierMask: number): number {\r\n const CHIRAL_ALT = Codes.modifierCodes[\"LALT\"] | Codes.modifierCodes[\"RALT\"];\r\n const CHIRAL_CTRL = Codes.modifierCodes[\"LCTRL\"] | Codes.modifierCodes[\"RCTRL\"];\r\n\r\n let modifiers = eventModifiers;\r\n\r\n // If the target rule does not use chiral alt...\r\n if(!(targetModifierMask & CHIRAL_ALT)) {\r\n const altIntersection = modifiers & CHIRAL_ALT;\r\n\r\n if(altIntersection) {\r\n // Undo the chiral part and replace with non-chiral.\r\n modifiers ^= altIntersection | Codes.modifierCodes[\"ALT\"];\r\n }\r\n }\r\n\r\n // If the target rule does not use chiral ctrl...\r\n if(!(targetModifierMask & CHIRAL_CTRL)) {\r\n const ctrlIntersection = modifiers & CHIRAL_CTRL;\r\n\r\n if(ctrlIntersection) {\r\n // Undo the chiral part and replace with non-chiral.\r\n modifiers ^= ctrlIntersection | Codes.modifierCodes[\"CTRL\"];\r\n }\r\n }\r\n\r\n return modifiers;\r\n }\r\n\r\n /**\r\n * Function keyMatch KKM\r\n * Scope Public\r\n * @param {Object} e keystroke event\r\n * @param {number} Lruleshift\r\n * @param {number} Lrulekey\r\n * @return {boolean} True if key matches rule\r\n * Description Test keystroke with modifiers against rule\r\n */\r\n keyMatch(e: KeyEvent, Lruleshift:number, Lrulekey:number): boolean {\r\n var retVal = false; // I3318\r\n var keyCode = (e.Lcode == 173 ? 189 : e.Lcode); //I3555 (Firefox hyphen issue)\r\n\r\n let bitmask = this.activeKeyboard.modifierBitmask;\r\n var modifierBitmask = bitmask & Codes.modifierBitmasks[\"ALL\"];\r\n var stateBitmask = bitmask & Codes.stateBitmasks[\"ALL\"];\r\n\r\n const eventModifiers = KeyboardInterface.matchModifiersToRuleChirality(e.Lmodifiers, Lruleshift);\r\n\r\n if(e.vkCode > 255) {\r\n keyCode = e.vkCode; // added to support extended (touch-hold) keys for mnemonic layouts\r\n }\r\n\r\n if(e.LisVirtualKey || keyCode > 255) {\r\n if((Lruleshift & 0x4000) == 0x4000 || (keyCode > 255)) { // added keyCode test to support extended keys\r\n retVal = ((Lrulekey == keyCode) && ((Lruleshift & modifierBitmask) == eventModifiers)); //I3318, I3555\r\n retVal = retVal && this.stateMatch(e, Lruleshift & stateBitmask);\r\n }\r\n } else if((Lruleshift & 0x4000) == 0) {\r\n retVal = (keyCode == Lrulekey); // I3318, I3555\r\n }\r\n if(!retVal) {\r\n this.activeTargetOutput.deadkeys().resetMatched(); // I3318\r\n }\r\n return retVal; // I3318\r\n };\r\n\r\n /**\r\n * Function stateMatch KSM\r\n * Scope Public\r\n * @param {Object} e keystroke event\r\n * @param {number} Lstate\r\n * Description Test keystroke against state key rules\r\n */\r\n stateMatch(e: KeyEvent, Lstate: number) {\r\n return ((Lstate & e.Lstates) == Lstate);\r\n }\r\n\r\n /**\r\n * Function keyInformation KKI\r\n * Scope Public\r\n * @param {Object} e\r\n * @return {Object} Object with event's virtual key flag, key code, and modifiers\r\n * Description Get object with extended key event information\r\n */\r\n keyInformation(e: KeyEvent): KeyInformation {\r\n var ei = new KeyInformation();\r\n ei['vk'] = e.LisVirtualKey;\r\n ei['code'] = e.Lcode;\r\n ei['modifiers'] = e.Lmodifiers;\r\n return ei;\r\n };\r\n\r\n /**\r\n * Function deadkeyMatch KDM\r\n * Scope Public\r\n * @param {number} n offset from current cursor position\r\n * @param {Object} Ptarg target element\r\n * @param {number} d deadkey\r\n * @return {boolean} True if deadkey found selected context matches val\r\n * Description Match deadkey at current cursor position\r\n */\r\n deadkeyMatch(n: number, outputTarget: OutputTarget, d: number): boolean {\r\n return outputTarget.hasDeadkeyMatch(n, d);\r\n }\r\n\r\n /**\r\n * Function beep KB\r\n * Scope Public\r\n * @param {Object} Pelem element to flash\r\n * Description Flash body as substitute for audible beep; notify embedded device to vibrate\r\n */\r\n beep(outputTarget: OutputTarget): void {\r\n this.resetContextCache();\r\n\r\n // Denote as part of the matched rule's behavior.\r\n this.ruleBehavior.beep = true;\r\n }\r\n\r\n _ExplodeStore(store: KeyboardStore): ComplexKeyboardStore {\r\n if(typeof(store) == 'string') {\r\n let cachedStores = this.activeKeyboard.explodedStores;\r\n\r\n // Is the result cached?\r\n if(cachedStores[store]) {\r\n return cachedStores[store];\r\n }\r\n\r\n // Nope, so let's build its cache.\r\n var result: ComplexKeyboardStore = [];\r\n for(var i=0; i < store._kmwLength(); i++) {\r\n result.push(store._kmwCharAt(i));\r\n }\r\n\r\n // Cache the result for later!\r\n cachedStores[store] = result;\r\n return result;\r\n } else {\r\n return store;\r\n }\r\n }\r\n\r\n /**\r\n * Function any KA\r\n * Scope Public\r\n * @param {number} n character position (index)\r\n * @param {string} ch character to find in string\r\n * @param {string} s 'any' string\r\n * @return {boolean} True if character found in 'any' string, sets index accordingly\r\n * Description Test for character matching\r\n */\r\n any(n: number, ch: KeyboardStoreElement, s: KeyboardStore): boolean {\r\n if(ch == '') {\r\n return false;\r\n }\r\n\r\n s = this._ExplodeStore(s);\r\n var Lix = -1;\r\n for(var i=0; i < s.length; i++) {\r\n if(typeof(s[i]) == 'string') {\r\n if(s[i] == ch) {\r\n Lix = i;\r\n break;\r\n }\r\n } else if(s[i]['d'] === ch['d']) {\r\n Lix = i;\r\n break;\r\n }\r\n }\r\n this._AnyIndices[n] = Lix;\r\n return Lix >= 0;\r\n }\r\n\r\n /**\r\n * Function _Index\r\n * Scope Public\r\n * @param {string} Ps string\r\n * @param {number} Pn index\r\n * Description Returns the character from a store string according to the offset in the index array\r\n */\r\n _Index(Ps: KeyboardStore, Pn: number): KeyboardStoreElement {\r\n Ps = this._ExplodeStore(Ps);\r\n\r\n if(this._AnyIndices[Pn-1] < Ps.length) { //I3319\r\n return Ps[this._AnyIndices[Pn-1]];\r\n } else {\r\n /* Should not be possible for a compiled keyboard, but may arise\r\n * during the development of handwritten keyboards.\r\n */\r\n console.warn(\"Unmatched contextual index() statement detected in rule with index \" + Pn + \"!\");\r\n return \"\";\r\n }\r\n }\r\n\r\n /**\r\n * Function indexOutput KIO\r\n * Scope Public\r\n * @param {number} Pdn no of character to overwrite (delete)\r\n * @param {string} Ps string\r\n * @param {number} Pn index\r\n * @param {Object} Pelem element to output to\r\n * Description Output a character selected from the string according to the offset in the index array\r\n */\r\n indexOutput(Pdn: number, Ps: KeyboardStore, Pn: number, outputTarget: OutputTarget): void {\r\n this.resetContextCache();\r\n\r\n var assertNever = function(x: never): never {\r\n // Could be accessed by improperly handwritten calls to `fullContextMatch`.\r\n throw new Error(\"Unexpected object in fullContextMatch specification: \" + x);\r\n }\r\n\r\n var indexChar = this._Index(Ps, Pn);\r\n if(indexChar !== \"\") {\r\n if(typeof indexChar == 'string' ) {\r\n this.output(Pdn, outputTarget, indexChar); //I3319\r\n } else if(indexChar['t']) {\r\n var storeEntry = indexChar as StoreNonCharEntry;\r\n\r\n switch(storeEntry.t) {\r\n case 'b': // Beep commands may appear within stores.\r\n this.beep(outputTarget);\r\n break;\r\n case 'd':\r\n this.deadkeyOutput(Pdn, outputTarget, indexChar['d']);\r\n break;\r\n default:\r\n assertNever(storeEntry);\r\n }\r\n } else { // For keyboards developed during 10.0's alpha phase - t:'d' was assumed.\r\n this.deadkeyOutput(Pdn, outputTarget, indexChar['d']);\r\n }\r\n }\r\n }\r\n\r\n\r\n /**\r\n * Function deleteContext KDC\r\n * Scope Public\r\n * @param {number} dn number of context entries to overwrite\r\n * @param {Object} Pelem element to output to\r\n * @param {string} s string to output\r\n * Description Keyboard output\r\n */\r\n deleteContext(dn: number, outputTarget: OutputTarget): void {\r\n var context: CachedExEntry;\r\n\r\n // We want to control exactly which deadkeys get removed.\r\n if(dn > 0) {\r\n context = this._BuildExtendedContext(dn, dn, outputTarget);\r\n let nulCount = 0;\r\n\r\n for(var i=0; i < context.valContext.length; i++) {\r\n var dk = context.deadContext[i];\r\n\r\n if(dk) {\r\n // Remove deadkey in context.\r\n outputTarget.deadkeys().remove(dk);\r\n\r\n // Reduce our reported context size.\r\n dn--;\r\n } else if(context.valContext[i] == \"\\uFFFE\") {\r\n // Count any `nul` sentinels that would contribute to our deletion count.\r\n nulCount++;\r\n }\r\n }\r\n\r\n // Prevent attempts to delete nul sentinels, as they don't exist in the actual context.\r\n // (Addresses regression from KMW v 12.0 paired with Developer bug through same version)\r\n let contextLength = context.valContext.length - nulCount;\r\n if(dn > contextLength) {\r\n dn = contextLength;\r\n }\r\n }\r\n\r\n // If a matched deadkey hasn't been deleted, we don't WANT to delete it.\r\n outputTarget.deadkeys().resetMatched();\r\n\r\n // Why reinvent the wheel? Delete the remaining characters by 'inserting a blank string'.\r\n this.output(dn, outputTarget, '');\r\n }\r\n\r\n /**\r\n * Function output KO\r\n * Scope Public\r\n * @param {number} dn number of characters to overwrite\r\n * @param {Object} Pelem element to output to\r\n * @param {string} s string to output\r\n * Description Keyboard output\r\n */\r\n output(dn: number, outputTarget: OutputTarget, s:string): void {\r\n this.resetContextCache();\r\n\r\n outputTarget.saveProperties();\r\n outputTarget.clearSelection();\r\n outputTarget.deadkeys().deleteMatched(); // I3318\r\n if(dn >= 0) {\r\n // Automatically manages affected deadkey positions. Does not delete deadkeys b/c legacy behavior support.\r\n outputTarget.deleteCharsBeforeCaret(dn);\r\n }\r\n // Automatically manages affected deadkey positions.\r\n outputTarget.insertTextBeforeCaret(s);\r\n outputTarget.restoreProperties();\r\n }\r\n\r\n /**\r\n * `contextExOutput` function emits the character or object at `contextOffset` from the\r\n * current matched rule's context. Introduced in Keyman 14.0, in order to resolve a\r\n * gap between desktop and web core functionality for context(n) matching on notany().\r\n * See #917 for additional detail.\r\n * @alias KCXO\r\n * @public\r\n * @param {number} Pdn number of characters to delete left of cursor\r\n * @param {OutputTarget} outputTarget target to output to\r\n * @param {number} contextLength length of current rule context to retrieve\r\n * @param {number} contextOffset offset from start of current rule context, 1-based\r\n */\r\n contextExOutput(Pdn: number, outputTarget: OutputTarget, contextLength: number, contextOffset: number): void {\r\n this.resetContextCache();\r\n\r\n if(Pdn >= 0) {\r\n this.output(Pdn, outputTarget, \"\");\r\n }\r\n\r\n const context = this.ruleContextEx.get(contextLength, contextLength);\r\n const dk = context.deadContext[contextOffset-1], vc = context.valContext[contextOffset-1];\r\n if(dk) {\r\n outputTarget.insertDeadkeyBeforeCaret(dk.d);\r\n } else if(typeof vc == 'string') {\r\n this.output(-1, outputTarget, vc);\r\n } else {\r\n throw new Error(\"contextExOutput: should never be a numeric valContext with no corresponding deadContext\");\r\n }\r\n }\r\n\r\n /**\r\n * Function deadkeyOutput KDO\r\n * Scope Public\r\n * @param {number} Pdn no of character to overwrite (delete)\r\n * @param {Object} Pelem element to output to\r\n * @param {number} Pd deadkey id\r\n * Description Record a deadkey at current cursor position, deleting Pdn characters first\r\n */\r\n deadkeyOutput(Pdn: number, outputTarget: OutputTarget, Pd: number): void {\r\n this.resetContextCache();\r\n\r\n if(Pdn >= 0) {\r\n this.output(Pdn, outputTarget,\"\"); //I3318 corrected to >=\r\n }\r\n\r\n outputTarget.insertDeadkeyBeforeCaret(Pd);\r\n // _DebugDeadKeys(Pelem, 'KDeadKeyOutput: dn='+Pdn+'; deadKey='+Pd);\r\n }\r\n\r\n /**\r\n * KIFS compares the content of a system store with a string value\r\n *\r\n * @param {number} systemId ID of the system store to test (only TSS_LAYER currently supported)\r\n * @param {string} strValue String value to compare to\r\n * @param {Object} Pelem Currently active element (may be needed by future tests)\r\n * @return {boolean} True if the test succeeds\r\n */\r\n ifStore(systemId: number, strValue: string, outputTarget: OutputTarget): boolean {\r\n var result=true;\r\n let store = this.systemStores[systemId];\r\n if(store) {\r\n result = store.matches(strValue);\r\n }\r\n return result; //Moved from previous line, now supports layer selection, Build 350\r\n }\r\n\r\n /**\r\n * KSETS sets the value of a system store to a string\r\n *\r\n * @param {number} systemId ID of the system store to set (only TSS_LAYER currently supported)\r\n * @param {string} strValue String to set as the system store content\r\n * @param {Object} Pelem Currently active element (may be needed in future tests)\r\n * @return {boolean} True if command succeeds\r\n * (i.e. for TSS_LAYER, if the layer is successfully selected)\r\n *\r\n * Note that option/variable stores are instead set within keyboard script code, as they only\r\n * affect keyboard behavior.\r\n */\r\n setStore(systemId: number, strValue: string, outputTarget: OutputTarget): boolean {\r\n this.resetContextCache();\r\n // Unique case: we only allow set(&layer) ops from keyboard rules triggered by touch OSKs.\r\n if(systemId == SystemStoreIDs.TSS_LAYER && this.activeDevice.touchable) {\r\n // Denote the changed store as part of the matched rule's behavior.\r\n this.ruleBehavior.setStore[systemId] = strValue;\r\n } else {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Load an option store value from a cookie or default value\r\n *\r\n * @param {string} kbdName keyboard internal name\r\n * @param {string} storeName store (option) name, embedded in cookie name\r\n * @param {string} dfltValue default value\r\n * @return {string} current or default option value\r\n *\r\n * This will only ever be called when the keyboard is loaded, as it is used by keyboards\r\n * to initialize a store value on the keyboard's script object.\r\n */\r\n loadStore(kbdName: string, storeName:string, dfltValue:string): string {\r\n this.resetContextCache();\r\n if(this.variableStoreSerializer) {\r\n let cValue = this.variableStoreSerializer.loadStore(kbdName, storeName);\r\n return cValue[storeName] || dfltValue;\r\n } else {\r\n return dfltValue;\r\n }\r\n }\r\n\r\n /**\r\n * Save an option store value to a cookie\r\n *\r\n * @param {string} storeName store (option) name, embedded in cookie name\r\n * @param {string} optValue option value to save\r\n * @return {boolean} true if save successful\r\n *\r\n * Note that a keyboard will freely manipulate the value of its variable stores on the\r\n * script object within its own code. This function's use is merely to _persist_ that\r\n * value across sessions, providing a custom user default for later uses of the keyboard.\r\n */\r\n saveStore(storeName:string, optValue:string): boolean {\r\n this.resetContextCache();\r\n var kbd=this.activeKeyboard;\r\n if(!kbd || typeof kbd.id == 'undefined' || kbd.id == '') {\r\n return false;\r\n }\r\n\r\n // And the lookup under that entry looks for the value under the store name, again.\r\n let valueObj: VariableStore = {};\r\n valueObj[storeName] = optValue;\r\n\r\n // Null-check in case of invocation during unit-test\r\n if(this.ruleBehavior) {\r\n this.ruleBehavior.saveStore[storeName] = valueObj;\r\n } else {\r\n // We're in a unit-test environment, directly invoking this method from outside of a keyboard.\r\n // In this case, we should immediately commit the change.\r\n this.variableStoreSerializer.saveStore(this.activeKeyboard.id, storeName, valueObj);\r\n }\r\n return true;\r\n }\r\n\r\n resetContextCache(): void {\r\n this.cachedContext.reset();\r\n this.cachedContextEx.reset();\r\n }\r\n\r\n defaultBackspace(outputTarget: OutputTarget) {\r\n if(outputTarget.isSelectionEmpty()) {\r\n // Delete the character left of the caret\r\n this.output(1, outputTarget, \"\");\r\n } else {\r\n // Delete just the selection\r\n this.output(0, outputTarget, \"\");\r\n }\r\n }\r\n\r\n /**\r\n * Function processNewContextEvent\r\n * Scope Private\r\n * @param {Object} outputTarget The target receiving input\r\n * @param {Object} keystroke The input keystroke (with its properties) to be mapped by the keyboard.\r\n * Description Calls the keyboard's `begin newContext` group\r\n * @returns {RuleBehavior} Record of commands and state changes that result from executing `begin NewContext`\r\n */\r\n processNewContextEvent(outputTarget: OutputTarget, keystroke: KeyEvent): RuleBehavior {\r\n if(!this.activeKeyboard) {\r\n throw \"No active keyboard for keystroke processing!\";\r\n }\r\n return this.process(this.activeKeyboard.processNewContextEvent.bind(this.activeKeyboard), outputTarget, keystroke, true);\r\n }\r\n\r\n /**\r\n * Function processPostKeystroke\r\n * Scope Private\r\n * @param {Object} outputTarget The target receiving input\r\n * @param {Object} keystroke The input keystroke with relevant properties to be mapped by the keyboard.\r\n * Description Calls the keyboard's `begin postKeystroke` group\r\n * @returns {RuleBehavior} Record of commands and state changes that result from executing `begin PostKeystroke`\r\n */\r\n processPostKeystroke(outputTarget: OutputTarget, keystroke: KeyEvent): RuleBehavior {\r\n if(!this.activeKeyboard) {\r\n throw \"No active keyboard for keystroke processing!\";\r\n }\r\n return this.process(this.activeKeyboard.processPostKeystroke.bind(this.activeKeyboard), outputTarget, keystroke, true);\r\n }\r\n\r\n /**\r\n * Function processKeystroke\r\n * Scope Private\r\n * @param {Object} outputTarget The target receiving input\r\n * @param {Object} keystroke The input keystroke (with its properties) to be mapped by the keyboard.\r\n * Description Encapsulates calls to keyboard input processing.\r\n * @returns {RuleBehavior} Record of commands and state changes that result from executing `begin Unicode`\r\n */\r\n processKeystroke(outputTarget: OutputTarget, keystroke: KeyEvent): RuleBehavior {\r\n if(!this.activeKeyboard) {\r\n throw \"No active keyboard for keystroke processing!\";\r\n }\r\n return this.process(this.activeKeyboard.process.bind(this.activeKeyboard), outputTarget, keystroke, false);\r\n }\r\n\r\n private process(callee, outputTarget: OutputTarget, keystroke: KeyEvent, readonly: boolean): RuleBehavior {\r\n // Clear internal state tracking data from prior keystrokes.\r\n if(!outputTarget) {\r\n throw \"No target specified for keyboard output!\";\r\n } else if(!this.activeKeyboard) {\r\n throw \"No active keyboard for keystroke processing!\";\r\n } else if(!callee) {\r\n throw \"No callee for keystroke processing!\";\r\n }\r\n\r\n outputTarget.invalidateSelection();\r\n\r\n outputTarget.deadkeys().resetMatched(); // I3318\r\n this.resetContextCache();\r\n\r\n // Capture the initial state of the OutputTarget before any rules are matched.\r\n let preInput = Mock.from(outputTarget, true);\r\n\r\n // Capture the initial state of any variable stores\r\n const cachedVariableStores = this.activeKeyboard.variableStores;\r\n\r\n // Establishes the results object, allowing corresponding commands to set values here as appropriate.\r\n this.ruleBehavior = new RuleBehavior();\r\n\r\n // Ensure the settings are in place so that KIFS/ifState activates and deactivates\r\n // the appropriate rule(s) for the modeled device.\r\n this.activeDevice = keystroke.device;\r\n\r\n // Calls the start-group of the active keyboard.\r\n this.activeTargetOutput = outputTarget;\r\n var matched = callee(outputTarget, keystroke);\r\n this.activeTargetOutput = null;\r\n\r\n // Finalize the rule's results.\r\n this.ruleBehavior.transcription = outputTarget.buildTranscriptionFrom(preInput, keystroke, readonly);\r\n\r\n // We always backup the changes to variable stores to the RuleBehavior, to\r\n // be applied during finalization, then restore them to the cached initial\r\n // values to avoid side-effects with predictive text mocks.\r\n this.ruleBehavior.variableStores = this.activeKeyboard.variableStores;\r\n this.activeKeyboard.variableStores = cachedVariableStores;\r\n\r\n // `matched` refers to whether or not the FINAL rule (from any group) matched, rather than\r\n // whether or not ANY rule matched. If the final rule doesn't match, we trigger the key's\r\n // default behavior (if appropriate).\r\n //\r\n // See https://github.com/keymanapp/keyman/pull/4350#issuecomment-768753852\r\n this.ruleBehavior.triggerKeyDefault = !matched;\r\n\r\n // Clear our result-tracking variable to prevent any possible pollution for future processing.\r\n let behavior = this.ruleBehavior;\r\n this.ruleBehavior = null;\r\n\r\n return behavior;\r\n }\r\n\r\n /**\r\n * Applies the dictionary of variable store values to the active keyboard\r\n *\r\n * Has no effect on keyboards compiled with 14.0 or earlier; system store\r\n * names are not exposed unless compiled with Developer 15.0 or later.\r\n *\r\n * @param stores A dictionary of stores which should be found in the\r\n * keyboard\r\n */\r\n applyVariableStores(stores: VariableStoreDictionary): void {\r\n this.activeKeyboard.variableStores = stores;\r\n }\r\n\r\n /**\r\n * Publishes the KeyboardInterface's shorthand API names. As this assigns the current functions\r\n * held by the longform versions, note that this should be called after replacing any of them via\r\n * JS method extension.\r\n *\r\n * DOM-aware KeymanWeb should call this after its domKbdInterface.ts code is loaded, as it replaces\r\n * a few. (This is currently done within its kmwapi.ts.)\r\n */\r\n static __publishShorthandAPI() {\r\n // Keyboard callbacks\r\n let prototype = this.prototype;\r\n\r\n var exportKBCallback = function(miniName: string, longName: string) {\r\n if(prototype[longName]) {\r\n prototype[miniName] = prototype[longName];\r\n }\r\n }\r\n\r\n exportKBCallback('KSF', 'saveFocus');\r\n exportKBCallback('KBR', 'beepReset');\r\n exportKBCallback('KT', 'insertText');\r\n exportKBCallback('KR', 'registerKeyboard');\r\n exportKBCallback('KRS', 'registerStub');\r\n exportKBCallback('KC', 'context');\r\n exportKBCallback('KN', 'nul');\r\n exportKBCallback('KCM', 'contextMatch');\r\n exportKBCallback('KFCM', 'fullContextMatch');\r\n exportKBCallback('KIK', 'isKeypress');\r\n exportKBCallback('KKM', 'keyMatch');\r\n exportKBCallback('KSM', 'stateMatch');\r\n exportKBCallback('KKI', 'keyInformation');\r\n exportKBCallback('KDM', 'deadkeyMatch');\r\n exportKBCallback('KB', 'beep');\r\n exportKBCallback('KA', 'any');\r\n exportKBCallback('KDC', 'deleteContext');\r\n exportKBCallback('KO', 'output');\r\n exportKBCallback('KDO', 'deadkeyOutput');\r\n exportKBCallback('KCXO', 'contextExOutput');\r\n exportKBCallback('KIO', 'indexOutput');\r\n exportKBCallback('KIFS', 'ifStore');\r\n exportKBCallback('KSETS', 'setStore');\r\n exportKBCallback('KLOAD', 'loadStore');\r\n exportKBCallback('KSAVE', 'saveStore');\r\n }\r\n}\r\n\r\n(function() {\r\n // This will be the only call within the keyboard-processor module.\r\n KeyboardInterface.__publishShorthandAPI();\r\n}());", + "// #region Big ol' list of imports\r\n\r\nimport EventEmitter from 'eventemitter3';\r\n\r\nimport Codes from \"./codes.js\";\r\nimport type Keyboard from \"../keyboards/keyboard.js\";\r\nimport { MinimalKeymanGlobal } from '../keyboards/keyboardHarness.js';\r\nimport KeyEvent from \"./keyEvent.js\";\r\nimport { Layouts } from \"../keyboards/defaultLayouts.js\";\r\nimport type { MutableSystemStore } from \"./systemStores.js\";\r\n\r\nimport DefaultRules, { EmulationKeystrokes } from \"./defaultRules.js\";\r\nimport type OutputTarget from \"./outputTarget.js\";\r\nimport { Mock } from \"./outputTarget.js\";\r\n\r\nimport KeyboardInterface, { SystemStoreIDs, VariableStore } from \"./kbdInterface.js\";\r\nimport RuleBehavior from \"./ruleBehavior.js\";\r\n\r\nimport { DeviceSpec, globalObject } from \"@keymanapp/web-utils\";\r\n\r\n// #endregion\r\n\r\n// Also relies on @keymanapp/web-utils, which is included via tsconfig.json.\r\n\r\nexport type BeepHandler = (outputTarget: OutputTarget) => void;\r\nexport type LogMessageHandler = (str: string) => void;\r\n\r\nexport interface VariableStoreSerializer {\r\n loadStore(keyboardID: string, storeName: string): VariableStore;\r\n saveStore(keyboardID: string, storeName: string, storeMap: VariableStore);\r\n}\r\n\r\nexport interface ProcessorInitOptions {\r\n baseLayout?: string;\r\n keyboardInterface?: KeyboardInterface;\r\n defaultOutputRules?: DefaultRules; // Takes the class def object, not an instance thereof.\r\n}\r\n\r\ninterface EventMap {\r\n statekeychange: (stateKeys: typeof KeyboardProcessor.prototype.stateKeys) => void;\r\n}\r\n\r\nexport default class KeyboardProcessor extends EventEmitter {\r\n public static readonly DEFAULT_OPTIONS: ProcessorInitOptions = {\r\n baseLayout: 'us',\r\n defaultOutputRules: new DefaultRules()\r\n };\r\n\r\n // Tracks the simulated value for supported state keys, allowing the OSK to mirror a physical keyboard for them.\r\n // Using the exact keyCode name from the Codes definitions will allow for certain optimizations elsewhere in the code.\r\n stateKeys = {\r\n \"K_CAPS\":false,\r\n \"K_NUMLOCK\":false,\r\n \"K_SCROLL\":false\r\n };\r\n\r\n // Tracks the most recent modifier state information in order to quickly detect changes\r\n // in keyboard state not otherwise captured by the hosting page in the browser.\r\n // Needed for AltGr simulation.\r\n modStateFlags: number = 0;\r\n\r\n keyboardInterface: KeyboardInterface;\r\n\r\n /**\r\n * Indicates the device (platform) to be used for non-keystroke events,\r\n * such as those sent to `begin postkeystroke` and `begin newcontext`\r\n * entry points.\r\n */\r\n contextDevice: DeviceSpec;\r\n\r\n baseLayout: string;\r\n\r\n defaultRules: DefaultRules;\r\n\r\n // Callbacks for various feedback types\r\n beepHandler?: BeepHandler;\r\n warningLogger?: LogMessageHandler;\r\n errorLogger?: LogMessageHandler;\r\n\r\n constructor(device: DeviceSpec, options?: ProcessorInitOptions) {\r\n super();\r\n\r\n if(!options) {\r\n options = KeyboardProcessor.DEFAULT_OPTIONS;\r\n }\r\n\r\n this.contextDevice = device;\r\n\r\n this.baseLayout = options.baseLayout || KeyboardProcessor.DEFAULT_OPTIONS.baseLayout;\r\n this.keyboardInterface = options.keyboardInterface || new KeyboardInterface(globalObject(), MinimalKeymanGlobal);\r\n this.defaultRules = options.defaultOutputRules || KeyboardProcessor.DEFAULT_OPTIONS.defaultOutputRules;\r\n }\r\n\r\n public get activeKeyboard(): Keyboard {\r\n return this.keyboardInterface.activeKeyboard;\r\n }\r\n\r\n public set activeKeyboard(keyboard: Keyboard) {\r\n this.keyboardInterface.activeKeyboard = keyboard;\r\n\r\n // All old deadkeys and keyboard-specific cache should immediately be invalidated\r\n // on a keyboard change.\r\n this.resetContext();\r\n }\r\n\r\n get layerStore(): MutableSystemStore {\r\n return this.keyboardInterface.systemStores[SystemStoreIDs.TSS_LAYER] as MutableSystemStore;\r\n }\r\n\r\n public get newLayerStore(): MutableSystemStore {\r\n return this.keyboardInterface.systemStores[SystemStoreIDs.TSS_NEWLAYER] as MutableSystemStore;\r\n }\r\n\r\n public get oldLayerStore(): MutableSystemStore {\r\n return this.keyboardInterface.systemStores[SystemStoreIDs.TSS_OLDLAYER] as MutableSystemStore;\r\n }\r\n\r\n public get layerId(): string {\r\n return this.layerStore.value;\r\n }\r\n\r\n // Note: will trigger an 'event' callback designed to notify the OSK of layer changes.\r\n public set layerId(value: string) {\r\n this.layerStore.set(value);\r\n }\r\n\r\n /**\r\n * Get the default RuleBehavior for the specified key, attempting to mimic standard browser defaults\r\n * where and when appropriate.\r\n *\r\n * @param {object} Lkc The pre-analyzed KeyEvent object\r\n * @param {boolean} outputTarget The OutputTarget receiving the KeyEvent\r\n * @return {string}\r\n */\r\n defaultRuleBehavior(Lkc: KeyEvent, outputTarget: OutputTarget, readonly: boolean): RuleBehavior {\r\n let preInput = Mock.from(outputTarget, readonly);\r\n let ruleBehavior = new RuleBehavior();\r\n\r\n let matched = false;\r\n var char = '';\r\n var special: EmulationKeystrokes;\r\n if(Lkc.isSynthetic || outputTarget.isSynthetic) {\r\n matched = true; // All the conditions below result in matches until the final else, which restores the expected default\r\n // if no match occurs.\r\n\r\n if(this.defaultRules.isCommand(Lkc)) {\r\n // Note this in the rule behavior, return successfully. We'll consider applying it later.\r\n ruleBehavior.triggersDefaultCommand = true;\r\n\r\n // We'd rather let the browser handle these keys, but we're using emulated keystrokes, forcing KMW\r\n // to emulate default behavior here.\r\n } else if((special = this.defaultRules.forSpecialEmulation(Lkc)) != null) {\r\n switch(special) {\r\n case EmulationKeystrokes.Backspace:\r\n this.keyboardInterface.defaultBackspace(outputTarget);\r\n break;\r\n case EmulationKeystrokes.Enter:\r\n outputTarget.handleNewlineAtCaret();\r\n break;\r\n // case '\\u007f': // K_DEL\r\n // // For (possible) future implementation.\r\n // // Would recommend (conceptually) equaling K_RIGHT + K_BKSP, the former of which would technically be a 'command'.\r\n default:\r\n // In case we extend the allowed set, but forget to implement its handling case above.\r\n ruleBehavior.errorLog = \"Unexpected 'special emulation' character (\\\\u\" + (special as String).kmwCharCodeAt(0).toString(16) + \") went unhandled!\";\r\n }\r\n } else {\r\n // Back to the standard default, pending normal matching.\r\n matched = false;\r\n }\r\n }\r\n\r\n let isMnemonic = this.activeKeyboard && this.activeKeyboard.isMnemonic;\r\n\r\n if(!matched) {\r\n if((char = this.defaultRules.forAny(Lkc, isMnemonic)) != null) {\r\n special = this.defaultRules.forSpecialEmulation(Lkc)\r\n if(special == EmulationKeystrokes.Backspace) {\r\n // A browser's default backspace may fail to delete both parts of an SMP character.\r\n this.keyboardInterface.defaultBackspace(outputTarget);\r\n } else if(special || this.defaultRules.isCommand(Lkc)) { // Filters out 'commands' like TAB.\r\n // We only do the \"for special emulation\" cases under the condition above... aside from backspace\r\n // Let the browser handle those.\r\n return null;\r\n } else {\r\n this.keyboardInterface.output(0, outputTarget, char);\r\n }\r\n } else {\r\n // No match, no default RuleBehavior.\r\n return null;\r\n }\r\n }\r\n\r\n // Shortcut things immediately if there were issues generating this rule behavior.\r\n if(ruleBehavior.errorLog) {\r\n return ruleBehavior;\r\n }\r\n\r\n let transcription = outputTarget.buildTranscriptionFrom(preInput, Lkc, readonly);\r\n ruleBehavior.transcription = transcription;\r\n\r\n return ruleBehavior;\r\n }\r\n\r\n processNewContextEvent(device: DeviceSpec, outputTarget: OutputTarget): RuleBehavior {\r\n return this.activeKeyboard ?\r\n this.keyboardInterface.processNewContextEvent(outputTarget, this.activeKeyboard.constructNullKeyEvent(device, this.stateKeys)) :\r\n null;\r\n }\r\n\r\n processPostKeystroke(device: DeviceSpec, outputTarget: OutputTarget): RuleBehavior {\r\n return this.activeKeyboard ?\r\n this.keyboardInterface.processPostKeystroke(outputTarget, this.activeKeyboard.constructNullKeyEvent(device, this.stateKeys)) :\r\n null;\r\n }\r\n\r\n processKeystroke(keyEvent: KeyEvent, outputTarget: OutputTarget): RuleBehavior {\r\n var matchBehavior: RuleBehavior;\r\n\r\n // Pass this key code and state to the keyboard program\r\n if(this.activeKeyboard && keyEvent.Lcode != 0) {\r\n matchBehavior = this.keyboardInterface.processKeystroke(outputTarget, keyEvent);\r\n }\r\n\r\n if(!matchBehavior || matchBehavior.triggerKeyDefault) {\r\n // Restore the virtual key code if a mnemonic keyboard is being used\r\n // If no vkCode value was stored, maintain the original Lcode value.\r\n keyEvent.Lcode=keyEvent.vkCode || keyEvent.Lcode;\r\n\r\n // Handle unmapped keys, including special keys\r\n // The following is physical layout dependent, so should be avoided if possible. All keys should be mapped.\r\n this.keyboardInterface.activeTargetOutput = outputTarget;\r\n\r\n // Match against the 'default keyboard' - rules to mimic the default string output when typing in a browser.\r\n // Many keyboards rely upon these 'implied rules'.\r\n let defaultBehavior = this.defaultRuleBehavior(keyEvent, outputTarget, false);\r\n if(defaultBehavior) {\r\n if(!matchBehavior) {\r\n matchBehavior = defaultBehavior;\r\n } else {\r\n matchBehavior.mergeInDefaults(defaultBehavior);\r\n }\r\n matchBehavior.triggerKeyDefault = false; // We've triggered it successfully.\r\n } // If null, we must rely on something else (like the browser, in DOM-aware code) to fulfill the default.\r\n\r\n this.keyboardInterface.activeTargetOutput = null;\r\n }\r\n\r\n return matchBehavior;\r\n }\r\n\r\n /**\r\n * Function _UpdateVKShift\r\n * Scope Private\r\n * @param {Object} e OSK event\r\n * @return {boolean} Always true\r\n * Description Updates the current shift state within KMW, updating the OSK's visualization thereof.\r\n */\r\n _UpdateVKShift(e: KeyEvent): boolean {\r\n let keyShiftState=0;\r\n\r\n const lockNames = ['CAPS', 'NUM_LOCK', 'SCROLL_LOCK'];\r\n const lockKeys = ['K_CAPS', 'K_NUMLOCK', 'K_SCROLL'];\r\n\r\n if(!this.activeKeyboard) {\r\n return true;\r\n }\r\n\r\n if(e) {\r\n // read shift states from Pevent\r\n keyShiftState = e.Lmodifiers;\r\n\r\n // Are we simulating AltGr? If it's a simulation and not real, time to un-simulate for the OSK.\r\n if(this.activeKeyboard.isChiral && (this.activeKeyboard.emulatesAltGr) &&\r\n (this.modStateFlags & Codes.modifierBitmasks['ALT_GR_SIM']) == Codes.modifierBitmasks['ALT_GR_SIM']) {\r\n keyShiftState |= Codes.modifierBitmasks['ALT_GR_SIM'];\r\n keyShiftState &= ~Codes.modifierCodes['RALT'];\r\n }\r\n\r\n // Set stateKeys where corresponding value is passed in e.Lstates\r\n let stateMutation = false;\r\n for(let i=0; i < lockNames.length; i++) {\r\n if(e.Lstates & Codes.stateBitmasks[lockNames[i]]) {\r\n this.stateKeys[lockKeys[i]] = !!(e.Lstates & Codes.modifierCodes[lockNames[i]]);\r\n stateMutation = true;\r\n }\r\n }\r\n\r\n if(stateMutation) {\r\n this.emit('statekeychange', this.stateKeys);\r\n }\r\n }\r\n\r\n this.updateStates();\r\n\r\n if(this.activeKeyboard.isMnemonic && this.stateKeys['K_CAPS']) {\r\n // Modifier keypresses doesn't trigger mnemonic manipulation of modifier state.\r\n // Only an output key does; active use of Caps will also flip the SHIFT flag.\r\n if(!e || !e.isModifier) {\r\n // Mnemonic keystrokes manipulate the SHIFT property based on CAPS state.\r\n // We need to unflip them when tracking the OSK layer.\r\n keyShiftState ^= Codes.modifierCodes['SHIFT'];\r\n }\r\n }\r\n\r\n this.layerId = this.getLayerId(keyShiftState);\r\n return true;\r\n }\r\n\r\n private updateStates(): void {\r\n var lockNames = ['CAPS', 'NUM_LOCK', 'SCROLL_LOCK'];\r\n var lockKeys = ['K_CAPS', 'K_NUMLOCK', 'K_SCROLL'];\r\n\r\n for(let i=0; i < lockKeys.length; i++) {\r\n const key = lockKeys[i];\r\n const flag = this.stateKeys[key];\r\n const onBit = lockNames[i];\r\n const offBit = 'NO_' + lockNames[i];\r\n\r\n // Ensures that the current mod-state info properly matches the currently-simulated\r\n // state key states.\r\n if(flag) {\r\n this.modStateFlags |= Codes.modifierCodes[onBit];\r\n this.modStateFlags &= ~Codes.modifierCodes[offBit];\r\n } else {\r\n this.modStateFlags &= ~Codes.modifierCodes[onBit];\r\n this.modStateFlags |= Codes.modifierCodes[offBit];\r\n }\r\n }\r\n }\r\n\r\n getLayerId(modifier: number): string {\r\n return Layouts.getLayerId(modifier);\r\n }\r\n\r\n /**\r\n * Select the OSK's next keyboard layer based upon layer switching keys as a default\r\n * The next layer will be determined from the key name unless otherwise specifed\r\n *\r\n * @param {string} keyName key identifier\r\n * @return {boolean} return true if keyboard layer changed\r\n */\r\n selectLayer(keyEvent: KeyEvent): boolean {\r\n let keyName = keyEvent.kName;\r\n var nextLayer = keyEvent.kNextLayer;\r\n var isChiral = this.activeKeyboard && this.activeKeyboard.isChiral;\r\n\r\n // Layer must be identified by name, not number (27/08/2015)\r\n if(typeof nextLayer == 'number') {\r\n nextLayer = this.getLayerId(nextLayer * 0x10);\r\n }\r\n\r\n // Identify next layer, if required by key\r\n if(!nextLayer) {\r\n switch(keyName) {\r\n case 'K_LSHIFT':\r\n case 'K_RSHIFT':\r\n case 'K_SHIFT':\r\n nextLayer = 'shift';\r\n break;\r\n case 'K_LCONTROL':\r\n case 'K_LCTRL':\r\n if(isChiral) {\r\n nextLayer = 'leftctrl';\r\n break;\r\n }\r\n case 'K_RCONTROL':\r\n case 'K_RCTRL':\r\n if(isChiral) {\r\n nextLayer = 'rightctrl';\r\n break;\r\n }\r\n case 'K_CTRL':\r\n nextLayer = 'ctrl';\r\n break;\r\n case 'K_LMENU':\r\n case 'K_LALT':\r\n if(isChiral) {\r\n nextLayer = 'leftalt';\r\n break;\r\n }\r\n case 'K_RMENU':\r\n case 'K_RALT':\r\n if(isChiral) {\r\n nextLayer = 'rightalt';\r\n break;\r\n }\r\n case 'K_ALT':\r\n nextLayer = 'alt';\r\n break;\r\n case 'K_ALTGR':\r\n if(isChiral) {\r\n nextLayer = 'leftctrl-rightalt';\r\n } else {\r\n nextLayer = 'ctrl-alt';\r\n }\r\n break;\r\n case 'K_CURRENCIES':\r\n case 'K_NUMERALS':\r\n case 'K_SHIFTED':\r\n case 'K_UPPER':\r\n case 'K_LOWER':\r\n case 'K_SYMBOLS':\r\n nextLayer = 'default';\r\n break;\r\n }\r\n }\r\n\r\n // If no key corresponding to a layer transition is pressed, maintain the current layer.\r\n if(!nextLayer) {\r\n return false;\r\n }\r\n\r\n // Change layer and refresh OSK\r\n this.updateLayer(keyEvent, nextLayer);\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Sets the new layer id, allowing for toggling shift/ctrl/alt while preserving the remainder\r\n * of the modifiers represented by the current layer id (where applicable)\r\n *\r\n * @param {string} id layer id (e.g. ctrlshift)\r\n */\r\n updateLayer(keyEvent: KeyEvent, id: string) {\r\n let activeLayer = this.layerId;\r\n var s = activeLayer;\r\n\r\n // Do not change layer unless needed (27/08/2015)\r\n if(id == activeLayer && keyEvent.device.formFactor != DeviceSpec.FormFactor.Desktop) {\r\n return false;\r\n }\r\n\r\n var idx=id;\r\n var i;\r\n\r\n if(keyEvent.device.formFactor == DeviceSpec.FormFactor.Desktop) {\r\n // Need to test if target layer is a standard layer (based on the plain 'default')\r\n var replacements= ['leftctrl', 'rightctrl', 'ctrl', 'leftalt', 'rightalt', 'alt', 'shift'];\r\n\r\n for(i=0; i < replacements.length; i++) {\r\n // Don't forget to remove the kebab-case hyphens!\r\n idx=idx.replace(replacements[i] + '-', '');\r\n idx=idx.replace(replacements[i],'');\r\n }\r\n\r\n // If we are presently on the default layer, drop the 'default' and go straight to the shifted mode.\r\n // If on a common symbolic layer, drop out of symbolic mode and go straight to the shifted mode.\r\n if(activeLayer == 'default' || activeLayer == 'numeric' || activeLayer == 'symbol' || activeLayer == 'currency' || idx != '') {\r\n s = id;\r\n }\r\n // Otherwise, we are based upon a layer that accepts modifier variations.\r\n // Modify the layer according to the current state and key pressed.\r\n //\r\n // TODO: Consider: should this ever be allowed for a base layer other than 'default'? If not,\r\n // if(idx == '') with accompanying if-else structural shift would be a far better test here.\r\n else {\r\n // Save our current modifier state.\r\n var modifier=Codes.getModifierState(s);\r\n\r\n // Strip down to the base modifiable layer.\r\n for(i=0; i < replacements.length; i++) {\r\n // Don't forget to remove the kebab-case hyphens!\r\n s=s.replace(replacements[i] + '-', '');\r\n s=s.replace(replacements[i],'');\r\n }\r\n\r\n // Toggle the modifier represented by our input argument.\r\n switch(id) {\r\n case 'shift':\r\n modifier ^= Codes.modifierCodes['SHIFT'];\r\n break;\r\n case 'leftctrl':\r\n modifier ^= Codes.modifierCodes['LCTRL'];\r\n break;\r\n case 'rightctrl':\r\n modifier ^= Codes.modifierCodes['RCTRL'];\r\n break;\r\n case 'ctrl':\r\n modifier ^= Codes.modifierCodes['CTRL'];\r\n break;\r\n case 'leftalt':\r\n modifier ^= Codes.modifierCodes['LALT'];\r\n break;\r\n case 'rightalt':\r\n modifier ^= Codes.modifierCodes['RALT'];\r\n break;\r\n case 'alt':\r\n modifier ^= Codes.modifierCodes['ALT'];\r\n break;\r\n default:\r\n s = id;\r\n }\r\n\r\n // Combine our base modifiable layer and attach the new modifier variation info to obtain our destination layer.\r\n if(s != 'default') {\r\n if(s == '') {\r\n s = this.getLayerId(modifier);\r\n } else {\r\n s = this.getLayerId(modifier) + '-' + s;\r\n }\r\n }\r\n }\r\n\r\n if(s == '') {\r\n s = 'default';\r\n }\r\n } else {\r\n // Mobile form-factor. Either the layout is specified by a keyboard developer with direct layer name references\r\n // or all layers are accessed via subkey of a single layer-shifting key - no need for modifier-combining logic.\r\n s = id;\r\n }\r\n\r\n let layout = this.activeKeyboard.layout(keyEvent.device.formFactor);\r\n if(layout.getLayer(s)) {\r\n this.layerId = s;\r\n } else {\r\n this.layerId = 'default';\r\n }\r\n\r\n let baseModifierState = Codes.getModifierState(this.layerId);\r\n this.modStateFlags = baseModifierState | keyEvent.Lstates;\r\n }\r\n\r\n // Returns true if the key event is a modifier press, allowing keyPress to return selectively\r\n // in those cases.\r\n doModifierPress(Levent: KeyEvent, outputTarget: OutputTarget, isKeyDown: boolean): boolean {\r\n if(!this.activeKeyboard) {\r\n return false;\r\n }\r\n\r\n if(Levent.Lcode == 8) {\r\n // I3318 (always clear deadkeys after backspace)\r\n outputTarget.deadkeys().clear();\r\n } else if(Levent.isModifier) {\r\n this.activeKeyboard.notify(Levent.Lcode, outputTarget, isKeyDown ? 1 : 0);\r\n // For eventual integration - we bypass an OSK update for physical keystrokes when in touch mode.\r\n if(!Levent.device.touchable) {\r\n return this._UpdateVKShift(Levent); // I2187\r\n } else {\r\n return true;\r\n }\r\n }\r\n\r\n if(Levent.LmodifierChange) {\r\n this.activeKeyboard.notify(0, outputTarget, 1);\r\n if(!Levent.device.touchable) {\r\n this._UpdateVKShift(Levent);\r\n }\r\n }\r\n\r\n // No modifier keypresses detected.\r\n return false;\r\n }\r\n\r\n /**\r\n * Tell the currently active keyboard that a new context has been selected,\r\n * e.g. by focus change, selection change, keyboard change, etc.\r\n *\r\n * @param {Object} outputTarget The OutputTarget that has focus\r\n * @returns {Object} A RuleBehavior object describing the cumulative effects of\r\n * all matched keyboard rules\r\n */\r\n performNewContextEvent(outputTarget: OutputTarget): RuleBehavior {\r\n const ruleBehavior = this.processNewContextEvent(this.contextDevice, outputTarget);\r\n\r\n if(ruleBehavior) {\r\n ruleBehavior.finalize(this, outputTarget, true);\r\n }\r\n return ruleBehavior;\r\n }\r\n\r\n resetContext(target?: OutputTarget) {\r\n this.layerId = 'default';\r\n this.keyboardInterface.resetContextCache();\r\n\r\n // May be null if it's a keyboard swap.\r\n // Performed before _UpdateVKShift since the op may modify the displayed layer\r\n // Also updates the layer for predictions.\r\n if(target) {\r\n this.performNewContextEvent(target);\r\n }\r\n\r\n if(!this.contextDevice.touchable) {\r\n this._UpdateVKShift(null);\r\n }\r\n };\r\n\r\n setNumericLayer(device: DeviceSpec) {\r\n if (this.activeKeyboard) {\r\n let layout = this.activeKeyboard.layout(device.formFactor);\r\n if(layout.getLayer('numeric')) {\r\n this.layerId = 'numeric';\r\n }\r\n }\r\n };\r\n}\r\n", + "import { PathOptionSpec } from \"./optionSpec.interface.js\";\r\nimport { OSKResourcePathConfiguration } from 'keyman/engine/osk';\r\n\r\nconst addDelimiter = (p: string) => {\r\n // Add delimiter if missing\r\n if(p.substring(p.length-1) != '/') {\r\n return p + '/';\r\n } else {\r\n return p;\r\n }\r\n}\r\n\r\nexport default class PathConfiguration implements OSKResourcePathConfiguration {\r\n private readonly sourcePath: string;\r\n private _root: string;\r\n private _resources: string;\r\n private _keyboards: string;\r\n\r\n // May get its initial value from the Keyman Cloud API after a query if not\r\n // otherwise specified.\r\n private _fonts: string;\r\n readonly protocol: string;\r\n\r\n /*\r\n * Pre-modularization code corresponding to `sourcePath`:\r\n ```\r\n // Determine path and protocol of executing script, setting them as\r\n // construction defaults.\r\n //\r\n // This can only be done during load when the active script will be the\r\n // last script loaded. Otherwise the script must be identified by name.\r\n\r\n var scripts = document.getElementsByTagName('script');\r\n var ss = scripts[scripts.length-1].src;\r\n var sPath = ss.substr(0,ss.lastIndexOf('/')+1);\r\n ```\r\n */\r\n constructor(pathSpec: Required, sourcePath: string) {\r\n sourcePath = addDelimiter(sourcePath);\r\n this.sourcePath = sourcePath;\r\n this.protocol = sourcePath.replace(/(.{3,5}:)(.*)/,'$1');\r\n\r\n this.updateFromOptions(pathSpec);\r\n }\r\n\r\n updateFromOptions(pathSpec: Required) {\r\n const _rootPath = this.sourcePath.replace(/(https?:\\/\\/)([^\\/]*)(.*)/,'$1$2/');\r\n\r\n // Get default paths and device options\r\n this._root = _rootPath;\r\n if(pathSpec.root != '') {\r\n this._root = this.fixPath(pathSpec.root);\r\n } else {\r\n this._root = this.fixPath(_rootPath);\r\n }\r\n\r\n // Resources are located with respect to the engine by default\r\n let resources = pathSpec.resources; // avoid mutating the parameter!\r\n if(resources == '') {\r\n resources = this.sourcePath;\r\n }\r\n\r\n // Convert resource, keyboard and font paths to absolute URLs\r\n this._resources = this.fixPath(resources);\r\n this._keyboards = this.fixPath(pathSpec.keyboards);\r\n this._fonts = this.fixPath(pathSpec.fonts);\r\n }\r\n\r\n // Local function to convert relative to absolute URLs\r\n // with respect to the source path, server root and protocol\r\n fixPath(p: string) {\r\n if(p.length == 0) {\r\n return p;\r\n }\r\n\r\n p = addDelimiter(p);\r\n\r\n // Absolute\r\n if((p.replace(/^(http)s?:.*/,'$1') == 'http') || (p.replace(/^(file):.*/,'$1') == 'file')) {\r\n return p;\r\n }\r\n\r\n // Absolute (except for protocol)\r\n if(p.substring(0,2) == '//') {\r\n return this.protocol + p;\r\n }\r\n\r\n // Relative to server root\r\n if(p.substring(0,1) == '/') {\r\n return this.root + p.substring(1);\r\n }\r\n\r\n // Otherwise, assume relative to source path\r\n return this.sourcePath + p;\r\n }\r\n\r\n get fonts(): string {\r\n return this._fonts;\r\n }\r\n\r\n updateFontPath(path: string) {\r\n this._fonts = this.fixPath(path);\r\n }\r\n\r\n get root(): string {\r\n return this._root;\r\n }\r\n\r\n get resources(): string {\r\n return this._resources;\r\n }\r\n\r\n get keyboards(): string {\r\n return this._keyboards;\r\n }\r\n}", + "export interface PathOptionSpec {\r\n /**\r\n * If defined, specifies the root path of the default location hosting KMW resources.\r\n * Is typically just the protocol + domain name.\r\n */\r\n root?: string;\r\n\r\n /**\r\n * The base path to prepend on relative paths for other types of resources.\r\n */\r\n resources?: string;\r\n\r\n /**\r\n * The base path to prepend on relative paths when loading keyboards.\r\n */\r\n keyboards?: string;\r\n\r\n /**\r\n * The base path to prepend on relative paths when loading fonts.\r\n */\r\n fonts?: string;\r\n}\r\n\r\nexport const PathOptionDefaults: Required = {\r\n root: '',\r\n resources: '',\r\n keyboards: '',\r\n fonts: ''\r\n}", + "import { DeviceSpec } from \"@keymanapp/web-utils\";\r\n\r\n/*\r\n * This file is intended for CSS-styling constants that see use with the OSK.\r\n */\r\n\r\n/**\r\n * Defines device-level constants used for CSS styling.\r\n */\r\nexport default class StyleConstants {\r\n constructor(device: DeviceSpec) {\r\n // popupCanvasBackgroundColor\r\n if(device.OS == DeviceSpec.OperatingSystem.Android) {\r\n this.popupCanvasBackgroundColor = '#999';\r\n } else {\r\n this.popupCanvasBackgroundColor = StyleConstants.prefersDarkMode() ? '#0f1319' : '#ffffff';\r\n }\r\n }\r\n\r\n /**\r\n * Checks is a user's browser is in dark mode, if the feature is supported. Returns false otherwise.\r\n *\r\n * Thanks to https://stackoverflow.com/a/57795518 for this code.\r\n */\r\n static prefersDarkMode(): boolean {\r\n // Ensure the detector exists (otherwise, returns false)\r\n return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;\r\n }\r\n\r\n public readonly popupCanvasBackgroundColor: string;\r\n}", + "import StyleConstants from './utils/styleConstants.js';\r\nimport { DeviceSpec, Version } from \"@keymanapp/web-utils\";\r\n\r\n// The Device object definition -------------------------------------------------\r\n\r\nexport class Device {\r\n // These correspond directly to the properties & parameters for `DeviceSpec`.\r\n touchable: boolean;\r\n OS: string;\r\n formFactor: string;\r\n browser: string;\r\n\r\n // These components aren't needed for key events. All but `version` could be a sort\r\n // of `DeviceStyle`.\r\n dyPortrait: number; // Its value is only referenced by an unused method.\r\n dyLandscape: number; // Its value is only referenced by an unused method.\r\n orientation: string|number; // Appears to be unused as well?\r\n colorScheme: 'light' | 'dark'; // Also unused?\r\n version: string; // As in, device version; only really persisted for Android.\r\n // No real sign of actual use, though.\r\n\r\n private detected: boolean = false;\r\n\r\n // Generates a default Device value.\r\n constructor() {\r\n this.touchable = !!('ontouchstart' in window);\r\n this.OS = '';\r\n this.formFactor='desktop';\r\n this.browser='';\r\n\r\n this.dyPortrait=0;\r\n this.dyLandscape=0;\r\n this.version='0';\r\n this.orientation=window.orientation;\r\n }\r\n\r\n /**\r\n * Get device horizontal DPI for touch devices, to set actual size of active regions\r\n * Note that the actual physical DPI may be somewhat different.\r\n *\r\n * @return {number}\r\n */\r\n getDPI(): number {\r\n var t=document.createElement('DIV') ,s=t.style,dpi=96;\r\n if(document.readyState !== 'complete') {\r\n return dpi;\r\n }\r\n\r\n t.id='calculateDPI';\r\n s.position='absolute'; s.display='block';s.visibility='hidden';\r\n s.left='10px'; s.top='10px'; s.width='1in'; s.height='10px';\r\n document.body.appendChild(t);\r\n dpi=(typeof window.devicePixelRatio == 'undefined') ? t.offsetWidth : t.offsetWidth * window.devicePixelRatio;\r\n document.body.removeChild(t);\r\n return dpi;\r\n }\r\n\r\n detect() : DeviceSpec {\r\n var possMacSpoof = false;\r\n\r\n if(navigator && navigator.userAgent) {\r\n var agent=navigator.userAgent;\r\n\r\n if(agent.indexOf('iPad') >= 0) {\r\n this.OS='iOS';\r\n this.formFactor='tablet';\r\n this.dyPortrait=this.dyLandscape=0;\r\n } else if(agent.indexOf('iPhone') >= 0) {\r\n this.OS='iOS';\r\n this.formFactor='phone';\r\n this.dyPortrait=this.dyLandscape=25;\r\n } else if(agent.indexOf('Android') >= 0) {\r\n this.OS='Android';\r\n this.formFactor='phone'; // form factor may be redefined on initialization\r\n this.dyPortrait=75;\r\n this.dyLandscape=25;\r\n try {\r\n var rx=new RegExp(\"(?:Android\\\\s+)(\\\\d+\\\\.\\\\d+\\\\.\\\\d+)\");\r\n this.version=agent.match(rx)[1];\r\n } catch(ex) {}\r\n } else if(agent.indexOf('Linux') >= 0) {\r\n this.OS='Linux';\r\n } else if(agent.indexOf('Macintosh') >= 0) {\r\n // Starting with 13.1, \"Macintosh\" can reflect iPads (by default) or iPhones\r\n // (by user setting); a new \"Request Desktop Website\" setting for Safari will\r\n // change the user agent string to match a desktop Mac.\r\n //\r\n // Firefox uses '.' between version components, while Chrome and Safari use\r\n // '_' instead. So, we have to check for both. Yay.\r\n let regex = /Intel Mac OS X (\\d+(?:[_\\.]\\d+)+)/i;\r\n let results = regex.exec(agent);\r\n\r\n // Match result: a version string with components separated by underscores.\r\n if(!results) {\r\n console.warn(\"KMW could not properly parse the user agent string.\"\r\n + \"A suboptimal keyboard layout may result.\");\r\n this.OS='MacOSX';\r\n } else if(results.length > 1 && results[1]) {\r\n // Convert version string into a usable form.\r\n let versionString = results[1].replace('_', '.');\r\n let version = new Version(versionString);\r\n\r\n possMacSpoof = Version.MAC_POSSIBLE_IPAD_ALIAS.compareTo(version) <= 0;\r\n this.OS='MacOSX';\r\n }\r\n } else if(agent.indexOf('Windows NT') >= 0) {\r\n this.OS='Windows';\r\n if(agent.indexOf('Touch') >= 0) {\r\n this.formFactor='phone'; // will be redefined as tablet if resolution high enough\r\n }\r\n\r\n // Windows Phone and Tablet PC\r\n if(typeof (navigator).msMaxTouchPoints == 'number' && (navigator).msMaxTouchPoints > 0) {\r\n this.touchable=true;\r\n }\r\n }\r\n }\r\n\r\n // We look at the screen resolution for Android, because we can't tell from\r\n // the user agent string whether or not this is supposed to be a tablet.\r\n // It seems that there are a handful of older phones out there that report a\r\n // higher resolution than 700px*___px, but it is proving hard to test these,\r\n // and the majority have an aspect ratio <= 0.5625 anyway.\r\n // But we trust what iOS tells us for phone vs tablet.\r\n\r\n const dimMin = Math.min(screen.width,screen.height), dimMax = Math.max(screen.width,screen.height);\r\n const aspect = dimMin / dimMax;\r\n\r\n if(this.OS != 'iOS' &&\r\n this.formFactor == 'phone' &&\r\n ((dimMin >= 600 && aspect > 0.5625) || // 0.5625 -> 1920x1080 is common phone res\r\n (aspect >= 0.625)) // all reported devices with aspect >= 0.625 are tablets per https://screensiz.es/\r\n ) {\r\n this.formFactor='tablet';\r\n }\r\n\r\n // Test for potential Chrome emulation on Windows or macOS X (used only in next if-check)\r\n let possibleChromeEmulation = navigator.platform == 'Win32' || navigator.platform == 'MacIntel'\r\n\r\n // alert(sxx+'->'+device.formFactor);\r\n // Check for phony iOS devices (but don't undo for Chrome emulation used during development)\r\n if(this.OS == 'iOS' && !('ongesturestart' in window) && !possibleChromeEmulation) {\r\n this.OS='Android';\r\n }\r\n\r\n // Determine application or browser\r\n this.browser='web';\r\n if(this.OS == 'iOS' || this.OS.toLowerCase() == 'macosx') {\r\n this.browser='safari';\r\n }\r\n\r\n var bMatch=/Firefox|Chrome|OPR|Safari|Edge/;\r\n if(bMatch.test(navigator.userAgent)) {\r\n if((navigator.userAgent.indexOf('Firefox') >= 0) && ('onmozorientationchange' in screen)) {\r\n this.browser='firefox';\r\n } else if(navigator.userAgent.indexOf('OPR') >= 0) {\r\n this.browser='opera';\r\n } else if(navigator.userAgent.indexOf(' Edge/') >= 0) {\r\n // Edge is too common a word, so test for Edge/ :)\r\n // Must come before Chrome and Safari test because\r\n // Edge pretends to be both\r\n this.browser='edge';\r\n } else if(navigator.userAgent.indexOf('Chrome') >= 0) {\r\n // This test must come before Safari test because on macOS,\r\n // Chrome also reports \"Safari\"\r\n this.browser='chrome';\r\n } else if(navigator.userAgent.indexOf('Safari') >= 0) {\r\n this.browser='safari';\r\n }\r\n }\r\n\r\n if(possMacSpoof && this.browser == 'safari') {\r\n // Indistinguishable user agent string! We need a different test; fortunately, true macOS\r\n // Safari doesn't support TouchEvents. (Chrome does, though! Hence the filter above.)\r\n if(window['TouchEvent']) {\r\n this.OS='iOS';\r\n this.formFactor='tablet';\r\n this.dyPortrait=this.dyLandscape=0;\r\n\r\n // It's currently impossible to differentiate between iPhone and iPad here\r\n // except for by screen dimensions.\r\n let aspectRatio = screen.height / screen.width;\r\n if(aspectRatio < 1) {\r\n aspectRatio = 1 / aspectRatio;\r\n }\r\n\r\n // iPhones usually have a ratio of 16:9 (or 1.778) or higher, while iPads use 4:3 (or 1.333)\r\n if(aspectRatio > 1.6) {\r\n // Override - we'll treat this device as an iPhone.\r\n this.formFactor = 'phone';\r\n this.dyPortrait=this.dyLandscape=25;\r\n }\r\n }\r\n }\r\n\r\n this.colorScheme = StyleConstants.prefersDarkMode() ? 'dark' : 'light';\r\n this.detected = true;\r\n\r\n return this.coreSpec;\r\n }\r\n\r\n /**\r\n * Returns a slimmer, web-core compatible version of this object.\r\n */\r\n public get coreSpec(): DeviceSpec {\r\n return new DeviceSpec(this.browser, this.formFactor, this.OS, this.touchable);\r\n }\r\n}\r\n\r\nexport default Device;", + "import EventEmitter from 'eventemitter3';\r\nimport { DeviceSpec, ManagedPromise, type Keyboard, type KeyboardInterface, type OutputTarget } from '@keymanapp/keyboard-processor';\r\nimport { StubAndKeyboardCache, type KeyboardStub } from 'keyman/engine/package-cache';\r\nimport { PredictionContext } from '@keymanapp/input-processor';\r\nimport { EngineConfiguration } from './engineConfiguration.js';\r\n\r\ninterface EventMap {\r\n // target, then keyboard.\r\n 'targetchange': (target: OutputTarget) => boolean;\r\n\r\n /**\r\n * This event is raised whenever a keyboard change is requested.\r\n *\r\n * Note that if the keyboard has not been previously loaded, this event will be raised twice.\r\n * 1. Before the keyboard is loaded into Keyman Engine for Web.\r\n * 2. Once the keyboard is loaded, but before it is activated.\r\n * @param metadata The to-be-activated keyboard's properties\r\n * @returns\r\n */\r\n 'beforekeyboardchange': (metadata: KeyboardStub) => void;\r\n\r\n /**\r\n * This event is raised whenever an activating keyboard is being loaded into Keyman Engine for\r\n * the first time in the user's current session, which is an asynchronous operation. It is called\r\n * once the async request is initiated.\r\n * @param metadata The registered properties for the keyboard being asynchronously loaded\r\n * @param onload A Promise that resolves with `null` when loading successfully completes or\r\n * with an `error` if it fails.\r\n * @returns\r\n */\r\n 'keyboardasyncload': (metadata: KeyboardStub, onload: Promise) => void;\r\n\r\n /**\r\n * This event is raised whenever a keyboard is fully activated and set as the current active\r\n * keyboard within Keyman Engine for Web.\r\n * @param kbd\r\n * @returns\r\n */\r\n 'keyboardchange': (kbd: {keyboard: Keyboard, metadata: KeyboardStub}) => void;\r\n}\r\n\r\nexport interface ContextManagerConfiguration {\r\n /**\r\n * A function that resets any state-dependent keyboard key-state information such as\r\n * emulated modifier state and layer id. Also purges the context cache.\r\n * If an `outputTarget` is specified, it will also trigger new-context rule processing.\r\n *\r\n * Does not reset option-stores, variable-stores, etc.\r\n */\r\n readonly resetContext: (outputTarget?: OutputTarget) => void;\r\n\r\n /**\r\n * A predictive-state management object that interfaces the predictive-text banner\r\n * with the active context.\r\n */\r\n readonly predictionContext: PredictionContext;\r\n\r\n /**\r\n * The stub & keyboard curation cache holding preloaded keyboards and metadata useable\r\n * to load those not yet loaded.\r\n */\r\n readonly keyboardCache: StubAndKeyboardCache;\r\n}\r\n\r\ninterface PendingActivation {\r\n target: OutputTarget,\r\n keyboard: Promise,\r\n stub: KeyboardStub;\r\n}\r\n\r\nexport abstract class ContextManagerBase extends EventEmitter {\r\n public static readonly TIMEOUT_THRESHOLD = 10000;\r\n\r\n abstract initialize(): void;\r\n\r\n abstract get activeTarget(): OutputTarget;\r\n\r\n private _predictionContext: PredictionContext;\r\n protected keyboardCache: StubAndKeyboardCache;\r\n private _resetContext: (outputTarget?: OutputTarget) => void;\r\n\r\n private pendingActivations: PendingActivation[] = [];\r\n protected engineConfig: MainConfig;\r\n\r\n get predictionContext(): PredictionContext {\r\n return this._predictionContext;\r\n }\r\n\r\n constructor(engineConfig: MainConfig) {\r\n super();\r\n\r\n this.engineConfig = engineConfig;\r\n }\r\n\r\n configure(config: ContextManagerConfiguration) {\r\n this._resetContext = config.resetContext;\r\n this._predictionContext = config.predictionContext;\r\n this.keyboardCache = config.keyboardCache;\r\n }\r\n\r\n insertText(kbdInterface: KeyboardInterface, Ptext: string, PdeadKey: number) {\r\n // Find the correct output target to manipulate.\r\n const outputTarget = this.activeTarget;\r\n\r\n if(outputTarget != null) {\r\n if(Ptext != null) {\r\n kbdInterface.output(0, outputTarget, Ptext);\r\n }\r\n\r\n if((typeof(PdeadKey)!=='undefined') && (PdeadKey !== null)) {\r\n kbdInterface.deadkeyOutput(0, outputTarget, PdeadKey);\r\n }\r\n\r\n outputTarget.invalidateSelection();\r\n\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n resetContext() {\r\n this._resetContext(this.activeTarget);\r\n this.predictionContext.resetContext();\r\n }\r\n\r\n abstract get activeKeyboard(): {keyboard: Keyboard, metadata: KeyboardStub};\r\n\r\n /**\r\n * Determines the 'target' currently used to determine which keyboard should be active.\r\n * When `null`, keyboard-activation operations will affect the global default; otherwise,\r\n * such operations affect only the specified `target`.\r\n *\r\n * This method exists to facilitate independent-keyboard mode operations for specific\r\n * attached elements within the app/browser target. For `app/webview`, this should\r\n * always return a consistent value - likely, `null`.\r\n */\r\n protected abstract currentKeyboardSrcTarget(): OutputTarget;\r\n\r\n /**\r\n * Ensures that newly activated keyboards are set correctly within managed context, possibly\r\n * against inactive output targets.\r\n * @param kbd\r\n * @param target\r\n */\r\n protected abstract activateKeyboardForTarget(kbd: {keyboard: Keyboard, metadata: KeyboardStub}, target: OutputTarget);\r\n\r\n /**\r\n * Checks the pending keyboard-activation array for an entry corresponding to the specified\r\n * OutputTarget. If found, also removes the entry for bookkeeping purposes.\r\n * @param target The specific OutputTarget affected by the pending Keyboard activation.\r\n * May be `null`, which corresponds to the global default Keyboard.\r\n * @returns `true` if pending activation is still valid, `false` otherwise.\r\n */\r\n private findAndPopActivation(target: OutputTarget): PendingActivation {\r\n // Array.findIndex requires Chrome 45+. :(\r\n let activationIndex;\r\n for(activationIndex = 0; activationIndex < this.pendingActivations.length; activationIndex++) {\r\n if(this.pendingActivations[activationIndex].target == target) {\r\n break;\r\n }\r\n }\r\n\r\n if(activationIndex == this.pendingActivations.length) {\r\n return null;\r\n }\r\n\r\n return this.pendingActivations.splice(activationIndex, 1)[0];\r\n }\r\n\r\n /**\r\n * Internally registers a pending keyboard-activation's properties, only resolving to a non-null\r\n * activation if it is still the most recent keyboard-activation request that would affect the\r\n * corresponding context.\r\n * @param kbdPromise\r\n * @param metadata\r\n * @param target\r\n * @returns\r\n */\r\n protected async deferredKeyboardActivation(\r\n kbdPromise: Promise,\r\n metadata: KeyboardStub,\r\n target: OutputTarget\r\n ): Promise {\r\n const activation: PendingActivation = {\r\n target: target,\r\n keyboard: kbdPromise,\r\n stub: metadata\r\n };\r\n\r\n // Invalidate existing requests for the specified target.\r\n this.findAndPopActivation(target);\r\n this.pendingActivations.push(activation);\r\n await kbdPromise;\r\n\r\n // The keyboard-load is complete; is the activation still desired?\r\n const activationAfterAwait = this.findAndPopActivation(target);\r\n if(activationAfterAwait == activation) {\r\n return activation;\r\n } else if(activationAfterAwait) {\r\n // Restore the popped element; it doesn't match the current activation attempt.\r\n this.pendingActivations.push(activationAfterAwait);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Specifies the keyboard id and the language code to use when a 'default' keyboard\r\n * must be selected by the engine for fallback behaviors.\r\n */\r\n protected abstract getFallbackStubKey(): {\r\n id: string,\r\n langId: string\r\n };\r\n\r\n /**\r\n * Change active keyboard to keyboard selected by (internal) name and language code\r\n *\r\n * Test if selected keyboard already loaded, and simply update active stub if so.\r\n * Otherwise, insert a script to download and insert the keyboard from the repository\r\n * or user-indicated file location.\r\n *\r\n * @param keyboardId\r\n * @param languageCode\r\n * @param saveCookie\r\n * @returns\r\n */\r\n public async activateKeyboard(keyboardId: string, languageCode?: string, saveCookie?: boolean): Promise {\r\n // TODO: relocate default keyboard behavior here once we can also move core error handling for\r\n // unfound stubs here.\r\n\r\n // If there was a previous activation attempt set and still active for the specified keyboard target,\r\n // cancel it. For exmaple, if the user selects a preloaded keyboard after having tried to select one\r\n // still async-loading, we should go with the later setting - the preloaded one.\r\n this.findAndPopActivation(this.currentKeyboardSrcTarget());\r\n\r\n const activatingKeyboard = this.prepareKeyboardForActivation(keyboardId, languageCode);\r\n\r\n const originalKeyboardTarget = this.currentKeyboardSrcTarget();\r\n\r\n const keyboard = await activatingKeyboard.keyboard;\r\n if(keyboard == null && activatingKeyboard.metadata) {\r\n // The activation was async and was cancelled - either by `beforeKeyboardChange` first-pass\r\n // cancellation or because a different keyboard was requested before completion of the async load.\r\n return false;\r\n }\r\n\r\n /*\r\n * Triggers `beforeKeyboardChange` event if the current context at the time when activation is possible\r\n * would be affected by the requested keyboard change.\r\n * - if a keyboard was asynchronously loaded for this...\r\n * - it is possible for the context (in app/browser) to have changed to a page element in\r\n * \"independent keyboard\" mode (or away from one)\r\n * - This is the \"second\" `beforeKeyboardChange` call - a loaded keyboard may now be activated.\r\n *\r\n * If the now-current context would be unaffected by the keyboard change, we do not raise the corresponding\r\n * event.\r\n */\r\n if(this.currentKeyboardSrcTarget() == originalKeyboardTarget) {\r\n this.emit('beforekeyboardchange', activatingKeyboard.metadata);\r\n }\r\n\r\n let kbdStubPair: { keyboard: Keyboard, metadata: KeyboardStub } = null;\r\n if(keyboard) {\r\n kbdStubPair = {\r\n keyboard: keyboard,\r\n metadata: activatingKeyboard.metadata\r\n };\r\n }\r\n\r\n this.activateKeyboardForTarget(kbdStubPair, originalKeyboardTarget);\r\n\r\n // Only trigger `keyboardchange` events when they will affect the active context.\r\n if(this.currentKeyboardSrcTarget() == originalKeyboardTarget) {\r\n // Perform standard context-reset ops, including the processing of new-context events.\r\n this.resetContext();\r\n // Will trigger KeymanEngine handler that passes keyboard to the OSK, displays it.\r\n this.emit('keyboardchange', this.activeKeyboard);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Based on the provided keyboard id and language code, selects and (if necessary) loads the\r\n * corresponding keyboard but does not activate it.\r\n *\r\n * This acts as a helper to `activateKeyboard`, helping to centralize and DRY out the actual\r\n * activation of the requested keyboard. Note that it is a synchronous method and should stay\r\n * that way, though it should return a `Promise` for the activating keyboard.\r\n * @param keyboardId\r\n * @param languageCode\r\n * @returns\r\n */\r\n protected prepareKeyboardForActivation(\r\n keyboardId: string,\r\n languageCode?: string\r\n ): {keyboard: Promise, metadata: KeyboardStub} {\r\n // Set default language code\r\n languageCode ||= '';\r\n\r\n // Check that the saved keyboard is currently registered\r\n let requestedStub = null;\r\n if(keyboardId) {\r\n requestedStub = this.keyboardCache.getStub(keyboardId, languageCode);\r\n } else {\r\n languageCode == '';\r\n }\r\n\r\n if(!requestedStub) {\r\n if(keyboardId) {\r\n throw new Error(\"No matching stub has been registered.\");\r\n } else {\r\n return {\r\n keyboard: Promise.resolve(null),\r\n metadata: null\r\n }\r\n }\r\n }\r\n\r\n // Check if current keyboard matches requested keyboard, but not (necessarily) stub\r\n if(this.activeKeyboard?.metadata && keyboardId == this.activeKeyboard.metadata.id) {\r\n const keyboard = this.activeKeyboard.keyboard;\r\n // In this case, the keyboard is loaded; just update the stub.\r\n\r\n return {\r\n keyboard: Promise.resolve(keyboard),\r\n metadata: requestedStub\r\n };\r\n }\r\n\r\n // Determine if the keyboard was previously loaded but is not active; use the cached, pre-loaded version if so.\r\n let keyboard: Keyboard;\r\n if(keyboard = this.keyboardCache.getKeyboardForStub(requestedStub)) {\r\n return {\r\n keyboard: Promise.resolve(keyboard),\r\n metadata: requestedStub\r\n };\r\n } else {\r\n // It's async time - the keyboard is not preloaded within the cache. Use the stub's data to load it.\r\n\r\n // `beforeKeyboardChange` - first call\r\n this.emit('beforekeyboardchange', requestedStub);\r\n\r\n const defermentPromise = this.engineConfig.deferForInitialization.then(() => {\r\n // Provide a Promise for completion of the async load process.\r\n const completionPromise = new ManagedPromise();\r\n this.emit('keyboardasyncload', requestedStub, completionPromise.corePromise);\r\n\r\n let keyboardPromise = this.keyboardCache.fetchKeyboardForStub(requestedStub);\r\n let timeoutPromise = new Promise((resolve, reject) => {\r\n const timeoutMsg = `Sorry, the ${requestedStub.name} keyboard for ${requestedStub.langName} is not currently available.`;\r\n window.setTimeout(() => reject(new Error(timeoutMsg)), ContextManagerBase.TIMEOUT_THRESHOLD);\r\n });\r\n\r\n let combinedPromise = Promise.race([keyboardPromise, timeoutPromise]);\r\n\r\n // Ensure the async-load Promise completes properly.\r\n combinedPromise.then(() => {\r\n completionPromise.resolve(null);\r\n // Prevent any 'unhandled Promise rejection' events that may otherwise occur from the timeout promise.\r\n timeoutPromise.catch(() => {});\r\n });\r\n combinedPromise.catch((err) => {\r\n completionPromise.resolve(err);\r\n throw err;\r\n });\r\n\r\n return combinedPromise;\r\n });\r\n\r\n // Now the fun part: note the original call's parameters as a pending activation.\r\n let promise = this.deferredKeyboardActivation(defermentPromise, requestedStub, this.currentKeyboardSrcTarget());\r\n return {\r\n keyboard: promise.then(async (activation) => {\r\n // Is the activation we requested still pending, or was it cancelled in favor of a\r\n // different activation in some manner?\r\n if(!activation) {\r\n // If the user chose to load a different keyboard afterward that would affect the same\r\n // output target, the activation is no longer valid.\r\n return Promise.resolve(null);\r\n } else {\r\n return defermentPromise;\r\n }\r\n }),\r\n metadata: requestedStub\r\n }\r\n }\r\n }\r\n}", + "import EventEmitter from \"eventemitter3\";\r\nimport { type KeyEvent, type RuleBehavior } from \"@keymanapp/keyboard-processor\";\r\nimport KeyEventSourceInterface from './keyEventSource.interface.js';\r\n\r\ninterface EventMap {\r\n /**\r\n * Designed to pass key events off to any consuming modules/libraries.\r\n */\r\n 'keyevent': (event: KeyEvent, callback?: (result: RuleBehavior, error?: Error) => void) => void\r\n}\r\n\r\nexport default class HardKeyboard extends EventEmitter implements KeyEventSourceInterface { }\r\n\r\n// Intended design:\r\n// - KeyEventKeyboard: website-integrated handler for hardware-keystroke input; interprets DOM events.\r\n// - app/web\r\n// - AppPassthroughKeyboard: WebView-hosted forwarding of hardware key events through to the Web engine.\r\n// - app/embed", + "import { Keyboard, KeyboardLoaderBase as KeyboardLoader } from \"@keymanapp/keyboard-processor\";\r\nimport EventEmitter from \"eventemitter3\";\r\n\r\nimport KeyboardStub from \"./keyboardStub.js\";\r\n\r\nconst KEYBOARD_PREFIX = \"Keyboard_\";\r\n\r\nfunction prefixed(text: string) {\r\n if(!text.startsWith(KEYBOARD_PREFIX)) {\r\n return KEYBOARD_PREFIX + text;\r\n } else {\r\n return text;\r\n }\r\n}\r\n\r\nexport {prefixed as toPrefixedKeyboardId};\r\n\r\nfunction withoutPrefix(text: string) {\r\n if(text.startsWith(KEYBOARD_PREFIX)) {\r\n return text.substring(KEYBOARD_PREFIX.length);\r\n } else {\r\n return text;\r\n }\r\n}\r\n\r\nexport {withoutPrefix as toUnprefixedKeyboardId};\r\n\r\ninterface EventMap {\r\n /**\r\n * Indicates that the specified stub has just been registered within the cache.\r\n *\r\n * Note for future hook: establish a listener for this event during engine init\r\n * to denote the first added stub to facilitate auto-activation of the first\r\n * keyboard to be registered.\r\n */\r\n stubadded: (stub: KeyboardStub) => void;\r\n\r\n /**\r\n * Indicates that the specified Keyboard has just been added to the cache.\r\n */\r\n keyboardadded: (keyboard: Keyboard) => void;\r\n}\r\n\r\nexport default class StubAndKeyboardCache extends EventEmitter {\r\n private stubSetTable: Record> = {};\r\n private keyboardTable: Record> = {};\r\n\r\n private readonly keyboardLoader: KeyboardLoader;\r\n\r\n constructor(keyboardLoader?: KeyboardLoader) {\r\n super();\r\n this.keyboardLoader = keyboardLoader;\r\n }\r\n\r\n getKeyboardForStub(stub: KeyboardStub): Keyboard {\r\n return this.getKeyboard(stub.KI);\r\n }\r\n\r\n getKeyboard(keyboardID: string): Keyboard {\r\n if(!keyboardID) {\r\n return null;\r\n }\r\n const entry = this.keyboardTable[prefixed(keyboardID)];\r\n\r\n // Unit testing may 'trip up' in the DOM, as bundled versions of a class from one bundled\r\n // module will fail against an `instanceof` expecting the version bundled in a second.\r\n //\r\n // Thus, we filter based on `Promise`, which needs no module.\r\n return entry instanceof Promise ? null : entry;\r\n }\r\n\r\n get defaultStub(): KeyboardStub {\r\n /* See the following two StackOverflow links:\r\n * - https://stackoverflow.com/a/23202095\r\n * - https://stackoverflow.com/a/5525820\r\n *\r\n * Also: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values#description\r\n *\r\n * As keyboard IDs are never purely numeric, any sufficiently-recent browser will\r\n * maintain the order in which stubs were added to this cache.\r\n *\r\n * Note that if a keyboard is removed, its matching stubs are also removed, so the next most-recent\r\n * property will take precedence.\r\n *\r\n * Might possibly fail to return the oldest registered stub for the oldest of supported browsers\r\n * (i.e, Android 5.0), but will work for anything decently recent. Even then... we still supply\r\n * _a_ keyboard. Just not in a way that will seem deterministic/controllable to site designers.\r\n *\r\n * Warning: Object.values and Object.entries require Chrome for Android 54, which is higher than\r\n * API 21's base. Object.keys only requires Chrome for Android 18, so is safe.\r\n */\r\n\r\n // An `Object.keys`-based helper function. Gets the first entry of Object.values for the object.\r\n // Can be written with stronger type safety... if we get very explicit with generics during calls.\r\n // That'd be more verbose than desired here.\r\n function getFirstValue(obj: any) {\r\n const keys = Object.keys(obj);\r\n if(keys.length == 0) {\r\n return undefined;\r\n } else {\r\n return obj[keys[0]];\r\n }\r\n };\r\n\r\n const stubTable = getFirstValue(this.stubSetTable) as Record;\r\n if(!stubTable) {\r\n return undefined;\r\n } else {\r\n // First value = first registered stub for that first keyboard.\r\n // Does not consider later-added stubs, but neither does removeKeyboard - removal is \"all or nothing\".\r\n return getFirstValue(stubTable) as KeyboardStub;\r\n }\r\n }\r\n\r\n addKeyboard(keyboard: Keyboard) {\r\n const keyboardID = prefixed(keyboard.id);\r\n this.keyboardTable[keyboardID] = keyboard;\r\n\r\n this.emit('keyboardadded', keyboard);\r\n }\r\n\r\n fetchKeyboardForStub(stub: KeyboardStub) : Promise {\r\n return this.fetchKeyboard(stub.KI);\r\n }\r\n\r\n isFetchingKeyboard(keyboardID: string): boolean {\r\n if(!keyboardID) {\r\n throw new Error(\"Keyboard ID must be specified\");\r\n }\r\n\r\n keyboardID = prefixed(keyboardID);\r\n\r\n const cachedEntry = this.keyboardTable[keyboardID];\r\n return cachedEntry instanceof Promise;\r\n }\r\n\r\n fetchKeyboard(keyboardID: string): Promise {\r\n if(!keyboardID) {\r\n throw new Error(\"Keyboard ID must be specified\");\r\n }\r\n\r\n if(!this.keyboardLoader) {\r\n throw new Error(\"Cannot load keyboards; this cache was configured without a loader\");\r\n }\r\n\r\n keyboardID = prefixed(keyboardID);\r\n\r\n const cachedEntry = this.keyboardTable[keyboardID];\r\n if(cachedEntry instanceof Keyboard) {\r\n return Promise.resolve(cachedEntry);\r\n } else if(cachedEntry instanceof Promise) {\r\n return cachedEntry;\r\n }\r\n\r\n const stub = this.getStub(keyboardID, null);\r\n if(!stub) {\r\n throw new Error(`No stub for ${withoutPrefix(keyboardID)} has been registered`);\r\n }\r\n\r\n if(!stub.filename) {\r\n throw new Error(`The registered stub for ${withoutPrefix(keyboardID)} lacks a path to the main keyboard file`);\r\n }\r\n\r\n const promise = this.keyboardLoader.loadKeyboardFromStub(stub);\r\n this.keyboardTable[keyboardID] = promise;\r\n\r\n promise.then((kbd) => {\r\n // Overrides the built-in ID in case of keyboard namespacing.\r\n kbd.scriptObject[\"KI\"] = keyboardID;\r\n this.addKeyboard(kbd);\r\n }).catch((err) => {\r\n delete this.keyboardTable[keyboardID];\r\n throw err;\r\n })\r\n\r\n return promise;\r\n }\r\n\r\n addStub(stub: KeyboardStub) {\r\n const keyboardID = prefixed(stub.KI);\r\n const stubTable = this.stubSetTable[keyboardID] = this.stubSetTable[keyboardID] ?? {};\r\n stubTable[stub.KLC] = stub;\r\n\r\n this.emit('stubadded', stub);\r\n }\r\n\r\n findMatchingStub(stub: KeyboardStub) {\r\n return this.getStub(stub.KI, stub.KLC);\r\n }\r\n\r\n getStub(keyboardID: string, languageID: string): KeyboardStub;\r\n getStub(keyboard: Keyboard, languageID?: string): KeyboardStub;\r\n getStub(arg0: string | Keyboard, arg1?: string): KeyboardStub {\r\n let keyboardID: string;\r\n let languageID = arg1 || '---';\r\n\r\n if(arg0 instanceof Keyboard) {\r\n keyboardID = arg0.id;\r\n } else {\r\n keyboardID = arg0;\r\n }\r\n\r\n if(keyboardID) {\r\n keyboardID = prefixed(keyboardID);\r\n }\r\n\r\n const stubTable = this.stubSetTable[keyboardID] ?? {};\r\n\r\n if(languageID != '---') {\r\n return stubTable[languageID];\r\n } else {\r\n const keys = Object.keys(stubTable);\r\n if(keys.length == 0) {\r\n return null;\r\n } else {\r\n return stubTable[keys[0]];\r\n }\r\n };\r\n }\r\n\r\n /**\r\n * Removes all metadata (stubs) associated with a specific keyboard from the cache, optionally\r\n * removing the cached keyboard as well.\r\n * @param keyboard Either the keyboard ID or `Keyboard` instance\r\n * @param purge If `true`, will also purge the `Keyboard` instance itself from the cache.\r\n * If `false`, only forgets the metadata (stubs).\r\n */\r\n forgetKeyboard(keyboard: string | Keyboard, purge: boolean = false) {\r\n let id: string = (keyboard instanceof Keyboard) ? keyboard.id : prefixed(keyboard);\r\n\r\n if(this.stubSetTable[id]) {\r\n delete this.stubSetTable[id];\r\n }\r\n\r\n if(purge && this.keyboardTable[id]) {\r\n delete this.keyboardTable[id];\r\n }\r\n }\r\n\r\n getStubList(): KeyboardStub[] {\r\n let arr: KeyboardStub[] = [];\r\n\r\n const kbdIds = Object.keys(this.stubSetTable);\r\n for(let kbdId of kbdIds) {\r\n let row = this.stubSetTable[kbdId];\r\n const langIds = Object.keys(row);\r\n for(let langId of langIds) {\r\n arr.push(row[langId]);\r\n }\r\n }\r\n\r\n return arr;\r\n }\r\n}", + "import {\r\n type KeyboardAPIPropertySpec as APISimpleKeyboard,\r\n type KeyboardAPIPropertyMultilangSpec as APICompoundKeyboard,\r\n KeyboardProperties,\r\n type LanguageAPIPropertySpec,\r\n} from '@keymanapp/keyboard-processor';\r\nimport { toPrefixedKeyboardId as prefixed } from './stubAndKeyboardCache.js';\r\n\r\n\r\n// Language regions as defined by cloud server\r\nexport const REGIONS = ['World','Africa','Asia','Europe','South America','North America','Oceania','Central America','Middle East'];\r\nexport const REGION_CODES = ['un','af','as','eu','sa','na','oc','ca','me'];\r\n\r\nexport type KeyboardAPISpec = (APISimpleKeyboard | APICompoundKeyboard) & {\r\n displayName?: string;\r\n filename: string\r\n};\r\n\r\nexport interface RawKeyboardStub extends KeyboardStub {};\r\n\r\n/*\r\n * Get keyboard path (relative or absolute)\r\n * KeymanWeb 2 revised keyboard location specification:\r\n * (a) absolute URL (includes ':') - load from specified URL\r\n * (b) relative URL (starts with /, ./, ../) - load with respect to current page\r\n * (c) filename only (anything else) - prepend keyboards option to URL\r\n * (e.g. default keyboards option will be set by Cloud)\r\n *\r\n * So, to fully interpret the following regex, it detects the following patterns (at minimum):\r\n * ../file (but not .../file)\r\n * ./file\r\n * /file\r\n * http:// (on the colon)\r\n * hello:world (on the colon) - that one miiiight be less intentional, though. Would 'fall\r\n * over' on attempted use anyway, since it's not a valid path.\r\n *\r\n * Alternative clearer version - '^(\\.{0,2}/)|(:)'?\r\n * Unless backslashes should be able to replace dots?\r\n */\r\nconst REGEX_FOR_PRECONFIGURED_PATH=RegExp('^(([\\\\.]/)|([\\\\.][\\\\.]/)|(/))|(:)');\r\n\r\nfunction configureFilePathing(path: string, configurationBasePath: string) {\r\n configurationBasePath = configurationBasePath || '';\r\n if(path && !REGEX_FOR_PRECONFIGURED_PATH.test(path)) {\r\n return configurationBasePath + path;\r\n } else {\r\n return path;\r\n }\r\n}\r\n\r\nexport default class KeyboardStub extends KeyboardProperties {\r\n KR: string;\r\n KRC: string;\r\n KF: string;\r\n\r\n KP?: string;\r\n\r\n // For the first flavor of constructor, note that Developer relies on KMW's path config to complete the paths...\r\n // even though supplying an 'internal'-style stub.\r\n public constructor(rawStub: RawKeyboardStub, keyboardBaseUri?: string, fontBaseUri?: string);\r\n public constructor(apiSpec: APISimpleKeyboard & { filename: string }, keyboardBaseUri?: string, fontBaseUri?: string);\r\n public constructor(kbdId: string, lngId: string);\r\n constructor(arg0: string | RawKeyboardStub | (APISimpleKeyboard & { filename: string }), arg1?: string, arg2?: string) {\r\n if(typeof arg0 !== 'string') {\r\n if(arg0.id !== undefined) {\r\n let apiSpec = arg0 as APISimpleKeyboard & { filename: string };\r\n apiSpec.id = prefixed(apiSpec.id);\r\n super(apiSpec, arg2);\r\n this.KF = configureFilePathing(apiSpec.filename, arg1);\r\n this.mapRegion(apiSpec.languages);\r\n } else {\r\n let rawStub = arg0 as RawKeyboardStub;\r\n rawStub.KI = prefixed(rawStub.KI);\r\n super(rawStub, arg2);\r\n\r\n this.KF = configureFilePathing(rawStub.KF, arg1);\r\n this.KP = rawStub.KP;\r\n this.KR = rawStub.KR;\r\n this.KRC = rawStub.KRC;\r\n\r\n\r\n return;\r\n }\r\n } else {\r\n super(prefixed(arg0), arg1);\r\n }\r\n }\r\n\r\n private mapRegion(language: LanguageAPIPropertySpec) {\r\n // Accept region as number (from Cloud server), code, or name\r\n const region=language.region;\r\n let rIndex=0;\r\n if(typeof(region) == 'number') {\r\n if(region < 1 || region > 9) {\r\n rIndex = 0;\r\n } else {\r\n rIndex = region-1;\r\n }\r\n } else if(typeof(region) == 'string') {\r\n let list = (region.length == 2 ? REGION_CODES : REGIONS);\r\n for(let i=0; i {\r\n // The deprecated `language` is assigned to satisfy TS type-checking.\r\n const intermediate = {...arg, languages: language, language: undefined};\r\n const stub: KeyboardStub = new KeyboardStub(intermediate, keyboardBaseUri, fontBaseUri);\r\n\r\n stubs.push(stub);\r\n })\r\n\r\n return stubs;\r\n }\r\n\r\n public merge(stub: KeyboardStub) {\r\n this.KL ||= stub.KL;\r\n this.KR ||= stub.KR;\r\n this.KRC ||= stub.KRC;\r\n this.KN ||= stub.KN;\r\n this.KF ||= stub.KF;\r\n this.KFont ||= stub.KFont;\r\n this.KOskFont ||= stub.KOskFont;\r\n\r\n if(stub._displayName) {\r\n this._displayName ||= stub._displayName;\r\n }\r\n }\r\n\r\n public validateForCustomKeyboard(): Error {\r\n if(super.validateForCustomKeyboard() || !this.KF || !this.KR) {\r\n return new Error('To use a custom keyboard, you must specify file name, keyboard id, keyboard name, language, language code, and region.');\r\n } else {\r\n return null;\r\n }\r\n }\r\n}\r\n\r\n// Information about a keyboard that fails to get added\r\nexport interface ErrorStub {\r\n keyboard?: {\r\n id: string;\r\n name: string;\r\n },\r\n language?: {\r\n id?: string;\r\n name?: string;\r\n }\r\n\r\n error: Error;\r\n}\r\n\r\nexport function mergeAndResolveStubPromises(keyboardStubs: (KeyboardStub|ErrorStub)[], errorStubs: ErrorStub[]) :\r\n Promise<(KeyboardStub|ErrorStub)[]> {\r\n if (errorStubs.length == 0) {\r\n return Promise.resolve(keyboardStubs);\r\n } if (keyboardStubs.length == 0) {\r\n return Promise.reject(errorStubs);\r\n } else {\r\n // Merge this with errorStubs\r\n let result: (KeyboardStub|ErrorStub)[] = keyboardStubs;\r\n return Promise.resolve(result.concat(errorStubs));\r\n }\r\n}", + "import EventEmitter from 'eventemitter3';\r\n\r\nimport { PathConfiguration } from 'keyman/engine/paths';\r\n\r\nimport { default as KeyboardStub, ErrorStub, KeyboardAPISpec, mergeAndResolveStubPromises } from '../keyboardStub.js';\r\nimport { LanguageAPIPropertySpec, ManagedPromise, Version } from '@keymanapp/keyboard-processor';\r\nimport CloudRequesterInterface from './requesterInterface.js';\r\n\r\n// For when the API call straight-up times out.\r\nexport const CLOUD_TIMEOUT_ERR = \"The Cloud API request timed out.\";\r\n// Currently cannot distinguish between \"no matching keyboard\" and other script-load errors.\r\nexport const CLOUD_MALFORMED_OBJECT_ERR = \"Could not find a keyboard with that ID.\";\r\n// Represents unspecified errors that occur when registering the results of a successful API call.\r\nexport const CLOUD_STUB_REGISTRATION_ERR = \"The Cloud API failed to find an appropriate keyboard.\";\r\n// Represents custom, specified KMW errors that occur when registering the results of a successful API call.\r\nexport const CLOUD_REGISTRATION_ERR = \"Error occurred while registering keyboards: \";\r\n\r\nexport const MISSING_KEYBOARD = function(kbdid: string) {\r\n return kbdid + ' keyboard not found.';\r\n}\r\n\r\ntype CloudAPIFont = {\r\n family: string,\r\n source: string | string[]\r\n}\r\n\r\ntype CloudQueryOptions = {\r\n context: 'keyboard' | 'language';\r\n keyboardid?: string,\r\n keyboardBaseUri?: string,\r\n fontBaseUri?: string\r\n}\r\n\r\ntype CloudKeyboardQueryResult = {\r\n /**\r\n * A 1D array is returned for keyboard-id based queries: `addKeyboards('sil_cameroon_qwerty')`\r\n * returns a single array with one entry.\r\n *\r\n * A 2D array is returned for language-code based keyboard queries: `addKeyboards('@fr,@en')`\r\n * returns two arrays of keyboards, one per language code.\r\n * - First index = fr\r\n * - Second = en\r\n */\r\n keyboard: KeyboardAPISpec | KeyboardAPISpec[] | KeyboardAPISpec[][],\r\n options: { context: 'keyboard' } & CloudQueryOptions,\r\n error?: string,\r\n timerid: string\r\n};\r\n\r\ntype CloudLanguagesQueryResult = {\r\n languages: LanguageAPIPropertySpec[],\r\n options: { context: 'language' } & CloudQueryOptions,\r\n error?: string,\r\n keyboardid?: string,\r\n timerid: string\r\n}\r\n\r\nexport type CloudQueryResult = CloudKeyboardQueryResult | CloudLanguagesQueryResult;\r\n\r\ninterface EventMap {\r\n 'unboundregister': (registration: ReturnType) => void\r\n}\r\n\r\nexport default class CloudQueryEngine extends EventEmitter {\r\n private cloudResolutionPromises: Record>> = {};\r\n\r\n private _languageListPromise: ManagedPromise;\r\n private languageFetchStarted: boolean = false;\r\n\r\n private requestEngine: CloudRequesterInterface;\r\n private pathConfig: PathConfiguration;\r\n\r\n constructor(requestEngine: CloudRequesterInterface, pathConfig: PathConfiguration) {\r\n super();\r\n\r\n this.requestEngine = requestEngine;\r\n this.pathConfig = pathConfig;\r\n\r\n this._languageListPromise = new ManagedPromise;\r\n }\r\n\r\n public get languageListPromise(): Promise {\r\n if(!this.languageFetchStarted) {\r\n this.languageFetchStarted = true;\r\n\r\n this.keymanCloudRequest('', true).catch((error) => {\r\n // If promise is not error, then...\r\n this.languageFetchStarted = false;\r\n\r\n // We should allow retries.\r\n this._languageListPromise.reject(error);\r\n this._languageListPromise = new ManagedPromise;\r\n });\r\n }\r\n\r\n return this._languageListPromise.corePromise;\r\n }\r\n\r\n /**\r\n * Request keyboard metadata from the Keyman Cloud keyboard metadata server\r\n *\r\n * @param {string} cmd command string\r\n * @param {boolean?} byLanguage if true, context=languages, else context=keyboards\r\n * @returns {Promise<(KeyboardStub[]>} Promise of added keyboard stubs\r\n **/\r\n keymanCloudRequest(cmd: string, byLanguage: true): Promise;\r\n keymanCloudRequest(cmd: string, byLanguage: false): Promise;\r\n keymanCloudRequest(cmd: string, byLanguage?: boolean): Promise | Promise {\r\n // Some basic support toward #5044, but definitely not a full solution toward it.\r\n // Wraps the cloud API keyboard-stub request in a Promise, allowing response on network\r\n // and/or parser errors. Also detects when `register` returns due to an error case that\r\n // does not throw errors. (There are a few such \"empty\" `return` statements there.)\r\n const URL='https://api.keyman.com/cloud/4.0/'\r\n + ((arguments.length > 1) && byLanguage ? 'languages' : 'keyboards');\r\n\r\n\r\n const queryConfig = '?jsonp=keyman.register&languageidtype=bcp47&version='+Version.CURRENT.toString();\r\n\r\n const query = URL + queryConfig + cmd;\r\n\r\n let { promise, queryId } = this.requestEngine.request(query);\r\n this.cloudResolutionPromises[queryId] = promise as any;\r\n\r\n promise.finally(() => {\r\n delete this.cloudResolutionPromises[queryId];\r\n });\r\n\r\n return promise.corePromise as any;\r\n }\r\n\r\n /**\r\n * Call back from cloud for adding keyboard metadata\r\n *\r\n * This function should be published to scripts returned from the cloud;\r\n * they will expect to call it as `keyman.register`.\r\n *\r\n * @param {Object} x metadata object\r\n **/\r\n registerFromCloud = (x: CloudQueryResult) => {\r\n const promiseid = x.timerid;\r\n\r\n let result: KeyboardStub[] | LanguageAPIPropertySpec[] | Error;\r\n try {\r\n result = this._registerCore(x);\r\n } catch(err) {\r\n result = new Error(CLOUD_REGISTRATION_ERR + err);\r\n }\r\n\r\n if(!promiseid) {\r\n this.emit('unboundregister', result);\r\n return;\r\n } else {\r\n const promise: ManagedPromise | ManagedPromise = this.cloudResolutionPromises[promiseid];\r\n\r\n if(!promise) {\r\n this.emit('unboundregister', result);\r\n return;\r\n } else {\r\n try {\r\n if(result instanceof Error) {\r\n promise.reject(result as Error);\r\n } else {\r\n promise.resolve(result as any);\r\n }\r\n } finally {\r\n delete this.cloudResolutionPromises[promiseid];\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Call back from cloud for adding keyboard metadata\r\n *\r\n * @param {Object} queryResult metadata object\r\n **/\r\n private _registerCore(queryResult: CloudQueryResult): KeyboardStub[] | LanguageAPIPropertySpec[] | Error { // TODO (#5044): should return heterogenous type; allow array of stubs.\r\n const options: CloudQueryOptions = queryResult.options;\r\n\r\n // Font path defined by cloud entry\r\n let fontPath=options['fontBaseUri'];\r\n\r\n // or overridden locally, in page source\r\n if(this.pathConfig.fonts != '') {\r\n fontPath=this.pathConfig.fonts;\r\n }\r\n else {\r\n // If there's no preconfigured option for font paths, uses the cloud's returned `fontPath` in its place.\r\n this.pathConfig.updateFontPath(fontPath);\r\n }\r\n\r\n // Indicate if unable to register keyboard\r\n if(typeof(queryResult.error) == 'string') {\r\n // Currently unreachable (24 May 2021 - API returns a 404; returned 'script' does not call register)\r\n var badName='';\r\n if(typeof(queryResult.options.keyboardid) == 'string') {\r\n let keyboardId = queryResult.options.keyboardid;\r\n badName = keyboardId.substring(0,1).toUpperCase() + keyboardId.substring(1);\r\n }\r\n\r\n return new Error(MISSING_KEYBOARD(badName));\r\n }\r\n\r\n // Ignore callback unless the context is defined\r\n if(typeof(options) == 'undefined' || typeof(options['context']) == 'undefined') {\r\n return new Error(CLOUD_MALFORMED_OBJECT_ERR);\r\n }\r\n\r\n // Register each keyboard for the specified language codes\r\n let stubs: KeyboardStub[] = [];\r\n\r\n if(options.context == 'keyboard') {\r\n let i, kp=(queryResult as CloudKeyboardQueryResult).keyboard;\r\n // Process array of keyboard definitions\r\n if(Array.isArray(kp)) {\r\n for(i=0; i stub.KLC == lgCode)];\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Build 362: addKeyboardArray() link to Cloud. One or more arguments may be used\r\n *\r\n * @param x keyboard name string or keyboard metadata JSON object\r\n * @returns resolved or rejected promise with merged array of stubs.\r\n */\r\n async fetchCloudStubs(cloudList: string[]): Promise<(KeyboardStub|ErrorStub)[]> {\r\n // // Ensure keymanweb is initialized before continuing to add keyboards\r\n // if(!this.keymanweb.initialized) {\r\n // await this.deferment;\r\n // }\r\n\r\n if(cloudList.length == 0) {\r\n return Promise.resolve([]);\r\n }\r\n\r\n // Update the keyboard metadata list from keyman.com - build the command\r\n let cmd='&keyboardid=';\r\n let comma = '';\r\n for(let i=0; i {\r\n // Internal, undocumented use-case of `keyman.register`: precached keyboard loading\r\n // Other uses may trigger errors, especially if there's a type-structure mismatch.\r\n // Those errors should not be handled here; let them surface.\r\n if(Array.isArray(registration)) {\r\n registration.forEach((entry) => {\r\n this.cache.addStub(entry);\r\n });\r\n }\r\n });\r\n }\r\n\r\n addKeyboardArray(x: (string|RawKeyboardMetadata)[]): Promise<(KeyboardStub | ErrorStub)[]> {\r\n let completeStubs: KeyboardStub[] = [];\r\n let incompleteStubs: KeyboardStub[] = [];\r\n let stubs: KeyboardStub[] = [];\r\n let identifiers: string[] = [];\r\n let errorStubs: ErrorStub[] = [];\r\n\r\n // #region Parameter preprocessing: is incoming data already 'complete', or do we need to fetch the 'complete' version?\r\n\r\n for(let entry of x) {\r\n if(typeof entry == 'string' && entry.length > 0) {\r\n identifiers.push(entry);\r\n } else { // is some sort of object.\r\n if(entry['KI'] || entry['KL'] || entry['KLC'] || entry['KFont'] || entry['KOskFont']) {\r\n stubs.push(new KeyboardStub(entry as RawKeyboardStub));\r\n } else {\r\n let apiSpecEntry = entry as KeyboardAPISpec;\r\n if(typeof(apiSpecEntry.language) != \"undefined\") {\r\n console.warn(\"The 'language' property for keyboard stubs has been deprecated. Please use the 'languages' property instead.\");\r\n }\r\n apiSpecEntry.languages ||= apiSpecEntry.language;\r\n\r\n if(typeof apiSpecEntry.languages === 'undefined') {\r\n let msg = 'To use keyboard \\'' + apiSpecEntry.id + '\\', you must specify languages.';\r\n errorStubs.push(convertToErrorStub(apiSpecEntry, msg));\r\n } else if(Array.isArray(apiSpecEntry.languages)) {\r\n let splitStubs = KeyboardStub.toStubs(apiSpecEntry, this.pathConfig.keyboards, this.pathConfig.fonts);\r\n for(let stub of splitStubs) {\r\n if(stub instanceof KeyboardStub) {\r\n stubs.push(stub);\r\n } else {\r\n errorStubs.push(stub);\r\n }\r\n }\r\n } else { // is a single-language entry.\r\n const singleLangEntry = apiSpecEntry as KeyboardAPIPropertySpec & { filename: string };\r\n stubs.push(new KeyboardStub(singleLangEntry, this.pathConfig.keyboards, this.pathConfig.fonts));\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Next pass: determine which stubs are fully-formed and which are not.\r\n for(let stub of stubs) {\r\n if(stub.KF) {\r\n let err = stub.validateForCustomKeyboard();\r\n if(err) {\r\n errorStubs.push(convertToErrorStub(stub, err));\r\n } else {\r\n completeStubs.push(stub); // completes are directly added (if possible without error)\r\n }\r\n } else {\r\n incompleteStubs.push(stub); // incompletes are only used to build a query & are not merged back later.\r\n }\r\n }\r\n\r\n // #endregion\r\n\r\n // After that, request stubs that aren't fully-formed / are just requested by string.\r\n // Verify that we have at least a keyboard ID or a language code.\r\n let cloudList: CloudRequestEntry[] = [];\r\n for(let incomplete of incompleteStubs) {\r\n if(!incomplete.KI && !incomplete.KLC) {\r\n errorStubs.push(convertToErrorStub(incomplete, \"Cannot fetch keyboard information without a keyboard ID or language code.\"));\r\n continue;\r\n }\r\n\r\n // Requests not of string form never specify a specific version.\r\n // If an 'incomplete stub', we may have prefixed the keyboard ID - undo that!\r\n const querySpec = toQuerySpecs(unprefixed(incomplete.id), incomplete.langId);\r\n if(isUniqueRequest(this.cache, cloudList, querySpec)) {\r\n cloudList.push(querySpec);\r\n }\r\n }\r\n\r\n // Double-check the incoming string-based queries, too!\r\n for(let identifier of identifiers) {\r\n const pList=identifier.split('@');\r\n let lList=[''];\r\n if(pList[0].toLowerCase() == 'english') {\r\n pList[0] = 'us';\r\n }\r\n\r\n if(pList.length > 1) {\r\n lList=pList[1].split(',');\r\n }\r\n\r\n for(let j=0; j 0 && lList[j] == '') { // for j==0 => '', no language was specified.\r\n continue;\r\n }\r\n\r\n const querySpec = toQuerySpecs(pList[0], lList[j], pList[2]);\r\n if(isUniqueRequest(this.cache, cloudList, querySpec)) {\r\n cloudList.push(querySpec);\r\n }\r\n }\r\n }\r\n\r\n // Go ahead and register 'complete' stubs.\r\n completeStubs.forEach((stub) => this.cache.addStub(stub));\r\n\r\n // After that, note that we do merge non-fully-formed stubs with returned stubs that match?\r\n let resultPromise = this.cloudQueryEngine.fetchCloudStubs(cloudList.map((spec) => spec.toString()));\r\n return resultPromise.then((queryResults) => {\r\n for(let result of queryResults) {\r\n if(result instanceof KeyboardStub) {\r\n // Register the newly-complete stub.\r\n this.cache.addStub(result);\r\n completeStubs.push(result);\r\n } else {\r\n errorStubs.push(result);\r\n }\r\n }\r\n\r\n return [].concat(errorStubs).concat(completeStubs);\r\n });\r\n }\r\n\r\n async addLanguageKeyboards(languages: string[]): Promise<(ErrorStub | KeyboardStub)[]> {\r\n // Covers the 'add a keyboard by language name' angle.\r\n let errorStubs: ErrorStub[] = [];\r\n let fetchedLanguageList: LanguageAPIPropertySpec[] = [];\r\n\r\n try {\r\n fetchedLanguageList = await this.cloudQueryEngine.languageListPromise;\r\n } catch (error) {\r\n console.error(error);\r\n errorStubs.push({error: error});\r\n return errorStubs;\r\n }\r\n\r\n // Defer registering keyboards by language until the language list has been loaded\r\n const languageList = fetchedLanguageList;\r\n\r\n // Identify and register each keyboard by language name\r\n let cmd = '';\r\n for(let i=0; i {\r\n console.error(err);\r\n let stub: ErrorStub = {error: err};\r\n errorStubs.push(stub);\r\n return Promise.reject(errorStubs);\r\n });\r\n }\r\n\r\n async fetchCloudCatalog() {\r\n try {\r\n const stubs = await this.cloudQueryEngine.keymanCloudRequest('', false);\r\n stubs.forEach((stub) => this.cache.addStub(stub));\r\n return stubs;\r\n } catch(error) {\r\n return Promise.reject([{error: error}]);\r\n }\r\n }\r\n\r\n /**\r\n * Display warning if language name unavailable to add keyboard\r\n * @param {string} languageName\r\n * @returns string of Error message\r\n */\r\n private alertLanguageUnavailable(languageName: string): string {\r\n let msg = 'No keyboards are available for '+ languageName + '. '\r\n +'Does it have another language name?';\r\n\r\n // TODO: hooks for internal alerts!\r\n // this.keymanweb.util.internalAlert(msg);\r\n return msg;\r\n }\r\n}", + "import { ModelSpec } from \"@keymanapp/input-processor\";\r\n\r\nexport default class ModelManager {\r\n // Tracks registered models by ID.\r\n private registeredModels: {[id: string]: ModelSpec} = {};\r\n\r\n // Allows for easy model lookup by language code; useful when switching keyboards.\r\n languageModelMap: {[language:string]: ModelSpec} = {};\r\n\r\n modelForLanguage(lgCode: string) {\r\n return this.languageModelMap[lgCode];\r\n }\r\n\r\n // Accessible publicly as keyman.modelManager.register(model: ModelSpec)\r\n register(model: ModelSpec): void {\r\n // Forcibly lowercase the model ID before proceeding.\r\n model.id = model.id.toLowerCase();\r\n\r\n if(JSON.stringify(model) == JSON.stringify(this.registeredModels[model.id])) {\r\n // We are already registered, let's not go through and re-register\r\n // because we'll already have the correct model active\r\n return;\r\n }\r\n this.registeredModels[model.id] = model;\r\n\r\n // Register the model for each targeted language code variant.\r\n let mm = this;\r\n model.languages.forEach(function(code: string) {\r\n // Prevent null / undefined codes; they're invalid.\r\n if(!code) {\r\n console.warn(\"Null / undefined language codes are not permitted for registration.\");\r\n return;\r\n }\r\n\r\n mm.languageModelMap[code] = model;\r\n });\r\n }\r\n\r\n unregister(modelId: string): ModelSpec {\r\n let model: ModelSpec;\r\n\r\n modelId = modelId.toLowerCase();\r\n\r\n // Remove the model from the id-lookup associative array.\r\n if(this.registeredModels[modelId]) {\r\n model = this.registeredModels[modelId];\r\n delete this.registeredModels[modelId];\r\n } else {\r\n return;\r\n }\r\n\r\n // Ensure the model is deregistered for each targeted language code variant.\r\n let mm = this;\r\n model.languages.forEach(function(code: string) {\r\n if(mm.languageModelMap[code].id == modelId) {\r\n delete mm.languageModelMap[code];\r\n }\r\n });\r\n\r\n return model;\r\n }\r\n\r\n isRegistered(model: ModelSpec): boolean {\r\n return !! this.registeredModels[model.id.toLowerCase()];\r\n }\r\n}\r\n", + "/**\r\n * Function arrayFromNodeList\r\n * Scope Public\r\n * @param {Object} nl a node list, as returned from getElementsBy_____ methods.\r\n * Description Transforms a node list into an array. *\r\n * @return {Array}\r\n */\r\nexport function arrayFromNodeList(nl: NodeList|HTMLCollectionOf): HTMLElement[] {\r\n let res: (HTMLElement)[] = [];\r\n for(let i=0; i < nl.length; i++) {\r\n // Typing says we could get Node instances; it's up to use to use this method responsibly.\r\n res.push(nl[i] as HTMLElement);\r\n }\r\n return res;\r\n}", + "// Found a bit of magic formatting that allows dynamic return typing for a specified element tag!\r\nexport default function createUnselectableElement(nodeName:E) {\r\n const e = document.createElement(nodeName);\r\n\r\n e.style.userSelect=\"none\";\r\n e.style.webkitUserSelect=\"none\";\r\n e.style['MozUserSelect'] = \"none\";\r\n return e;\r\n}", + "import { DeviceSpec } from '@keymanapp/web-utils';\r\nimport { type InternalKeyboardFont as KeyboardFont } from '@keymanapp/keyboard-processor';\r\n\r\ntype FontFamilyStyleMap = {[family: string]: HTMLStyleElement};\r\n\r\nexport class StylesheetManager {\r\n private fontStyleDefinitions: { [os: string]: FontFamilyStyleMap} = {};\r\n private linkedSheets: HTMLStyleElement[] = [];\r\n private doCacheBusting: boolean;\r\n\r\n public readonly linkNode: Node;\r\n\r\n public constructor(linkNode?: Node, doCacheBusting?: boolean) {\r\n if(!linkNode) {\r\n let _ElemHead=document.getElementsByTagName('HEAD');\r\n if(_ElemHead.length > 0) {\r\n linkNode = _ElemHead[0];\r\n } else {\r\n linkNode = document.body; // Won't work on [old?] Chrome, ah well\r\n }\r\n }\r\n this.linkNode = linkNode;\r\n this.doCacheBusting = doCacheBusting || false;\r\n }\r\n\r\n linkStylesheet(sheet: HTMLStyleElement) {\r\n this.linkedSheets.push(sheet);\r\n this.linkNode.appendChild(sheet);\r\n }\r\n\r\n /**\r\n * Build a stylesheet with a font-face CSS descriptor for the embedded font appropriate\r\n * for the browser being used\r\n *\r\n * @param {Object} fd keymanweb font descriptor (internal format; should be preprocessed)\r\n * @param {string} fontPathRoot Should correspond to `this.keyman.options['fonts']`\r\n **/\r\n addStyleSheetForFont(fd: KeyboardFont, fontPathRoot: string, os?: DeviceSpec.OperatingSystem) {\r\n // Test if a valid font descriptor\r\n if(!fd) {\r\n return;\r\n }\r\n\r\n if(typeof(fd.files) == 'undefined') {\r\n return;\r\n }\r\n\r\n const fontKey = fd.family;\r\n\r\n let i, ttf='', woff='', eot='', svg='', fList=[];\r\n\r\n // TODO: 22 Aug 2014: check that font path passed from cloud is actually used!\r\n\r\n if(!os) {\r\n os = DeviceSpec.OperatingSystem.Other; // as a fallback option.\r\n }\r\n\r\n // Do not add a new font-face style sheet if already added for this font\r\n const fontStyleMap = this.fontStyleDefinitions[os] = this.fontStyleDefinitions[os] || {};\r\n\r\n if(fontStyleMap[fontKey]) {\r\n const sheet = fontStyleMap[fontKey];\r\n\r\n if(!sheet.parentNode) {\r\n this.linkStylesheet(sheet);\r\n }\r\n return;\r\n }\r\n\r\n if(typeof(fd.files) == 'string') {\r\n fList[0]=fd.files;\r\n } else {\r\n fList=fd.files;\r\n }\r\n\r\n for(i=0;i 0) ttf=fList[i];\r\n if(fList[i].toLowerCase().indexOf('.ttf') > 0) ttf=fList[i];\r\n if(fList[i].toLowerCase().indexOf('.woff') > 0) woff=fList[i];\r\n if(fList[i].toLowerCase().indexOf('.eot') > 0) eot=fList[i];\r\n if(fList[i].toLowerCase().indexOf('.svg') > 0) svg=fList[i];\r\n }\r\n\r\n // Font path qualified to support page-relative fonts (build 347)\r\n if(ttf != '' && (ttf.indexOf('/') < 0)) {\r\n ttf = fontPathRoot+ttf;\r\n }\r\n\r\n if(woff != '' && (woff.indexOf('/') < 0)) {\r\n woff = fontPathRoot+woff;\r\n }\r\n\r\n if(eot != '' && (eot.indexOf('/') < 0)) {\r\n eot = fontPathRoot+eot;\r\n }\r\n\r\n if(svg != '' && (svg.indexOf('/') < 0)) {\r\n svg = fontPathRoot+svg;\r\n }\r\n\r\n // Build the font-face definition according to the browser being used\r\n var s='@font-face {\\nfont-family:'\r\n + fd.family + ';\\nfont-style:normal;\\nfont-weight:normal;\\n';\r\n\r\n // Build the font source string according to the browser,\r\n // but return without adding the style sheet if the required font type is unavailable\r\n\r\n // Modern browsers: use WOFF, TTF and fallback finally to SVG. Don't provide EOT\r\n if(os == DeviceSpec.OperatingSystem.iOS) {\r\n if(ttf != '') {\r\n if(this.doCacheBusting) {\r\n ttf = this.cacheBust(ttf);\r\n }\r\n s=s+'src:url(\\''+ttf+'\\') format(\\'truetype\\');';\r\n } else {\r\n return null;\r\n }\r\n } else {\r\n var s0 = [];\r\n\r\n if(os == DeviceSpec.OperatingSystem.Android) {\r\n // Android 4.2 and 4.3 have bugs in their rendering for some scripts\r\n // with embedded ttf or woff. svg mostly works so is a better initial\r\n // choice on the Android browser.\r\n if(svg != '') {\r\n s0.push(\"url('\"+svg+\"') format('svg')\");\r\n }\r\n\r\n if(woff != '') {\r\n s0.push(\"url('\"+woff+\"') format('woff')\");\r\n }\r\n\r\n if(ttf != '') {\r\n s0.push(\"url('\"+ttf+\"') format('truetype')\");\r\n }\r\n } else {\r\n if(woff != '') {\r\n s0.push(\"url('\"+woff+\"') format('woff')\");\r\n }\r\n\r\n if(ttf != '') {\r\n s0.push(\"url('\"+ttf+\"') format('truetype')\");\r\n }\r\n\r\n if(svg != '') {\r\n s0.push(\"url('\"+svg+\"') format('svg')\");\r\n }\r\n }\r\n\r\n if(s0.length == 0) {\r\n return null;\r\n }\r\n\r\n s += 'src:'+s0.join(',')+';';\r\n }\r\n\r\n s=s+'\\n}\\n';\r\n\r\n const sheet = createStyleSheet(s);\r\n fontStyleMap[fontKey] = sheet;\r\n\r\n this.linkStylesheet(sheet);\r\n\r\n return sheet;\r\n }\r\n\r\n private cacheBust(uri: string) {\r\n // Our WebView version directly sets the keyboard path, and it may replace the file\r\n // after KMW has loaded. We need cache-busting to prevent the new version from\r\n // being ignored.\r\n return uri + \"?v=\" + (new Date()).getTime(); /*cache buster*/\r\n }\r\n\r\n /**\r\n * Add a reference to an external stylesheet file\r\n *\r\n * @param {string} href path to stylesheet file\r\n */\r\n linkExternalSheet(href: string): void {\r\n try {\r\n if(document.querySelector(\"link[href=\"+JSON.stringify(href)+\"]\") != null) {\r\n // We've already linked this stylesheet, don't do it again\r\n return;\r\n }\r\n } catch(e) {\r\n // We've built an invalid href, somehow?\r\n return;\r\n }\r\n\r\n const linkElement=document.createElement('link');\r\n linkElement.type='text/css';\r\n linkElement.rel='stylesheet';\r\n linkElement.href=href;\r\n\r\n this.linkStylesheet(linkElement);\r\n }\r\n\r\n public unlink(stylesheet: HTMLStyleElement) {\r\n const index = this.linkedSheets.indexOf(stylesheet);\r\n if(index > -1) {\r\n this.linkedSheets.splice(index, 1);\r\n stylesheet.parentNode.removeChild(stylesheet);\r\n return true;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n public unlinkAll() {\r\n for(let sheet of this.linkedSheets) {\r\n if(sheet.parentNode) {\r\n sheet.parentNode.removeChild(sheet);\r\n }\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Add a stylesheet to a page programmatically, for use by the OSK, the UI or the page creator\r\n *\r\n * @param {string} s style string\r\n * @return {Object} returns the object reference\r\n **/\r\nexport function createStyleSheet(styleString: string): HTMLStyleElement {\r\n var _ElemStyle: HTMLStyleElement = document.createElement<'style'>('style');\r\n\r\n _ElemStyle.type = 'text/css';\r\n _ElemStyle.appendChild(document.createTextNode(styleString));\r\n\r\n return _ElemStyle;\r\n}", + "/**\r\n * Get orientation of tablet or phone display\r\n *\r\n * @return {boolean}\r\n */\r\nexport default function landscapeView(): boolean\t{ // new for I3363 (Build 301)\r\n var orientation: number;\r\n\r\n // Assume portrait mode if orientation undefined\r\n if(typeof window.orientation != 'undefined') { // Used by iOS Safari\r\n // Else landscape for +/-90, portrait for 0, +/-180\r\n orientation = window.orientation as number;\r\n } else if(typeof window.screen.orientation != 'undefined') { // Used by Firefox, Chrome\r\n orientation = window.screen.orientation.angle;\r\n }\r\n\r\n if(orientation !== undefined) {\r\n return (Math.abs(orientation/90) == 1);\r\n } else {\r\n return false;\r\n }\r\n}", + "type DecodedCookieFieldValue = string | number | boolean;\r\n\r\ntype FilteredRecordEncoder = (value: DecodedCookieFieldValue, key: string) => string;\r\ntype FilteredRecordDecoder = (value: string, key: string) => DecodedCookieFieldValue;\r\nconst no_change = (val: string) => val as string;\r\n\r\nexport default class CookieSerializer> {\r\n readonly name: string;\r\n\r\n constructor(name: string) {\r\n this.name = name;\r\n }\r\n\r\n load(decoder?: FilteredRecordDecoder): Type {\r\n return this.loadCookie(this.name, decoder || no_change) as Type;\r\n }\r\n\r\n save(cookie: Type, encoder?: FilteredRecordEncoder) {\r\n this.saveCookie(this.name, cookie, encoder || no_change);\r\n }\r\n\r\n /**\r\n * Document cookie parsing for use by kernel, OSK, UI etc.\r\n *\r\n * @return {Object} array of names and strings\r\n */\r\n private _loadRawCookies(): Record {\r\n let v: Record = {};\r\n if(typeof(document.cookie) != 'undefined' && document.cookie != '') {\r\n let c = document.cookie.split(/;\\s*/);\r\n for(let i = 0; i < c.length; i++) {\r\n let d = c[i].split('=');\r\n if(d.length == 2) {\r\n v[d[0]] = d[1];\r\n }\r\n }\r\n }\r\n\r\n return v;\r\n }\r\n\r\n /**\r\n * Document cookie parsing for use by kernel, OSK, UI etc.\r\n *\r\n * @param {string} cookieName cookie name\r\n * @return {Object} array of variables and values\r\n */\r\n private loadCookie(cookieName: string, decoder: FilteredRecordDecoder): Record {\r\n let cookie: Record = {};\r\n let allCookies = this._loadRawCookies();\r\n const encodedCookie = allCookies[cookieName];\r\n\r\n if(encodedCookie) {\r\n let rawDecode = decodeURIComponent(encodedCookie).split(';');\r\n for(let i=0; i 1) {\r\n const [key, value] = record;\r\n // key, value\r\n cookie[key] = decoder(value, key);\r\n } else {\r\n // key, \r\n cookie[record[0]] = '';\r\n }\r\n }\r\n }\r\n return cookie;\r\n }\r\n\r\n /**\r\n * Standard cookie saving for use by kernel, OSK, UI etc.\r\n *\r\n * @param {string} cookieName name of cookie\r\n * @param {Object} cookieValueMap object with array of named arguments and values\r\n */\r\n private saveCookie(cookieName: string, cookieValueMap: Record, encoder: FilteredRecordEncoder) {\r\n let serialization='';\r\n for(let key in cookieValueMap) {\r\n serialization += key + '=' + encoder(cookieValueMap[key], key) + \";\";\r\n }\r\n\r\n let d = new Date(new Date().valueOf() + 1000 * 60 * 60 * 24 * 30).toUTCString();\r\n let cookieConfig = ' path=/; expires=' + d; //Fri, 31 Dec 2099 23:59:59 GMT;';\r\n document.cookie = `${cookieName}=${encodeURIComponent(serialization)}; ${cookieConfig}`;\r\n }\r\n}", + "/**\r\n * Function getAbsoluteX\r\n * Scope Public\r\n * @param {Object} Pobj HTML element\r\n * @return {number}\r\n * Description Returns x-coordinate of Pobj element absolute position with respect to page\r\n */\r\nexport function getAbsoluteX(Pobj: HTMLElement): number { // I1476 - Handle SELECT overlapping END\r\n var Lobj: HTMLElement\r\n\r\n if(!Pobj) {\r\n return 0;\r\n }\r\n\r\n var Lcurleft = Pobj.offsetLeft ? Pobj.offsetLeft : 0;\r\n Lobj = Pobj; \t// I2404 - Support for IFRAMEs\r\n\r\n if (Lobj.offsetParent) {\r\n while (Lobj.offsetParent) {\r\n Lobj = Lobj.offsetParent as HTMLElement;\r\n Lcurleft += Lobj.offsetLeft;\r\n }\r\n\r\n // On mobile devices, the OSK uses 'fixed' - this requires some extra offset work to handle.\r\n let Ldoc = Lobj.ownerDocument;\r\n if(Lobj.style.position == 'fixed' && Ldoc && Ldoc.scrollingElement) {\r\n Lcurleft += Ldoc.scrollingElement.scrollLeft;\r\n }\r\n }\r\n // Correct position if element is within a frame (but not if the controller is in document within that frame)\r\n // We used to reference a KMW state variable `this.keyman._MasterDocument`, but it was only ever set to `window.document`.\r\n if(Lobj && Lobj.ownerDocument && (Pobj.ownerDocument != window.document)) {\r\n var Ldoc=Lobj.ownerDocument; // I2404 - Support for IFRAMEs\r\n\r\n if(Ldoc && Ldoc.defaultView && Ldoc.defaultView.frameElement) {\r\n return Lcurleft + getAbsoluteX(Ldoc.defaultView.frameElement) - Ldoc.documentElement.scrollLeft;\r\n }\r\n }\r\n return Lcurleft;\r\n}\r\n\r\n/**\r\n * Function getAbsoluteY\r\n * Scope Public\r\n * @param {Object} Pobj HTML element\r\n * @return {number}\r\n * Description Returns y-coordinate of Pobj element absolute position with respect to page\r\n */\r\nexport function getAbsoluteY(Pobj: HTMLElement): number {\r\n var Lobj: HTMLElement\r\n\r\n if(!Pobj) {\r\n return 0;\r\n }\r\n var Lcurtop = Pobj.offsetTop ? Pobj.offsetTop : 0;\r\n Lobj = Pobj; // I2404 - Support for IFRAMEs\r\n\r\n if (Lobj.ownerDocument && Lobj instanceof Lobj.ownerDocument.defaultView.HTMLElement) {\r\n while (Lobj.offsetParent) {\r\n Lobj = Lobj.offsetParent as HTMLElement;\r\n Lcurtop += Lobj.offsetTop;\r\n }\r\n\r\n // On mobile devices, the OSK uses 'fixed' - this requires some extra offset work to handle.\r\n let Ldoc = Lobj.ownerDocument;\r\n if(Lobj.style.position == 'fixed' && Ldoc && Ldoc.scrollingElement) {\r\n Lcurtop += Ldoc.scrollingElement.scrollTop;\r\n }\r\n }\r\n\r\n // Correct position if element is within a frame (but not if the controller is in document within that frame)\r\n // We used to reference a KMW state variable `this.keyman._MasterDocument`, but it was only ever set to `window.document`.\r\n if(Lobj && Lobj.ownerDocument && (Pobj.ownerDocument != window.document)) {\r\n var Ldoc=Lobj.ownerDocument; // I2404 - Support for IFRAMEs\r\n\r\n if(Ldoc && Ldoc.defaultView && Ldoc.defaultView.frameElement) {\r\n return Lcurtop + getAbsoluteY(Ldoc.defaultView.frameElement);\r\n }\r\n }\r\n return Lcurtop;\r\n}", + "import { VariableStore, VariableStoreSerializer } from \"@keymanapp/keyboard-processor\";\r\nimport { CookieSerializer } from \"keyman/engine/dom-utils\";\r\n\r\n// While there's little reason we couldn't store all of a keyboard's store values within\r\n// the same cookie... that's not what we had implemented in the last pre-es-module version\r\n// of KeymanWeb. We're keeping this transformation _very_ straightforward.\r\n//\r\n// Also of note: there's nothing we can do to allow TS to provide type-checking of\r\n// dynamic property names; they'd have to be known at compile time to facilitate\r\n// strict type checking.\r\nclass VarStoreSerializer extends CookieSerializer {\r\n constructor(keyboardID: string, storeName: string) {\r\n super(`KeymanWeb_${keyboardID}_Option_${storeName}`);\r\n }\r\n\r\n load() {\r\n return super.load(decodeURIComponent);\r\n }\r\n\r\n save(storeMap: VariableStore) {\r\n super.save(storeMap, encodeURIComponent);\r\n }\r\n}\r\n\r\nexport class VariableStoreCookieSerializer implements VariableStoreSerializer {\r\n loadStore(keyboardID: string, storeName: string): VariableStore {\r\n const storeCookieSerializer = new VarStoreSerializer(keyboardID, storeName);\r\n return storeCookieSerializer.load();\r\n }\r\n\r\n saveStore(keyboardID: string, storeName: string, storeMap: VariableStore) {\r\n const storeCookieSerializer = new VarStoreSerializer(keyboardID, storeName);\r\n storeCookieSerializer.save(storeMap);\r\n }\r\n}", + "import {\r\n KeyboardInterface as KeyboardInterfaceBase,\r\n} from \"@keymanapp/keyboard-processor\";\r\nimport { KeyboardStub, RawKeyboardStub, toUnprefixedKeyboardId as unprefixed } from 'keyman/engine/package-cache';\r\n\r\nimport { ContextManagerBase } from './contextManagerBase.js';\r\nimport { VariableStoreCookieSerializer } from \"./variableStoreCookieSerializer.js\";\r\nimport KeymanEngine from \"./keymanEngine.js\";\r\nimport { EngineConfiguration } from \"./engineConfiguration.js\";\r\n\r\nexport default class KeyboardInterface> extends KeyboardInterfaceBase {\r\n protected readonly engine: KeymanEngine;\r\n private stubNamespacer?: (stub: RawKeyboardStub) => void;\r\n\r\n constructor(\r\n _jsGlobal: any,\r\n engine: KeymanEngine,\r\n stubNamespacer?: (stub: RawKeyboardStub) => void\r\n ) {\r\n super(_jsGlobal, engine, new VariableStoreCookieSerializer());\r\n this.engine = engine;\r\n this.stubNamespacer = stubNamespacer;\r\n }\r\n\r\n // Preserves a keyboard's ID, even if namespaced, via script tag tagging.\r\n preserveID(Pk: any /** a `Keyboard`'s `scriptObject` entry */) {\r\n var trueID;\r\n\r\n // Find the currently-executing script tag; KR is called directly from each keyboard's definition script.\r\n if(document.currentScript) {\r\n trueID = document.currentScript.id;\r\n } else {\r\n var scripts = document.getElementsByTagName('script');\r\n var currentScript = scripts[scripts.length-1];\r\n\r\n trueID = currentScript.id;\r\n }\r\n\r\n // Final check that the script tag is valid and appropriate for the loading keyboard.\r\n if(!trueID) {\r\n return;\r\n } else if(trueID.indexOf(unprefixed(Pk['KI'])) != -1) {\r\n Pk['KI'] = trueID; // Take the script's version of the ID, which may include package namespacing.\r\n } else {\r\n console.error(\"Error when registering keyboard: current SCRIPT tag's ID does not match!\");\r\n }\r\n }\r\n\r\n registerKeyboard(Pk): void {\r\n // Among other things, sets Pk as a newly-active Keyboard.\r\n super.registerKeyboard(Pk);\r\n const registeredKeyboard = this.loadedKeyboard;\r\n\r\n this.preserveID(Pk);\r\n\r\n this.engine.config.deferForInitialization.then(() => {\r\n if(!this.engine.keyboardRequisitioner.cache.isFetchingKeyboard(registeredKeyboard.id)) {\r\n // Deliberate keyboard pre-loading via direct script-tag link on the page.\r\n // Just load the keyboard and reset the harness's keyboard-receiver field.\r\n this.engine.keyboardRequisitioner.cache.addKeyboard(registeredKeyboard);\r\n this.loadedKeyboard = null;\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Add the basic keyboard parameters (keyboard stub) to the array of keyboard stubs\r\n * If no language code is specified in a keyboard it cannot be registered,\r\n * and a keyboard stub must be registered before the keyboard is loaded\r\n * for the keyboard to be usable.\r\n *\r\n * @param {Object} Pstub Keyboard stub object\r\n * @return {?number} 1 if already registered, else null\r\n */\r\n registerStub(Pstub: RawKeyboardStub): number {\r\n if(this.stubNamespacer) {\r\n this.stubNamespacer(Pstub);\r\n }\r\n\r\n // This is where app-hosted KeymanWeb receives pre-formed stubs.\r\n // They're specified in the \"internal\" format (KI, KN, KLC...)\r\n // (SHIFT-CTRL-F @ repo-level for the mobile apps: `setKeymanLanguage`)\r\n // Keyman Developer may also use this method directly for its test-host page.\r\n //\r\n // It may also be used by documented legacy API:\r\n // https://help.keyman.com/DEVELOPER/ENGINE/WEB/2.0/guide/examples/manual-control\r\n // (See: referenced laokeys_load.js)\r\n //\r\n // The mobile apps typically have fully-preconfigured paths, but Developer's\r\n // test-host page does not.\r\n const pathConfig = this.engine.config.paths;\r\n const stub = new KeyboardStub(Pstub, pathConfig.keyboards, pathConfig.fonts);\r\n if(this.engine.keyboardRequisitioner?.cache.findMatchingStub(stub)) {\r\n return 1;\r\n }\r\n\r\n if(!this.engine.config.deferForInitialization.hasFinalized) {\r\n this.engine.config.deferForInitialization.then(() => this.engine.keyboardRequisitioner.cache.addStub(stub));\r\n } else {\r\n this.engine.keyboardRequisitioner.cache.addStub(stub);\r\n }\r\n\r\n return null;\r\n }\r\n\r\n insertText = (Ptext: string, PdeadKey:number): void => {\r\n this.resetContextCache();\r\n // As this function isn't provided a handle to an active outputTarget, we rely on\r\n // the context manager to resolve said issue.\r\n this.engine.contextManager.insertText(this, Ptext, PdeadKey);\r\n }\r\n\r\n // Short-hand name: necessary to do it this way due to assignment style.\r\n KT = this.insertText;\r\n}\r\n\r\n(function() {\r\n KeyboardInterface.__publishShorthandAPI();\r\n}());", + "// Enables DOM types, but just for this one module.\r\n\r\n///\r\n\r\nimport { Keyboard, KeyboardHarness, KeyboardLoaderBase, KeyboardLoadErrorBuilder, MinimalKeymanGlobal } from '@keymanapp/keyboard-processor';\r\n\r\nimport { ManagedPromise } from '@keymanapp/web-utils';\r\n\r\nexport class DOMKeyboardLoader extends KeyboardLoaderBase {\r\n public readonly element: HTMLIFrameElement;\r\n private readonly performCacheBusting: boolean;\r\n\r\n constructor()\r\n constructor(harness: KeyboardHarness);\r\n constructor(harness: KeyboardHarness, cacheBust?: boolean)\r\n constructor(harness?: KeyboardHarness, cacheBust?: boolean) {\r\n if(harness && harness._jsGlobal != window) {\r\n // Copy the String typing over; preserve string extensions!\r\n harness._jsGlobal['String'] = window['String'];\r\n }\r\n\r\n if(!harness) {\r\n super(new KeyboardHarness(window, MinimalKeymanGlobal));\r\n } else {\r\n super(harness);\r\n }\r\n\r\n this.performCacheBusting = cacheBust || false;\r\n }\r\n\r\n protected loadKeyboardInternal(\r\n uri: string,\r\n errorBuilder: KeyboardLoadErrorBuilder,\r\n id?: string\r\n ): Promise {\r\n const promise = new ManagedPromise();\r\n\r\n if(this.performCacheBusting) {\r\n uri = this.cacheBust(uri);\r\n }\r\n\r\n try {\r\n const document = this.harness._jsGlobal.document;\r\n const script = document.createElement('script');\r\n if(id) {\r\n script.id = id;\r\n }\r\n document.head.appendChild(script);\r\n script.onerror = (err) => {\r\n promise.reject(errorBuilder.missingError(err));\r\n }\r\n script.onload = () => {\r\n if(this.harness.loadedKeyboard) {\r\n const keyboard = this.harness.loadedKeyboard;\r\n this.harness.loadedKeyboard = null;\r\n promise.resolve(keyboard);\r\n } else {\r\n promise.reject(errorBuilder.scriptError());\r\n }\r\n }\r\n\r\n // On the oldest mobile devices we support, Promise.finally may not actually exist.\r\n // Fortunately... it's not that hard of an issue to work around.\r\n // Note: es6-shim doesn't polyfill Promise.finally!\r\n promise.then(() => {\r\n // It is safe to remove the script once it has been run (https://stackoverflow.com/a/37393041)\r\n script.remove();\r\n }).catch(() => {\r\n script.remove();\r\n });\r\n\r\n // Now that EVERYTHING ELSE is ready, establish the link to the keyboard's script.\r\n script.src = uri;\r\n } catch (err) {\r\n return Promise.reject(err);\r\n }\r\n\r\n return promise.corePromise;\r\n }\r\n\r\n private cacheBust(uri: string) {\r\n // Our WebView version directly sets the keyboard path, and it may replace the file\r\n // after KMW has loaded. We need cache-busting to prevent the new version from\r\n // being ignored.\r\n return uri + \"?v=\" + (new Date()).getTime(); /*cache buster*/\r\n }\r\n}", + "import { Mock } from \"@keymanapp/keyboard-processor\";\r\n\r\nexport default class ContextWindow implements Context {\r\n // Used to limit the range of context replicated for use of keyboard rules within\r\n // the engine, as used for fat-finger prep / `Alternate` generation.\r\n public static readonly ENGINE_RULE_WINDOW: Configuration = {\r\n leftContextCodePoints: 64,\r\n rightContextCodePoints: 32\r\n };\r\n\r\n left: string;\r\n right?: string;\r\n\r\n startOfBuffer: boolean;\r\n endOfBuffer: boolean;\r\n\r\n casingForm?: CasingForm;\r\n\r\n constructor(mock: Mock, config: Configuration, layerId: string) {\r\n this.left = mock.getTextBeforeCaret();\r\n this.startOfBuffer = this.left._kmwLength() <= config.leftContextCodePoints;\r\n if(!this.startOfBuffer) {\r\n // Our custom substring version will return the last n characters if param #1 is given -n.\r\n this.left = this.left._kmwSubstr(-config.leftContextCodePoints);\r\n }\r\n\r\n this.right = mock.getTextAfterCaret();\r\n this.endOfBuffer = this.right._kmwLength() <= config.rightContextCodePoints;\r\n if(!this.endOfBuffer) {\r\n this.right = this.right._kmwSubstr(0, config.rightContextCodePoints);\r\n }\r\n\r\n this.casingForm =\r\n layerId == 'shift' ? 'initial' :\r\n layerId == 'caps' ? 'upper' :\r\n null;\r\n }\r\n\r\n public toMock(): Mock {\r\n let caretPos = this.left._kmwLength();\r\n\r\n return new Mock(this.left + (this.right || \"\"), caretPos);\r\n }\r\n}", + "import EventEmitter from \"eventemitter3\";\r\nimport { LMLayer } from \"@keymanapp/lexical-model-layer/web\";\r\nimport { OutputTarget, Transcription, Mock } from \"@keymanapp/keyboard-processor\";\r\nimport ContextWindow from \"../contextWindow.js\";\r\nimport ModelSpec from \"./modelSpec.js\"\r\n\r\n/**\r\n * Corresponds to the 'suggestionsready' LanguageProcessor event.\r\n */\r\nexport type ReadySuggestionsHandler = (prediction: ReadySuggestions) => boolean;\r\n\r\nexport type StateChangeEnum = 'active'|'configured'|'inactive';\r\n/**\r\n * Corresponds to the 'statechange' LanguageProcessor event.\r\n */\r\nexport type StateChangeHandler = (state: StateChangeEnum) => any;\r\n\r\n/**\r\n * Covers 'tryaccept' events.\r\n */\r\nexport type TryUIHandler = (source: string) => boolean;\r\n\r\nexport type InvalidateSourceEnum = 'new'|'context';\r\n\r\n/**\r\n * Corresponds to the 'invalidatesuggestions' LanguageProcessor event.\r\n */\r\nexport type InvalidateSuggestionsHandler = (source: InvalidateSourceEnum) => boolean;\r\n\r\nexport class ReadySuggestions {\r\n suggestions: Suggestion[];\r\n transcriptionID: number;\r\n\r\n constructor(suggestions: Suggestion[], id: number) {\r\n this.suggestions = suggestions;\r\n this.transcriptionID = id;\r\n }\r\n}\r\n\r\ninterface LanguageProcessorEventMap {\r\n 'suggestionsready': ReadySuggestionsHandler,\r\n 'invalidatesuggestions': InvalidateSuggestionsHandler,\r\n 'statechange': StateChangeHandler,\r\n 'tryaccept': TryUIHandler,\r\n 'tryrevert': () => void,\r\n\r\n /**\r\n * Is called synchronously once suggestion application is successful and the context has been updated.\r\n *\r\n * @param outputTarget The `OutputTarget` representation of the context the suggestion was applied to.\r\n * @returns\r\n */\r\n 'suggestionapplied': (outputTarget: OutputTarget) => boolean\r\n}\r\n\r\n/* Is more like the model configuration engine */\r\nexport default class LanguageProcessor extends EventEmitter {\r\n private lmEngine: LMLayer;\r\n private currentModel?: ModelSpec;\r\n private configuration?: Configuration;\r\n private currentPromise?: Promise;\r\n\r\n private recentTranscriptions: Transcription[] = [];\r\n\r\n private _mayPredict: boolean = true;\r\n private _mayCorrect: boolean = true;\r\n\r\n private _state: StateChangeEnum = 'inactive';\r\n\r\n private static readonly TRANSCRIPTION_BUFFER: 10 = 10;\r\n\r\n public constructor(predictiveTextWorker: Worker, supportsRightDeletions: boolean = false) {\r\n super();\r\n\r\n // Establishes KMW's platform 'capabilities', which limit the range of context a LMLayer\r\n // model may expect.\r\n let capabilities: Capabilities = {\r\n maxLeftContextCodePoints: 64,\r\n // Since the apps don't yet support right-deletions.\r\n maxRightContextCodePoints: supportsRightDeletions ? 0 : 64\r\n }\r\n\r\n if(!predictiveTextWorker) {\r\n return;\r\n }\r\n\r\n this.lmEngine = new LMLayer(capabilities, predictiveTextWorker);\r\n }\r\n\r\n public get activeModel(): ModelSpec {\r\n return this.currentModel;\r\n }\r\n\r\n public get isConfigured(): boolean {\r\n return !!this.configuration;\r\n }\r\n\r\n public get state(): StateChangeEnum {\r\n return this._state;\r\n }\r\n\r\n public unloadModel() {\r\n this.lmEngine.unloadModel();\r\n delete this.currentModel;\r\n delete this.configuration;\r\n\r\n this._state = 'inactive';\r\n this.emit('statechange', 'inactive');\r\n }\r\n\r\n loadModel(model: ModelSpec): Promise {\r\n if(!model) {\r\n throw new Error(\"Null reference not allowed.\");\r\n }\r\n\r\n let specType: 'file'|'raw' = model.path ? 'file' : 'raw';\r\n let source = specType == 'file' ? model.path : model.code;\r\n\r\n // We pre-emptively emit so that the banner's DOM elements may update synchronously.\r\n // Prevents an ugly \"flash of unstyled content\" layout issue during keyboard load\r\n // on our mobile platforms when embedded.\r\n this.currentModel = model;\r\n if(this.mayPredict) {\r\n this._state = 'active';\r\n this.emit('statechange', 'active');\r\n }\r\n\r\n return this.lmEngine.loadModel(source, specType).then((config: Configuration) => {\r\n this.configuration = config;\r\n this._state = 'configured';\r\n this.emit('statechange', 'configured');\r\n }).catch((error) => {\r\n // Does this provide enough logging information?\r\n let message: string;\r\n if(error instanceof Error) {\r\n message = error.message;\r\n } else {\r\n message = String(error);\r\n }\r\n console.error(\"Could not load model '\" + model.id + \"': \" + message);\r\n\r\n // Since the model couldn't load, immediately deactivate. Visually, it'll look\r\n // like the banner crashed shortly after load.\r\n this.currentModel = null;\r\n this._state = 'inactive';\r\n this.emit('statechange', 'inactive');\r\n });\r\n }\r\n\r\n public invalidateContext(outputTarget: OutputTarget, layerId: string): Promise {\r\n // Signal to any predictive text UI that the context has changed, invalidating recent predictions.\r\n this.emit('invalidatesuggestions', 'context');\r\n\r\n // If there's no active model, there can be no predictions.\r\n // We'll also be missing important data needed to even properly REQUEST the predictions.\r\n if(!this.currentModel || !this.configuration) {\r\n return Promise.resolve([]);\r\n }\r\n\r\n // Don't attempt predictions when disabled!\r\n // invalidateContext otherwise bypasses .predict()'s check against this.\r\n if(!this.isActive) {\r\n return Promise.resolve([]);\r\n } else if(outputTarget) {\r\n let transcription = outputTarget.buildTranscriptionFrom(outputTarget, null, false);\r\n return this.predict_internal(transcription, true, layerId);\r\n }\r\n }\r\n\r\n public wordbreak(target: OutputTarget, layerId: string): Promise {\r\n if(!this.isActive) {\r\n return null;\r\n }\r\n\r\n let context = new ContextWindow(Mock.from(target, false), this.configuration, layerId);\r\n return this.lmEngine.wordbreak(context);\r\n }\r\n\r\n public predict(transcription: Transcription, layerId: string): Promise {\r\n if(!this.isActive) {\r\n return null;\r\n }\r\n\r\n // If there's no active model, there can be no predictions.\r\n // We'll also be missing important data needed to even properly REQUEST the predictions.\r\n if(!this.currentModel || !this.configuration) {\r\n return null;\r\n }\r\n\r\n // We've already invalidated any suggestions resulting from any previously-existing Promise -\r\n // may as well officially invalidate them via event.\r\n this.emit(\"invalidatesuggestions\", 'new');\r\n\r\n return this.predict_internal(transcription, false, layerId);\r\n }\r\n\r\n /**\r\n *\r\n * @param suggestion\r\n * @param outputTarget\r\n * @param getLayerId a function that returns the current layerId,\r\n * required because layerid can be changed by PostKeystroke\r\n * @returns\r\n */\r\n public applySuggestion(suggestion: Suggestion, outputTarget: OutputTarget, getLayerId: ()=>string): Promise {\r\n if(!outputTarget) {\r\n throw \"Accepting suggestions requires a destination OutputTarget instance.\"\r\n }\r\n\r\n // Find the state of the context at the time the suggestion was generated.\r\n // This may refer to the context before an input keystroke or before application\r\n // of a predictive suggestion.\r\n let original = this.getPredictionState(suggestion.transformId);\r\n if(!original) {\r\n console.warn(\"Could not apply the Suggestion!\");\r\n return null;\r\n } else {\r\n // Apply the Suggestion!\r\n\r\n // Step 1: determine the final output text\r\n let final = Mock.from(original.preInput, false);\r\n final.apply(suggestion.transform);\r\n\r\n // Step 2: build a final, master Transform that will produce the desired results from the CURRENT state.\r\n // In embedded mode, both Android and iOS are best served by calculating this transform and applying its\r\n // values as needed for use with their IME interfaces.\r\n let transform = final.buildTransformFrom(outputTarget);\r\n outputTarget.apply(transform);\r\n\r\n // Tell the banner that a suggestion was applied, so it can call the\r\n // keyboard's PostKeystroke entry point as needed\r\n this.emit('suggestionapplied', outputTarget);\r\n\r\n // Build a 'reversion' Transcription that can be used to undo this apply() if needed,\r\n // replacing the suggestion transform with the original input text.\r\n let preApply = Mock.from(original.preInput, false);\r\n preApply.apply(original.transform);\r\n\r\n // Builds the reversion option according to the loaded lexical model's known\r\n // syntactic properties.\r\n let suggestionContext = new ContextWindow(original.preInput, this.configuration, getLayerId());\r\n\r\n // We must accept the Suggestion from its original context, which was before\r\n // `original.transform` was applied.\r\n let reversionPromise: Promise = this.lmEngine.acceptSuggestion(suggestion, suggestionContext, original.transform);\r\n\r\n // Also, request new prediction set based on the resulting context.\r\n reversionPromise = reversionPromise.then((reversion) => {\r\n let mappedReversion: Reversion = {\r\n // By mapping back to the original Transcription that generated the Suggestion,\r\n // the input will be automatically rewound to the preInput state.\r\n transform: original.transform,\r\n // The ID part is critical; the reversion can't be applied without it.\r\n transformId: -original.token, // reversions use the additive inverse.\r\n displayAs: reversion.displayAs, // The real reason we needed to call the LMLayer.\r\n id: reversion.id,\r\n tag: reversion.tag\r\n }\r\n // // If using the version from lm-layer:\r\n // let mappedReversion = reversion;\r\n // mappedReversion.transformId = reversionTranscription.token;\r\n this.predictFromTarget(outputTarget, getLayerId());\r\n return mappedReversion;\r\n });\r\n\r\n return reversionPromise;\r\n }\r\n }\r\n\r\n public applyReversion(reversion: Reversion, outputTarget: OutputTarget) {\r\n if(!outputTarget) {\r\n throw \"Accepting suggestions requires a destination OutputTarget instance.\"\r\n }\r\n\r\n // Find the state of the context at the time the suggestion was generated.\r\n // This may refer to the context before an input keystroke or before application\r\n // of a predictive suggestion.\r\n //\r\n // Reversions use the additive inverse of the id token of the Transcription being\r\n // reverted to.\r\n let original = this.getPredictionState(-reversion.transformId);\r\n if(!original) {\r\n console.warn(\"Could not apply the Suggestion!\");\r\n return;\r\n }\r\n\r\n // Apply the Reversion!\r\n\r\n // Step 1: determine the final output text\r\n let final = Mock.from(original.preInput, false);\r\n final.apply(reversion.transform); // Should match original.transform, actually. (See applySuggestion)\r\n\r\n // Step 2: build a final, master Transform that will produce the desired results from the CURRENT state.\r\n // In embedded mode, both Android and iOS are best served by calculating this transform and applying its\r\n // values as needed for use with their IME interfaces.\r\n let transform = final.buildTransformFrom(outputTarget);\r\n outputTarget.apply(transform);\r\n\r\n // The reason we need to preserve the additive-inverse 'transformId' property on Reversions.\r\n let promise = this.lmEngine.revertSuggestion(reversion, new ContextWindow(original.preInput, this.configuration, null))\r\n\r\n return promise.then((suggestions: Suggestion[]) => {\r\n let result = new ReadySuggestions(suggestions, transform.id);\r\n this.emit(\"suggestionsready\", result);\r\n this.currentPromise = null;\r\n\r\n return suggestions;\r\n });\r\n }\r\n\r\n public predictFromTarget(outputTarget: OutputTarget, layerId: string): Promise {\r\n if(!outputTarget) {\r\n return null;\r\n }\r\n\r\n let transcription = outputTarget.buildTranscriptionFrom(outputTarget, null, false);\r\n return this.predict(transcription, layerId);\r\n }\r\n\r\n /**\r\n * Called internally to do actual predictions after any relevant \"invalidatesuggestions\" events\r\n * have been raised.\r\n * @param transcription The triggering transcription (if it exists)\r\n */\r\n private predict_internal(transcription: Transcription, resetContext: boolean, layerId: string): Promise {\r\n if(!transcription) {\r\n return null;\r\n }\r\n\r\n let context = new ContextWindow(transcription.preInput, this.configuration, layerId);\r\n this.recordTranscription(transcription);\r\n\r\n if(resetContext) {\r\n this.lmEngine.resetContext(context);\r\n }\r\n\r\n let alternates = transcription.alternates;\r\n if(!alternates || alternates.length == 0) {\r\n alternates = [{\r\n sample: transcription.transform,\r\n p: 1.0\r\n }];\r\n }\r\n\r\n let transform = transcription.transform;\r\n var promise = this.currentPromise = this.lmEngine.predict(alternates, context);\r\n\r\n return promise.then((suggestions: Suggestion[]) => {\r\n if(promise == this.currentPromise) {\r\n let result = new ReadySuggestions(suggestions, transform.id);\r\n this.emit(\"suggestionsready\", result);\r\n this.currentPromise = null;\r\n }\r\n\r\n return suggestions;\r\n });\r\n }\r\n\r\n private recordTranscription(transcription: Transcription) {\r\n this.recentTranscriptions.push(transcription);\r\n\r\n if(this.recentTranscriptions.length > LanguageProcessor.TRANSCRIPTION_BUFFER) {\r\n this.recentTranscriptions.splice(0, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Retrieves the context and output state of KMW immediately before the prediction with\r\n * token `id` was generated. Must correspond to a 'recent' one, as only so many are stored\r\n * in `ModelManager`'s history buffer.\r\n * @param id A unique identifier corresponding to a recent `Transcription`.\r\n * @returns The matching `Transcription`, or `null` none is found.\r\n */\r\n public getPredictionState(id: number): Transcription {\r\n let match = this.recentTranscriptions.filter((t: Transcription) => {\r\n return t.token == id;\r\n })\r\n\r\n return match.length == 0 ? null : match[0];\r\n }\r\n\r\n public shutdown() {\r\n this.lmEngine.shutdown();\r\n }\r\n\r\n public get isActive(): boolean {\r\n if(!this.canEnable()) {\r\n this._mayPredict = false;\r\n return false;\r\n }\r\n return (this.activeModel || false) && this._mayPredict;\r\n }\r\n\r\n canEnable(): boolean {\r\n // Is not initialized if there is no worker.\r\n return !!this.lmEngine;\r\n }\r\n\r\n public get mayPredict() {\r\n return this._mayPredict;\r\n }\r\n\r\n public set mayPredict(flag: boolean) {\r\n if(!this.canEnable()) {\r\n return;\r\n }\r\n\r\n let oldVal = this._mayPredict;\r\n this._mayPredict = flag;\r\n\r\n if(oldVal != flag) {\r\n // If there's no model to be activated and we've reached this point,\r\n // the banner should remain inactive, as it already was.\r\n // If it there was one and we've reached this point, we're globally\r\n // deactivating, so we're fine.\r\n if(this.activeModel) {\r\n // If someone toggles predictions on and off without changing the model, it is possible\r\n // that the model is already configured!\r\n let state: StateChangeEnum = flag ? 'active' : 'inactive';\r\n\r\n // We always signal the 'active' state here, even if 'configured', b/c of an\r\n // anti-banner-flicker optimization in the Android app.\r\n this._state = state;\r\n this.emit('statechange', state);\r\n\r\n if(this.isConfigured) {\r\n this._state = 'configured';\r\n this.emit('statechange', 'configured');\r\n }\r\n }\r\n }\r\n }\r\n\r\n public get mayCorrect() {\r\n return this._mayCorrect;\r\n }\r\n\r\n public set mayCorrect(flag: boolean) {\r\n this._mayCorrect = flag;\r\n }\r\n\r\n public get wordbreaksAfterSuggestions() {\r\n return this.configuration.wordbreaksAfterSuggestions;\r\n }\r\n\r\n public tryAcceptSuggestion(source: string): boolean {\r\n // If and when we do auto-correct, the suggestion is to pass this object to the event and\r\n // denote any mutations to the contained value.\r\n //let returnObj = {shouldSwallow: false};\r\n this.emit('tryaccept', source);\r\n\r\n return false;\r\n }\r\n\r\n public tryRevertSuggestion(): boolean {\r\n // If and when we do auto-revert, the suggestion is to pass this object to the event and\r\n // denote any mutations to the contained value.\r\n //let returnObj = {shouldSwallow: false};\r\n this.emit('tryrevert');\r\n\r\n return false;\r\n }\r\n}", + "/*\r\n * Copyright (c) 2018 National Research Council Canada (author: Eddie A. Santos)\r\n * Copyright (c) 2018 SIL International\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy of\r\n * this software and associated documentation files (the \"Software\"), to deal in\r\n * the Software without restriction, including without limitation the rights to\r\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\r\n * the Software, and to permit persons to whom the Software is furnished to do so,\r\n * subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in all\r\n * copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\r\n * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\r\n * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\r\n * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\r\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\n/// \r\n\r\ntype Resolve = (value?: T | PromiseLike) => void;\r\ntype Reject = (reason?: any) => void;\r\ninterface PromiseCallbacks {\r\n resolve: Resolve;\r\n reject: Reject;\r\n}\r\n\r\n\r\n/**\r\n * Associate tokens with promises.\r\n *\r\n * First, .make() a promise -- associate a token with resolve/reject callbacks.\r\n *\r\n * You can either .keep() a promise -- resolve() and forget it;\r\n * Or you may also .break() a promise -- reject() and forget it.\r\n *\r\n * is the type of resolved value (value yielded successfully by promise).\r\n */\r\nexport default class PromiseStore {\r\n // IE11 offers partial support for new Map().\r\n // Assume only .get(), .set(), .has(), .delete(), and .size work.\r\n // See: http://kangax.github.io/compat-table/es6/#test-Map\r\n private _promises: Map>;\r\n constructor() {\r\n this._promises = new Map();\r\n }\r\n /**\r\n * How many promises are currently being tracked?\r\n */\r\n get length(): number {\r\n return this._promises.size;\r\n }\r\n /**\r\n * Associate a token with its respective resolve and reject callbacks.\r\n */\r\n make(token: Token, resolve: Resolve, reject: Reject): void {\r\n if (this._promises.has(token)) {\r\n return reject(`Existing request with token ${token}`);\r\n }\r\n this._promises.set(token, { reject, resolve });\r\n }\r\n /**\r\n * Resolve the promise associated with a token (with a value!).\r\n * Once the promise is resolved, the token is removed..\r\n */\r\n keep(token: Token, value: T) {\r\n let callbacks = this._promises.get(token);\r\n if (!callbacks) {\r\n throw new Error(`No promise associated with token: ${token}`);\r\n }\r\n let accept = callbacks.resolve;\r\n this._promises.delete(token);\r\n return accept(value);\r\n }\r\n /**\r\n * Instantly reject and forget a promise associated with the token.\r\n */\r\n break(token: Token, reason?: any): void {\r\n let callbacks = this._promises.get(token);\r\n if (!callbacks) {\r\n throw new Error(`No promise associated with token: ${token}`);\r\n }\r\n this._promises.delete(token);\r\n callbacks.reject(reason);\r\n }\r\n}", + "/*\r\n * Copyright (c) 2018 National Research Council Canada (author: Eddie A. Santos)\r\n * Copyright (c) 2018 SIL International\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy of\r\n * this software and associated documentation files (the \"Software\"), to deal in\r\n * the Software without restriction, including without limitation the rights to\r\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\r\n * the Software, and to permit persons to whom the Software is furnished to do so,\r\n * subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in all\r\n * copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\r\n * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\r\n * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\r\n * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\r\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n\r\nimport PromiseStore from \"./promise-store.js\";\r\n\r\n/// \r\n/// \r\n\r\n/**\r\n * Top-level interface to the Language Modelling layer, or \"LMLayer\" for short.\r\n *\r\n * The Language Modelling layer provides a way for keyboards to offer prediction and\r\n * correction functionalities. The LMLayer proper runs within a Web Worker, however,\r\n * this class is intended to run in the main thread, and automatically spawn a Web\r\n * Worker, capable of offering predictions.\r\n *\r\n * Since the Worker runs in a different thread, the public methods of this class are\r\n * asynchronous. Methods of note include:\r\n *\r\n * - #loadModel() -- loads a specified model file\r\n * - #predict() -- ask the LMLayer to offer suggestions (predictions or corrections) for\r\n * the input event\r\n * - #unloadModel() -- unloads the LMLayer's currently loaded model, preparing it to\r\n * receive (load) a new model\r\n *\r\n * The top-level LMLayer will automatically starts up its own Web Worker.\r\n */\r\n\r\nexport default class LMLayer {\r\n /**\r\n * The underlying worker instance. By default, this is the LMLayerWorker.\r\n */\r\n private _worker: Worker;\r\n /** Call this when the LMLayer has sent us the 'ready' message! */\r\n private _declareLMLayerReady: (conf: Configuration) => void;\r\n private _predictPromises: PromiseStore;\r\n private _wordbreakPromises: PromiseStore;\r\n private _acceptPromises: PromiseStore;\r\n private _revertPromises: PromiseStore;\r\n private _nextToken: number;\r\n private capabilities: Capabilities;\r\n\r\n /**\r\n * Construct the top-level LMLayer interface. This also starts the underlying Worker.\r\n *\r\n * @param uri URI of the underlying LMLayer worker code. This will usually be a blob:\r\n * or file: URI. If uri is not provided, this will start the default Worker.\r\n */\r\n constructor(capabilities: Capabilities, worker: Worker, testMode?: boolean) {\r\n // Either use the given worker, or instantiate the default worker.\r\n this._worker = worker;\r\n this._worker.onmessage = this.onMessage.bind(this)\r\n this._declareLMLayerReady = null;\r\n this._predictPromises = new PromiseStore();\r\n this._wordbreakPromises = new PromiseStore();\r\n this._acceptPromises = new PromiseStore();\r\n this._revertPromises = new PromiseStore();\r\n this._nextToken = Number.MIN_SAFE_INTEGER;\r\n\r\n this.sendConfig(capabilities, !!testMode);\r\n }\r\n\r\n /**\r\n * Initializes the LMLayer worker with the host platform's capability set.\r\n *\r\n * @param capabilities The host platform's capability spec - a model cannot assume access to more context\r\n * than specified by this parameter.\r\n */\r\n private sendConfig(capabilities: Capabilities, testMode: boolean) {\r\n this._worker.postMessage({\r\n message: 'config',\r\n capabilities: capabilities,\r\n testMode: testMode\r\n });\r\n }\r\n\r\n /**\r\n * Initializes the LMLayer worker with a path to the desired model file.\r\n */\r\n loadModel(modelSource: string, loadType: 'file' | 'raw' = 'file'): Promise {\r\n return new Promise((resolve, _reject) => {\r\n // Sets up so the promise is resolved in the onMessage() callback, when it receives\r\n // the 'ready' message.\r\n this._declareLMLayerReady = resolve;\r\n\r\n let modelSourceSpec: any = {\r\n type: loadType\r\n };\r\n\r\n if(loadType == 'file') {\r\n modelSourceSpec.file = modelSource;\r\n } else {\r\n modelSourceSpec.code = modelSource;\r\n }\r\n\r\n this._worker.postMessage({\r\n message: 'load',\r\n source: modelSourceSpec\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Unloads the previously-active model from memory, resetting the LMLayer to prep\r\n * for transition to use of a new model.\r\n */\r\n public unloadModel() {\r\n this._worker.postMessage({\r\n message: 'unload'\r\n });\r\n }\r\n\r\n predict(transform: Transform | Distribution, context: Context): Promise {\r\n let token = this._nextToken++;\r\n return new Promise((resolve, reject) => {\r\n this._predictPromises.make(token, resolve, reject);\r\n this._worker.postMessage({\r\n message: 'predict',\r\n token: token,\r\n transform: transform,\r\n context: context,\r\n });\r\n });\r\n }\r\n\r\n wordbreak(context: Context): Promise {\r\n let token = this._nextToken++;\r\n return new Promise((resolve, reject) => {\r\n this._wordbreakPromises.make(token, resolve, reject);\r\n this._worker.postMessage({\r\n message: 'wordbreak',\r\n token: token,\r\n context: context\r\n })\r\n });\r\n }\r\n\r\n acceptSuggestion(suggestion: Suggestion, context: Context, postTransform: Transform): Promise {\r\n let token = this._nextToken++;\r\n return new Promise((resolve, reject) => {\r\n this._acceptPromises.make(token, resolve, reject);\r\n this._worker.postMessage({\r\n message: 'accept',\r\n token: token,\r\n suggestion: suggestion,\r\n context: context,\r\n postTransform: postTransform\r\n });\r\n });\r\n }\r\n\r\n revertSuggestion(reversion: Reversion, context: Context): Promise {\r\n let token = this._nextToken++;\r\n return new Promise((resolve, reject) => {\r\n this._revertPromises.make(token, resolve, reject);\r\n this._worker.postMessage({\r\n message: 'revert',\r\n token: token,\r\n reversion: reversion,\r\n context: context\r\n })\r\n });\r\n }\r\n\r\n resetContext(context: Context) {\r\n this._worker.postMessage({\r\n message: 'reset-context',\r\n context: context\r\n });\r\n }\r\n\r\n // TODO: asynchronous close() method.\r\n // Worker code must recognize message and call self.close().\r\n\r\n private onMessage(event: MessageEvent): void {\r\n let payload: OutgoingMessage = event.data;\r\n if (payload.message === 'error') {\r\n console.error(payload.log);\r\n if(payload.error) {\r\n console.error(payload.error);\r\n }\r\n }\r\n else if (payload.message === 'ready') {\r\n this._declareLMLayerReady(event.data.configuration);\r\n } else if (payload.message === 'suggestions') {\r\n this._predictPromises.keep(payload.token, payload.suggestions);\r\n } else if (payload.message === 'currentword') {\r\n this._wordbreakPromises.keep(payload.token, payload.word);\r\n } else if (payload.message === 'postaccept') {\r\n this._acceptPromises.keep(payload.token, payload.reversion);\r\n } else if (payload.message === 'postrevert') {\r\n this._revertPromises.keep(payload.token, payload.suggestions);\r\n } else {\r\n // This branch should never execute, but just in case...\r\n //@ts-ignore\r\n throw new Error(`Message not implemented: ${payload.message}`);\r\n }\r\n }\r\n\r\n /**\r\n * Clears out any computational resources in use by the LMLayer, including shutting\r\n * down any internal WebWorkers.\r\n */\r\n public shutdown() {\r\n this._worker.terminate();\r\n }\r\n}\r\n", + "/**\r\n * Given a function, this utility returns the source code within it, as a string.\r\n * This is intended to unwrap the \"wrapped\" source code created in the LMLayerWorker\r\n * build process.\r\n *\r\n * @param fn The function whose body will be returned.\r\n */\r\nexport default function unwrap(encodedSrc: string): string {\r\n // There used to be more to this, but now it's a pretty simple passthrough!\r\n return encodedSrc;\r\n}", + "\n// Autogenerated code. Do not modify!\n// --START:LMLayerWorkerCode--\n\nexport var LMLayerWorkerCode = \"var ae=Object.defineProperty;var v=function(r,e){return ae(r,\\\"name\\\",{value:e,configurable:!0})};/*! https://mths.be/codepointat v0.2.0 by @mathias */String.prototype.codePointAt||function(){\\\"use strict\\\";var r=function(){try{var t={},n=Object.defineProperty,i=n(t,t,t)&&n}catch(a){}return i}(),e=v(function(t){if(this==null)throw TypeError();var n=String(this),i=n.length,a=t?Number(t):0;if(a!=a&&(a=0),!(a<0||a>=i)){var u=n.charCodeAt(a),o;return u>=55296&&u<=56319&&i>a+1&&(o=n.charCodeAt(a+1),o>=56320&&o<=57343)?(u-55296)*1024+o-56320+65536:u}},\\\"codePointAt\\\");r?r(String.prototype,\\\"codePointAt\\\",{value:e,configurable:!0,writable:!0}):String.prototype.codePointAt=e}(),Array.prototype.fill||Object.defineProperty(Array.prototype,\\\"fill\\\",{value:function(r){if(this==null)throw new TypeError(\\\"this is null or not defined\\\");for(var e=Object(this),t=e.length>>>0,n=arguments[1],i=n>>0,a=i<0?Math.max(t+i,0):Math.min(i,t),u=arguments[2],o=u===void 0?t:u>>0,l=o<0?Math.max(t+o,0):Math.min(o,t);a>>0;if(typeof r!=\\\"function\\\")throw new TypeError(\\\"predicate must be a function\\\");for(var n=arguments[1],i=0;i0?1:-1)*Math.floor(Math.abs(u))},\\\"toInteger\\\"),n=Math.pow(2,53)-1,i=v(function(a){var u=t(a);return Math.min(Math.max(u,0),n)},\\\"toLength\\\");return v(function(u){var o=this,l=Object(u);if(u==null)throw new TypeError(\\\"Array.from requires an array-like object - not null or undefined\\\");var s=arguments.length>1?arguments[1]:void 0,f;if(typeof s!=\\\"undefined\\\"){if(!e(s))throw new TypeError(\\\"Array.from: when provided, the second argument must be a function\\\");arguments.length>2&&(f=arguments[2])}for(var c=i(l.length),d=e(o)?Object(new o(c)):new Array(c),p=0,g;p=0&&typeof m.length==\\\"number\\\"&&m.length>=0){var O=Math.floor(h.length),k=Math.floor(m.length),A=0;for(h.length=O+k;A=0&&this._nextIndex=0))return(I=new k(0)).length=0,I;for(A=Math.floor(h.length),(I=new k(A)).length=A;E0&&g[g.length-1])&&(S[0]===6||S[0]===2)){c=0;continue}if(S[0]===3&&(!g||S[1]>g[0]&&S[1]=s.length&&(s=void 0),{value:s&&s[d++],done:!s}}};throw new TypeError(f?\\\"Object is not iterable.\\\":\\\"Symbol.iterator is not defined.\\\")},\\\"__values7\\\"),u=v(function(s,f){var c=typeof Symbol==\\\"function\\\"&&s[Symbol.iterator];if(!c)return s;var d=c.call(s),p,g=[],w;try{for(;(f===void 0||f-- >0)&&!(p=d.next()).done;)g.push(p.value)}catch(b){w={error:b}}finally{try{p&&!p.done&&(c=d.return)&&c.call(d)}finally{if(w)throw w.error}}return g},\\\"__read3\\\"),o(\\\"__extends\\\",t),o(\\\"__assign\\\",n),o(\\\"__generator\\\",i),o(\\\"__values\\\",a),o(\\\"__read\\\",u)})}}),build_exports={};__export(build_exports,{tslib:function(){return tslib_1}}),__reExport(build_exports,__toESM(require_tslib(),1));var tslib_1=__toESM(require_tslib(),1),DeviceSpec=function(){function r(e,t,n,i){switch(e.toLowerCase()){case r.Browser.Chrome:case r.Browser.Edge:case r.Browser.Firefox:case r.Browser.Native:case r.Browser.Opera:case r.Browser.Safari:this.browser=e.toLowerCase();break;default:this.browser=r.Browser.Other}switch(t.toLowerCase()){case r.FormFactor.Desktop:case r.FormFactor.Phone:case r.FormFactor.Tablet:this.formFactor=t.toLowerCase();break;default:throw\\\"Invalid form factor specified for device: \\\"+t}switch(n.toLowerCase()){case r.OperatingSystem.Windows.toLowerCase():case r.OperatingSystem.macOS.toLowerCase():case r.OperatingSystem.Linux.toLowerCase():case r.OperatingSystem.Android.toLowerCase():case r.OperatingSystem.iOS.toLowerCase():this.OS=n.toLowerCase();break;default:this.OS=r.OperatingSystem.Other}this.touchable=i}return v(r,\\\"DeviceSpec2\\\"),r}();(function(r){var e;(function(i){i.Chrome=\\\"chrome\\\",i.Edge=\\\"edge\\\",i.Firefox=\\\"firefox\\\",i.Native=\\\"native\\\",i.Opera=\\\"opera\\\",i.Safari=\\\"safari\\\",i.Other=\\\"other\\\"})(e=r.Browser||(r.Browser={}));var t;(function(i){i.Windows=\\\"windows\\\",i.macOS=\\\"macosx\\\",i.Linux=\\\"linux\\\",i.Android=\\\"android\\\",i.iOS=\\\"ios\\\",i.Other=\\\"other\\\"})(t=r.OperatingSystem||(r.OperatingSystem={}));var n;(function(i){i.Desktop=\\\"desktop\\\",i.Phone=\\\"phone\\\",i.Tablet=\\\"tablet\\\"})(n=r.FormFactor||(r.FormFactor={}))})(DeviceSpec||(DeviceSpec={}));function extendString(){String.kmwFromCharCode=function(r){var e=[],t;for(t=0;t1114111||Math.floor(n)!==n)throw new RangeError(\\\"Invalid code point \\\"+n);n<65536?e.push(n):(n-=65536,e.push((n>>10)+55296),e.push(n%1024+56320))}return String.fromCharCode.apply(void 0,e)},String.prototype.kmwCharCodeAt=function(r){var e=String(this),t=0;if(r<0||r>=e.length)return NaN;for(var n=0;n=55296&&i<=56319&&e.length>t+1){var a=e.charCodeAt(t+1);if(a>=56320&&a<=57343)return(i-55296<<10)+(a-56320)+65536}return i},String.prototype.kmwIndexOf=function(r,e){var t=String(this),n=t.indexOf(r,e);if(n<0)return n;for(var i=0,a=0;a!==null&&ae){var a=r;r=e,e=a}n=t.kmwCodePointToCodeUnit(r),i=t.kmwCodePointToCodeUnit(e)}return(isNaN(n)||n===null)&&(n=0),(isNaN(i)||i===null)&&(i=t.length),t.substring(n,i)},String.prototype.kmwNextChar=function(r){var e=String(this);if(r===null||r<0||r>=e.length-1)return null;var t=e.charCodeAt(r);if(t>=55296&&t<=56319&&e.length>r+1){var n=e.charCodeAt(r+1);if(n>=56320&&n<=57343)return r==e.length-2?null:r+2}return r+1},String.prototype.kmwPrevChar=function(r){var e=String(this);if(r==null||r<=0||r>e.length)return null;var t=e.charCodeAt(r-1);if(t>=56320&&t<=57343&&r>1){var n=e.charCodeAt(r-2);if(n>=55296&&n<=56319)return r-2}return r-1},String.prototype.kmwCodePointToCodeUnit=function(r){if(r===null)return null;var e=String(this),t=0;if(r<0){t=e.length;for(var n=0;n>r;n--)t=e.kmwPrevChar(t);return t}if(r==e.kmwLength())return e.length;for(var n=0;n=0?e.kmwSubstr(r,1):\\\"\\\"},String.prototype.kmwBMPNextChar=function(r){var e=String(this);return r<0||r>=e.length-1?null:r+1},String.prototype.kmwBMPPrevChar=function(r){var e=String(this);return r<=0||r>e.length?null:r-1},String.prototype.kmwBMPCodePointToCodeUnit=function(r){return r},String.prototype.kmwBMPCodeUnitToCodePoint=function(r){return r},String.prototype.kmwBMPLength=function(){var r=String(this);return r.length},String.prototype.kmwBMPSubstr=function(r,e){var t=String(this);return r>-1?t.substr(r,e):t.substr(t.length+r,-r)},String.kmwEnableSupplementaryPlane=function(r){var e=String.prototype;String._kmwFromCharCode=r?String.kmwFromCharCode:String.fromCharCode,e._kmwCharAt=r?e.kmwCharAt:e.charAt,e._kmwCharCodeAt=r?e.kmwCharCodeAt:e.charCodeAt,e._kmwIndexOf=r?e.kmwIndexOf:e.indexOf,e._kmwLastIndexOf=r?e.kmwLastIndexOf:e.lastIndexOf,e._kmwSlice=r?e.kmwSlice:e.slice,e._kmwSubstring=r?e.kmwSubstring:e.substring,e._kmwSubstr=r?e.kmwSubstr:e.kmwBMPSubstr,e._kmwLength=r?e.kmwLength:e.kmwBMPLength,e._kmwNextChar=r?e.kmwNextChar:e.kmwBMPNextChar,e._kmwPrevChar=r?e.kmwPrevChar:e.kmwBMPPrevChar,e._kmwCodePointToCodeUnit=r?e.kmwCodePointToCodeUnit:e.kmwBMPCodePointToCodeUnit,e._kmwCodeUnitToCodePoint=r?e.kmwCodeUnitToCodePoint:e.kmwBMPCodeUnitToCodePoint},String._kmwFromCharCode||String.kmwEnableSupplementaryPlane(!1)}v(extendString,\\\"extendString\\\"),extendString();var models_exports={};__export(models_exports,{DummyModel:function(){return dummy_model_default},PriorityQueue:function(){return priority_queue_default},QuoteBehavior:function(){return quote_behavior_default},SENTINEL_CODE_UNIT:function(){return SENTINEL_CODE_UNIT},TrieModel:function(){return trie_model_default},applyTransform:function(){return applyTransform},buildMergedTransform:function(){return buildMergedTransform},defaultApplyCasing:function(){return defaultApplyCasing},getLastPreCaretToken:function(){return getLastPreCaretToken},isHighSurrogate:function(){return isHighSurrogate},isLowSurrogate:function(){return isLowSurrogate},isSentinel:function(){return isSentinel},tokenize:function(){return tokenize},transformToSuggestion:function(){return transformToSuggestion},wordbreak:function(){return wordbreak}}),extendString();var SENTINEL_CODE_UNIT=\\\"\\\\uFDD0\\\";function applyTransform(r,e){var t,n,i=e.left||\\\"\\\",a=i.kmwLength(),u=a=55296&&r<=56319}v(isHighSurrogate,\\\"isHighSurrogate\\\");function isLowSurrogate(r){return typeof r==\\\"string\\\"&&(r=r.charCodeAt(0)),r>=56320&&r<=57343}v(isLowSurrogate,\\\"isLowSurrogate\\\");function isSentinel(r){return r==SENTINEL_CODE_UNIT}v(isSentinel,\\\"isSentinel\\\");function transformToSuggestion(r,e){var t={transform:r,transformId:r.id,displayAs:r.insert};return(e===0||e)&&(t.p=e),t}v(transformToSuggestion,\\\"transformToSuggestion\\\");function defaultApplyCasing(r,e){switch(r){case\\\"lower\\\":return e.toLowerCase();case\\\"upper\\\":return e.toUpperCase();case\\\"initial\\\":var t=1;return e.length>1&&isHighSurrogate(e.charAt(0))&&isLowSurrogate(e.charCodeAt(1))&&(t=2),e.substring(0,t).toUpperCase().concat(e.substring(t))}}v(defaultApplyCasing,\\\"defaultApplyCasing\\\");var PriorityQueue=function(){function r(e,t){t===void 0&&(t=[]),this.comparator=e,this.heap=Array.from(t),this.heapify()}return v(r,\\\"PriorityQueue2\\\"),r.leftChildIndex=function(e){return e*2+1},r.rightChildIndex=function(e){return e*2+2},r.parentIndex=function(e){return Math.floor((e-1)/2)},r.prototype.heapify=function(e,t){if(e==null||t==null){this.heapify(0,this.count-1);return}for(var n=[],i=-1,a=t;a>=e;a--){var u=r.parentIndex(a);this.siftDown(a)&&u0;){var o=n.shift(),u=r.parentIndex(o);this.siftDown(o)&&u>=0&&i!=u&&(n.push(u),i=u)}},Object.defineProperty(r.prototype,\\\"count\\\",{get:function(){return this.heap.length},enumerable:!1,configurable:!0}),r.prototype.peek=function(){return this.heap[0]},r.prototype.enqueue=function(e){var t=this.heap.length;this.heap.push(e);for(var n=r.parentIndex,i=n(t);t!==0&&this.comparator(this.heap[t],this.heap[i])<0;){var a=this.heap[t];this.heap[t]=this.heap[i],this.heap[i]=a,t=i,i=n(t)}},r.prototype.enqueueAll=function(e){if(e.length!=0){var t=this.count;this.heap=this.heap.concat(e);var n=r.parentIndex(t);this.heapify(n>=0?n:0,r.parentIndex(this.count-1))}},r.prototype.dequeue=function(){if(this.count!=0){var e=this.heap[0],t=this.heap.pop();return this.heap.length>0&&(this.heap[0]=t,this.siftDown(0)),e}},r.prototype.siftDown=function(e){var t=r.leftChildIndex(e),n=r.rightChildIndex(e),i=e;if(t0&&(i=t[t.length-1]),t.length>1){var a=t[t.length-2];if(a.end==i.start&&i.text==\\\"'\\\"){var u={text:a.text+i.text,start:a.start,end:i.end,length:a.length+i.length};t.pop(),t.pop(),t.push(u),i=u}}var o={left:t.map(function(c){return c.text}),right:n.map(function(c){return c.text}),caretSplitsToken:!1};if(t.length>0&&n.length>0){var l=n[0],s=i.end!=e.left.length,f=l.start!=0;if(s||f)return o;r(i.text+l.text).length==1&&(o.caretSplitsToken=!0)}return o}v(tokenize,\\\"tokenize\\\");function getLastPreCaretToken(r,e){var t=tokenize(r,e);return t.left.length>0?t.left.pop():\\\"\\\"}v(getLastPreCaretToken,\\\"getLastPreCaretToken\\\");function wordbreak(r,e){return getLastPreCaretToken(r,e)}v(wordbreak,\\\"wordbreak\\\");var obj_exports={};__export(obj_exports,{ascii:function(){return ascii},default:function(){return default_},defaultWordbreaker:function(){return default_},placeholder:function(){return placeholder}});function placeholder(r){var e=0;return r.split(/\\\\s+/).map(function(t){var n={start:e,end:e+t.length,text:t,length:t.length};return e=n.end,n})}v(placeholder,\\\"placeholder\\\");function ascii(r){for(var e=/[A-Za-z0-9']+/g,t=[],n;(n=e.exec(r))!==null;)t.push(new RegExpDerivedSpan(n[0],n.index));return t}v(ascii,\\\"ascii\\\");var RegExpDerivedSpan=function(){function r(e,t){this.text=e,this.start=t}return v(r,\\\"RegExpDerivedSpan2\\\"),Object.defineProperty(r.prototype,\\\"length\\\",{get:function(){return this.text.length},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,\\\"end\\\",{get:function(){return this.start+this.text.length},enumerable:!1,configurable:!0}),r}(),propertyMap=[\\\"Other\\\",\\\"LF\\\",\\\"Newline\\\",\\\"CR\\\",\\\"WSegSpace\\\",\\\"Double_Quote\\\",\\\"Single_Quote\\\",\\\"MidNum\\\",\\\"MidNumLet\\\",\\\"Numeric\\\",\\\"MidLetter\\\",\\\"ALetter\\\",\\\"ExtendNumLet\\\",\\\"Format\\\",\\\"Extend\\\",\\\"Hebrew_Letter\\\",\\\"ZWJ\\\",\\\"Katakana\\\",\\\"Regional_Indicator\\\",\\\"sot\\\",\\\"eot\\\"],WORD_BREAK_PROPERTY=[[0,0],[10,1],[11,2],[13,3],[14,0],[32,4],[33,0],[34,5],[35,0],[39,6],[40,0],[44,7],[45,0],[46,8],[47,0],[48,9],[58,10],[59,7],[60,0],[65,11],[91,0],[95,12],[96,0],[97,11],[123,0],[133,2],[134,0],[170,11],[171,0],[173,13],[174,0],[181,11],[182,0],[183,10],[184,0],[186,11],[187,0],[192,11],[215,0],[216,11],[247,0],[248,11],[728,0],[734,11],[768,14],[880,11],[885,0],[886,11],[888,0],[890,11],[894,7],[895,11],[896,0],[902,11],[903,10],[904,11],[907,0],[908,11],[909,0],[910,11],[930,0],[931,11],[1014,0],[1015,11],[1154,0],[1155,14],[1162,11],[1328,0],[1329,11],[1367,0],[1369,11],[1373,0],[1374,11],[1375,10],[1376,11],[1417,7],[1418,11],[1419,0],[1425,14],[1470,0],[1471,14],[1472,0],[1473,14],[1475,0],[1476,14],[1478,0],[1479,14],[1480,0],[1488,15],[1515,0],[1519,15],[1523,11],[1524,10],[1525,0],[1536,13],[1542,0],[1548,7],[1550,0],[1552,14],[1563,0],[1564,13],[1565,0],[1568,11],[1611,14],[1632,9],[1642,0],[1643,9],[1644,7],[1645,0],[1646,11],[1648,14],[1649,11],[1748,0],[1749,11],[1750,14],[1757,13],[1758,0],[1759,14],[1765,11],[1767,14],[1769,0],[1770,14],[1774,11],[1776,9],[1786,11],[1789,0],[1791,11],[1792,0],[1807,13],[1808,11],[1809,14],[1810,11],[1840,14],[1867,0],[1869,11],[1958,14],[1969,11],[1970,0],[1984,9],[1994,11],[2027,14],[2036,11],[2038,0],[2040,7],[2041,0],[2042,11],[2043,0],[2045,14],[2046,0],[2048,11],[2070,14],[2074,11],[2075,14],[2084,11],[2085,14],[2088,11],[2089,14],[2094,0],[2112,11],[2137,14],[2140,0],[2144,11],[2155,0],[2208,11],[2229,0],[2230,11],[2248,0],[2259,14],[2274,13],[2275,14],[2308,11],[2362,14],[2365,11],[2366,14],[2384,11],[2385,14],[2392,11],[2402,14],[2404,0],[2406,9],[2416,0],[2417,11],[2433,14],[2436,0],[2437,11],[2445,0],[2447,11],[2449,0],[2451,11],[2473,0],[2474,11],[2481,0],[2482,11],[2483,0],[2486,11],[2490,0],[2492,14],[2493,11],[2494,14],[2501,0],[2503,14],[2505,0],[2507,14],[2510,11],[2511,0],[2519,14],[2520,0],[2524,11],[2526,0],[2527,11],[2530,14],[2532,0],[2534,9],[2544,11],[2546,0],[2556,11],[2557,0],[2558,14],[2559,0],[2561,14],[2564,0],[2565,11],[2571,0],[2575,11],[2577,0],[2579,11],[2601,0],[2602,11],[2609,0],[2610,11],[2612,0],[2613,11],[2615,0],[2616,11],[2618,0],[2620,14],[2621,0],[2622,14],[2627,0],[2631,14],[2633,0],[2635,14],[2638,0],[2641,14],[2642,0],[2649,11],[2653,0],[2654,11],[2655,0],[2662,9],[2672,14],[2674,11],[2677,14],[2678,0],[2689,14],[2692,0],[2693,11],[2702,0],[2703,11],[2706,0],[2707,11],[2729,0],[2730,11],[2737,0],[2738,11],[2740,0],[2741,11],[2746,0],[2748,14],[2749,11],[2750,14],[2758,0],[2759,14],[2762,0],[2763,14],[2766,0],[2768,11],[2769,0],[2784,11],[2786,14],[2788,0],[2790,9],[2800,0],[2809,11],[2810,14],[2816,0],[2817,14],[2820,0],[2821,11],[2829,0],[2831,11],[2833,0],[2835,11],[2857,0],[2858,11],[2865,0],[2866,11],[2868,0],[2869,11],[2874,0],[2876,14],[2877,11],[2878,14],[2885,0],[2887,14],[2889,0],[2891,14],[2894,0],[2901,14],[2904,0],[2908,11],[2910,0],[2911,11],[2914,14],[2916,0],[2918,9],[2928,0],[2929,11],[2930,0],[2946,14],[2947,11],[2948,0],[2949,11],[2955,0],[2958,11],[2961,0],[2962,11],[2966,0],[2969,11],[2971,0],[2972,11],[2973,0],[2974,11],[2976,0],[2979,11],[2981,0],[2984,11],[2987,0],[2990,11],[3002,0],[3006,14],[3011,0],[3014,14],[3017,0],[3018,14],[3022,0],[3024,11],[3025,0],[3031,14],[3032,0],[3046,9],[3056,0],[3072,14],[3077,11],[3085,0],[3086,11],[3089,0],[3090,11],[3113,0],[3114,11],[3130,0],[3133,11],[3134,14],[3141,0],[3142,14],[3145,0],[3146,14],[3150,0],[3157,14],[3159,0],[3160,11],[3163,0],[3168,11],[3170,14],[3172,0],[3174,9],[3184,0],[3200,11],[3201,14],[3204,0],[3205,11],[3213,0],[3214,11],[3217,0],[3218,11],[3241,0],[3242,11],[3252,0],[3253,11],[3258,0],[3260,14],[3261,11],[3262,14],[3269,0],[3270,14],[3273,0],[3274,14],[3278,0],[3285,14],[3287,0],[3294,11],[3295,0],[3296,11],[3298,14],[3300,0],[3302,9],[3312,0],[3313,11],[3315,0],[3328,14],[3332,11],[3341,0],[3342,11],[3345,0],[3346,11],[3387,14],[3389,11],[3390,14],[3397,0],[3398,14],[3401,0],[3402,14],[3406,11],[3407,0],[3412,11],[3415,14],[3416,0],[3423,11],[3426,14],[3428,0],[3430,9],[3440,0],[3450,11],[3456,0],[3457,14],[3460,0],[3461,11],[3479,0],[3482,11],[3506,0],[3507,11],[3516,0],[3517,11],[3518,0],[3520,11],[3527,0],[3530,14],[3531,0],[3535,14],[3541,0],[3542,14],[3543,0],[3544,14],[3552,0],[3558,9],[3568,0],[3570,14],[3572,0],[3633,14],[3634,0],[3636,14],[3643,0],[3655,14],[3663,0],[3664,9],[3674,0],[3761,14],[3762,0],[3764,14],[3773,0],[3784,14],[3790,0],[3792,9],[3802,0],[3840,11],[3841,0],[3864,14],[3866,0],[3872,9],[3882,0],[3893,14],[3894,0],[3895,14],[3896,0],[3897,14],[3898,0],[3902,14],[3904,11],[3912,0],[3913,11],[3949,0],[3953,14],[3973,0],[3974,14],[3976,11],[3981,14],[3992,0],[3993,14],[4029,0],[4038,14],[4039,0],[4139,14],[4159,0],[4160,9],[4170,0],[4182,14],[4186,0],[4190,14],[4193,0],[4194,14],[4197,0],[4199,14],[4206,0],[4209,14],[4213,0],[4226,14],[4238,0],[4239,14],[4240,9],[4250,14],[4254,0],[4256,11],[4294,0],[4295,11],[4296,0],[4301,11],[4302,0],[4304,11],[4347,0],[4348,11],[4681,0],[4682,11],[4686,0],[4688,11],[4695,0],[4696,11],[4697,0],[4698,11],[4702,0],[4704,11],[4745,0],[4746,11],[4750,0],[4752,11],[4785,0],[4786,11],[4790,0],[4792,11],[4799,0],[4800,11],[4801,0],[4802,11],[4806,0],[4808,11],[4823,0],[4824,11],[4881,0],[4882,11],[4886,0],[4888,11],[4955,0],[4957,14],[4960,0],[4992,11],[5008,0],[5024,11],[5110,0],[5112,11],[5118,0],[5121,11],[5741,0],[5743,11],[5760,4],[5761,11],[5787,0],[5792,11],[5867,0],[5870,11],[5881,0],[5888,11],[5901,0],[5902,11],[5906,14],[5909,0],[5920,11],[5938,14],[5941,0],[5952,11],[5970,14],[5972,0],[5984,11],[5997,0],[5998,11],[6001,0],[6002,14],[6004,0],[6068,14],[6100,0],[6109,14],[6110,0],[6112,9],[6122,0],[6155,14],[6158,13],[6159,0],[6160,9],[6170,0],[6176,11],[6265,0],[6272,11],[6277,14],[6279,11],[6313,14],[6314,11],[6315,0],[6320,11],[6390,0],[6400,11],[6431,0],[6432,14],[6444,0],[6448,14],[6460,0],[6470,9],[6480,0],[6608,9],[6618,0],[6656,11],[6679,14],[6684,0],[6741,14],[6751,0],[6752,14],[6781,0],[6783,14],[6784,9],[6794,0],[6800,9],[6810,0],[6832,14],[6849,0],[6912,14],[6917,11],[6964,14],[6981,11],[6988,0],[6992,9],[7002,0],[7019,14],[7028,0],[7040,14],[7043,11],[7073,14],[7086,11],[7088,9],[7098,11],[7142,14],[7156,0],[7168,11],[7204,14],[7224,0],[7232,9],[7242,0],[7245,11],[7248,9],[7258,11],[7294,0],[7296,11],[7305,0],[7312,11],[7355,0],[7357,11],[7360,0],[7376,14],[7379,0],[7380,14],[7401,11],[7405,14],[7406,11],[7412,14],[7413,11],[7415,14],[7418,11],[7419,0],[7424,11],[7616,14],[7674,0],[7675,14],[7680,11],[7958,0],[7960,11],[7966,0],[7968,11],[8006,0],[8008,11],[8014,0],[8016,11],[8024,0],[8025,11],[8026,0],[8027,11],[8028,0],[8029,11],[8030,0],[8031,11],[8062,0],[8064,11],[8117,0],[8118,11],[8125,0],[8126,11],[8127,0],[8130,11],[8133,0],[8134,11],[8141,0],[8144,11],[8148,0],[8150,11],[8156,0],[8160,11],[8173,0],[8178,11],[8181,0],[8182,11],[8189,0],[8192,4],[8199,0],[8200,4],[8203,0],[8204,14],[8205,16],[8206,13],[8208,0],[8216,8],[8218,0],[8228,8],[8229,0],[8231,10],[8232,2],[8234,13],[8239,12],[8240,0],[8255,12],[8257,0],[8260,7],[8261,0],[8276,12],[8277,0],[8287,4],[8288,13],[8293,0],[8294,13],[8304,0],[8305,11],[8306,0],[8319,11],[8320,0],[8336,11],[8349,0],[8400,14],[8433,0],[8450,11],[8451,0],[8455,11],[8456,0],[8458,11],[8468,0],[8469,11],[8470,0],[8473,11],[8478,0],[8484,11],[8485,0],[8486,11],[8487,0],[8488,11],[8489,0],[8490,11],[8494,0],[8495,11],[8506,0],[8508,11],[8512,0],[8517,11],[8522,0],[8526,11],[8527,0],[8544,11],[8585,0],[9398,11],[9450,0],[11264,11],[11311,0],[11312,11],[11359,0],[11360,11],[11493,0],[11499,11],[11503,14],[11506,11],[11508,0],[11520,11],[11558,0],[11559,11],[11560,0],[11565,11],[11566,0],[11568,11],[11624,0],[11631,11],[11632,0],[11647,14],[11648,11],[11671,0],[11680,11],[11687,0],[11688,11],[11695,0],[11696,11],[11703,0],[11704,11],[11711,0],[11712,11],[11719,0],[11720,11],[11727,0],[11728,11],[11735,0],[11736,11],[11743,0],[11744,14],[11776,0],[11823,11],[11824,0],[12288,4],[12289,0],[12293,11],[12294,0],[12330,14],[12336,0],[12337,17],[12342,0],[12347,11],[12349,0],[12441,14],[12443,17],[12445,0],[12448,17],[12539,0],[12540,17],[12544,0],[12549,11],[12592,0],[12593,11],[12687,0],[12704,11],[12736,0],[12784,17],[12800,0],[13008,17],[13055,0],[13056,17],[13144,0],[40960,11],[42125,0],[42192,11],[42238,0],[42240,11],[42509,0],[42512,11],[42528,9],[42538,11],[42540,0],[42560,11],[42607,14],[42611,0],[42612,14],[42622,0],[42623,11],[42654,14],[42656,11],[42736,14],[42738,0],[42760,11],[42944,0],[42946,11],[42955,0],[42997,11],[43010,14],[43011,11],[43014,14],[43015,11],[43019,14],[43020,11],[43043,14],[43048,0],[43052,14],[43053,0],[43072,11],[43124,0],[43136,14],[43138,11],[43188,14],[43206,0],[43216,9],[43226,0],[43232,14],[43250,11],[43256,0],[43259,11],[43260,0],[43261,11],[43263,14],[43264,9],[43274,11],[43302,14],[43310,0],[43312,11],[43335,14],[43348,0],[43360,11],[43389,0],[43392,14],[43396,11],[43443,14],[43457,0],[43471,11],[43472,9],[43482,0],[43493,14],[43494,0],[43504,9],[43514,0],[43520,11],[43561,14],[43575,0],[43584,11],[43587,14],[43588,11],[43596,14],[43598,0],[43600,9],[43610,0],[43643,14],[43646,0],[43696,14],[43697,0],[43698,14],[43701,0],[43703,14],[43705,0],[43710,14],[43712,0],[43713,14],[43714,0],[43744,11],[43755,14],[43760,0],[43762,11],[43765,14],[43767,0],[43777,11],[43783,0],[43785,11],[43791,0],[43793,11],[43799,0],[43808,11],[43815,0],[43816,11],[43823,0],[43824,11],[43882,0],[43888,11],[44003,14],[44011,0],[44012,14],[44014,0],[44016,9],[44026,0],[44032,11],[55204,0],[55216,11],[55239,0],[55243,11],[55292,0],[64256,11],[64263,0],[64275,11],[64280,0],[64285,15],[64286,14],[64287,15],[64297,0],[64298,15],[64311,0],[64312,15],[64317,0],[64318,15],[64319,0],[64320,15],[64322,0],[64323,15],[64325,0],[64326,15],[64336,11],[64434,0],[64467,11],[64830,0],[64848,11],[64912,0],[64914,11],[64968,0],[65008,11],[65020,0],[65024,14],[65040,7],[65041,0],[65043,10],[65044,7],[65045,0],[65056,14],[65072,0],[65075,12],[65077,0],[65101,12],[65104,7],[65105,0],[65106,8],[65107,0],[65108,7],[65109,10],[65110,0],[65136,11],[65141,0],[65142,11],[65277,0],[65279,13],[65280,0],[65287,8],[65288,0],[65292,7],[65293,0],[65294,8],[65295,0],[65296,9],[65306,10],[65307,7],[65308,0],[65313,11],[65339,0],[65343,12],[65344,0],[65345,11],[65371,0],[65382,17],[65438,14],[65440,11],[65471,0],[65474,11],[65480,0],[65482,11],[65488,0],[65490,11],[65496,0],[65498,11],[65501,0],[65529,13],[65532,0],[65536,11],[65548,0],[65549,11],[65575,0],[65576,11],[65595,0],[65596,11],[65598,0],[65599,11],[65614,0],[65616,11],[65630,0],[65664,11],[65787,0],[65856,11],[65909,0],[66045,14],[66046,0],[66176,11],[66205,0],[66208,11],[66257,0],[66272,14],[66273,0],[66304,11],[66336,0],[66349,11],[66379,0],[66384,11],[66422,14],[66427,0],[66432,11],[66462,0],[66464,11],[66500,0],[66504,11],[66512,0],[66513,11],[66518,0],[66560,11],[66718,0],[66720,9],[66730,0],[66736,11],[66772,0],[66776,11],[66812,0],[66816,11],[66856,0],[66864,11],[66916,0],[67072,11],[67383,0],[67392,11],[67414,0],[67424,11],[67432,0],[67584,11],[67590,0],[67592,11],[67593,0],[67594,11],[67638,0],[67639,11],[67641,0],[67644,11],[67645,0],[67647,11],[67670,0],[67680,11],[67703,0],[67712,11],[67743,0],[67808,11],[67827,0],[67828,11],[67830,0],[67840,11],[67862,0],[67872,11],[67898,0],[67968,11],[68024,0],[68030,11],[68032,0],[68096,11],[68097,14],[68100,0],[68101,14],[68103,0],[68108,14],[68112,11],[68116,0],[68117,11],[68120,0],[68121,11],[68150,0],[68152,14],[68155,0],[68159,14],[68160,0],[68192,11],[68221,0],[68224,11],[68253,0],[68288,11],[68296,0],[68297,11],[68325,14],[68327,0],[68352,11],[68406,0],[68416,11],[68438,0],[68448,11],[68467,0],[68480,11],[68498,0],[68608,11],[68681,0],[68736,11],[68787,0],[68800,11],[68851,0],[68864,11],[68900,14],[68904,0],[68912,9],[68922,0],[69248,11],[69290,0],[69291,14],[69293,0],[69296,11],[69298,0],[69376,11],[69405,0],[69415,11],[69416,0],[69424,11],[69446,14],[69457,0],[69552,11],[69573,0],[69600,11],[69623,0],[69632,14],[69635,11],[69688,14],[69703,0],[69734,9],[69744,0],[69759,14],[69763,11],[69808,14],[69819,0],[69821,13],[69822,0],[69837,13],[69838,0],[69840,11],[69865,0],[69872,9],[69882,0],[69888,14],[69891,11],[69927,14],[69941,0],[69942,9],[69952,0],[69956,11],[69957,14],[69959,11],[69960,0],[69968,11],[70003,14],[70004,0],[70006,11],[70007,0],[70016,14],[70019,11],[70067,14],[70081,11],[70085,0],[70089,14],[70093,0],[70094,14],[70096,9],[70106,11],[70107,0],[70108,11],[70109,0],[70144,11],[70162,0],[70163,11],[70188,14],[70200,0],[70206,14],[70207,0],[70272,11],[70279,0],[70280,11],[70281,0],[70282,11],[70286,0],[70287,11],[70302,0],[70303,11],[70313,0],[70320,11],[70367,14],[70379,0],[70384,9],[70394,0],[70400,14],[70404,0],[70405,11],[70413,0],[70415,11],[70417,0],[70419,11],[70441,0],[70442,11],[70449,0],[70450,11],[70452,0],[70453,11],[70458,0],[70459,14],[70461,11],[70462,14],[70469,0],[70471,14],[70473,0],[70475,14],[70478,0],[70480,11],[70481,0],[70487,14],[70488,0],[70493,11],[70498,14],[70500,0],[70502,14],[70509,0],[70512,14],[70517,0],[70656,11],[70709,14],[70727,11],[70731,0],[70736,9],[70746,0],[70750,14],[70751,11],[70754,0],[70784,11],[70832,14],[70852,11],[70854,0],[70855,11],[70856,0],[70864,9],[70874,0],[71040,11],[71087,14],[71094,0],[71096,14],[71105,0],[71128,11],[71132,14],[71134,0],[71168,11],[71216,14],[71233,0],[71236,11],[71237,0],[71248,9],[71258,0],[71296,11],[71339,14],[71352,11],[71353,0],[71360,9],[71370,0],[71453,14],[71468,0],[71472,9],[71482,0],[71680,11],[71724,14],[71739,0],[71840,11],[71904,9],[71914,0],[71935,11],[71943,0],[71945,11],[71946,0],[71948,11],[71956,0],[71957,11],[71959,0],[71960,11],[71984,14],[71990,0],[71991,14],[71993,0],[71995,14],[71999,11],[72e3,14],[72001,11],[72002,14],[72004,0],[72016,9],[72026,0],[72096,11],[72104,0],[72106,11],[72145,14],[72152,0],[72154,14],[72161,11],[72162,0],[72163,11],[72164,14],[72165,0],[72192,11],[72193,14],[72203,11],[72243,14],[72250,11],[72251,14],[72255,0],[72263,14],[72264,0],[72272,11],[72273,14],[72284,11],[72330,14],[72346,0],[72349,11],[72350,0],[72384,11],[72441,0],[72704,11],[72713,0],[72714,11],[72751,14],[72759,0],[72760,14],[72768,11],[72769,0],[72784,9],[72794,0],[72818,11],[72848,0],[72850,14],[72872,0],[72873,14],[72887,0],[72960,11],[72967,0],[72968,11],[72970,0],[72971,11],[73009,14],[73015,0],[73018,14],[73019,0],[73020,14],[73022,0],[73023,14],[73030,11],[73031,14],[73032,0],[73040,9],[73050,0],[73056,11],[73062,0],[73063,11],[73065,0],[73066,11],[73098,14],[73103,0],[73104,14],[73106,0],[73107,14],[73112,11],[73113,0],[73120,9],[73130,0],[73440,11],[73459,14],[73463,0],[73648,11],[73649,0],[73728,11],[74650,0],[74752,11],[74863,0],[74880,11],[75076,0],[77824,11],[78895,0],[78896,13],[78905,0],[82944,11],[83527,0],[92160,11],[92729,0],[92736,11],[92767,0],[92768,9],[92778,0],[92880,11],[92910,0],[92912,14],[92917,0],[92928,11],[92976,14],[92983,0],[92992,11],[92996,0],[93008,9],[93018,0],[93027,11],[93048,0],[93053,11],[93072,0],[93760,11],[93824,0],[93952,11],[94027,0],[94031,14],[94032,11],[94033,14],[94088,0],[94095,14],[94099,11],[94112,0],[94176,11],[94178,0],[94179,11],[94180,14],[94181,0],[94192,14],[94194,0],[110592,17],[110593,0],[110948,17],[110952,0],[113664,11],[113771,0],[113776,11],[113789,0],[113792,11],[113801,0],[113808,11],[113818,0],[113821,14],[113823,0],[113824,13],[113828,0],[119141,14],[119146,0],[119149,14],[119155,13],[119163,14],[119171,0],[119173,14],[119180,0],[119210,14],[119214,0],[119362,14],[119365,0],[119808,11],[119893,0],[119894,11],[119965,0],[119966,11],[119968,0],[119970,11],[119971,0],[119973,11],[119975,0],[119977,11],[119981,0],[119982,11],[119994,0],[119995,11],[119996,0],[119997,11],[120004,0],[120005,11],[120070,0],[120071,11],[120075,0],[120077,11],[120085,0],[120086,11],[120093,0],[120094,11],[120122,0],[120123,11],[120127,0],[120128,11],[120133,0],[120134,11],[120135,0],[120138,11],[120145,0],[120146,11],[120486,0],[120488,11],[120513,0],[120514,11],[120539,0],[120540,11],[120571,0],[120572,11],[120597,0],[120598,11],[120629,0],[120630,11],[120655,0],[120656,11],[120687,0],[120688,11],[120713,0],[120714,11],[120745,0],[120746,11],[120771,0],[120772,11],[120780,0],[120782,9],[120832,0],[121344,14],[121399,0],[121403,14],[121453,0],[121461,14],[121462,0],[121476,14],[121477,0],[121499,14],[121504,0],[121505,14],[121520,0],[122880,14],[122887,0],[122888,14],[122905,0],[122907,14],[122914,0],[122915,14],[122917,0],[122918,14],[122923,0],[123136,11],[123181,0],[123184,14],[123191,11],[123198,0],[123200,9],[123210,0],[123214,11],[123215,0],[123584,11],[123628,14],[123632,9],[123642,0],[124928,11],[125125,0],[125136,14],[125143,0],[125184,11],[125252,14],[125259,11],[125260,0],[125264,9],[125274,0],[126464,11],[126468,0],[126469,11],[126496,0],[126497,11],[126499,0],[126500,11],[126501,0],[126503,11],[126504,0],[126505,11],[126515,0],[126516,11],[126520,0],[126521,11],[126522,0],[126523,11],[126524,0],[126530,11],[126531,0],[126535,11],[126536,0],[126537,11],[126538,0],[126539,11],[126540,0],[126541,11],[126544,0],[126545,11],[126547,0],[126548,11],[126549,0],[126551,11],[126552,0],[126553,11],[126554,0],[126555,11],[126556,0],[126557,11],[126558,0],[126559,11],[126560,0],[126561,11],[126563,0],[126564,11],[126565,0],[126567,11],[126571,0],[126572,11],[126579,0],[126580,11],[126584,0],[126585,11],[126589,0],[126590,11],[126591,0],[126592,11],[126602,0],[126603,11],[126620,0],[126625,11],[126628,0],[126629,11],[126634,0],[126635,11],[126652,0],[127280,11],[127306,0],[127312,11],[127338,0],[127344,11],[127370,0],[127462,18],[127488,0],[127995,14],[128e3,0],[130032,9],[130042,0],[917505,13],[917506,0],[917536,14],[917632,0],[917760,14],[918e3,0]];function default_(r,e){var t=findBoundaries(r,e);if(t.length==0)return[];for(var n=[],i=0;i=this.text.length?20:isStartOfSurrogatePair(this.text[e])?property(this.text[e]+this.text[e+1]):property(this.text[e],this.options)},r.prototype.match=function(e,t,n,i){var a,u,o,l,s=(a=e==null?void 0:e.includes(this.lookbehind))!==null&&a!==void 0?a:!0;return s=s&&((u=t==null?void 0:t.includes(this.left))!==null&&u!==void 0?u:!0),s=s&&((o=n==null?void 0:n.includes(this.right))!==null&&o!==void 0?o:!0),s&&((l=i==null?void 0:i.includes(this.lookahead))!==null&&l!==void 0?l:!0)},r.prototype.propertyMatch=function(e,t,n,i){var a=this,u=v(function(o){return propertyVal(o,a.options)},\\\"propMapper\\\");return this.match(e==null?void 0:e.map(u),t==null?void 0:t.map(u),n==null?void 0:n.map(u),i==null?void 0:i.map(u))},r}();function isNonSpace(r,e){return!Array.from(r).map(function(t){return property(t,e)}).every(function(t){return t===3||t===1||t===2||t===4})}v(isNonSpace,\\\"isNonSpace\\\");function findBoundaries(r,e){var t,n,i;if(r.length===0)return[];e&&!e.rules&&(e.rules=[]);var a=[],u,o=0,l=new BreakerContext(r,e,o),s=0;do{if(u=o,o=C(o),l=l.next(o),l.match(null,[19],null,null)){a.push(u);continue}if(l.match(null,null,[20],null)){a.push(u);break}if(!l.match(null,[3],[1],null)){var f=[2,3,1];if(l.match(null,f,null,null)){a.push(u);continue}if(l.match(null,null,f,null)){a.push(u);continue}if(!l.match(null,[4],[4],null)){for(var c=[13,14,16];l.match(null,null,c,null);)t=(0,build_exports.__read)([o,C(o)],2),u=t[0],o=t[1],l=l.ignoringRight(o);if(l.right===20){a.push(u);break}for(;l.match(null,null,null,c);)o=C(o),l=l.ignoringLookahead(o);var d=[11,15],p=[8,6];if(e!=null&&e.rules){var g=!1;try{for(var w=(n=void 0,(0,build_exports.__values)(e.rules)),b=w.next();!b.done;b=w.next()){var T=b.value;if(g=T.match(l),g){T.breakIfMatch&&a.push(u);break}}}catch(h){n={error:h}}finally{try{b&&!b.done&&(i=w.return)&&i.call(w)}finally{if(n)throw n.error}}if(g)continue}if(!l.match(null,d,d,null)){var S=[10].concat(p);if(!l.match(null,d,S,d)&&!l.match(d,S,d,null)&&!l.match(null,[15],[6],null)&&!l.match(null,[15],[5],[15])&&!l.match([15],[5],[15],null)&&!l.match(null,[9],[9],null)&&!l.match(null,d,[9],null)&&!l.match(null,[9],d,null)){var _=[7].concat(p);if(!l.match([9],_,[9],null)&&!l.match(null,[9],_,[9])&&!l.match(null,[17],[17],null)){var y=[17,9].concat(d);if(!l.match(null,y,[12],null)&&!l.match(null,[12],[12],null)&&!l.match(null,[12],y,null)){if(l.right===18){if(s+=1,s%2==1)continue}else s=0;a.push(u)}}}}}}}while(u=r.length?r.length:isStartOfSurrogatePair(r[h])?h+2:h+1}}v(findBoundaries,\\\"findBoundaries\\\");function isStartOfSurrogatePair(r){var e=r.charCodeAt(0);return e>=55296&&e<=56319}v(isStartOfSurrogatePair,\\\"isStartOfSurrogatePair\\\");function property(r,e){if(e!=null&&e.propertyMapping){var t=e.propertyMapping(r);if(t)return propertyVal(t,e)}var n=r.codePointAt(0);return searchForProperty(n,0,WORD_BREAK_PROPERTY.length-1)}v(property,\\\"property\\\");function propertyVal(r,e){var t,n,i=v(function(u){return u.toLowerCase()==r.toLowerCase()},\\\"matcher\\\"),a=(n=(t=e==null?void 0:e.customProperties)===null||t===void 0?void 0:t.findIndex(i))!==null&&n!==void 0?n:-1;return a!=-1?-a-1:propertyMap.findIndex(i)}v(propertyVal,\\\"propertyVal\\\");function searchForProperty(r,e,t){if(t=u?searchForProperty(r,n+1,t):i[1]}v(searchForProperty,\\\"searchForProperty\\\");var MAX_SUGGESTIONS=12,TrieModel=function(){function r(e,t){t===void 0&&(t={}),this.languageUsesCasing=t.languageUsesCasing,this.applyCasing=t.applyCasing,this._trie=new Trie(e.root,e.totalWeight,t.searchTermToKey||defaultSearchTermToKey),this.breakWords=t.wordBreaker||default_,this.punctuation=t.punctuation}return v(r,\\\"TrieModel2\\\"),r.prototype.configure=function(e){var t;return this.configuration={leftContextCodePoints:e.maxLeftContextCodePoints,rightContextCodePoints:(t=e.maxRightContextCodePoints)!==null&&t!==void 0?t:0}},r.prototype.toKey=function(e){return this._trie.toKey(e)},r.prototype.predict=function(e,t){if(!e.insert&&!t.left&&!t.right&&t.startOfBuffer&&t.endOfBuffer)return u(this._trie.firstN(MAX_SUGGESTIONS).map(function(o){var l=o.text,s=o.p;return{transform:{insert:l,deleteLeft:0},displayAs:l,p:s}}));var n=applyTransform(e,t),i=e.deleteLeft-e.insert.kmwLength(),a=getLastPreCaretToken(this.breakWords,n);return u(this._trie.lookup(a).map(function(o){var l=o.text,s=o.p;return transformToSuggestion({insert:l,deleteLeft:i+a.kmwLength()},s)}));function u(o){var l,s,f=[];try{for(var c=(0,build_exports.__values)(o),d=c.next();!d.done;d=c.next()){var p=d.value;f.push({sample:p,p:p.p})}}catch(g){l={error:g}}finally{try{d&&!d.done&&(s=c.return)&&s.call(c)}finally{if(l)throw l.error}}return f}},Object.defineProperty(r.prototype,\\\"wordbreaker\\\",{get:function(){return this.breakWords},enumerable:!1,configurable:!0}),r.prototype.traverseFromRoot=function(){return new r.Traversal(this._trie.root,\\\"\\\")},r.Traversal=function(){function e(t,n){this.root=t,this.prefix=n}return v(e,\\\"class_1\\\"),e.prototype.children=function(){var t,n,i,a,u,o,l,s,f,c,d,p,g,w,b,T,S,_;return(0,build_exports.__generator)(this,function(y){switch(y.label){case 0:if(t=this.root,t.type!=\\\"internal\\\")return[3,9];n=v(function(C){var h,m,O,k,A,I,E,D,F,q,R,U;return(0,build_exports.__generator)(this,function(M){switch(M.label){case 0:if(h=t.children[C],!isHighSurrogate(C))return[3,12];if(h.type!=\\\"internal\\\")return[3,9];m=h,O=v(function(N){var j;return(0,build_exports.__generator)(this,function(V){switch(V.label){case 0:return j=i.prefix+C+N,[4,{char:C+N,traversal:function(){return new r.Traversal(m.children[N],j)}}];case 1:return V.sent(),[2]}})},\\\"_loop_3\\\"),M.label=1;case 1:M.trys.push([1,6,7,8]),k=(R=void 0,(0,build_exports.__values)(m.values)),A=k.next(),M.label=2;case 2:return A.done?[3,5]:(I=A.value,[5,O(I)]);case 3:M.sent(),M.label=4;case 4:return A=k.next(),[3,2];case 5:return[3,8];case 6:return E=M.sent(),R={error:E},[3,8];case 7:try{A&&!A.done&&(U=k.return)&&U.call(k)}finally{if(R)throw R.error}return[7];case 8:return[3,11];case 9:return D=h.entries[0].key,C=C+D[i.prefix.length+1],F=i.prefix+C,[4,{char:C,traversal:function(){return new r.Traversal(h,F)}}];case 10:M.sent(),M.label=11;case 11:return[3,16];case 12:return isSentinel(C)?[2,\\\"continue\\\"]:[3,13];case 13:return C?[3,14]:[2,\\\"continue\\\"];case 14:return q=i.prefix+C,[4,{char:C,traversal:function(){return new r.Traversal(h,q)}}];case 15:M.sent(),M.label=16;case 16:return[2]}})},\\\"_loop_1\\\"),i=this,y.label=1;case 1:y.trys.push([1,6,7,8]),a=(0,build_exports.__values)(t.values),u=a.next(),y.label=2;case 2:return u.done?[3,5]:(o=u.value,[5,n(o)]);case 3:y.sent(),y.label=4;case 4:return u=a.next(),[3,2];case 5:return[3,8];case 6:return l=y.sent(),b={error:l},[3,8];case 7:try{u&&!u.done&&(T=a.return)&&T.call(a)}finally{if(b)throw b.error}return[7];case 8:return[2];case 9:s=this.prefix,f=t.entries.filter(function(C){return C.key!=s&&s.length=n)return o}}}catch(b){i={error:b}}finally{try{s&&!s.done&&(a=l.return)&&a.call(l)}finally{if(i)throw i.error}}else{u.enqueue(r);for(var p=void 0,g=v(function(){if(isNode(p))if(p.type===\\\"leaf\\\")u.enqueueAll(p.entries);else{var b=p;u.enqueueAll(p.values.map(function(T){return b.children[T]}))}else if(o.push({text:p.content,p:p.weight/t}),o.length>=n)return{value:o}},\\\"_loop_4\\\");p=u.dequeue();){var w=g();if(typeof w==\\\"object\\\")return w.value}}return o}v(getSortedResults,\\\"getSortedResults\\\");function isNode(r){return\\\"type\\\"in r}v(isNode,\\\"isNode\\\");function defaultSearchTermToKey(r){return r.normalize(\\\"NFD\\\").replace(/[\\\\u0300-\\\\u036f]/g,\\\"\\\").toLowerCase()}v(defaultSearchTermToKey,\\\"defaultSearchTermToKey\\\"),extendString();var DummyModel=function(){function r(e){e=e||{},this._futureSuggestions=e.futureSuggestions?e.futureSuggestions.slice():[],e.punctuation&&(this.punctuation=e.punctuation)}return v(r,\\\"DummyModel2\\\"),r.prototype.configure=function(e){return this.configuration={leftContextCodePoints:e.maxLeftContextCodePoints,rightContextCodePoints:e.maxRightContextCodePoints},this.configuration},r.prototype.predict=function(e,t,n){var i=v(function(u){var o,l,s=[],f=u.length;try{for(var c=(0,build_exports.__values)(u),d=c.next();!d.done;d=c.next()){var p=d.value;s.push({sample:p,p:1})}}catch(g){o={error:g}}finally{try{d&&!d.done&&(l=c.return)&&l.call(c)}finally{if(o)throw o.error}}return s},\\\"makeUniformDistribution\\\");if(n)return i(n);var a=this._futureSuggestions.shift();return a?i(a):[]},r}(),dummy_model_default=DummyModel,correction_exports={};__export(correction_exports,{ClassicalDistanceCalculation:function(){return ClassicalDistanceCalculation},ContextTracker:function(){return ContextTracker},QUEUE_NODE_COMPARATOR:function(){return QUEUE_NODE_COMPARATOR},SearchNode:function(){return SearchNode},SearchResult:function(){return SearchResult},SearchSpace:function(){return SearchSpace},TrackedContextState:function(){return TrackedContextState},TrackedContextSuggestion:function(){return TrackedContextSuggestion},TrackedContextToken:function(){return TrackedContextToken}});var ClassicalDistanceCalculation=function(){function r(e){if(this.diagonalWidth=2,this.inputSequence=[],this.matchSequence=[],e){var t=e.resolvedDistances.length;this.resolvedDistances=Array(t);for(var n=0;n2*n)&&(i.sparse=!0),i},r.prototype.getCostAt=function(e,t,n){if(n===void 0&&(n=this.diagonalWidth),e<0||t<0)return e==-1&&t>=-1?t+1:t==-1&&e>=-1?e+1:Number.MAX_VALUE;var i=this.getTrueIndex(e,t,n);return i.sparse?Number.MAX_VALUE:this.resolvedDistances[i.row][i.col]},r.prototype.getFinalCost=function(){for(var e=this,t=e.getHeuristicFinalCost();t>e.diagonalWidth;)e=e.increaseMaxDistance(),t=e.getHeuristicFinalCost();return t},r.prototype.getHeuristicFinalCost=function(){return this.getCostAt(this.inputSequence.length-1,this.matchSequence.length-1)},r.prototype.hasFinalCostWithin=function(e){var t=this,n=t.getHeuristicFinalCost(),i=this.diagonalWidth;do{if(n<=e)return!0;if(i=0&&c>=0){var d=1;if(i=[\\\"transpose-start\\\"],f!=e-1){var p=e-f-1;i=i.concat(Array(p).fill(\\\"transpose-delete\\\")),d+=p}else{var p=t-c-1;i=i.concat(Array(p).fill(\\\"transpose-insert\\\")),d+=p}i.push(\\\"transpose-end\\\"),this.getCostAt(f-1,c-1)!=n-d&&(i=null),a=[f-1,c-1]}return i||(l==n-1?(i=[\\\"substitute\\\"],a=[e-1,t-1]):u==n-1?(i=[\\\"insert\\\"],a=[e,t-1]):o==n-1?(i=[\\\"delete\\\"],a=[e-1,t]):(i=[\\\"match\\\"],a=[e-1,t-1])),a[0]>=0&&a[1]>=0?this.editPath(a[0],a[1]).concat(i):a[0]>-1?Array(a[0]+1).fill(\\\"delete\\\").concat(i):a[1]>-1?Array(a[1]+1).fill(\\\"insert\\\").concat(i):i},r.getTransposeParent=function(e,t,n){if(t<0||n<0||e.inputSequence[t].key==e.matchSequence[n].key)return[-1,-1];for(var i=-1,a=t-1;a>=0;a--)if(e.inputSequence[a].key==e.matchSequence[n].key){i=a;break}for(var u=-1,a=n-1;a>=0;a--)if(e.matchSequence[a].key==e.inputSequence[t].key){u=a;break}return[i,u]},r.initialCostAt=function(e,t,n,i,a){var u=e.inputSequence[t].key==e.matchSequence[n].key?0:1,o=e.getCostAt(t-1,n-1)+u,l=i||e.getCostAt(t,n-1)+1,s=a||e.getCostAt(t-1,n)+1,f=Number.MAX_VALUE;if(t>0&&n>0){var c=(0,build_exports.__read)(r.getTransposeParent(e,t,n),2),d=c[0],p=c[1];f=e.getCostAt(d-1,p-1)+(t-d-1)+1+(n-p-1)}return Math.min(o,s,l,f)},r.prototype.getSubset=function(e,t){var n=new r(this);if(e>this.inputSequence.length||t>this.matchSequence.length)throw\\\"Invalid dimensions specified for trim operation\\\";n.inputSequence.splice(e),n.matchSequence.splice(t),n.resolvedDistances.splice(e);for(var i=this.getTrueIndex(e-1,t-1,this.diagonalWidth),a=i.col;a<=2*this.diagonalWidth;a++){var u=i.row-(a-i.col);if(u<0)break;if(a<0)n.resolvedDistances[u]=Array(2*n.diagonalWidth+1).fill(Number.MAX_VALUE);else{var o=2*this.diagonalWidth-a,l=n.resolvedDistances[u].splice(0,a+1),s=Array(o).fill(Number.MAX_VALUE);n.resolvedDistances[u]=l.concat(s)}}return n},r.forDiagonalOfAxis=function(e,t,n,i){for(var a=n-t=0){var f=s==0?o+2:Number.MAX_VALUE;l=r.initialCostAt(e,o,s,f,void 0);var c=l;if(s0&&u){var l=i+1;this.propagateUpdateFrom(e,t+1,n,l,a-1)}if(u&&o){var l=i+(e.inputSequence[t+1].key==e.matchSequence[n+1].key?0:1);this.propagateUpdateFrom(e,t+1,n+1,l,a);for(var s=-1,f=t+2;f0&&c>0){var d=i+(s-t-2)+1+(c-n-2);this.propagateUpdateFrom(e,s,c,d,e.diagonalWidth-1+c-s)}}},Object.defineProperty(r.prototype,\\\"mapKey\\\",{get:function(){var e=this.inputSequence.map(function(n){return n.key}).join(\\\"\\\"),t=this.matchSequence.map(function(n){return n.key}).join(\\\"\\\");return e+SENTINEL_CODE_UNIT+t+SENTINEL_CODE_UNIT+this.diagonalWidth},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,\\\"lastInputEntry\\\",{get:function(){return this.inputSequence[this.inputSequence.length-1]},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,\\\"lastMatchEntry\\\",{get:function(){return this.matchSequence[this.matchSequence.length-1]},enumerable:!1,configurable:!0}),r.computeDistance=function(e,t,n){n===void 0&&(n=1);var i=new r;n=n||1,i.diagonalWidth=n;for(var a=0;ae?t.p:e}).reduce(function(t,n){return t-Math.log(n)},0),this._inputCost},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,\\\"currentCost\\\",{get:function(){return SearchSpace.EDIT_DISTANCE_COST_SCALE*this.knownCost+this.inputSamplingCost},enumerable:!1,configurable:!0}),r.prototype.buildInsertionEdges=function(){var e,t,n=[];try{for(var i=(0,build_exports.__values)(this.currentTraversal.children()),a=i.next();!a.done;a=i.next()){var u=a.value,o=u.traversal(),l={key:u.char,traversal:o},s=this.calculation.addMatchChar(l),f=new r(this);f.calculation=s,f.priorInput=this.priorInput,f.currentTraversal=o,n.push(f)}}catch(c){e={error:c}}finally{try{a&&!a.done&&(t=i.return)&&t.call(i)}finally{if(e)throw e.error}}return n},r.prototype.buildDeletionEdges=function(e){var t,n,i=[];try{for(var a=(0,build_exports.__values)(e),u=a.next();!u.done;u=a.next()){var o=u.value;if(o.p0:!1},r.prototype.handleNextNode=function(){if(!this.hasNextMatchEntry())return{type:\\\"none\\\"};var e=this.selectionQueue.dequeue(),t=e.correctionQueue.dequeue(),n={type:\\\"intermediate\\\",cost:t.currentCost};if(this.processedEdgeSet[t.mapKey])return this.selectionQueue.enqueue(e),n;this.processedEdgeSet[t.mapKey]=!0;var i=!1;if(t.knownCost>2)return n;t.knownCost==2&&(i=!0);for(var a=0,u=0;u<=e.index;u++)a+=this.minInputCost[u];if(t.currentCost>a+2.5*r.EDIT_DISTANCE_COST_SCALE)return n;if(!i){var o=t.buildInsertionEdges();e.correctionQueue.enqueueAll(o)}if(e.index==this.tierOrdering.length-1)return this.completedPaths.push(t),this.selectionQueue.enqueue(e),{type:\\\"complete\\\",cost:t.currentCost,finalNode:t};var l=this.tierOrdering[e.index+1],s=l.index,f=[];i||(f=t.buildDeletionEdges(this.inputSequence[s-1]));var c=t.buildSubstitutionEdges(this.inputSequence[s-1]);return l.correctionQueue.enqueueAll(f.concat(c)),this.selectionQueue=new priority_queue_default(this.QUEUE_SPACE_COMPARATOR,this.tierOrdering),n},r.prototype.getBestMatches=function(e){var t,n,i,a,u,o,l,s,f,c,d,p,g,w,b,T;return(0,build_exports.__generator)(this,function(S){switch(S.label){case 0:if(t=this,n={},e==0?i=1/0:e==null||Number.isNaN(e)?i=r.DEFAULT_ALLOTTED_CORRECTION_TIME_INTERVAL:i=e,a=function(){function _(y,C){this.largestIntervals=[0],this.loopStart=this.start=Date.now(),this.maxExecutionTime=y,this.maxTrueTime=C}return v(_,\\\"ExecutionTimer2\\\"),_.prototype.startLoop=function(){this.loopStart=Date.now()},_.prototype.markIteration=function(){var y=Date.now(),C=y-this.loopStart;this.executionTime+=C,C&&(this.largestIntervals.length>2&&C>this.largestIntervals[0]?this.largestIntervals[0]=C:this.largestIntervals.push(C),this.largestIntervals.sort(),this.updateOutliers())},_.prototype.updateOutliers=function(){this.largestIntervals.length>2&&this.largestIntervals[2]>=2*(this.largestIntervals[0]+this.largestIntervals[1])&&(this.executionTime-=this.largestIntervals[2],this.largestIntervals.pop())},_.prototype.shouldTimeout=function(){var y=Date.now();return y-this.start>this.maxTrueTime?!0:this.executionTime>this.maxExecutionTime},_.prototype.resetOutlierCheck=function(){this.largestIntervals=[]},_}(),u=function(){function _(){this.currentCost=Number.MIN_SAFE_INTEGER,this.entries=[]}return v(_,\\\"BatchingAssistant2\\\"),_.prototype.checkAndAdd=function(y){var C=null;y.currentCost>this.currentCost&&(C=this.tryFinalize(),this.currentCost=y.currentCost);var h=y.calculation.matchSequence.map(function(m){return m.key}).join(\\\"\\\");return t.returnedValues[h]||(t.returnedValues[h]=y),n[h]||(this.entries.push(new SearchResult(y)),n[h]=y),C},_.prototype.tryFinalize=function(){var y=null;return this.entries.length>0&&(y=this.entries,this.entries=[]),y},_}(),o=new u,l=new a(i*1.5,i),s=Object.values(this.returnedValues),!(s.length>0))return[3,6];f=new priority_queue_default(QUEUE_NODE_COMPARATOR,s),l.startLoop(),S.label=1;case 1:return f.count>0?(c=f.dequeue(),c.isFullReplacement?[3,1]:(d=o.checkAndAdd(c),l.markIteration(),d?[4,d]:[3,3])):[3,4];case 2:S.sent(),S.label=3;case 3:return[3,1];case 4:return p=o.tryFinalize(),p?[4,p]:[3,6];case 5:S.sent(),S.label=6;case 6:l.resetOutlierCheck(),l.startLoop(),g=!1,S.label=7;case 7:w=void 0;do w=this.handleNextNode(),l.markIteration(),l.shouldTimeout()&&(g=!0);while(!g&&w.type==\\\"intermediate\\\");if(b=void 0,w.type==\\\"none\\\")return[3,10];if(w.type==\\\"complete\\\"){if(w.finalNode.isFullReplacement)return[3,10];b=o.checkAndAdd(w.finalNode)}return b?[4,b]:[3,9];case 8:S.sent(),S.label=9;case 9:if(!g&&this.hasNextMatchEntry())return[3,7];S.label=10;case 10:return T=o.tryFinalize(),T?[4,T]:[3,12];case 11:S.sent(),S.label=12;case 12:return[2,null]}})},r.EDIT_DISTANCE_COST_SCALE=5,r.MIN_KEYSTROKE_PROBABILITY=1e-4,r.DEFAULT_ALLOTTED_CORRECTION_TIME_INTERVAL=33,r}(),TransformUtils=function(){function r(){}return v(r,\\\"TransformUtils2\\\"),r.isWhitespace=function(e){var t=/^[\\\\u0009\\\\u000A\\\\u000D\\\\u0020\\\\u00a0\\\\u1680\\\\u2000\\\\u2001\\\\u2002\\\\u2003\\\\u2004\\\\u2005\\\\u2006\\\\u2007\\\\u2008\\\\u2009\\\\u200a\\\\u200b\\\\u2028\\\\u2029\\\\u202f\\\\u205f\\\\u3000]+$/i;return e.insert.match(t)!=null},r.isBackspace=function(e){return e.insert==\\\"\\\"&&e.deleteLeft>0&&!e.deleteRight},r.isEmpty=function(e){return e.insert==\\\"\\\"&&e.deleteLeft==0&&!e.deleteRight},r}(),transformUtils_default=TransformUtils;function textToCharTransforms(r,e){for(var t=[],n=0;n0&&e.transformDistributions.forEach(function(n){return t.searchSpace[0].addInput(n)})},r.prototype.pushWhitespaceToTail=function(e){e===void 0&&(e=null);var t=new TrackedContextToken;t.transformDistributions=e?[e]:[],t.raw=null,this.tokens.push(t)},r.prototype.replaceTailForBackspace=function(e,t){this.tokens.pop();var n=textToCharTransforms(e,t).map(function(a){return[{sample:a,p:1}]}),i=new TrackedContextToken;i.raw=e,i.transformDistributions=n,this.pushTail(i)},r.prototype.updateTail=function(e,t){var n=this.tail;t=t||(t===\\\"\\\"?\\\"\\\":n.raw),e&&e.length>0&&(n.transformDistributions.push(e),this.searchSpace&&this.searchSpace.forEach(function(i){return i.addInput(e)})),n.raw=t},r.prototype.toRawTokenization=function(){var e,t,n=[];try{for(var i=(0,build_exports.__values)(this.tokens),a=i.next();!a.done;a=i.next()){var u=a.value;u.currentText!==null&&n.push(u.currentText)}}catch(o){e={error:o}}finally{try{a&&!a.done&&(t=i.return)&&t.call(i)}finally{if(e)throw e.error}}return n},r}(),CircularArray=function(){function r(e){e===void 0&&(e=r.DEFAULT_ARRAY_SIZE),this.currentHead=0,this.currentTail=0,this.circle=Array(e)}return v(r,\\\"CircularArray2\\\"),Object.defineProperty(r.prototype,\\\"count\\\",{get:function(){var e=this.currentHead-this.currentTail;return e<0&&(e=e+this.circle.length),e},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,\\\"maxCount\\\",{get:function(){return this.circle.length},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,\\\"oldest\\\",{get:function(){if(this.count!=0)return this.item(0)},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,\\\"newest\\\",{get:function(){if(this.count!=0)return this.item(this.count-1)},enumerable:!1,configurable:!0}),r.prototype.enqueue=function(e){var t=null,n=(this.currentHead+1)%this.maxCount;return n==this.currentTail&&(t=this.circle[this.currentTail],this.currentTail=(this.currentTail+1)%this.maxCount),this.circle[this.currentHead]=e,this.currentHead=n,t},r.prototype.dequeue=function(){if(this.currentTail==this.currentHead)return null;var e=this.circle[this.currentTail];return this.currentTail=(this.currentTail+1)%this.maxCount,e},r.prototype.popNewest=function(){if(this.currentTail==this.currentHead)return null;var e=this.circle[this.currentHead];return this.currentHead=(this.currentHead-1+this.maxCount)%this.maxCount,e},r.prototype.item=function(e){if(e>=this.count)throw\\\"Invalid array index\\\";var t=(this.currentTail+e)%this.maxCount;return this.circle[t]},r.DEFAULT_ARRAY_SIZE=5,r}(),ContextTracker=function(r){(0,build_exports.__extends)(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return v(e,\\\"ContextTracker2\\\"),e.attemptMatchContext=function(t,n,i){var a=n.toRawTokenization(),u=ClassicalDistanceCalculation.computeDistance(a.map(function(h){return{key:h}}),t.map(function(h){return{key:h}}),1),o=u.editPath(),l=!1,s=!1;if(o.length>1){if(o[0]==\\\"insert\\\"&&!(o[1]==\\\"substitute\\\"&&o.length==2)||o[0].indexOf(\\\"transpose\\\")>=0)return null;o[0]==\\\"delete\\\"&&(l=!0)}var f=o.length-1,c=!1;if(o[f]==\\\"delete\\\"||o[0].indexOf(\\\"transpose\\\")>=0||(o[f]==\\\"insert\\\"?s=!0:f>0&&o[f-1]==\\\"insert\\\"&&o[f]==\\\"substitute\\\"&&(s=!0,c=!0),f>0&&o[f-1]==\\\"delete\\\"&&o[f]==\\\"substitute\\\"))return null;for(var d=1;d1)if(l&&p.popHead(),s){var _=t[t.length-1],y=new TrackedContextToken;y.raw=_,b||!w?(p.pushWhitespaceToTail(i!=null?i:[]),y.transformDistributions=[]):(p.pushWhitespaceToTail(),y.transformDistributions=i?[i]:[]),p.pushTail(y)}else T?p.replaceTailForBackspace(S,w.id):p.updateTail(w?i:null,S);else if(o[f]==\\\"insert\\\"){var C=new TrackedContextToken;C.raw=t[0],C.transformDistributions=[i],p.pushTail(C)}else T?p.replaceTailForBackspace(S,w.id):p.updateTail(w?i:null,S);return p},e.modelContextState=function(t,n,i){var a=t.map(function(l){var s=new TrackedContextToken;return s.raw=l,s.raw?s.transformDistributions=textToCharTransforms(s.raw).map(function(f){return[{sample:f,p:1}]}):s.transformDistributions=[],s}),u=new TrackedContextState(i);for(a.length>0&&u.pushTail(a.splice(0,1)[0]);a.length>0;)u.pushWhitespaceToTail(),u.pushTail(a.splice(0,1)[0]);if(u.tokens.length==0){var o=new TrackedContextToken;o.raw=\\\"\\\",u.pushTail(o)}return u},e.prototype.analyzeState=function(t,n,i){if(!t.traverseFromRoot)throw\\\"This lexical model does not provide adequate data for correction algorithms and context reuse\\\";var a=tokenize(t.wordbreaker||default_,n);if(a.left.length>0)for(var u=this.count-1;u>=0;u--){var o=e.attemptMatchContext(a.left,this.item(u),i);if(o)return o.taggedContext=n,o!=this.item(u)&&this.enqueue(o),o}var l=e.modelContextState(a.left,i,t);return l.taggedContext=n,this.enqueue(l),l},e}(CircularArray),ModelCompositor=function(){function r(e,t){this.SUGGESTION_ID_SEED=0,this.testMode=!1,this.lexicalModel=e,e.traverseFromRoot&&(this.contextTracker=new ContextTracker),this.punctuation=r.determinePunctuationFromModel(e),this.testMode=!!t}return v(r,\\\"ModelCompositor2\\\"),r.prototype.predictFromCorrections=function(e,t){var n,i,a=[],u=v(function(c){var d=o.lexicalModel.predict(c.sample,t),p=d.map(function(g){var w=c.sample,b=c.p;w.id!==void 0&&(g.sample.transformId=w.id);var T={sample:g.sample,p:g.p*b};return T},o);a=a.concat(p)},\\\"_loop_1\\\"),o=this;try{for(var l=(0,build_exports.__values)(e),s=l.next();!s.done;s=l.next()){var f=s.value;u(f)}}catch(c){n={error:c}}finally{try{s&&!s.done&&(i=l.return)&&i.call(l)}finally{if(n)throw n.error}}return a},r.prototype.predict=function(e,t){var n,i,a,u,o=[],l=this.lexicalModel,s=this.punctuation;e instanceof Array?e.length==0&&e.push({sample:{insert:\\\"\\\",deleteLeft:0},p:1}):e=[{sample:e,p:1}];var f=e.sort(function(x,P){return P.p-x.p})[0].sample,c=transformUtils_default.isWhitespace(f),d=transformUtils_default.isBackspace(f),p=applyTransform(f,t),g=this.wordbreak(p),w=null,b=[],T,S=null;if(this.contextTracker){var y=this.contextTracker.analyzeState(this.lexicalModel,t,null);S=this.contextTracker.analyzeState(this.lexicalModel,p,transformUtils_default.isEmpty(f)?null:e);var C=S.searchSpace[0],h=0,m=S.tokens,O=m.length,k=m.length-y.tokens.length;O==0||k>0?(h=0,transformUtils_default.isWhitespace(f)&&(T=f,t=p,y=S)):k<0?h=this.wordbreak(p).kmwLength()+f.deleteLeft:h=this.wordbreak(t).kmwLength();var A=m[m.length-1],I=A.transformDistributions.length<=1,E=void 0,D=this.testMode?0:SearchSpace.DEFAULT_ALLOTTED_CORRECTION_TIME_INTERVAL;try{for(var F=(0,build_exports.__values)(C.getBestMatches(D)),q=F.next();!q.done;q=F.next()){var R=q.value,_=R.map(function(P){var W=P.matchString,H;P.inputSequence.length>0?H=P.inputSequence[P.inputSequence.length-1].sample:H=f;var ie={insert:W,deleteLeft:h,id:f.id},ee=P.totalCost;return I&&(ee*=r.SINGLE_CHAR_KEY_PROB_EXPONENT),{sample:ie,p:Math.exp(-ee)}},this),U=this.predictFromCorrections(_,t);U.length>0&&E===void 0&&(E=-Math.log(_[0].p)),b=b.concat(U);var M=R[0].totalCost;if(M>=E+8)break;if(b.length>=r.MAX_SUGGESTIONS){if(M>=E+4)break;if(b.sort(function(P,W){return W.p-P.p}),b[r.MAX_SUGGESTIONS-1].p>Math.exp(-M))break}}}catch(x){n={error:x}}finally{try{q&&!q.done&&(i=F.return)&&i.call(F)}finally{if(n)throw n.error}}}else{var _=void 0;c?(_=[{sample:f,p:1}],T=f):_=e.map(function(x){var P=x.sample;return transformUtils_default.isWhitespace(P)&&!c||transformUtils_default.isBackspace(P)&&!d?null:x}),_=_.filter(function(x){return!!x}),b=this.predictFromCorrections(_,t)}var N={},j=null;l.languageUsesCasing&&(j=this.detectCurrentCasing(p));var V=this.wordbreak(t);try{for(var G=(0,build_exports.__values)(b),B=G.next();!B.done;B=G.next()){var L=B.value,Q=L.sample.displayAs,X=Q==g;if(this.lexicalModel.languageUsesCasing&&(X=X||Q==this.lexicalModel.applyCasing(\\\"lower\\\",g)),X)if(w)w.p&&L.p&&(w.p+=L.p);else{var z=L.sample.transform,Y={insert:g,deleteLeft:z.deleteLeft,deleteRight:z.deleteRight,id:z.id},te=transformToSuggestion(Y,L.p);w=this.toAnnotatedSuggestion(te,\\\"keep\\\",quote_behavior_default.noQuotes),w.matchesModel=!0,w.transformId=L.sample.transformId}else{j&&j!=\\\"lower\\\"&&(this.applySuggestionCasing(L.sample,V,j),Q=L.sample.displayAs);var J=N[Q];J?J.p+=L.p:N[Q]=L}}}catch(x){a={error:x}}finally{try{B&&!B.done&&(u=G.return)&&u.call(G)}finally{if(a)throw a.error}}if(!w&&g!=\\\"\\\"){var Y=(0,build_exports.__assign)({},f),$=transformToSuggestion(Y,1);$.displayAs=g,w=this.toAnnotatedSuggestion($,\\\"keep\\\"),w.matchesModel=!1}for(var re in N){var ne=N[re];o.push(ne)}o=o.sort(function(x,P){return P.p-x.p});var K=o.splice(0,r.MAX_SUGGESTIONS).map(function(x){return x.sample.p&&(x.sample[\\\"lexical-p\\\"]=x.sample.p,x.sample[\\\"correction-p\\\"]=x.p/x.sample.p,x.sample.p=x.p),x.sample});w&&(K=[w].concat(K));var Z=this;return K.forEach(function(x){if(!t.right)x.transform.insert+=s.insertAfterWord;else{var P=Z.tokenize(t);P&&P.caretSplitsToken&&(x.transform.insert+=s.insertAfterWord)}if(T){var W=buildMergedTransform(T,x.transform);W.id=x.transformId;var H=x;H.transform=W}x.id=Z.SUGGESTION_ID_SEED,Z.SUGGESTION_ID_SEED++}),S&&(S.tail.replacements=K.map(function(x){return{suggestion:x,tokenWidth:1}})),K},r.prototype.applySuggestionCasing=function(e,t,n){var i=t.kmwLength()-e.transform.deleteLeft;i>0&&(e.transform.deleteLeft+=i,e.transform.insert=t.kmwSubstr(0,i)+e.transform.insert),e.transform.insert=this.lexicalModel.applyCasing(n,e.transform.insert),e.displayAs=this.lexicalModel.applyCasing(n,e.displayAs)},r.prototype.toAnnotatedSuggestion=function(e,t,n){n===void 0&&(n=quote_behavior_default.default);var i=quote_behavior_default,a=i.noQuotes;return(t==\\\"keep\\\"||t==\\\"revert\\\")&&(a=i.useQuotes),{transform:e.transform,transformId:e.transformId,displayAs:i.apply(n,e.displayAs,this.punctuation,a),tag:t,p:e.p}},r.determinePunctuationFromModel=function(e){var t=DEFAULT_PUNCTUATION;if(!e.punctuation)return t;var n=e.punctuation,i=n.insertAfterWord;i!==\\\"\\\"&&!i&&(i=t.insertAfterWord);var a=n.quotesForKeepSuggestion;a||(a=t.quotesForKeepSuggestion);var u=n.isRTL;return{insertAfterWord:i,quotesForKeepSuggestion:a,isRTL:u}},r.prototype.acceptSuggestion=function(e,t,n){var i=e.transform,a=t.left.kmwSubstr(-i.deleteLeft,i.deleteLeft),u=i.insert.kmwLength(),o={insert:a,deleteLeft:u},l=t;n&&(o=buildMergedTransform(o,n),l=applyTransform(n,l));var s,f=this.tokenize(l);f?(f.left.length>0?s=f.left[f.left.length-1]:s=\\\"\\\",s+=f.caretSplitsToken?f.right[0]:\\\"\\\"):s=this.wordbreak(l);var c=transformToSuggestion(o);c.displayAs=s;var d=this.toAnnotatedSuggestion(c,\\\"revert\\\");if(e.transformId!=null&&(d.transformId=-e.transformId),e.id!=null?d.id=-e.id:(d.id=-this.SUGGESTION_ID_SEED,this.SUGGESTION_ID_SEED++),this.contextTracker){var p=this.contextTracker.newest;p||(p=this.contextTracker.analyzeState(this.lexicalModel,t)),p.tail.activeReplacementId=e.id;var g=applyTransform(e.transform,t);this.contextTracker.analyzeState(this.lexicalModel,g)}return d},r.prototype.applyReversion=function(e,t){var n=this,i=v(function(){var s=applyTransform(e.transform,t),f=n.predict({insert:\\\"\\\",deleteLeft:0},s);return f.forEach(function(c){c.transformId=-e.transformId}),f},\\\"fallbackSuggestions\\\");if(!this.contextTracker)return i();for(var a=!1,u=this.contextTracker.count-1;u>=0;u--){var o=this.contextTracker.item(u);if(o.tail.activeReplacementId==-e.id){a=!0;break}}if(!a)return i();for(;this.contextTracker.newest.tail.activeReplacementId!=-e.id;)this.contextTracker.popNewest();this.contextTracker.newest.tail.revert();var l=this.contextTracker.newest.tail.replacements.map(function(s){return s.suggestion});return l.forEach(function(s){s.transformId=-e.transformId}),l},r.prototype.wordbreak=function(e){var t=this.lexicalModel;if(t.wordbreaker||!t.wordbreak){var n=t.wordbreaker||default_;return wordbreak(n,e)}else return t.wordbreak(e)},r.prototype.tokenize=function(e){var t=this.lexicalModel;return t.wordbreaker?tokenize(t.wordbreaker,e):null},r.prototype.resetContext=function(e){if(this.contextTracker){var t=tokenize(this.lexicalModel.wordbreaker||default_,e),n=ContextTracker.modelContextState(t.left,null,this.lexicalModel);this.contextTracker.enqueue(n)}},r.prototype.detectCurrentCasing=function(e){var t,n=this.lexicalModel,i=this.wordbreak(e);if(!n.languageUsesCasing)throw\\\"Invalid attempt to detect casing: languageUsesCasing is set to false\\\";if(!n.applyCasing)throw\\\"Invalid LMLayer state: languageUsesCasing is set to true, but no applyCasing function exists\\\";return e.casingForm==\\\"upper\\\"||e.casingForm==\\\"initial\\\"?e.casingForm:n.applyCasing(\\\"lower\\\",i)==i?\\\"lower\\\":n.applyCasing(\\\"upper\\\",i)==i?i.kmwLength()>1?\\\"upper\\\":\\\"initial\\\":n.applyCasing(\\\"initial\\\",i)==i?\\\"initial\\\":(t=e.casingForm)!==null&&t!==void 0?t:null},r.MAX_SUGGESTIONS=12,r.SINGLE_CHAR_KEY_PROB_EXPONENT=16,r}(),model_compositor_default=ModelCompositor,DEFAULT_PUNCTUATION={quotesForKeepSuggestion:{open:\\\"\\\\u201C\\\",close:\\\"\\\\u201D\\\"},insertAfterWord:\\\" \\\"};extendString();var LMLayerWorker=function(){function r(e){e===void 0&&(e={importScripts:null,postMessage:null}),this._testMode=!1,this._postMessage=e.postMessage||postMessage,this._importScripts=e.importScripts||importScripts,this.setupConfigState()}return v(r,\\\"LMLayerWorker2\\\"),r.prototype.error=function(e,t){this.cast(\\\"error\\\",{log:e,error:t&&t.stack?t.stack:void 0})},r.prototype.onMessage=function(e){var t=e.data.message;if(!t)throw new Error(\\\"Missing required 'message' property: \\\".concat(e.data));var n=e.data;if(n.message==\\\"load\\\"){var i=n,a=!1;if(this._currentModelSource&&i.source.type==this._currentModelSource.type&&(i.source.type==\\\"file\\\"&&i.source.file==this._currentModelSource.file||i.source.type==\\\"raw\\\"&&i.source.code==this._currentModelSource.code)&&(a=!0),a){typeof console!=\\\"undefined\\\"&&console.warn(\\\"Duplicate model load message detected - squashing!\\\");return}else this._currentModelSource=i.source}else n.message==\\\"unload\\\"&&(this._currentModelSource=null);this.state.handleMessage(n)},r.prototype.cast=function(e,t){var n=this._postMessage;n((0,build_exports.__assign)({message:e},t))},r.prototype.loadModel=function(e){try{var t=e.configure(this._platformCapabilities);t.leftContextCodePoints||(t.leftContextCodePoints=t.leftContextCodeUnits),t.rightContextCodePoints||(t.rightContextCodePoints=t.rightContextCodeUnits),t.leftContextCodePoints||(t.leftContextCodePoints=this._platformCapabilities.maxLeftContextCodePoints),t.rightContextCodePoints||(t.rightContextCodePoints=this._platformCapabilities.maxRightContextCodePoints||0),e.languageUsesCasing&&!e.applyCasing&&(e.applyCasing=defaultApplyCasing);var n=this.transitionToReadyState(e);t.wordbreaksAfterSuggestions===void 0&&(t.wordbreaksAfterSuggestions=n.punctuation.insertAfterWord!=\\\"\\\"),this.cast(\\\"ready\\\",{configuration:t})}catch(i){this.error(\\\"loadModel failed!\\\",i)}},r.prototype.loadModelFile=function(e){try{this._importScripts(e)}catch(t){this.error(\\\"Error occurred when attempting to load dictionary\\\",t)}},r.prototype.unloadModel=function(){this.transitionToLoadingState()},r.prototype.setupConfigState=function(){var e=this;this.state={name:\\\"unconfigured\\\",handleMessage:function(t){if(t.message!==\\\"config\\\")throw new Error(\\\"invalid message; expected 'config' but got \\\".concat(t.message));e._platformCapabilities=t.capabilities,e._testMode=!!t.testMode,e.transitionToLoadingState()}}},r.prototype.transitionToLoadingState=function(){var e=this;this.state={name:\\\"modelless\\\",handleMessage:function(t){if(t.message!==\\\"load\\\")throw new Error(\\\"invalid message; expected 'load' but got \\\".concat(t.message));if(t.source.type==\\\"file\\\")e.loadModelFile(t.source.file);else{var n=t.source.code,i=new Function(\\\"LMLayerWorker\\\",\\\"models\\\",\\\"correction\\\",\\\"wordBreakers\\\",n);i(e,models_exports,correction_exports,obj_exports)}}}},r.prototype.transitionToReadyState=function(e){var t=this,n=new model_compositor_default(e,this._testMode);return this.state={name:\\\"ready\\\",handleMessage:function(i){switch(i.message){case\\\"predict\\\":var a=i.transform,c=i.context,f=n.predict(a,c);t.cast(\\\"suggestions\\\",{token:i.token,suggestions:f});break;case\\\"wordbreak\\\":var u=wordbreak(e.wordbreaker||default_,i.context);t.cast(\\\"currentword\\\",{token:i.token,word:u});break;case\\\"unload\\\":t.unloadModel();break;case\\\"accept\\\":var o=i.suggestion,c=i.context,l=i.postTransform,s=n.acceptSuggestion(o,c,l);t.cast(\\\"postaccept\\\",{token:i.token,reversion:s});break;case\\\"revert\\\":var s=i.reversion,c=i.context,f=n.applyReversion(s,c);t.cast(\\\"postrevert\\\",{token:i.token,suggestions:f});break;case\\\"reset-context\\\":var c=i.context;n.resetContext(c);break;default:throw new Error(\\\"invalid message; expected one of {'predict', 'wordbreak', 'accept', 'revert', 'reset-context', 'unload'} but got \\\".concat(i.message))}},compositor:n},n},r.install=function(e){var t=new r({postMessage:e.postMessage,importScripts:e.importScripts.bind(e)});return e.onmessage=t.onMessage.bind(t),t.self=e,e.LMLayerWorker=t,e.models=models_exports,e.correction=correction_exports,e.wordBreakers=obj_exports,t},r}(),obj_default=LMLayerWorker;typeof self!=\\\"undefined\\\"&&\\\"postMessage\\\"in self&&\\\"importScripts\\\"in self?obj_default.install(self):window.LMLayerWorker=obj_default;\\n\";\n\n// Sourcemaps have been omitted for this release build.\nexport var LMLayerWorkerSourcemapComment = \"\";\n\n// --END:LMLayerWorkerCode\n", + "import unwrap from '../unwrap.js';\r\nimport { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from \"@keymanapp/lm-worker/worker-main.wrapped.min.js\";\r\n\r\nexport default class DefaultWorker {\r\n static constructInstance(): Worker {\r\n return new Worker(this.asBlobURI(LMLayerWorkerCode));\r\n }\r\n\r\n /**\r\n * Converts the INSIDE of a function into a blob URI that can\r\n * be passed as a valid URI for a Worker.\r\n * @param fn Function whose body will be referenced by a URI.\r\n *\r\n * This function makes the following possible:\r\n *\r\n * let worker = new Worker(LMLayer.asBlobURI(function myWorkerCode () {\r\n * postMessage('inside Web Worker')\r\n * function onmessage(event) {\r\n * // handle message inside Web Worker.\r\n * }\r\n * }));\r\n */\r\n static asBlobURI(encodedSrc: string): string {\r\n let code = unwrap(encodedSrc);\r\n\r\n // If this is definitively set to either true or false, tree-shaking can take effect.\r\n // An imported const variable doesn't seem to do it, though.\r\n // if(false) {\r\n code += '\\n' + LMLayerWorkerSourcemapComment;\r\n // }\r\n let blob = new Blob([code], { type: 'text/javascript' });\r\n return URL.createObjectURL(blob);\r\n }\r\n}", + "// Defines a 'polyfill' of sorts for NPM's events module\r\n\r\n/// \r\n\r\nimport ContextWindow from \"./contextWindow.js\";\r\nimport LanguageProcessor from \"./prediction/languageProcessor.js\";\r\nimport type ModelSpec from \"./prediction/modelSpec.js\";\r\nimport { globalObject, DeviceSpec } from \"@keymanapp/web-utils\";\r\n\r\nimport {\r\n type Alternate,\r\n Codes,\r\n isEmptyTransform,\r\n type Keyboard,\r\n KeyboardInterface,\r\n KeyboardProcessor,\r\n type KeyEvent,\r\n Mock,\r\n type OutputTarget,\r\n type ProcessorInitOptions,\r\n RuleBehavior,\r\n SystemStoreIDs,\r\n type TextTransform\r\n} from \"@keymanapp/keyboard-processor\";\r\n\r\nexport default class InputProcessor {\r\n public static readonly DEFAULT_OPTIONS: ProcessorInitOptions = {\r\n baseLayout: 'us'\r\n }\r\n\r\n /**\r\n * Indicates the device (platform) to be used for non-keystroke events,\r\n * such as those sent to `begin postkeystroke` and `begin newcontext`\r\n * entry points.\r\n */\r\n private contextDevice: DeviceSpec;\r\n private kbdProcessor: KeyboardProcessor;\r\n private lngProcessor: LanguageProcessor;\r\n\r\n constructor(device: DeviceSpec, predictiveTextWorker: Worker, options?: ProcessorInitOptions) {\r\n if(!device) {\r\n throw new Error('device must be defined');\r\n }\r\n\r\n if(!options) {\r\n options = InputProcessor.DEFAULT_OPTIONS;\r\n }\r\n\r\n this.contextDevice = device;\r\n this.kbdProcessor = new KeyboardProcessor(device, options);\r\n this.lngProcessor = new LanguageProcessor(predictiveTextWorker);\r\n }\r\n\r\n public get languageProcessor(): LanguageProcessor {\r\n return this.lngProcessor;\r\n }\r\n\r\n public get keyboardProcessor(): KeyboardProcessor {\r\n return this.kbdProcessor;\r\n }\r\n\r\n public get keyboardInterface(): KeyboardInterface {\r\n return this.keyboardProcessor.keyboardInterface;\r\n }\r\n\r\n public get activeKeyboard(): Keyboard {\r\n return this.keyboardInterface.activeKeyboard;\r\n }\r\n\r\n public set activeKeyboard(keyboard: Keyboard) {\r\n this.keyboardInterface.activeKeyboard = keyboard;\r\n\r\n // All old deadkeys and keyboard-specific cache should immediately be invalidated\r\n // on a keyboard change.\r\n this.resetContext();\r\n }\r\n\r\n public get activeModel(): ModelSpec {\r\n return this.languageProcessor.activeModel;\r\n }\r\n\r\n /**\r\n * Simulate a keystroke according to the touched keyboard button element\r\n *\r\n * Handles default output and keyboard processing for both OSK and physical keystrokes.\r\n *\r\n * @param {Object} keyEvent The abstracted KeyEvent to use for keystroke processing\r\n * @param {Object} outputTarget The OutputTarget receiving the KeyEvent\r\n * @returns {Object} A RuleBehavior object describing the cumulative effects of\r\n * all matched keyboard rules.\r\n */\r\n processKeyEvent(keyEvent: KeyEvent, outputTarget: OutputTarget): RuleBehavior {\r\n const kbdMismatch = keyEvent.srcKeyboard && this.activeKeyboard != keyEvent.srcKeyboard;\r\n const trueActiveKeyboard = this.activeKeyboard;\r\n\r\n try {\r\n if(kbdMismatch) {\r\n // Avoid force-reset of context per our setter above.\r\n this.keyboardInterface.activeKeyboard = keyEvent.srcKeyboard;\r\n }\r\n\r\n return this._processKeyEvent(keyEvent, outputTarget);\r\n } finally {\r\n if(kbdMismatch) {\r\n // Restore our \"current\" activeKeyboard to its setting before the mismatching KeyEvent.\r\n this.keyboardInterface.activeKeyboard = trueActiveKeyboard;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Acts as the core of `processKeyEvent` once we're comfortable asserting that the incoming\r\n * keystroke matches the current `activeKeyboard`.\r\n * @param keyEvent\r\n * @param outputTarget\r\n * @returns\r\n */\r\n private _processKeyEvent(keyEvent: KeyEvent, outputTarget: OutputTarget): RuleBehavior {\r\n let formFactor = keyEvent.device.formFactor;\r\n let fromOSK = keyEvent.isSynthetic;\r\n\r\n // The default OSK layout for desktop devices does not include nextlayer info, relying on modifier detection here.\r\n // It's the OSK equivalent to doModifierPress on 'desktop' form factors.\r\n if((formFactor == DeviceSpec.FormFactor.Desktop || !this.activeKeyboard || this.activeKeyboard.usesDesktopLayoutOnDevice(keyEvent.device)) && fromOSK) {\r\n // If it's a desktop OSK style and this triggers a layer change,\r\n // a modifier key was clicked. No output expected, so it's safe to instantly exit.\r\n if(this.keyboardProcessor.selectLayer(keyEvent)) {\r\n return new RuleBehavior();\r\n }\r\n }\r\n\r\n // Will handle keystroke-based non-layer change modifier & state keys, mapping them through the physical keyboard's version\r\n // of state management. `doModifierPress` must always run.\r\n if(this.keyboardProcessor.doModifierPress(keyEvent, outputTarget, !fromOSK)) {\r\n // If run on a desktop platform, we know that modifier & state key presses may not\r\n // produce output, so we may make an immediate return safely.\r\n if(!fromOSK) {\r\n return new RuleBehavior();\r\n }\r\n }\r\n\r\n // If suggestions exist AND space is pressed, accept the suggestion and do not process the keystroke.\r\n // If a suggestion was just accepted AND backspace is pressed, revert the change and do not process the backspace.\r\n // We check the first condition here, while the prediction UI handles the second through the try__() methods below.\r\n if(this.languageProcessor.isActive) {\r\n // The following code relies on JS's logical operator \"short-circuit\" properties to prevent unwanted triggering of the second condition.\r\n\r\n // Can the suggestion UI revert a recent suggestion? If so, do that and swallow the backspace.\r\n if((keyEvent.kName == \"K_BKSP\" || keyEvent.Lcode == Codes.keyCodes[\"K_BKSP\"]) && this.languageProcessor.tryRevertSuggestion()) {\r\n return new RuleBehavior();\r\n // Can the suggestion UI accept an existing suggestion? If so, do that and swallow the space character.\r\n } else if((keyEvent.kName == \"K_SPACE\" || keyEvent.Lcode == Codes.keyCodes[\"K_SPACE\"]) && this.languageProcessor.tryAcceptSuggestion('space')) {\r\n return new RuleBehavior();\r\n }\r\n }\r\n\r\n // // ...end I3363 (Build 301)\r\n\r\n // Create a \"mock\" backup of the current outputTarget in its pre-input state.\r\n // Current, long-existing assumption - it's DOM-backed.\r\n let preInputMock = Mock.from(outputTarget, true);\r\n\r\n const startingLayerId = this.keyboardProcessor.layerId;\r\n\r\n // We presently need the true keystroke to run on the FULL context. That index is still\r\n // needed for some indexing operations when comparing two different output targets.\r\n let ruleBehavior = this.keyboardProcessor.processKeystroke(keyEvent, outputTarget);\r\n\r\n // Swap layer as appropriate.\r\n if(keyEvent.kNextLayer) {\r\n this.keyboardProcessor.selectLayer(keyEvent);\r\n }\r\n\r\n // If it's a key that we 'optimize out' of our fat-finger correction algorithm,\r\n // we MUST NOT trigger it for this keystroke.\r\n let isOnlyLayerSwitchKey = Codes.isKnownOSKModifierKey(keyEvent.kName);\r\n\r\n // Best-guess stopgap for possible custom modifier keys.\r\n // If a key (1) does not affect the context and (2) shifts the active layer,\r\n // we assume it's a modifier key. (Touch keyboards may define custom modifier keys.)\r\n //\r\n // Note: this will mean we won't generate alternates in the niche scenario where:\r\n // 1. Keypress does not alter the actual context\r\n // 2. It DOES emit a deadkey with an earlier processing rule.\r\n // 3. The FINAL processing rule does not match.\r\n // 4. The key ALSO signals a layer shift.\r\n // If any of the four above conditions aren't met - no problem!\r\n // So it's a pretty niche scenario.\r\n\r\n if(isEmptyTransform(ruleBehavior?.transcription?.transform) && keyEvent.kNextLayer) {\r\n isOnlyLayerSwitchKey = true;\r\n }\r\n\r\n const keepRuleBehavior = ruleBehavior != null;\r\n // Should we swallow any further processing of keystroke events for this keydown-keypress sequence?\r\n if(keepRuleBehavior) {\r\n // alternates are our fat-finger alternate outputs. We don't build these for keys we detect as\r\n // layer switch keys\r\n let alternates = isOnlyLayerSwitchKey ? null : this.buildAlternates(ruleBehavior, keyEvent, preInputMock);\r\n\r\n // Now that we've done all the keystroke processing needed, ensure any extra effects triggered\r\n // by the actual keystroke occur.\r\n ruleBehavior.finalize(this.keyboardProcessor, outputTarget, false);\r\n\r\n // -- All keystroke (and 'alternate') processing is now complete. Time to finalize everything! --\r\n\r\n // Notify the ModelManager of new input - it's predictive text time!\r\n if(alternates && alternates.length > 0) {\r\n ruleBehavior.transcription.alternates = alternates;\r\n }\r\n } else {\r\n // We need a dummy RuleBehavior for keys which have no output (e.g. Shift)\r\n ruleBehavior = new RuleBehavior();\r\n ruleBehavior.transcription = outputTarget.buildTranscriptionFrom(outputTarget, null, false);\r\n ruleBehavior.triggersDefaultCommand = true;\r\n }\r\n\r\n // The keyboard may want to take an action after all other keystroke processing is\r\n // finished, for example to switch layers. This action may not have any output\r\n // but may change system store or variable store values. Given this, we don't need to\r\n // save anything about the post behavior, after finalizing it\r\n\r\n // We need to tell the keyboard if the layer has been changed, either by a keyboard rule itself,\r\n // or by the touch layout 'nextlayer' control.\r\n const hasLayerChanged = ruleBehavior.setStore[SystemStoreIDs.TSS_LAYER] || keyEvent.kNextLayer;\r\n this.keyboardProcessor.newLayerStore.set(hasLayerChanged ? this.keyboardProcessor.layerId : '');\r\n this.keyboardProcessor.oldLayerStore.set(hasLayerChanged ? startingLayerId : '');\r\n\r\n let postRuleBehavior = this.keyboardProcessor.processPostKeystroke(this.contextDevice, outputTarget);\r\n if(postRuleBehavior) {\r\n postRuleBehavior.finalize(this.keyboardProcessor, outputTarget, true);\r\n }\r\n\r\n // Yes, even for ruleBehavior.triggersDefaultCommand. Those tend to change the context.\r\n ruleBehavior.predictionPromise = this.languageProcessor.predict(ruleBehavior.transcription, this.keyboardProcessor.layerId);\r\n\r\n // Text did not change (thus, no text \"input\") if we tabbed or merely moved the caret.\r\n if(!ruleBehavior.triggersDefaultCommand) {\r\n // For DOM-aware targets, this will trigger a DOM event page designers may listen for.\r\n outputTarget.doInputEvent();\r\n }\r\n\r\n return keepRuleBehavior ? ruleBehavior : null;\r\n }\r\n\r\n private buildAlternates(ruleBehavior: RuleBehavior, keyEvent: KeyEvent, preInputMock: Mock): Alternate[] {\r\n let alternates: Alternate[];\r\n\r\n // If we're performing a 'default command', it's not a standard 'typing' event - don't do fat-finger stuff.\r\n // Also, don't do fat-finger stuff if predictive text isn't enabled.\r\n if(this.languageProcessor.isActive && !ruleBehavior.triggersDefaultCommand) {\r\n let keyDistribution = keyEvent.keyDistribution;\r\n\r\n // We don't need to track absolute indexing during alternate-generation;\r\n // only position-relative, so it's better to use a sliding window for context\r\n // when making alternates. (Slightly worse for short text, matters greatly\r\n // for long text.)\r\n let contextWindow = new ContextWindow(preInputMock, ContextWindow.ENGINE_RULE_WINDOW, this.keyboardProcessor.layerId);\r\n let windowedMock = contextWindow.toMock();\r\n\r\n // Note - we don't yet do fat-fingering with longpress keys.\r\n if(this.languageProcessor.isActive && keyDistribution && keyEvent.kbdLayer) {\r\n // Tracks a 'deadline' for fat-finger ops, just in case both context is long enough\r\n // and device is slow enough that the calculation takes too long.\r\n //\r\n // Consider use of https://developer.mozilla.org/en-US/docs/Web/API/Performance/now instead?\r\n // Would allow finer-tuned control.\r\n let TIMEOUT_THRESHOLD: number = Number.MAX_VALUE;\r\n let _globalThis = globalObject();\r\n let timer: () => number;\r\n\r\n // Available by default on `window` in browsers, but _not_ on `global` in Node,\r\n // surprisingly. Since we can't use code dependent on `require` statements\r\n // at present, we have to condition upon it actually existing.\r\n if(_globalThis['performance'] && _globalThis['performance']['now']) {\r\n timer = function() {\r\n return _globalThis['performance']['now']();\r\n };\r\n\r\n TIMEOUT_THRESHOLD = timer() + 16; // + 16ms.\r\n } // else {\r\n // We _could_ just use Date.now() as a backup... but that (probably) only matters\r\n // when unit testing. So... we actually don't _need_ time thresholding when in\r\n // a Node environment.\r\n // }\r\n\r\n // Tracks a minimum probability for keystroke probability. Anything less will not be\r\n // included in alternate calculations.\r\n //\r\n // Seek to match SearchSpace.EDIT_DISTANCE_COST_SCALE from the predictive-text engine.\r\n // Reasoning for the selected value may be seen there. Short version - keystrokes\r\n // that _appear_ very precise may otherwise not even consider directly-neighboring keys.\r\n let KEYSTROKE_EPSILON = Math.exp(-5);\r\n\r\n // Sort the distribution into probability-descending order.\r\n keyDistribution.sort((a, b) => b.p - a.p);\r\n\r\n let activeLayout = this.activeKeyboard.layout(keyEvent.device.formFactor);\r\n alternates = [];\r\n\r\n let totalMass = 0; // Tracks sum of non-error probabilities.\r\n for(let pair of keyDistribution) {\r\n if(pair.p < KEYSTROKE_EPSILON) {\r\n totalMass += pair.p;\r\n break;\r\n } else if(timer && timer() >= TIMEOUT_THRESHOLD) {\r\n // Note: it's always possible that the thread _executing_ our JS\r\n // got paused by the OS, even if JS itself is single-threaded.\r\n //\r\n // The case where `alternates` is initialized (line 167) but empty\r\n // (because of net-zero loop iterations) MUST be handled.\r\n break;\r\n }\r\n\r\n let mock = Mock.from(windowedMock, false);\r\n\r\n let altKey = activeLayout.getLayer(keyEvent.kbdLayer).getKey(pair.keyId);\r\n if(!altKey) {\r\n console.warn(\"Potential fat-finger key could not be found in layer!\");\r\n continue;\r\n }\r\n\r\n let altEvent = this.keyboardProcessor.activeKeyboard.constructKeyEvent(altKey, keyEvent.device, this.keyboardProcessor.stateKeys);\r\n let alternateBehavior = this.keyboardProcessor.processKeystroke(altEvent, mock);\r\n\r\n // If alternateBehavior.beep == true, ignore it. It's a disallowed key sequence,\r\n // so we expect users to never intend their use.\r\n //\r\n // Also possible that this set of conditions fail for all evaluated alternates.\r\n if(alternateBehavior && !alternateBehavior.beep && pair.p > 0) {\r\n let transform: Transform = alternateBehavior.transcription.transform;\r\n\r\n // Ensure that the alternate's token id matches that of the current keystroke, as we only\r\n // record the matched rule's context (since they match)\r\n transform.id = ruleBehavior.transcription.token;\r\n alternates.push({sample: transform, 'p': pair.p});\r\n totalMass += pair.p;\r\n }\r\n }\r\n\r\n // Renormalizes the distribution, as any error (beep) results\r\n // will result in a distribution that doesn't sum to 1 otherwise.\r\n // All `.p` values are strictly positive, so totalMass is\r\n // guaranteed to be > 0 if the array has entries.\r\n alternates.forEach(function(alt) {\r\n alt.p /= totalMass;\r\n });\r\n }\r\n }\r\n return alternates;\r\n }\r\n\r\n public resetContext(outputTarget?: OutputTarget) {\r\n // Also handles new-context events, which may modify the layer\r\n this.keyboardProcessor.resetContext(outputTarget);\r\n // With the layer now set, we trigger new predictions.\r\n this.languageProcessor.invalidateContext(outputTarget, this.keyboardProcessor.layerId);\r\n }\r\n}", + "import EventEmitter from \"eventemitter3\";\r\nimport type LanguageProcessor from \"./languageProcessor.js\";\r\nimport { type ReadySuggestions, type InvalidateSourceEnum, StateChangeEnum, StateChangeHandler } from './languageProcessor.js';\r\nimport { type KeyboardProcessor, type OutputTarget } from \"@keymanapp/keyboard-processor\";\r\n\r\ninterface PredictionContextEventMap {\r\n update: (suggestions: Suggestion[]) => void;\r\n}\r\n\r\n/**\r\n * Maintains predictive-text state information corresponding to the current context.\r\n */\r\nexport default class PredictionContext extends EventEmitter {\r\n // Historical note: before 17.0, this code was intertwined with /web/source/osk/banner.ts's\r\n // SuggestionBanner class. This class serves as the main implementation of the banner's core logic.\r\n\r\n // Designed for use with auto-correct behavior\r\n private selected: Suggestion;\r\n\r\n private initNewContext: boolean = true;\r\n\r\n private _currentSuggestions: Suggestion[] = [];\r\n private keepSuggestion: Keep;\r\n private revertSuggestion: Reversion;\r\n\r\n private recentAccept: boolean = false;\r\n private revertAcceptancePromise: Promise;\r\n\r\n private swallowPrediction: boolean = false;\r\n\r\n private doRevert: boolean = false;\r\n private recentRevert: boolean = false;\r\n\r\n private langProcessor: LanguageProcessor;\r\n private kbdProcessor: KeyboardProcessor;\r\n\r\n /**\r\n * Represents the active context used when requesting and applying predictive-text operations.\r\n */\r\n private _currentTarget: OutputTarget;\r\n\r\n public get currentTarget(): OutputTarget {\r\n return this._currentTarget;\r\n }\r\n\r\n public setCurrentTarget(target: OutputTarget): Promise {\r\n const originalTarget = this._currentTarget;\r\n this._currentTarget = target;\r\n\r\n if(originalTarget != target) {\r\n // Note: should be triggered after the corresponding new-context event rule has been processed,\r\n // as that may affect the value of layerId here.\r\n return this.resetContext();\r\n } else {\r\n return Promise.resolve([]);\r\n }\r\n }\r\n\r\n private readonly suggestionApplier: (suggestion: Suggestion) => Promise;\r\n private readonly suggestionReverter: (reversion: Reversion) => void;\r\n\r\n /**\r\n * Handler for post-processing once a suggestion has been applied: calls\r\n * into the active keyboard's `begin postKeystroke` entry point.\r\n *\r\n * Called after the suggestion is applied but _before_ new predictions are\r\n * requested based on the resulting context.\r\n */\r\n private readonly postApplicationHandler: () => void;\r\n\r\n public constructor(langProcessor: LanguageProcessor, kbdProcessor: KeyboardProcessor) {\r\n super();\r\n\r\n this.langProcessor = langProcessor;\r\n this.kbdProcessor = kbdProcessor;\r\n\r\n const validSuggestionState: () => boolean = () =>\r\n this.currentTarget && langProcessor.state == 'configured';\r\n\r\n this.suggestionApplier = (suggestion) => {\r\n if(validSuggestionState()) {\r\n return langProcessor.applySuggestion(suggestion, this.currentTarget, () => kbdProcessor.layerId);\r\n }\r\n }\r\n\r\n this.suggestionReverter = (reversion) => {\r\n if(validSuggestionState()) {\r\n langProcessor.applyReversion(reversion, this.currentTarget);\r\n }\r\n }\r\n\r\n // As it's called synchronously via event-callback during `this.suggestionApplier`,\r\n // `this.currentTarget` is guaranteed to remain unchanged.\r\n this.postApplicationHandler = () => {\r\n // Tell the keyboard that the current layer has not changed\r\n kbdProcessor.newLayerStore.set('');\r\n kbdProcessor.oldLayerStore.set('');\r\n // Call the keyboard's entry point.\r\n kbdProcessor.processPostKeystroke(kbdProcessor.contextDevice, this.currentTarget)\r\n // If we have a RuleBehavior as a result, run it on the target. This should\r\n // only change system store and variable store values.\r\n ?.finalize(kbdProcessor, this.currentTarget, true);\r\n };\r\n\r\n this.connect();\r\n }\r\n\r\n private connect() {\r\n this.langProcessor.addListener('invalidatesuggestions', this.invalidateSuggestions);\r\n this.langProcessor.addListener('suggestionsready', this.updateSuggestions);\r\n this.langProcessor.addListener('tryaccept', this.doTryAccept);\r\n this.langProcessor.addListener('tryrevert', this.doTryRevert);\r\n this.langProcessor.addListener('statechange', this.onModelStateChange);\r\n\r\n this.langProcessor.addListener('suggestionapplied', this.postApplicationHandler);\r\n }\r\n\r\n public disconnect() {\r\n this.langProcessor.removeListener('invalidatesuggestions', this.invalidateSuggestions);\r\n this.langProcessor.removeListener('suggestionsready', this.updateSuggestions);\r\n this.langProcessor.removeListener('tryaccept', this.doTryAccept);\r\n this.langProcessor.removeListener('tryrevert', this.doTryRevert);\r\n this.langProcessor.removeListener('statechange', this.onModelStateChange);\r\n\r\n this.langProcessor.removeListener('suggestionapplied', this.postApplicationHandler);\r\n this.clearSuggestions();\r\n }\r\n\r\n public get currentSuggestions(): Suggestion[] {\r\n let suggestions = [];\r\n // Insert 'current text' if/when valid as the leading option.\r\n // Since we don't yet do auto-corrections, we only show 'keep' whenever it's\r\n // a valid word (according to the model).\r\n\r\n if(this.activateKeep() && this.keepSuggestion && this.keepSuggestion.matchesModel) {\r\n suggestions.push(this.keepSuggestion);\r\n } else if(this.doRevert) {\r\n suggestions.push(this.revertSuggestion);\r\n }\r\n\r\n return suggestions.concat(this._currentSuggestions);\r\n }\r\n\r\n /**\r\n * Function apply\r\n * Description Applies the predictive `Suggestion` represented by this `BannerSuggestion`.\r\n */\r\n private acceptInternal(suggestion: Suggestion): Promise {\r\n if(!suggestion) {\r\n return null;\r\n }\r\n\r\n // Should be safe to convert into an event handled externally.\r\n // layerID can be obtained by whoever/whatever holds the InputProcessor instance.\r\n if(suggestion.tag == 'revert') {\r\n this.suggestionReverter(suggestion as Reversion);\r\n return null;\r\n } else {\r\n return this.suggestionApplier(suggestion);\r\n }\r\n }\r\n\r\n /**\r\n * Applies predictive-text suggestions and post-acceptance reversions to the current\r\n * prediction context.\r\n *\r\n * Note that both cases will additionally trigger a new asynchronous `predict` operation,\r\n * though no corresponding Promise is returned by this function. As such, the current\r\n * suggestions should be considered outdated after calling this method, pending replacement\r\n * upon the completed async `predict`.\r\n *\r\n * @param suggestion Either a `Suggestion` or `Reversion`.\r\n * @returns if `suggestion` is a `Suggestion`, will return a `Promise`; else, `null`.\r\n */\r\n public accept(suggestion: Suggestion): Promise | null {\r\n let _this = this;\r\n\r\n // Selecting a suggestion or a reversion should both clear selection\r\n // and clear the reversion-displaying state of the banner.\r\n this.selected = null;\r\n this.doRevert = false;\r\n\r\n this.revertAcceptancePromise = this.acceptInternal(suggestion);\r\n if(!this.revertAcceptancePromise) {\r\n // We get here either if suggestion acceptance fails or if it was a reversion.\r\n if(suggestion && suggestion.tag == 'revert') {\r\n // Reversion state management\r\n this.recentAccept = false;\r\n this.recentRevert = true;\r\n }\r\n return null;\r\n }\r\n\r\n this.revertAcceptancePromise.then(function(suggestion) {\r\n // Always null-check!\r\n if(suggestion) {\r\n _this.revertSuggestion = suggestion;\r\n }\r\n });\r\n\r\n this.recentAccept = true;\r\n this.recentRevert = false;\r\n\r\n this.swallowPrediction = true;\r\n\r\n return this.revertAcceptancePromise;\r\n }\r\n\r\n private showRevert() {\r\n // Construct a 'revert suggestion' to facilitate a reversion UI component.\r\n this.doRevert = true;\r\n this.sendUpdateEvent();\r\n }\r\n\r\n /**\r\n * Receives messages from the keyboard that the 'accept' keystroke has been entered.\r\n * Should return 'false' if the current state allows accepting a suggestion and act accordingly.\r\n * Otherwise, return true.\r\n */\r\n private doTryAccept = (source: string /*, returnObj: {shouldSwallow: boolean}*/): void => {\r\n //let keyman = com.keyman.singleton;\r\n\r\n if(!this.recentAccept && this.selected) {\r\n this.accept(this.selected);\r\n // returnObj.shouldSwallow = true;\r\n } else if(this.recentAccept && source == 'space') {\r\n this.recentAccept = false;\r\n // // If the model doesn't insert wordbreaks, don't swallow the space. If it does,\r\n // // we consider that insertion to be the results of the first post-accept space.\r\n // returnObj.shouldSwallow = !!keyman.core.languageProcessor.wordbreaksAfterSuggestions; // can be handed outside\r\n } else {\r\n // returnObj.shouldSwallow = false;\r\n }\r\n }\r\n\r\n /**\r\n * Receives messages from the keyboard that the 'revert' keystroke has been entered.\r\n * Should return 'false' if the current state allows reverting a recently-applied suggestion and act accordingly.\r\n * Otherwise, return true.\r\n */\r\n private doTryRevert = (/*returnObj: {shouldSwallow: boolean}*/): boolean => {\r\n // Has the revert keystroke (BKSP) already been sent once since the last accept?\r\n if(this.doRevert) {\r\n // If so, clear the 'revert' option and start doing normal predictions again.\r\n this.doRevert = false;\r\n this.recentAccept = false;\r\n // Otherwise, did we just accept something before the revert signal was received?\r\n } else if(this.recentAccept) {\r\n this.showRevert();\r\n this.swallowPrediction = true;\r\n }\r\n\r\n // // We don't yet actually do key-based reversions.\r\n // returnObj.shouldSwallow = false;\r\n return;\r\n }\r\n\r\n /**\r\n * Function invalidateSuggestions\r\n * Scope Public\r\n * Description Clears the suggestions in the suggestion banner\r\n */\r\n private invalidateSuggestions = (source: InvalidateSourceEnum): void => {\r\n // By default, we assume that the context is the same until we notice otherwise.\r\n this.initNewContext = false;\r\n\r\n if(!this.swallowPrediction || source == 'context') {\r\n this.recentAccept = false;\r\n this.doRevert = false;\r\n this.recentRevert = false;\r\n\r\n if(source == 'context') {\r\n this.swallowPrediction = false;\r\n this.initNewContext = true;\r\n }\r\n }\r\n\r\n // Not checking this can result in a perceptible 'flash' of sorts due to the suggestion-update delay.\r\n if(source != 'new') {\r\n this.clearSuggestions();\r\n // this.options.forEach((option: BannerSuggestion) => {\r\n // option.update(null);\r\n // });\r\n }\r\n }\r\n\r\n private clearSuggestions() {\r\n this.updateSuggestions({\r\n suggestions: [],\r\n transcriptionID: 0\r\n });\r\n }\r\n\r\n private activateKeep(): boolean {\r\n return !this.recentAccept && !this.recentRevert && !this.initNewContext;\r\n }\r\n\r\n /**\r\n * Function updateSuggestions\r\n * Scope Public\r\n * @param {Suggestion[]} suggestions Array of suggestions from the lexical model.\r\n * Description Update the displayed suggestions in the SuggestionBanner\r\n */\r\n private updateSuggestions = (prediction: ReadySuggestions): void => {\r\n let suggestions = prediction.suggestions;\r\n\r\n this._currentSuggestions = suggestions;\r\n\r\n // Do we have a keep suggestion? If so, remove it from the list so that we can control its display position\r\n // and prevent it from being hidden after reversion operations.\r\n this.keepSuggestion = null;\r\n for(let s of suggestions) {\r\n if(s.tag == 'keep') {\r\n this.keepSuggestion = s as Keep;\r\n }\r\n }\r\n\r\n if(this.keepSuggestion) {\r\n this._currentSuggestions.splice(this._currentSuggestions.indexOf(this.keepSuggestion), 1);\r\n }\r\n\r\n // If we've gotten an update request like this, it's almost always user-triggered and means the context has shifted.\r\n if(!this.swallowPrediction) {\r\n this.recentAccept = false;\r\n this.doRevert = false;\r\n this.recentRevert = false;\r\n } else { // This prediction was triggered by a recent 'accept.' Now that it's fulfilled, we clear the flag.\r\n this.swallowPrediction = false;\r\n }\r\n\r\n // The rest is the same, whether from input or from \"self-updating\" after a reversion to provide new suggestions.\r\n this.sendUpdateEvent();\r\n }\r\n\r\n public sendUpdateEvent() {\r\n this.emit('update', this.currentSuggestions);\r\n }\r\n\r\n public resetContext(): Promise {\r\n const target = this.currentTarget;\r\n\r\n if(target) {\r\n // Note: should be triggered after the corresponding new-context event rule has been processed,\r\n // as that may affect the value of layerId here.\r\n return this.langProcessor.invalidateContext(target, this.kbdProcessor.layerId);\r\n } else {\r\n return Promise.resolve([]);\r\n }\r\n }\r\n\r\n private onModelStateChange: StateChangeHandler = (state) => {\r\n // Either way, the model has changed; either state marks the completion of such a transition.\r\n // The 'active' state displays the banner while a model loads... but its predictions are\r\n // only possible once fully 'configured'. They may appear to 'blink on' after a small delay\r\n // as a result.\r\n if(state == 'configured' || state == 'inactive') {\r\n this.resetContext();\r\n }\r\n }\r\n}", + "class DomEventTracking {\r\n Pelem: EventTarget;\r\n Peventname: string;\r\n Phandler: (Object) => boolean;\r\n PuseCapture?: boolean\r\n\r\n constructor(Pelem: EventTarget, Peventname: string, Phandler: (Object) => boolean, PuseCapture?: boolean) {\r\n this.Pelem = Pelem;\r\n this.Peventname = Peventname.toLowerCase();\r\n this.Phandler = Phandler;\r\n this.PuseCapture = PuseCapture;\r\n }\r\n\r\n equals(other: DomEventTracking): boolean {\r\n return this.Pelem == other.Pelem && this.Peventname == other.Peventname &&\r\n this.Phandler == other.Phandler && this.PuseCapture == other.PuseCapture;\r\n }\r\n};\r\n\r\n/**\r\n * Facilitates adding and removing event listeners to and from DOM elements in a manner\r\n * that allows widespread removal/cleanup of the listeners at a future time if and when needed.\r\n *\r\n * Said \"widespread removal\" helps to prevent separate instances of KeymanWeb from stomping on\r\n * each other during unit tests.\r\n */\r\nexport class DomEventTracker {\r\n private domEvents: DomEventTracking[] = [];\r\n\r\n /**\r\n * Function attachDOMEvent: Note for most browsers, adds an event to a chain, doesn't stop existing events\r\n * Scope Public\r\n * @param {Object} Pelem Element (or IFrame-internal Document) to which event is being attached\r\n * @param {string} Peventname Name of event without 'on' prefix\r\n * @param {function(Object)} Phandler Event handler for event\r\n * @param {boolean=} PuseCapture True only if event to be handled on way to target element\r\n * Description Attaches event handler to element DOM event\r\n */\r\n attachDOMEvent(\r\n Pelem: Window,\r\n Peventname: K,\r\n Phandler: (ev: WindowEventMap[K]) => any,\r\n PuseCapture?: boolean\r\n ): void;\r\n attachDOMEvent(\r\n Pelem: Document,\r\n Peventname: K,\r\n Phandler: (ev: DocumentEventMap[K]) => any,\r\n PuseCapture?: boolean\r\n ): void;\r\n attachDOMEvent(\r\n Pelem: HTMLElement,\r\n Peventname: K,\r\n Phandler: (ev: HTMLElementEventMap[K]) => any,\r\n PuseCapture?: boolean\r\n ): void;\r\n attachDOMEvent(Pelem: EventTarget, Peventname: string, Phandler: (Object) => boolean, PuseCapture?: boolean): void {\r\n // @ts-ignore // Since the trickery unfortunately don't also clear things up for anything we call within.\r\n // It's possible to fix, but that gets way more complex to spec out completely.\r\n this.detachDOMEvent(Pelem, Peventname, Phandler, PuseCapture);\r\n Pelem.addEventListener(Peventname, Phandler, PuseCapture ? true : false);\r\n\r\n // Since we're attaching to the DOM, these events should be tracked for detachment during shutdown.\r\n var event = new DomEventTracking(Pelem, Peventname, Phandler, PuseCapture);\r\n this.domEvents.push(event);\r\n }\r\n\r\n /**\r\n * Function detachDOMEvent\r\n * Scope Public\r\n * @param {Object} Pelem Element from which event is being detached\r\n * @param {string} Peventname Name of event without 'on' prefix\r\n * @param {function(Object)} Phandler Event handler for event\r\n * @param {boolean=} PuseCapture True if event was being handled on way to target element\r\n * Description Detaches event handler from element [to prevent memory leaks]\r\n */\r\n detachDOMEvent(\r\n Pelem: Window,\r\n Peventname: K,\r\n Phandler: (ev: WindowEventMap[K]) => any,\r\n PuseCapture?: boolean\r\n ): void;\r\n detachDOMEvent(\r\n Pelem: Document,\r\n Peventname: K,\r\n Phandler: (ev: DocumentEventMap[K]) => any,\r\n PuseCapture?: boolean\r\n ): void;\r\n detachDOMEvent(\r\n Pelem: HTMLElement,\r\n Peventname: K,\r\n Phandler: (ev: HTMLElementEventMap[K]) => any,\r\n PuseCapture?: boolean\r\n ): void;\r\n detachDOMEvent(Pelem: EventTarget, Peventname: string, Phandler: (Object) => boolean, PuseCapture?: boolean): void {\r\n Pelem.removeEventListener(Peventname, Phandler, PuseCapture);\r\n\r\n // Since we're detaching, we should drop the tracking data from the old event.\r\n var event = new DomEventTracking(Pelem, Peventname, Phandler, PuseCapture);\r\n for(var i = 0; i < this.domEvents.length; i++) {\r\n if(this.domEvents[i].equals(event)) {\r\n this.domEvents.splice(i, 1);\r\n break;\r\n }\r\n }\r\n }\r\n\r\n shutdown() {\r\n // Remove all events linking to elements of the original, unaltered page.\r\n // This should sever any still-existing page ties to this instance of KMW,\r\n // allowing browser GC to do its thing.\r\n for(let event of this.domEvents) {\r\n // @ts-ignore // since it's simpler this way and doesn't earn us much to re-check types.\r\n this.detachDOMEvent(event.Pelem, event.Peventname, event.Phandler, event.PuseCapture);\r\n }\r\n }\r\n}", + "import EventEmitter, { EventNames, EventListener } from \"eventemitter3\";\r\nimport { LegacyEventEmitter } from \"./legacyEventEmitter.js\";\r\n\r\ninterface EventMap {\r\n // Provides IntelliSense suggestions in conditionals based on the parameters!\r\n\r\n /**\r\n * Indicates that a listener for the named event has been registered for the\r\n * EventEmitter being spied upon.\r\n * @param eventName\r\n */\r\n listeneradded(eventName: EventNames);\r\n\r\n /**\r\n * Indicates that a listener for the named event has been unregistered from the\r\n * EventEmitter being spied upon.\r\n * @param eventName\r\n */\r\n listenerremoved(eventName: EventNames);\r\n}\r\n\r\ntype Emitter = EventEmitter | LegacyEventEmitter;\r\n\r\n/**\r\n * A spy-object that wraps event-emitters in order to listen in on listener addition methods and\r\n * raise events when new listeners are attached.\r\n */\r\nexport class EmitterListenerSpy extends EventEmitter> {\r\n constructor(emitter: Emitter) {\r\n super();\r\n\r\n if(emitter instanceof EventEmitter) {\r\n emitter.on = this.listenerRegistrationSpy('listeneradded', emitter, emitter.on);\r\n emitter.addListener = this.listenerRegistrationSpy('listeneradded', emitter, emitter.addListener);\r\n emitter.off = this.listenerRegistrationSpy('listenerremoved', emitter, emitter.off);\r\n emitter.removeListener = this.listenerRegistrationSpy('listenerremoved', emitter, emitter.off);\r\n } else {\r\n // TS gets really fussy about how the legacy event typing is a bit more\r\n // restrictive (due to less-restricted event name types in EventEmitter)\r\n // It's not worth the effort to make this 100% perfect at the moment.\r\n //\r\n // @ts-ignore\r\n emitter.addEventListener = this.listenerRegistrationSpy('listeneradded', emitter, emitter.addEventListener);\r\n // @ts-ignore\r\n emitter.removeEventListener = this.listenerRegistrationSpy('listenerremoved', emitter, emitter.removeEventListener);\r\n }\r\n }\r\n\r\n /**\r\n * Given an event emitter and one of its methods used to register or unregister associated events,\r\n * this method will construct a replacement method that calls the original AND raises the specified\r\n * corresponding listener event provided by this class afterward. The replacement method\r\n * should be assigned to the emitter afterward, overwriting the original version.\r\n *\r\n * Refer to https://stackoverflow.com/a/10057969.\r\n */\r\n private listenerRegistrationSpy(\r\n spyEventName: EventNames>,\r\n emitter: Emitter,\r\n method: (\r\n eventName: EventNames,\r\n listener: EventListener>,\r\n ) => any\r\n ): ( // returns a method of the same signature as the original implementation.\r\n eventName: EventNames,\r\n listener: EventListener>,\r\n ) => any {\r\n return (eventName, listener) => {\r\n const retVal = method.apply(emitter, [eventName, listener]);\r\n this.emit(spyEventName, eventName);\r\n return retVal;\r\n }\r\n }\r\n}\r\n\r\n/**** A code block for verifying that typing, etc checks out: ****/\r\n\r\n// interface TestMap {\r\n// 'a': (str: string) => void;\r\n// 'b': (num: number, str: string) => void;\r\n// }\r\n\r\n// const emitter = new LegacyEventEmitter; // or `new EventEmitter`.\r\n// const emitterSpy = new EmitterListenerSpy(emitter);\r\n// emitterSpy.on('listeneradded', (eventName) => {\r\n// // eventName = 'c'; // will error; there is no event 'c' in the event map.\r\n// if(eventName == 'a') {\r\n// // stuff\r\n// }\r\n// })", + "// Most of the typing below is derived from that of EventEmitter, but customized for\r\n// modeling legacy KMW events. Including the heavy typing gets us event Intellisense\r\n// and compile-time errors if and when types don't match expectations.\r\n\r\n/**\r\n * Can define the set of events as follows:\r\n * ```\r\n * interface Test extends EventMap {\r\n * 'event': (param: {'prop': any}) => boolean;\r\n * }\r\n * ```\r\n *\r\n * Each event may have either no parameters or a single parameter of type object.\r\n * The type definition of the parameter will be utilized by TS's type-inference engine\r\n * for type checking on handlers and for raising the event.\r\n *\r\n * Note: the `extends EventMap` part is actually important for TS type inference here.\r\n */\r\nexport type LegacyEventMap = object;\r\n\r\n/**\r\n * Matches the name of any single event defined within the specified event-map definition.\r\n */\r\nexport type EventNames = Exclude;\r\n\r\n/**\r\n * Builds a type-array of the arguments for each named event, indexed by that name.\r\n */\r\ntype ArgumentMap = {\r\n [K in Exclude]: T[K] extends (arg: any) => boolean\r\n ? Parameters[0]\r\n : (\r\n T[K] extends Function\r\n ? never\r\n : T[K]\r\n );\r\n};\r\n\r\n/**\r\n * Provides the type signature of event listeners able to handle defined events.\r\n */\r\nexport type EventListener<\r\n T extends LegacyEventMap,\r\n K extends EventNames\r\n > = ( // argumentMap[eventName] - retrieves the specific parameter typing for the event.\r\n args: ArgumentMap[Extract]\r\n ) => any;\r\n\r\n/**\r\n * Provides fairly strong typing for all legacy KMW events. Note that all events\r\n * assume a handler receiving up to one object, though that object's properties will\r\n * vary from event to event.\r\n *\r\n * Note that the behavior differs from EventEmitter events on a few points:\r\n * 1. Event functions are expected to return a boolean value - generally, `true`.\r\n * If 'false' or `undefined` is returned, no further listeners will receive the event.\r\n * 2. These events receive up to one parameter, always of an object type.\r\n * 3. These events proactively prevent accidental event-handler recursion. Should an event's\r\n * handler retrigger the event, the newly-triggered event will be prevented entirely.\r\n */\r\nexport class LegacyEventEmitter {\r\n // An object mapping event names to individual event lists. Maps strings to arrays.\r\n private events: { [name: string]: ((Object) => boolean)[];} = {};\r\n private currentEvents: string[] = []; // The event messaging call stack.\r\n\r\n /**\r\n * Function addEventListener\r\n * Scope Private\r\n * @param {string} event name of event prefixed by module, e.g. osk.touchmove\r\n * @param {function(Object)} func event handler\r\n * @return {boolean}\r\n * Description Add (or replace) an event listener for this component\r\n */\r\n addEventListener> (\r\n event: T,\r\n func: EventListener\r\n ): boolean {\r\n this._removeEventListener(event, func);\r\n this.events[event].push(func);\r\n return true;\r\n }\r\n\r\n /**\r\n * Function removeEventListener\r\n * Scope Private\r\n * @param {string} event name of event prefixed by module, e.g. osk.touchmove\r\n * @param {function(Object)} func event handler\r\n * @return {boolean}\r\n * Description Remove the specified function from the listeners for this event\r\n */\r\n public removeEventListener> (\r\n event: T,\r\n func: EventListener\r\n ): boolean {\r\n return this._removeEventListener(event, func);\r\n }\r\n\r\n // Separate, in order to prevent `addEventListener` from sending 'listenerremoved' events with\r\n // EmitterListenerSpy.\r\n private _removeEventListener> (\r\n event: T,\r\n func: EventListener\r\n ): boolean {\r\n if(typeof this.events[event] == 'undefined') {\r\n this.events[event] = [];\r\n }\r\n\r\n for(var i=0; i> (\r\n event: T,\r\n params: ArgumentMap[T]\r\n ): boolean {\r\n if(typeof this.events[event] == 'undefined') {\r\n return true;\r\n }\r\n\r\n if(this.currentEvents.indexOf(event) != -1) {\r\n return false; // Avoid event messaging recursion!\r\n }\r\n\r\n this.currentEvents.push(event);\r\n\r\n for(var i=0; i, result=false;\r\n try {\r\n result=func(params as any);\r\n } catch(strExcept) {\r\n console.error(strExcept);\r\n result=false;\r\n } //don't know whether to use true or false here\r\n if(result === false) {\r\n this.currentEvents.pop();\r\n return false;\r\n }\r\n }\r\n this.currentEvents.pop();\r\n return true;\r\n }\r\n\r\n listenerCount>(event: T) {\r\n const listeners = this.events[event];\r\n return listeners ? listeners.length : 0;\r\n }\r\n\r\n shutdown() {\r\n // Remove all event-handler references rooted in KMW events.\r\n this.events = {};\r\n }\r\n}\r\n", + "import { ManagedPromise } from '@keymanapp/keyboard-processor';\r\nimport CloudRequesterInterface from './cloud/requesterInterface.js';\r\nimport { CLOUD_MALFORMED_OBJECT_ERR, CLOUD_TIMEOUT_ERR, CLOUD_STUB_REGISTRATION_ERR } from './cloud/queryEngine.js';\r\n\r\nexport default class DOMCloudRequester implements CloudRequesterInterface {\r\n private readonly fileLocal: boolean;\r\n\r\n constructor(fileLocal: boolean = false) {\r\n this.fileLocal = fileLocal;\r\n }\r\n\r\n request(query: string) {\r\n let promise = new ManagedPromise();\r\n\r\n // Set callback timer\r\n const timeoutID = window.setTimeout(() => {\r\n promise.reject(new Error(CLOUD_TIMEOUT_ERR));\r\n }, 10000);\r\n\r\n const tFlag='&timerid='+ timeoutID;\r\n const fullRef = query + tFlag;\r\n\r\n const Lscript: HTMLScriptElement = document.createElement('script');\r\n Lscript.onload = (event: Event) => {\r\n window.clearTimeout(timeoutID);\r\n\r\n // This case should only happen if a returned, otherwise-valid keyboard\r\n // script does not ever call `register`. Also provides default handling\r\n // should `register` fail to report results/failure correctly.\r\n if(!promise.hasFinalized) {\r\n promise.reject(new Error(CLOUD_STUB_REGISTRATION_ERR));\r\n }\r\n };\r\n\r\n // Note: at this time (24 May 2021), this is also happens for \"successful\"\r\n // API calls where there is no matching keyboard ID.\r\n //\r\n // The returned 'error' JSON object is sent with an HTML error code (404)\r\n // and does not call `keyman.register`. Even if it did the latter, the\r\n // 404 code would likely prevent the returned script's call.\r\n Lscript.onerror = (event: string | Event, source?: string,\r\n lineno?: number, colno?: number, error?: Error) => {\r\n window.clearTimeout(timeoutID);\r\n\r\n let msg = CLOUD_MALFORMED_OBJECT_ERR;\r\n if(error) {\r\n msg = msg + \": \" + error.message;\r\n }\r\n\r\n promise.reject(new Error(msg));\r\n }\r\n\r\n if(this.fileLocal) {\r\n Lscript.src = query;\r\n } else {\r\n Lscript.src = fullRef;\r\n }\r\n\r\n try {\r\n document.body.appendChild(Lscript);\r\n } catch(ex) {\r\n document.getElementsByTagName('head')[0].appendChild(Lscript);\r\n }\r\n\r\n promise.finally(() => {\r\n clearTimeout(timeoutID);\r\n });\r\n\r\n return {\r\n promise: promise,\r\n queryId: timeoutID\r\n };\r\n }\r\n}", + "import { type Keyboard, KeyboardKeymanGlobal, ProcessorInitOptions } from \"@keymanapp/keyboard-processor\";\r\nimport { DOMKeyboardLoader as KeyboardLoader } from \"@keymanapp/keyboard-processor/dom-keyboard-loader\";\r\nimport { InputProcessor, PredictionContext } from \"@keymanapp/input-processor\";\r\nimport { OSKView } from \"keyman/engine/osk\";\r\nimport { KeyboardRequisitioner, ModelCache, ModelSpec, toUnprefixedKeyboardId as unprefixed } from \"keyman/engine/package-cache\";\r\n\r\nimport { EngineConfiguration, InitOptionSpec } from \"./engineConfiguration.js\";\r\nimport KeyboardInterface from \"./keyboardInterface.js\";\r\nimport { ContextManagerBase } from \"./contextManagerBase.js\";\r\nimport { KeyEventHandler } from './keyEventSource.interface.js';\r\nimport HardKeyboardBase from \"./hardKeyboard.js\";\r\nimport { LegacyAPIEvents } from \"./legacyAPIEvents.js\";\r\nimport { EventNames, EventListener, LegacyEventEmitter } from \"keyman/engine/events\";\r\nimport DOMCloudRequester from \"keyman/engine/package-cache/dom-requester\";\r\nimport KEYMAN_VERSION from \"@keymanapp/keyman-version\";\r\n\r\n// From https://stackoverflow.com/a/69328045\r\ntype WithRequired = T & { [P in K]-?: T[P] };\r\n// Sets two parts non-optional at this level, while they were at lower levels.\r\ntype ProcessorConfiguration = WithRequired, 'defaultOutputRules'>;\r\n\r\nfunction determineBaseLayout(): string {\r\n if(typeof(window['KeymanWeb_BaseLayout']) !== 'undefined') {\r\n return window['KeymanWeb_BaseLayout'];\r\n } else {\r\n return 'us';\r\n }\r\n}\r\n\r\nexport default class KeymanEngine<\r\n Configuration extends EngineConfiguration,\r\n ContextManager extends ContextManagerBase,\r\n HardKeyboard extends HardKeyboardBase\r\n> implements KeyboardKeymanGlobal {\r\n readonly config: Configuration;\r\n contextManager: ContextManager;\r\n interface: KeyboardInterface;\r\n readonly core: InputProcessor;\r\n keyboardRequisitioner: KeyboardRequisitioner;\r\n modelCache: ModelCache;\r\n\r\n protected legacyAPIEvents = new LegacyEventEmitter();\r\n private _hardKeyboard: HardKeyboard;\r\n private _osk: OSKView;\r\n\r\n protected keyEventRefocus?: () => void;\r\n\r\n private keyEventListener: KeyEventHandler = (event, callback) => {\r\n const outputTarget = this.contextManager.activeTarget;\r\n\r\n if(!this.contextManager.activeKeyboard || !outputTarget) {\r\n if(callback) {\r\n callback(null, null);\r\n }\r\n }\r\n\r\n //... probably only applies for physical keystrokes.\r\n if(!event.isSynthetic) {\r\n if(this.osk?.vkbd?.keyPending) {\r\n this.osk.vkbd.keyPending = null;\r\n }\r\n } else if(this.keyEventRefocus) { // && event.isSynthetic // as in, is from the OSK.\r\n // Do anything needed to guarantee that the outputTarget stays active (`app/browser`: maintains focus).\r\n // (Interaction with the OSK may have de-focused the element providing active context;\r\n // we want to restore it in case the user swaps back to the hardware keyboard afterward.)\r\n this.keyEventRefocus();\r\n }\r\n\r\n // Clear any cached codepoint data; we can rebuild it if it's unchanged.\r\n outputTarget.invalidateSelection();\r\n // Deadkey matching continues to be troublesome.\r\n // Deleting matched deadkeys here seems to correct some of the issues. (JD 6/6/14)\r\n outputTarget.deadkeys().deleteMatched(); // Delete any matched deadkeys before continuing\r\n\r\n const result = this.core.processKeyEvent(event, outputTarget);\r\n\r\n if(result && result.transcription?.transform) {\r\n this.config.onRuleFinalization(result, this.contextManager.activeTarget);\r\n }\r\n\r\n if(callback) {\r\n callback(result, null);\r\n }\r\n\r\n // No try-catch here because we don't want to mask any errors that occur during keystroke\r\n // processing - silent failures are far harder to diagnose.\r\n };\r\n\r\n /**\r\n * @param worker A configured WebWorker to serve as the predictive-text engine's main thread.\r\n * Available in the following variants:\r\n * - sourcemapped, unminified (debug)\r\n * - non-sourcemapped + minified (release)\r\n * @param config\r\n * @param contextManager\r\n * @param processorConfigInitializer A one-time use closure used to initialize certain critical components reliant\r\n * upon the class instance, configured by the derived class, but needed during\r\n * the superclass constructor.\r\n */\r\n constructor(\r\n worker: Worker,\r\n config: Configuration,\r\n contextManager: ContextManager,\r\n processorConfigInitializer: (engine: KeymanEngine) => ProcessorConfiguration\r\n ) {\r\n this.config = config;\r\n this.contextManager = contextManager;\r\n\r\n const processorConfiguration = processorConfigInitializer(this);\r\n processorConfiguration.baseLayout = determineBaseLayout();\r\n this.interface = processorConfiguration.keyboardInterface as KeyboardInterface;\r\n this.core = new InputProcessor(config.hostDevice, worker, processorConfiguration);\r\n\r\n this.core.languageProcessor.on('statechange', (state) => {\r\n // The banner controller cannot directly trigger a layout-refresh at this time,\r\n // so we handle that here.\r\n this.osk?.bannerController.selectBanner(state);\r\n this.osk?.refreshLayout();\r\n });\r\n\r\n // The OSK does not possess a direct connection to the KeyboardProcessor's state-key\r\n // management object; this event + handler allow us to keep the OSK's related states\r\n // in sync.\r\n this.core.keyboardProcessor.on('statekeychange', (stateKeys) => {\r\n this.osk?.vkbd?.updateStateKeys(stateKeys);\r\n })\r\n\r\n this.contextManager.on('beforekeyboardchange', (metadata) => {\r\n this.legacyAPIEvents.callEvent('beforekeyboardchange', {\r\n internalName: metadata?.id,\r\n languageCode: metadata?.langId\r\n });\r\n });\r\n\r\n this.contextManager.on('keyboardchange', (kbd) => {\r\n this.refreshModel();\r\n this.core.activeKeyboard = kbd?.keyboard;\r\n\r\n this.legacyAPIEvents.callEvent('keyboardchange', {\r\n internalName: kbd?.metadata.id ?? '',\r\n languageCode: kbd?.metadata.langId ?? ''\r\n });\r\n\r\n // Hide OSK and do not update keyboard list if using internal keyboard (desktops).\r\n // Condition will not be met for touch form-factors; they force selection of a\r\n // default keyboard.\r\n if(!kbd) {\r\n this.osk.startHide(false);\r\n }\r\n\r\n if(this.osk) {\r\n this.osk.setNeedsLayout();\r\n this.osk.activeKeyboard = kbd;\r\n this.osk.present();\r\n }\r\n });\r\n\r\n this.contextManager.on('keyboardasyncload', (metadata) => {\r\n /* Original implementation pre-modularization:\r\n *\r\n * > Force OSK display for CJK keyboards (keyboards using a pick list)\r\n *\r\n * A matching subcondition in the block below will ensure that the OSK activates pre-load\r\n * for CJK keyboards. Yes, even before a CJK picker could ever show. We should be fine\r\n * without the CJK check so long as a picker keyboard's OSK is kept activated post-load,\r\n * when the picker actually needs to be kept persistently-active.\r\n * `metadata` would be relevant a the CJK-check, which was based on language codes.\r\n *\r\n * Of course, as mobile devices don't have guaranteed physical keyboards... we need to\r\n * keep the OSK visible for them, hence the actual block below.\r\n */\r\n if(this.config.hostDevice.touchable && this.osk?.activationModel) {\r\n this.osk.activationModel.enabled = true;\r\n // Also note: the OSKView.mayDisable method returns false when hostDevice.touchable = false.\r\n // The .startHide() call below will check that method before actually starting an OSK hide.\r\n }\r\n\r\n // Always (temporarily) hide the OSK when loading a new keyboard, to ensure\r\n // that a failure to load doesn't leave the current OSK displayed\r\n this.osk?.startHide(false);\r\n });\r\n }\r\n\r\n public async init(optionSpec: Required){\r\n // There may be some valid mutations possible even on repeated calls?\r\n // The original seems to allow it.\r\n\r\n const config = this.config;\r\n if(config.deferForInitialization.hasFinalized) {\r\n // abort! Maybe throw an error, too.\r\n return Promise.resolve();\r\n }\r\n\r\n config.initialize(optionSpec);\r\n\r\n // Since we're not sandboxing keyboard loads yet, we just use `window` as the jsGlobal object.\r\n // All components initialized below require a properly-configured `config.paths` or similar.\r\n const keyboardLoader = new KeyboardLoader(this.interface, config.applyCacheBusting);\r\n this.keyboardRequisitioner = new KeyboardRequisitioner(keyboardLoader, new DOMCloudRequester(), this.config.paths);\r\n this.modelCache = new ModelCache();\r\n const kbdCache = this.keyboardRequisitioner.cache;\r\n\r\n this.contextManager.configure({\r\n resetContext: (target) => {\r\n this.core.resetContext(target);\r\n },\r\n predictionContext: new PredictionContext(this.core.languageProcessor, this.core.keyboardProcessor),\r\n keyboardCache: this.keyboardRequisitioner.cache\r\n });\r\n\r\n // #region Event handler wiring\r\n this.config.on('spacebartext', () => {\r\n // On change of spacebar-text mode, we currently need a layout refresh to update the\r\n // spacebar's text.\r\n this.osk?.refreshLayout();\r\n });\r\n\r\n kbdCache.on('stubadded', (stub) => {\r\n let eventRaiser = () => {\r\n // The corresponding event is needed in order to update UI modules as new keyboard stubs \"come online\".\r\n this.legacyAPIEvents.callEvent('keyboardregistered', {\r\n internalName: stub.KI,\r\n language: stub.KL,\r\n keyboardName: stub.KN,\r\n languageCode: stub.KLC,\r\n package: stub.KP\r\n });\r\n\r\n // If this is the first stub loaded, set it as active.\r\n if(this.config.activateFirstKeyboard && this.keyboardRequisitioner.cache.defaultStub == stub) {\r\n // Note: leaving this out is super-useful for debugging issues that occur when no keyboard is active.\r\n this.contextManager.activateKeyboard(stub.id, stub.langId, true);\r\n }\r\n }\r\n\r\n if(this.config.deferForInitialization.hasFinalized) {\r\n eventRaiser();\r\n } else {\r\n this.config.deferForInitialization.then(eventRaiser);\r\n }\r\n });\r\n\r\n kbdCache.on('keyboardadded', (keyboard) => {\r\n let eventRaiser = () => {\r\n // Execute any external (UI) code needed after loading keyboard\r\n this.legacyAPIEvents.callEvent('keyboardloaded', {\r\n keyboardName: keyboard.id\r\n });\r\n }\r\n\r\n if(this.config.deferForInitialization.hasFinalized) {\r\n eventRaiser();\r\n } else {\r\n this.config.deferForInitialization.then(eventRaiser);\r\n }\r\n });\r\n\r\n this.keyboardRequisitioner.cache.on('keyboardadded', (keyboard) => {\r\n this.legacyAPIEvents.callEvent('keyboardloaded', { keyboardName: keyboard.id });\r\n });\r\n //\r\n // #endregion\r\n }\r\n\r\n /**\r\n * Public API: Denotes the 'patch' component of the version of the current engine.\r\n *\r\n * https://help.keyman.com/developer/engine/web/current-version/reference/core/build\r\n */\r\n public get build(): number {\r\n return Number.parseInt(KEYMAN_VERSION.VERSION_PATCH, 10);\r\n }\r\n\r\n /**\r\n * Public API: Denotes the major & minor components of the version of the current engine.\r\n *\r\n * https://help.keyman.com/developer/engine/web/current-version/reference/core/version\r\n */\r\n public get version(): string {\r\n return KEYMAN_VERSION.VERSION_RELEASE;\r\n }\r\n\r\n public get hardKeyboard(): HardKeyboard {\r\n return this._hardKeyboard;\r\n }\r\n\r\n protected set hardKeyboard(keyboard: HardKeyboard) {\r\n if(this._hardKeyboard) {\r\n this._hardKeyboard.off('keyevent', this.keyEventListener);\r\n }\r\n this._hardKeyboard = keyboard;\r\n keyboard.on('keyevent', this.keyEventListener);\r\n }\r\n\r\n public get osk(): OSKView {\r\n return this._osk;\r\n }\r\n\r\n public set osk(value: OSKView) {\r\n if(this._osk) {\r\n this._osk.off('keyevent', this.keyEventListener);\r\n this.core.keyboardProcessor.layerStore.handler = this.osk.layerChangeHandler;\r\n }\r\n this._osk = value;\r\n if(value) {\r\n value.activeKeyboard = this.contextManager.activeKeyboard;\r\n value.on('keyevent', this.keyEventListener);\r\n this.core.keyboardProcessor.layerStore.handler = value.layerChangeHandler;\r\n }\r\n }\r\n\r\n public getDebugInfo(): Record {\r\n const activeKbd = this.contextManager?.activeKeyboard;\r\n\r\n const report = {\r\n configReport: this.config?.debugReport(),\r\n keyboard: {\r\n id: unprefixed(activeKbd?.metadata?.id ?? ''),\r\n langId: activeKbd?.metadata?.langId || '',\r\n version: activeKbd?.keyboard?.version ?? ''\r\n },\r\n model: {\r\n id: this.core?.activeModel?.id || ''\r\n },\r\n osk: {\r\n banner: this.osk?.bannerController?.activeType ?? '',\r\n layer: this.osk?.vkbd?.layerId || ''\r\n }\r\n };\r\n\r\n return report;\r\n }\r\n\r\n // Returned Promise: gives the model-spec object. Only resolves when any model loading or unloading\r\n // is fully complete.\r\n private refreshModel(): Promise {\r\n const kbd = this.contextManager.activeKeyboard;\r\n const model = this.modelCache.modelForLanguage(kbd?.metadata.langId);\r\n\r\n if(this.core.activeModel != model) {\r\n if(this.core.activeModel) {\r\n this.core.languageProcessor.unloadModel();\r\n }\r\n\r\n // Semi-hacky management of banner display state.\r\n if(model) {\r\n return this.core.languageProcessor.loadModel(model).then(() => {\r\n return model;\r\n });\r\n }\r\n }\r\n\r\n return Promise.resolve(model);\r\n }\r\n\r\n /**\r\n * Subscribe to Keyman Engine events documented at\r\n * https://help.keyman.com/developer/engine/web/current-version/reference/events. Note that any OSK-related\r\n * events should instead register on `keyman.osk.addEventListener`, not on this method.\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/core/addEventListener\r\n */\r\n public addEventListener>(event: Name, listener: EventListener) {\r\n this.legacyAPIEvents.addEventListener(event, listener);\r\n }\r\n\r\n /**\r\n * Public API: Unsubscribe from Keyman Engine events documented at\r\n * https://help.keyman.com/developer/engine/web/current-version/reference/events.\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/core/removeEventListener\r\n */\r\n public removeEventListener>(event: Name, listener: EventListener) {\r\n this.legacyAPIEvents.removeEventListener(event, listener);\r\n }\r\n\r\n shutdown() {\r\n this.legacyAPIEvents.shutdown();\r\n this.osk?.shutdown();\r\n }\r\n\r\n // API methods\r\n\r\n // 17.0: new! Only used by apps utilizing app/webview and one app/browser test page.\r\n // Is not part of our 'published' API.\r\n\r\n /**\r\n * Registers the specified lexical model within Keyman Engine. If a keyboard with a\r\n * matching language code is currently activated, it will also activate the model.\r\n *\r\n * @param model An object defining model ID, associated language IDs, and either the\r\n * model's definition or a path to a file containing it.\r\n */\r\n addModel(model: ModelSpec): Promise {\r\n this.modelCache.register(model);\r\n\r\n const activeStub = this.contextManager.activeKeyboard?.metadata;\r\n if(activeStub && model.languages.indexOf(activeStub.langId) != -1) {\r\n return this.refreshModel().then(() => { return; });\r\n } else {\r\n return Promise.resolve();\r\n }\r\n }\r\n\r\n // 17.0: new! Only used by apps utilizing app/webview and one app/browser test page.\r\n\r\n /**\r\n * Unregisters any previously-registered lexical model with a matching ID from Keyman Engine.\r\n * If a keyboard with a matching language code is currently activated, it will also\r\n * deactivate the model.\r\n *\r\n * @param modelId The ID for the model to be deregistered and forgotten by Keyman Engine.\r\n */\r\n removeModel(modelId: string) {\r\n this.modelCache.unregister(modelId);\r\n\r\n // Is it the active model?\r\n if(this.core.activeModel && this.core.activeModel.id == modelId) {\r\n this.core.languageProcessor.unloadModel();\r\n }\r\n }\r\n\r\n /**\r\n * Allow to change active keyboard by (internal) keyboard name\r\n *\r\n * @param {string} PInternalName Internal name\r\n * @param {string} PLgCode Language code\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/core/setActiveKeyboard\r\n */\r\n public async setActiveKeyboard(keyboardId: string, languageCode?: string): Promise {\r\n return this.contextManager.activateKeyboard(keyboardId, languageCode, true);\r\n }\r\n\r\n /**\r\n * Function getActiveKeyboard\r\n * Scope Public\r\n * @return {string} Name of active keyboard\r\n * Description Return internal name of currently active keyboard\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/core/getActiveKeyboard\r\n */\r\n public getActiveKeyboard(): string {\r\n return this.contextManager.activeKeyboard?.metadata.id ?? '';\r\n }\r\n\r\n /**\r\n * Function getActiveLanguage\r\n * Scope Public\r\n * @param {boolean=} true to retrieve full language name, false/undefined to retrieve code.\r\n * @return {string} language code\r\n * Description Return language code for currently selected language\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/core/getActiveLanguage\r\n */\r\n public getActiveLanguage(fullName?: boolean): string {\r\n // In short... the activeStub.\r\n const metadata = this.contextManager.activeKeyboard?.metadata;\r\n\r\n if(!fullName) {\r\n return metadata?.langId ?? '';\r\n } else {\r\n return metadata?.langName ?? '';\r\n }\r\n }\r\n\r\n /**\r\n * Function isChiral\r\n * Scope Public\r\n * @param {string|Object=} k0\r\n * @return {boolean}\r\n * Description Tests if the active keyboard (or optional argument) uses chiral modifiers.\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/core/isChiral\r\n */\r\n public isChiral(k0?: string | Keyboard) {\r\n let kbd: Keyboard;\r\n if(k0) {\r\n if(typeof k0 == 'string') {\r\n const kbdObj = this.keyboardRequisitioner.cache.getKeyboard(k0);\r\n if(!kbdObj) {\r\n throw new Error(`Keyboard '${k0}' has not been loaded.`);\r\n } else {\r\n k0 = kbdObj;\r\n }\r\n }\r\n\r\n kbd = k0;\r\n } else {\r\n kbd = this.core.activeKeyboard;\r\n }\r\n return kbd.isChiral;\r\n }\r\n\r\n /**\r\n * Function resetContext\r\n * Scope Public\r\n * Description Reverts the OSK to the default layer, clears any processing caches and modifier states,\r\n * and clears deadkeys and prediction-processing states on the active element (if it exists)\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/core/resetContext\r\n */\r\n public resetContext() {\r\n this.contextManager.resetContext();\r\n };\r\n\r\n /**\r\n * Function setNumericLayer\r\n * Scope Public\r\n * Description Set OSK to numeric layer if it exists\r\n */\r\n setNumericLayer() {\r\n this.core.keyboardProcessor.setNumericLayer(this.config.softDevice);\r\n };\r\n}\r\n\r\n// Intent: define common behaviors for both primary app types; each then subclasses & extends where needed.", + "import EventEmitter from 'eventemitter3';\r\n\r\nimport BannerView, { BannerController } from '../banner/bannerView.js';\r\nimport OSKViewComponent from '../components/oskViewComponent.interface.js';\r\nimport EmptyView from '../components/emptyView.js';\r\nimport HelpPageView from '../components/helpPageView.js';\r\nimport KeyboardView from '../components/keyboardView.interface.js';\r\nimport VisualKeyboard from '../visualKeyboard.js';\r\nimport { LengthStyle, ParsedLengthStyle } from '../lengthStyle.js';\r\nimport { type KeyElement } from '../keyElement.js';\r\n\r\nimport {\r\n Codes,\r\n DeviceSpec,\r\n Keyboard,\r\n KeyEvent,\r\n KeyboardProperties,\r\n ManagedPromise,\r\n type MinimalCodesInterface,\r\n type MutableSystemStore,\r\n type SystemStoreMutationHandler\r\n} from '@keymanapp/keyboard-processor';\r\nimport { createUnselectableElement, getAbsoluteX, getAbsoluteY, StylesheetManager } from 'keyman/engine/dom-utils';\r\nimport { EventListener, EventNames, LegacyEventEmitter } from 'keyman/engine/events';\r\n\r\nimport Configuration from '../config/viewConfiguration.js';\r\nimport Activator, { StaticActivator } from './activator.js';\r\nimport TouchEventPromiseMap from './touchEventPromiseMap.js';\r\n\r\n// These will likely be eliminated from THIS file at some point.\\\r\n\r\nexport type OSKPos = {'left'?: number, 'top'?: number};\r\n\r\nexport type OSKRect = {\r\n 'left'?: number,\r\n 'top'?: number,\r\n 'width'?: number,\r\n 'height'?: number,\r\n 'nosize'?: boolean,\r\n 'nomove'?: boolean\r\n};\r\n\r\n/**\r\n * Definition for OSK events documented at\r\n * https://help.keyman.com/DEVELOPER/ENGINE/WEB/16.0/reference/events/.\r\n */\r\nexport interface LegacyOSKEventMap {\r\n 'configclick'(obj: {});\r\n 'helpclick'(obj: {});\r\n 'resizemove'(obj: {});\r\n 'show'(obj: {});\r\n 'hide'(obj: {\r\n HiddenByUser: boolean\r\n });\r\n}\r\n\r\n/**\r\n * For now, these will serve as undocumented, internal events. We need a proper\r\n * design round and discussion before we consider promoting them to long-term,\r\n * documented official API events.\r\n */\r\nexport interface EventMap {\r\n /**\r\n * Designed to pass key events off to any consuming modules/libraries.\r\n *\r\n * Note: the following code block was originally used to integrate with the keyboard & input\r\n * processors, but it requires entanglement with components external to this OSK module.\r\n */\r\n 'keyevent': (event: KeyEvent) => void,\r\n\r\n /**\r\n * Indicates that the globe key has either been pressed (`on` == `true`)\r\n * or released (`on` == `false`).\r\n */\r\n globekey: (e: KeyElement, on: boolean) => void;\r\n\r\n /**\r\n * A virtual keystroke corresponding to a \"hide\" command has been received.\r\n */\r\n hiderequested: (key: KeyElement) => void;\r\n\r\n /**\r\n * Signals the special command to display the engine's version + build number.\r\n */\r\n showbuild: () => void;\r\n\r\n // While the next two are near-duplicates of the legacy event `resizemove`, these\r\n // have the advantage of providing a Promise for the end of the ongoing user\r\n // interaction. We need that Promise for focus-management.\r\n\r\n /**\r\n * Signals that the OSK is being moved by the user via a drag operation.\r\n *\r\n * The provided Promise will resolve once the drag operation is complete.\r\n *\r\n * Note that position-restoration (unpinning the OSK) is treated as a drag-move\r\n * event. It resolves near-instantly.\r\n */\r\n dragmove: (promise: Promise) => void;\r\n\r\n /**\r\n * Signals that the OSK is being resized via a drag operation (on a resize 'handle').\r\n *\r\n * The provided Promise will resolve once the resize operation is complete.\r\n */\r\n resizemove: (promise: Promise) => void;\r\n\r\n /**\r\n * Signals that either the mouse or an active touchpoint is interacting with the OSK.\r\n *\r\n * The provided `Promise` will resolve once the corresponding interaction is complete.\r\n * Note that for touch events, more than one touchpoint may coexist, each with its own\r\n * corresponding call of this event and corresponding `Promise`.\r\n */\r\n pointerinteraction: (promise: Promise) => void;\r\n}\r\n\r\nexport default abstract class OSKView extends EventEmitter implements MinimalCodesInterface {\r\n _Box: HTMLDivElement;\r\n readonly legacyEvents = new LegacyEventEmitter();\r\n\r\n // #region Key code definition aliases for legacy keyboards (that expect window['keyman']['osk'].___)\r\n get keyCodes() {\r\n return Codes.keyCodes;\r\n }\r\n\r\n get modifierCodes() {\r\n return Codes.modifierCodes;\r\n }\r\n\r\n get modifierBitmasks() {\r\n return Codes.modifierBitmasks;\r\n }\r\n\r\n get stateBitmasks() {\r\n return Codes.stateBitmasks;\r\n }\r\n // #endregion\r\n\r\n headerView: OSKViewComponent;\r\n bannerView: BannerView; // Which implements OSKViewComponent\r\n keyboardView: KeyboardView; // Which implements OSKViewComponent\r\n footerView: OSKViewComponent;\r\n\r\n private _bannerController: BannerController;\r\n\r\n private kbdStyleSheetManager: StylesheetManager;\r\n private uiStyleSheetManager: StylesheetManager;\r\n\r\n private config: Configuration;\r\n\r\n private _boxBaseMouseDown: (e: MouseEvent) => boolean;\r\n private _boxBaseTouchStart: (e: TouchEvent) => boolean;\r\n private _boxBaseTouchEventCancel: (e: TouchEvent) => boolean;\r\n\r\n private keyboardData: {\r\n keyboard: Keyboard,\r\n metadata: KeyboardProperties\r\n };\r\n\r\n /**\r\n * The configured width for this OSKManager. May be `undefined` or `null`\r\n * to allow automatic width scaling.\r\n */\r\n private _width: ParsedLengthStyle;\r\n\r\n /**\r\n * The configured height for this OSKManager. May be `undefined` or `null`\r\n * to allow automatic height scaling.\r\n */\r\n private _height: ParsedLengthStyle;\r\n\r\n /**\r\n * The computed width for this OSKManager. May be null if auto sizing\r\n * is allowed and the OSKManager is not currently in the DOM hierarchy.\r\n */\r\n private _computedWidth: number;\r\n\r\n /**\r\n * The computed height for this OSKManager. May be null if auto sizing\r\n * is allowed and the OSKManager is not currently in the DOM hierarchy.\r\n */\r\n private _computedHeight: number;\r\n\r\n /**\r\n * The base font size to use for hosted `Banner`s and `VisualKeyboard`\r\n * instances.\r\n */\r\n private _baseFontSize: ParsedLengthStyle;\r\n\r\n private needsLayout: boolean = true;\r\n\r\n private _animatedHideTimeout: number;\r\n\r\n private mouseEnterPromise?: ManagedPromise;\r\n private touchEventPromiseManager = new TouchEventPromiseMap();\r\n\r\n private static readonly STYLESHEET_FILES = ['kmwosk.css', 'globe-hint.css'];\r\n\r\n constructor(configuration: Configuration) {\r\n super();\r\n\r\n // Clone the config; do not allow object references to be altered later.\r\n this.config = configuration = {...configuration};\r\n\r\n // `undefined` is falsy, but we want a `true` default behavior for this config property.\r\n if(this.config.allowHideAnimations === undefined) {\r\n this.config.allowHideAnimations = true;\r\n }\r\n\r\n this.config.device = configuration.device || configuration.hostDevice;\r\n\r\n this.config.isEmbedded = configuration.isEmbedded || false;\r\n this.config.embeddedGestureConfig = configuration.embeddedGestureConfig || {};\r\n this.config.activator.on('activate', this.activationListener);\r\n\r\n // OSK initialization - create DIV and set default styles\r\n this._Box = createUnselectableElement('div'); // Container for OSK (Help DIV, displayed when user clicks Help icon)\r\n this.kbdStyleSheetManager = new StylesheetManager(this._Box, this.config.doCacheBusting || false);\r\n this.uiStyleSheetManager = new StylesheetManager(this._Box);\r\n\r\n // Initializes the two constant OSKComponentView fields.\r\n this.bannerView = new BannerView();\r\n this.bannerView.events.on('bannerchange', () => this.refreshLayout());\r\n\r\n this._bannerController = new BannerController(this.bannerView, this.hostDevice, this.config.predictionContextManager);\r\n\r\n this.keyboardView = null;\r\n\r\n this.setBaseMouseEventListeners();\r\n if(this.hostDevice.touchable) {\r\n this.setBaseTouchEventListeners();\r\n }\r\n }\r\n\r\n protected get configuration(): Configuration {\r\n return this.config;\r\n }\r\n\r\n public get bannerController(): BannerController {\r\n return this._bannerController;\r\n }\r\n\r\n public get hostDevice(): DeviceSpec {\r\n return this.config.hostDevice;\r\n }\r\n\r\n public get fontRootPath(): string {\r\n return this.config.pathConfig.fonts;\r\n }\r\n\r\n public get isEmbedded(): boolean {\r\n return this.config.isEmbedded;\r\n }\r\n\r\n /**\r\n * Function _VKbdMouseEnter\r\n * Scope Private\r\n * @param {Object} e event\r\n * Description Activate the KMW UI when mouse enters the OSK element hierarchy\r\n */\r\n private _VKbdMouseEnter: (e: MouseEvent) => void;\r\n\r\n /**\r\n * Function _VKbdMouseLeave\r\n * Scope Private\r\n * @param {Object} e event\r\n * Description Cancel activation of KMW UI when mouse leaves the OSK element hierarchy\r\n */\r\n private _VKbdMouseLeave: (e: MouseEvent) => void;\r\n\r\n private setBaseMouseEventListeners() {\r\n this._Box.onmouseenter = this._VKbdMouseEnter = (e) => {\r\n if(this.mouseEnterPromise) {\r\n // The chain was somehow interrupted, with the mouseleave never occurring!\r\n this.mouseEnterPromise.resolve();\r\n }\r\n\r\n this.mouseEnterPromise = new ManagedPromise();\r\n this.emit('pointerinteraction', this.mouseEnterPromise.corePromise);\r\n };\r\n\r\n this._Box.onmouseleave = this._VKbdMouseLeave = (e) => {\r\n this.mouseEnterPromise.resolve();\r\n this.mouseEnterPromise = null;\r\n // focusAssistant.setMaintainingFocus(false);\r\n };\r\n }\r\n\r\n private removeBaseMouseEventListeners() {\r\n this._Box.onmouseenter = null;\r\n this._Box.onmouseleave = null;\r\n }\r\n\r\n private setBaseTouchEventListeners() {\r\n // To prevent touch event default behaviour on mobile devices\r\n let commonPrevention = function(e: TouchEvent) {\r\n if(e.cancelable) {\r\n e.preventDefault();\r\n }\r\n e.stopPropagation();\r\n return false;\r\n }\r\n\r\n this._boxBaseTouchEventCancel = (e) => {\r\n this.touchEventPromiseManager.maintainTouches(e.touches);\r\n return commonPrevention(e);\r\n };\r\n\r\n this._boxBaseTouchStart = (e) => {\r\n for(let i = 0; i < e.changedTouches.length; i++) {\r\n let promise = this.touchEventPromiseManager.promiseForTouchpoint(e.changedTouches[i].identifier);\r\n this.emit('pointerinteraction', promise.corePromise);\r\n }\r\n\r\n this.touchEventPromiseManager.maintainTouches(e.touches);\r\n return commonPrevention(e);\r\n }\r\n\r\n this._Box.addEventListener('touchstart', this._boxBaseTouchStart, false);\r\n this._Box.addEventListener('touchmove', this._boxBaseTouchEventCancel, false);\r\n this._Box.addEventListener('touchend', this._boxBaseTouchEventCancel, false);\r\n this._Box.addEventListener('touchcancel', this._boxBaseTouchEventCancel, false);\r\n }\r\n\r\n private removeBaseTouchEventListeners() {\r\n if(!this._boxBaseTouchEventCancel) {\r\n return;\r\n }\r\n\r\n this._Box.removeEventListener('touchstart', this._boxBaseTouchStart, false);\r\n this._Box.removeEventListener('touchmove', this._boxBaseTouchEventCancel, false);\r\n this._Box.removeEventListener('touchend', this._boxBaseTouchEventCancel, false);\r\n this._Box.removeEventListener('touchcancel', this._boxBaseTouchEventCancel, false);\r\n\r\n this._boxBaseTouchEventCancel = null;\r\n this._boxBaseTouchStart = null;\r\n }\r\n\r\n // TODO: activeTarget has been 'moved' to activationModel.activationCondition (for TwoStateActivation instances).\r\n // Loosely speaking, anyway.\r\n\r\n\r\n\r\n public get targetDevice(): DeviceSpec {\r\n return this.config.device;\r\n }\r\n\r\n public set targetDevice(spec: DeviceSpec) {\r\n if(this.allowsDeviceChange(spec)) {\r\n this.config.device = spec;\r\n this.loadActiveKeyboard();\r\n } else {\r\n console.error(\"May not change target device for this OSKView type.\");\r\n }\r\n }\r\n\r\n protected allowsDeviceChange(newSpec: DeviceSpec): boolean {\r\n return false;\r\n }\r\n\r\n /**\r\n * Gets and sets the activation state model used to control presentation of the OSK.\r\n */\r\n get activationModel(): Activator {\r\n return this.config.activator;\r\n }\r\n\r\n set activationModel(model: Activator) {\r\n if(!model) {\r\n throw new Error(\"The activation model may not be set to null or undefined!\");\r\n }\r\n\r\n this.config.activator.off('activate', this.activationListener);\r\n model.on('activate', this.activationListener);\r\n\r\n this.config.activator = model;\r\n\r\n this.commonCheckAndDisplay();\r\n }\r\n\r\n public get mayDisable(): boolean {\r\n if(this.hostDevice.touchable) {\r\n return false;\r\n }\r\n\r\n if(this.activeKeyboard?.keyboard.isCJK) {\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n private readonly activationListener = (flag: boolean) => {\r\n // CJK override: may not be disabled, as the CJK elements are required.\r\n if(!this.mayDisable && !this.activationModel.enabled) {\r\n this.activationModel.off('activate', this.activationListener);\r\n try {\r\n this.activationModel.enabled = true;\r\n } finally {\r\n this.activationModel.on('activate', this.activationListener);\r\n }\r\n }\r\n this.commonCheckAndDisplay();\r\n };\r\n\r\n /**\r\n * A property denoting whether or not the OSK will be presented when it meets all\r\n * other activation conditions.\r\n *\r\n * Is equivalent to `.activationModel.enabled`.\r\n */\r\n get displayIfActive(): boolean {\r\n return this.activationModel.enabled;\r\n }\r\n\r\n /**\r\n * Used by the activation model's event listenerss and properties as a common helper;\r\n * they rely on this function to manage presentation (showing / hiding) of the OSK.\r\n */\r\n private commonCheckAndDisplay() {\r\n if(this.activationModel.activate && this.activeKeyboard) {\r\n this.present();\r\n } else {\r\n this.startHide(false);\r\n }\r\n }\r\n\r\n public get vkbd(): VisualKeyboard {\r\n if(this.keyboardView instanceof VisualKeyboard) {\r\n return this.keyboardView;\r\n } else {\r\n return null;\r\n }\r\n }\r\n\r\n public get banner(): BannerView { // Maintains old reference point used by embedding apps.\r\n return this.bannerView;\r\n }\r\n\r\n /**\r\n * The configured width for this VisualKeyboard. May be `undefined` or `null`\r\n * to allow automatic width scaling.\r\n */\r\n get width(): ParsedLengthStyle {\r\n return this._width;\r\n }\r\n\r\n /**\r\n * The configured height for this VisualKeyboard. May be `undefined` or `null`\r\n * to allow automatic height scaling.\r\n */\r\n get height(): ParsedLengthStyle {\r\n return this._height;\r\n }\r\n\r\n /**\r\n * The computed width for this VisualKeyboard. May be null if auto sizing\r\n * is allowed and the VisualKeyboard is not currently in the DOM hierarchy.\r\n */\r\n get computedWidth(): number {\r\n // Computed during layout operations; allows caching instead of continuous recomputation.\r\n if(this.needsLayout) {\r\n this.refreshLayout();\r\n }\r\n return this._computedWidth;\r\n }\r\n\r\n /**\r\n * The computed height for this VisualKeyboard. May be null if auto sizing\r\n * is allowed and the VisualKeyboard is not currently in the DOM hierarchy.\r\n */\r\n get computedHeight(): number {\r\n // Computed during layout operations; allows caching instead of continuous recomputation.\r\n if(this.needsLayout) {\r\n this.refreshLayout();\r\n }\r\n return this._computedHeight;\r\n }\r\n\r\n /**\r\n * The top-level style string for the font size used by the predictive banner\r\n * and the primary keyboard visualization elements.\r\n */\r\n get baseFontSize(): string {\r\n return this.parsedBaseFontSize?.styleString || '';\r\n }\r\n\r\n protected get parsedBaseFontSize(): ParsedLengthStyle {\r\n if(!this._baseFontSize) {\r\n this._baseFontSize = OSKView.defaultFontSize(this.targetDevice, this.computedHeight, this.isEmbedded);\r\n }\r\n\r\n return this._baseFontSize;\r\n }\r\n\r\n public static defaultFontSize(device: DeviceSpec, computedHeight: number, isEmbedded: boolean): ParsedLengthStyle {\r\n if(device.touchable) {\r\n const fontScale = device.formFactor == 'phone'\r\n ? 1.6 * (isEmbedded ? 0.65 : 0.6) * 1.2 // Combines original scaling factor with one previously applied to the layer group.\r\n : 2; // iPad or Android tablet\r\n return ParsedLengthStyle.special(fontScale, 'em');\r\n } else {\r\n return computedHeight ? ParsedLengthStyle.inPixels(computedHeight / 8) : undefined;\r\n }\r\n }\r\n\r\n public get activeKeyboard(): {\r\n keyboard: Keyboard,\r\n metadata: KeyboardProperties\r\n } {\r\n return this.keyboardData;\r\n }\r\n\r\n public set activeKeyboard(keyboardData: {\r\n keyboard: Keyboard,\r\n metadata: KeyboardProperties\r\n }) {\r\n this.keyboardData = keyboardData;\r\n this.loadActiveKeyboard();\r\n\r\n if(this.keyboardData?.keyboard.isCJK) {\r\n this.activationModel.enabled = true;\r\n }\r\n }\r\n\r\n private computeFrameHeight(): number {\r\n return (this.headerView?.layoutHeight.val || 0) + (this.footerView?.layoutHeight.val || 0);\r\n }\r\n\r\n setSize(width?: number | LengthStyle, height?: number | LengthStyle, pending?: boolean) {\r\n let mutatedFlag = false;\r\n\r\n let parsedWidth: ParsedLengthStyle;\r\n let parsedHeight: ParsedLengthStyle;\r\n\r\n if(!width && width !== 0) {\r\n return;\r\n }\r\n\r\n if(!height && height !== 0) {\r\n return;\r\n }\r\n\r\n if(Number.isFinite(width as number)) {\r\n parsedWidth = ParsedLengthStyle.inPixels(width as number);\r\n } else {\r\n parsedWidth = new ParsedLengthStyle(width as LengthStyle);\r\n }\r\n\r\n if(Number.isFinite(height as number)) {\r\n parsedHeight = ParsedLengthStyle.inPixels(height as number);\r\n } else {\r\n parsedHeight = new ParsedLengthStyle(height as LengthStyle);\r\n }\r\n\r\n if(width && height) {\r\n mutatedFlag = !this._width || !this._height;\r\n\r\n mutatedFlag = mutatedFlag || parsedWidth.styleString != this._width.styleString;\r\n mutatedFlag = mutatedFlag || parsedHeight.styleString != this._height.styleString;\r\n\r\n this._width = parsedWidth;\r\n this._height = parsedHeight;\r\n }\r\n\r\n this.needsLayout = this.needsLayout || mutatedFlag;\r\n this.refreshLayoutIfNeeded(pending);\r\n }\r\n\r\n public setNeedsLayout() {\r\n this.needsLayout = true;\r\n }\r\n\r\n public refreshLayout(pending?: boolean): void {\r\n if(!this.keyboardView) {\r\n return;\r\n }\r\n\r\n // Step 1: have the necessary conditions been met?\r\n const hasDimensions = this.width && this.height;\r\n\r\n if(!hasDimensions) {\r\n // If dimensions haven't been set yet, we have no basis for layout calculations.\r\n // We do not emit a warning here; if we did, at the time of writing this, we'd\r\n // consistently get Sentry events from the Keyman mobile apps.\r\n //\r\n // See #9206 & https://github.com/keymanapp/keyman/pull/9206#issuecomment-1627917615\r\n // for context and history.\r\n return;\r\n }\r\n\r\n const fixedSize = this.width.absolute && this.height.absolute;\r\n const computedStyle = getComputedStyle(this._Box);\r\n const isInDOM = computedStyle.height != '' && computedStyle.height != 'auto';\r\n\r\n // Step 2: determine basic layout geometry\r\n if(fixedSize) {\r\n this._computedWidth = this.width.val;\r\n this._computedHeight = this.height.val;\r\n } else if(isInDOM) {\r\n // Note: %-based auto-detect for dimensions currently has some issues; the stylesheets load\r\n // asynchronously, causing the format to be VERY off before the stylesheets fully load.\r\n //\r\n // Depending on initial effects, changes to the OSK size could cause changes to the _parent_ size,\r\n // too... so this potential bit likely needs something of a redesign.\r\n const parent = this._Box.parentElement as HTMLElement;\r\n this._computedWidth = this.width.val * (this.width.absolute ? 1 : parent.offsetWidth);\r\n this._computedHeight = this.height.val * (this.height.absolute ? 1 : parent.offsetHeight);\r\n } else {\r\n console.warn(\"Unable to properly perform layout - specification uses a relative spec, thus relies upon insertion into the DOM for layout.\");\r\n return;\r\n }\r\n\r\n // Must be set before any references to the .computedWidth and .computedHeight properties!\r\n this.needsLayout = false;\r\n\r\n // Step 3: perform layout operations.\r\n this.banner.element.style.fontSize = this.baseFontSize;\r\n if(this.vkbd) {\r\n this.vkbd.fontSize = this.parsedBaseFontSize;\r\n }\r\n\r\n if(!pending) {\r\n this.headerView?.refreshLayout();\r\n this.bannerView.refreshLayout();\r\n this.footerView?.refreshLayout();\r\n }\r\n\r\n if(this.vkbd) {\r\n let availableHeight = this.computedHeight - this.computeFrameHeight();\r\n\r\n // +5: from kmw-banner-bar's 'top' attribute when active\r\n if(this.bannerView.height > 0) {\r\n availableHeight -= this.bannerView.height + 5;\r\n }\r\n this.vkbd.setSize(this.computedWidth, availableHeight, pending);\r\n\r\n const bs = this._Box.style;\r\n // OSK size settings can only be reliably applied to standard VisualKeyboard\r\n // visualizations, not to help text or empty views.\r\n bs.width = bs.maxWidth = this.computedWidth + 'px';\r\n bs.height = bs.maxHeight = this.computedHeight + 'px';\r\n\r\n // Ensure that the layer's spacebar is properly captioned.\r\n this.vkbd.showLanguage();\r\n } else {\r\n const bs = this._Box.style;\r\n bs.width = 'auto';\r\n bs.height = 'auto';\r\n bs.maxWidth = bs.maxHeight = '';\r\n }\r\n }\r\n\r\n public refreshLayoutIfNeeded(pending?: boolean) {\r\n if(this.needsLayout) {\r\n this.refreshLayout(pending);\r\n }\r\n }\r\n\r\n public abstract getDefaultWidth(): number;\r\n public abstract getDefaultKeyboardHeight(): number;\r\n\r\n // /**\r\n // * Function _Load\r\n // * Scope Private\r\n // * Description OSK initialization when keyboard selected\r\n // */\r\n // _Load() { // Load Help - maintained only temporarily.\r\n // let keymanweb = com.keyman.singleton;\r\n // this.activeKeyboard = keymanweb.core.activeKeyboard;\r\n // }\r\n\r\n public postKeyboardLoad() {\r\n this._Visible = false; // I3363 (Build 301)\r\n\r\n // Perform any needed restructuring and/or layout tweaks (depending on the OSKView type).\r\n this.postKeyboardAdjustments();\r\n\r\n if(this.displayIfActive) {\r\n this.present();\r\n }\r\n }\r\n\r\n protected abstract postKeyboardAdjustments(): void;\r\n\r\n protected abstract setBoxStyling(): void;\r\n\r\n private loadActiveKeyboard() {\r\n this.setBoxStyling();\r\n\r\n if(this.vkbd) {\r\n this.vkbd.shutdown();\r\n }\r\n this.keyboardView = null;\r\n this.needsLayout = true;\r\n\r\n // Instantly resets the OSK container, erasing / delinking the previously-loaded keyboard.\r\n this._Box.innerHTML = '';\r\n\r\n // Temp-hack: embedded products prefer their stylesheet, etc linkages without the /osk path component.\r\n let subpath = 'osk/';\r\n if(this.config.isEmbedded) {\r\n subpath = '';\r\n }\r\n\r\n // Install the default OSK stylesheet - but don't have it managed by the keyboard-specific stylesheet manager.\r\n // We wish to maintain kmwosk.css whenever keyboard-specific styles are reset/removed.\r\n for(let sheetFile of OSKView.STYLESHEET_FILES) {\r\n const sheetHref = `${this.config.pathConfig.resources}/${subpath}${sheetFile}`;\r\n this.uiStyleSheetManager.linkExternalSheet(sheetHref);\r\n }\r\n\r\n // Any event-cancelers would go here, after the innerHTML reset.\r\n\r\n // Add header element to OSK only for desktop browsers\r\n if(this.headerView) {\r\n this._Box.appendChild(this.headerView.element);\r\n }\r\n\r\n // Add suggestion banner bar to OSK\r\n this._Box.appendChild(this.banner.element);\r\n\r\n if(this.bannerView.banner) {\r\n this.banner.banner.configureForKeyboard(this.keyboardData?.keyboard, this.keyboardData?.metadata);\r\n }\r\n\r\n let kbdView: KeyboardView = this.keyboardView = this._GenerateKeyboardView(this.keyboardData?.keyboard, this.keyboardData?.metadata);\r\n this._Box.appendChild(kbdView.element);\r\n kbdView.postInsert();\r\n\r\n // Add footer element to OSK only for desktop browsers\r\n if(this.footerView) {\r\n this._Box.appendChild(this.footerView.element);\r\n }\r\n // END: construction of the actual internal layout for the overall OSK\r\n\r\n this.banner.appendStyles();\r\n\r\n if(this.vkbd) {\r\n // Create the key preview (for phones)\r\n this.vkbd.createKeyTip();\r\n\r\n // Create the globe hint (for embedded contexts; has a stub for other contexts)\r\n const globeHint = this.vkbd.createGlobeHint();\r\n if(globeHint) {\r\n this._Box.appendChild(globeHint.element);\r\n }\r\n\r\n // Append a stylesheet for this keyboard for keyboard specific styles\r\n // or if needed to specify an embedded font\r\n this.vkbd.appendStyleSheet();\r\n }\r\n\r\n this.postKeyboardLoad();\r\n }\r\n\r\n private _GenerateKeyboardView(keyboard: Keyboard, keyboardMetadata: KeyboardProperties): KeyboardView {\r\n let device = this.targetDevice;\r\n\r\n if(this.vkbd) {\r\n this.vkbd.shutdown();\r\n }\r\n\r\n this._Box.className = \"\";\r\n\r\n // Case 1: since we hide the system keyboard on touch devices, we need\r\n // to display SOMETHING that can accept input.\r\n if(keyboard == null && !device.touchable) {\r\n // We do not (currently) allow selecting the default system keyboard on\r\n // touch form-factors. Likely b/c mnemonic difficulties.\r\n return new EmptyView();\r\n } else {\r\n // Generate a visual keyboard from the layout (or layout default)\r\n // Condition is false if no key definitions exist, formFactor == desktop, AND help text exists. All three.\r\n if(keyboard && keyboard.layout(device.formFactor as DeviceSpec.FormFactor)) {\r\n return this._GenerateVisualKeyboard(keyboard, keyboardMetadata);\r\n } else if(!keyboard /* && device.touchable (implied) */ || !keyboardMetadata) {\r\n // Show a basic, \"hollow\" OSK that at least allows input, since we're\r\n // on a touch device and hiding the system keyboard\r\n return this._GenerateVisualKeyboard(null, null);\r\n } else {\r\n // A keyboard help-page or help-text is still a visualization, even not a standard OSK.\r\n return new HelpPageView(keyboard);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Function _GenerateVisualKeyboard\r\n * Scope Private\r\n * @param {Object} keyboard The keyboard to visualize\r\n * Description Generates the visual keyboard element and attaches it to KMW\r\n */\r\n private _GenerateVisualKeyboard(keyboard: Keyboard, keyboardMetadata: KeyboardProperties): VisualKeyboard {\r\n let device = this.targetDevice;\r\n\r\n // Root element sets its own classes, one of which is 'kmw-osk-inner-frame'.\r\n let vkbd = new VisualKeyboard({\r\n keyboard: keyboard,\r\n keyboardMetadata: keyboardMetadata,\r\n device: device,\r\n hostDevice: this.hostDevice,\r\n topContainer: this._Box,\r\n styleSheetManager: this.kbdStyleSheetManager,\r\n pathConfig: this.config.pathConfig,\r\n embeddedGestureConfig: this.config.embeddedGestureConfig,\r\n isEmbedded: this.config.isEmbedded\r\n });\r\n\r\n vkbd.on('keyevent', (keyEvent) => this.emit('keyevent', keyEvent));\r\n vkbd.on('globekey', (keyElement, on) => this.emit('globekey', keyElement, on));\r\n vkbd.on('hiderequested', (keyElement) => {\r\n this.doHide(true);\r\n this.emit('hiderequested', keyElement);\r\n });\r\n\r\n // Set box class - OS and keyboard added for Build 360\r\n this._Box.className=device.formFactor+' '+ device.OS.toLowerCase() + ' kmw-osk-frame';\r\n\r\n // Add primary keyboard element to OSK\r\n return vkbd;\r\n }\r\n\r\n /**\r\n * This function may be provided to event sources to trigger changes in keyboard layer.\r\n * It is pre-bound to its OSKView instance.\r\n *\r\n ```\r\n {\r\n let core = com.keyman.singleton.core;\r\n core.keyboardProcessor.layerStore.handler = this.layerChangeHandler;\r\n }\r\n ```\r\n *\r\n * @param source\r\n * @param newValue\r\n * @returns\r\n */\r\n public layerChangeHandler: SystemStoreMutationHandler = (source: MutableSystemStore,\r\n newValue: string) => {\r\n // This handler is also triggered on state-key state changes (K_CAPS) that\r\n // may not actually change the layer.\r\n if(this.vkbd) {\r\n this.vkbd._UpdateVKShiftStyle(newValue);\r\n }\r\n\r\n if((this.vkbd && this.vkbd.layerId != newValue) || source.value != newValue) {\r\n // Prevents console errors when a keyboard only displays help.\r\n // Can occur when using SHIFT with sil_euro_latin on a desktop form-factor.\r\n //\r\n // Also, only change the layer ID itself if there is an actual corresponding layer\r\n // in the OSK.\r\n if(this.vkbd?.layerGroup.layers[newValue]) {\r\n this.vkbd.layerId = newValue;\r\n // Ensure that the layer's spacebar is properly captioned.\r\n this.vkbd.showLanguage();\r\n }\r\n\r\n // Ensure the keyboard view is modeling the correct state. (Correct layer, etc.)\r\n this.keyboardView.updateState(); // will also need the stateKeys.\r\n // We need to recalc the font size here because the layer did not have\r\n // calculated dimensions available before it was visible\r\n this.refreshLayout();\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * The main function for presenting the OSKView.\r\n *\r\n * This includes:\r\n * - refreshing its layout\r\n * - displaying it\r\n * - positioning it\r\n */\r\n public present(): void {\r\n // Do not try to display OSK if no active element\r\n if(!this.mayShow()) {\r\n return;\r\n }\r\n\r\n // Ensure the keyboard view is modeling the correct state. (Correct layer, etc.)\r\n this.keyboardView.updateState(); // get current state keys!\r\n\r\n this._Box.style.display='block'; // Is 'none' when hidden.\r\n\r\n // First thing after it's made visible.\r\n this.refreshLayoutIfNeeded();\r\n\r\n if(this.keyboardView instanceof VisualKeyboard) {\r\n this.keyboardView.showLanguage();\r\n }\r\n\r\n this._Visible=true;\r\n\r\n /* In case it's still '0' from a hide() operation.\r\n *\r\n * (Opacity is only modified when device.touchable = true,\r\n * though a couple of extra conditions may apply.)\r\n */\r\n this._Box.style.opacity = '1';\r\n\r\n // If OSK still hidden, make visible only after all calculation finished\r\n if(this._Box.style.visibility == 'hidden') {\r\n let _this = this;\r\n window.setTimeout(function() {\r\n _this._Box.style.visibility = 'visible';\r\n }, 0);\r\n }\r\n\r\n this.setDisplayPositioning();\r\n\r\n // Each subclass is responsible for raising the 'show' event on its own, since\r\n // certain ones supply extra information in their event param object.\r\n }\r\n\r\n /**\r\n * Method usable by subclasses of OSKView to control that OSKView type's\r\n * positioning behavior when needed by the present() method.\r\n */\r\n protected abstract setDisplayPositioning();\r\n\r\n /**\r\n * Method used to start a potentially-asynchronous hide of the OSK.\r\n * @param hiddenByUser `true` if this hide operation was directly requested by the user.\r\n */\r\n public startHide(hiddenByUser: boolean): void {\r\n if(!this.mayHide(hiddenByUser)) {\r\n return;\r\n }\r\n\r\n if(hiddenByUser) {\r\n // The one location outside of the `displayIfActive` property that bypasses the setter.\r\n // Avoids needless recursion that could be triggered by it, as we're already in the\r\n // process of hiding the OSK anyway.\r\n this.activationModel.enabled = ((this.keyboardData.keyboard.isCJK || this.hostDevice.touchable) ? true : false); // I3363 (Build 301)\r\n }\r\n\r\n let promise: Promise = null;\r\n if(this._Box && this.hostDevice.touchable && !(this.keyboardView instanceof EmptyView) && this.config.allowHideAnimations) {\r\n /**\r\n * Note: this refactored code appears to reflect a currently-dead code path. 14.0's\r\n * equivalent is either extremely niche or is actually inaccessible.\r\n */\r\n promise = this.useHideAnimation();\r\n } else {\r\n promise = Promise.resolve(true);\r\n }\r\n\r\n const _this = this;\r\n promise.then(function(shouldHide: boolean) {\r\n if(shouldHide) {\r\n _this.finalizeHide();\r\n }\r\n });\r\n\r\n // Allow UI to execute code when hiding the OSK\r\n this.doHide(hiddenByUser);\r\n }\r\n\r\n /**\r\n * Performs the _actual_ logic and functionality involved in hiding the OSK.\r\n */\r\n protected finalizeHide() {\r\n if(document.body.className.indexOf('osk-always-visible') >= 0) {\r\n return;\r\n }\r\n\r\n if(this._Box) {\r\n let bs=this._Box.style;\r\n bs.display = 'none';\r\n bs.transition = '';\r\n bs.opacity = '1';\r\n this._Visible=false;\r\n }\r\n\r\n if(this.vkbd) {\r\n this.vkbd.onHide();\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @returns `false` if the OSK is in an invalid state for being presented to the user.\r\n */\r\n protected mayShow(): boolean {\r\n if(!this.activationModel.conditionsMet) {\r\n return false;\r\n }\r\n\r\n // Never display the OSK for desktop browsers unless KMW element is focused, and a keyboard selected\r\n if(!this.keyboardView || this.keyboardView instanceof EmptyView || !this.activationModel.enabled) {\r\n return false;\r\n }\r\n\r\n if(!this._Box) {\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n *\r\n * @param hiddenByUser\r\n * @returns `false` if the OSK is in an invalid state for being hidden from the user.\r\n */\r\n protected mayHide(hiddenByUser: boolean): boolean {\r\n if(this.activationModel.conditionsMet && !this.mayDisable) {\r\n return false;\r\n }\r\n\r\n if(this.activationModel instanceof StaticActivator) {\r\n return false;\r\n }\r\n\r\n if(!hiddenByUser && this.hostDevice.formFactor == 'desktop') {\r\n //Allow desktop OSK to remain visible on blur if body class set\r\n if(document.body.className.indexOf('osk-always-visible') >= 0) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Applies CSS styling and handling needed to perform a fade animation when\r\n * hiding the OSK.\r\n *\r\n * Note: currently reflects an effectively-dead code path, though this is\r\n * likely not intentional. Other parts of the KMW engine seem to call hideNow()\r\n * synchronously after each and every part of the engine that calls this function,\r\n * cancelling the Promise.\r\n *\r\n * @returns A Promise denoting either cancellation of the hide (`false`) or\r\n * completion of the hide & its animation (`true`)\r\n */\r\n\r\n protected useHideAnimation(): Promise {\r\n const os = this._Box.style;\r\n const _this = this;\r\n\r\n return new Promise(function(resolve) {\r\n const cleanup = function() {\r\n // TODO(lowpri): attach event listeners on create and leave them there\r\n _this._Box.removeEventListener('transitionend', cleanup, false);\r\n _this._Box.removeEventListener('webkitTransitionEnd', cleanup, false);\r\n _this._Box.removeEventListener('transitioncancel', cleanup, false);\r\n _this._Box.removeEventListener('webkitTransitionCancel', cleanup, false);\r\n if(_this._animatedHideTimeout != 0) {\r\n window.clearTimeout(_this._animatedHideTimeout);\r\n }\r\n _this._animatedHideTimeout = 0;\r\n\r\n if(_this._Visible && _this.activationModel.conditionsMet) {\r\n // Leave opacity alone and clear transition if another element activated\r\n os.transition='';\r\n os.opacity='1';\r\n resolve(false);\r\n return false;\r\n } else {\r\n resolve(true);\r\n return true;\r\n }\r\n }, startup = function() {\r\n _this._Box.removeEventListener('transitionrun', startup, false);\r\n _this._Box.removeEventListener('webkitTransitionRun', startup, false);\r\n _this._Box.addEventListener('transitionend', cleanup, false);\r\n _this._Box.addEventListener('webkitTransitionEnd', cleanup, false);\r\n _this._Box.addEventListener('transitioncancel', cleanup, false);\r\n _this._Box.addEventListener('webkitTransitionCancel', cleanup, false);\r\n };\r\n\r\n _this._Box.addEventListener('transitionrun', startup, false);\r\n _this._Box.addEventListener('webkitTransitionRun', startup, false);\r\n\r\n os.transition='opacity 0.5s linear 0';\r\n os.opacity='0';\r\n\r\n // Cannot hide the OSK smoothly using a transitioned drop, since for\r\n // position:fixed elements transitioning is incompatible with translate3d(),\r\n // and also does not work with top, bottom or height styles.\r\n // Opacity can be transitioned and is probably the simplest alternative.\r\n // We must condition on osk._Visible in case focus has since been moved to another\r\n // input (in which case osk._Visible will be non-zero)\r\n _this._animatedHideTimeout = window.setTimeout(cleanup,\r\n 200); // Wait a bit before starting, to allow for moving to another element\r\n });\r\n }\r\n\r\n /**\r\n * Used to synchronously hide the OSK, cancelling any async hide animations that have\r\n * not started and immediately completing the hide of any hide ops pending completion\r\n * of their animation.\r\n */\r\n public hideNow() {\r\n if(!this.mayHide(false) || !this._Box) {\r\n return;\r\n }\r\n\r\n // Two possible uses for _animatedHideResolver:\r\n // - _animatedHideTimeout is set: animation is waiting to start\r\n // - _animatedHideTimeout is null: animation has already started.\r\n\r\n // Was an animated hide waiting to start? Just cancel it.\r\n if(this._animatedHideTimeout) {\r\n window.clearTimeout(this._animatedHideTimeout);\r\n this._animatedHideTimeout = 0;\r\n }\r\n\r\n // Was an animated hide already in progress? If so, just trigger it early.\r\n const os = this._Box.style;\r\n os.transition='';\r\n os.opacity='0';\r\n this.finalizeHide();\r\n }\r\n\r\n ['shutdown']() {\r\n // Disable the OSK's event handlers.\r\n this.removeBaseMouseEventListeners();\r\n this.removeBaseTouchEventListeners();\r\n\r\n // Remove the OSK's elements from the document, allowing them to be properly cleaned up.\r\n // Necessary for clean engine testing.\r\n var _box = this._Box;\r\n if(_box.parentElement) {\r\n _box.parentElement.removeChild(_box);\r\n }\r\n\r\n this.kbdStyleSheetManager.unlinkAll();\r\n this.uiStyleSheetManager.unlinkAll();\r\n }\r\n\r\n /**\r\n * Function getRect\r\n * Scope Public\r\n * @return {Object.} Array object with position and size of OSK container\r\n * Description Get rectangle containing KMW Virtual Keyboard\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/osk/getRect\r\n */\r\n public getRect(): OSKRect {\t\t// I2405\r\n var p: OSKRect = {};\r\n\r\n // Always return these based upon _Box; using this.vkbd will fail to account for banner and/or\r\n // the desktop OSK border.\r\n p['left'] = p.left = getAbsoluteX(this._Box);\r\n p['top'] = p.top = getAbsoluteY(this._Box);\r\n\r\n p['width'] = this.computedWidth;\r\n p['height'] = this.computedHeight;\r\n return p;\r\n }\r\n\r\n /* ---- Legacy interfacing methods and fields ----\r\n *\r\n * The endgoal is to eliminate the need for these entirely, but extra work and care\r\n * will be necessary to achieve said endgoal for these methods.\r\n *\r\n * The simplest way forward is to maintain them, then resolve them independently,\r\n * one at a time.\r\n */\r\n\r\n // OSK state fields & events\r\n //\r\n // These are relatively stable and may be preserved as they are.\r\n _Visible: boolean = false;\r\n\r\n /**\r\n * Function enabled\r\n * Scope Public\r\n * @return {boolean|number} True if KMW OSK enabled\r\n * Description Test if KMW OSK is enabled\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/osk/isEnabled\r\n */\r\n public isEnabled(): boolean {\r\n return this.displayIfActive;\r\n }\r\n\r\n /**\r\n * Function isVisible\r\n * Scope Public\r\n * @return {boolean|number} True if KMW OSK visible\r\n * Description Test if KMW OSK is actually visible\r\n * Note that this will usually return false after any UI event that results in (temporary) loss of input focus\r\n *\r\n * https://help.keyman.com/developer/engine/web/current-version/reference/osk/isVisible\r\n */\r\n public isVisible(): boolean {\r\n return this._Visible;\r\n }\r\n\r\n /**\r\n * Function hide\r\n * Scope Public\r\n * Description Prevent display of OSK window on focus\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/osk/hide\r\n */\r\n public hide() {\r\n this.activationModel.enabled = false;\r\n this.startHide(true);\r\n }\r\n\r\n /**\r\n * Description Display KMW OSK (at position set in callback to UI)\r\n * Function show\r\n * Scope Public\r\n * @param {(boolean|number)=} bShow True to display, False to hide, omitted to toggle\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/osk/show\r\n */\r\n public show(bShow?: boolean) {\r\n if(arguments.length > 0) {\r\n this.activationModel.enabled = bShow;\r\n } else {\r\n if(this.activationModel.conditionsMet) {\r\n this.activationModel.enabled = !this.activationModel.enabled;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Allow UI to respond to OSK being shown (passing position and properties)\r\n *\r\n * @param {Object=} p object with coordinates and userdefined flag\r\n * @return {boolean}\r\n *\r\n */\r\n doShow(p) {\r\n // Newer style 'doShow' emitted from .present by default.\r\n this.legacyEvents.callEvent('show', p);\r\n }\r\n\r\n /**\r\n * Allow UI to update respond to OSK being hidden\r\n *\r\n * @param {boolean} p object with coordinates and userdefined flag\r\n * @return {void}\r\n *\r\n */\r\n doHide(hiddenByUser: boolean) {\r\n const p={\r\n HiddenByUser: hiddenByUser\r\n };\r\n this.legacyEvents.callEvent('hide', p);\r\n }\r\n\r\n /**\r\n * Function addEventListener\r\n * Scope Public\r\n * @param {string} event event name\r\n * @param {function(Object)} func event handler\r\n * Description Wrapper function to add and identify OSK-specific event handlers\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/osk/addEventListener\r\n */\r\n public addEventListener(\r\n event: T,\r\n fn: EventListener\r\n ): void {\r\n this.legacyEvents.addEventListener(event, fn);\r\n }\r\n\r\n /**\r\n * Function removeEventListener\r\n * Scope Public\r\n * @param {string} event event name\r\n * @param {function(Object)} func event handler\r\n * Description Wrapper function to remove previously-added OSK-specific event handlers\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/osk/removeEventListener\r\n */\r\n public removeEventListener(\r\n event: T,\r\n fn: EventListener\r\n ): void {\r\n this.legacyEvents.removeEventListener(event, fn);\r\n }\r\n}", + "import EventEmitter from 'eventemitter3';\r\n\r\nimport { Banner, BlankBanner, ImageBanner, SuggestionBanner } from './banner.js';\r\nimport OSKViewComponent from '../components/oskViewComponent.interface.js';\r\nimport { ParsedLengthStyle } from '../lengthStyle.js';\r\n\r\nimport { DeviceSpec } from '@keymanapp/web-utils';\r\nimport type { PredictionContext, StateChangeEnum } from '@keymanapp/input-processor';\r\nimport { createUnselectableElement } from 'keyman/engine/dom-utils';\r\n\r\n/**\r\n * This object is used to specify options by both `BannerManager.getOptions`\r\n * and `BannerManager.setOptions`. Refer to the latter for specification of\r\n * each field.\r\n */\r\nexport interface BannerOptions {\r\n alwaysShow?: boolean;\r\n imagePath?: string;\r\n}\r\n\r\nexport type BannerType = \"blank\" | \"image\" | \"suggestion\";\r\n\r\ninterface BannerViewEventMap {\r\n 'bannerchange': () => void;\r\n}\r\n\r\n/**\r\n * The `BannerManager` module is designed to serve as a manager for the\r\n * different `Banner` types.\r\n * To facilitate this, it will provide a root element property that serves\r\n * as a container for any active `Banner`, helping KMW to avoid needless\r\n * DOM element shuffling.\r\n *\r\n * Goals for the `BannerManager`:\r\n *\r\n * * It will be exposed as `keyman.osk.banner` and will provide the following API:\r\n * * `getOptions`, `setOptions` - refer to the `BannerOptions` class for details.\r\n * * This provides a persistent point that the web page designers and our\r\n * model apps can utilize and can communicate with.\r\n * * These API functions are designed for live use and will allow\r\n * _hot-swapping_ the `Banner` instance; they're not initialization-only.\r\n * * Disabling the `Banner` (even for suggestions) outright with\r\n * `enablePredictions == false` will auto-unload any loaded predictive model\r\n * from `ModelManager` and setting it to `true` will revert this.\r\n * * This should help to avoid wasting computational resources.\r\n * * It will listen to ModelManager events and automatically swap Banner\r\n * instances as appropriate:\r\n * * The option `persistentBanner == true` is designed to replicate current\r\n * iOS system keyboard behavior.\r\n * * When true, an `ImageBanner` will be displayed.\r\n * * If false, it will be replaced with a `BlankBanner` of zero height,\r\n * corresponding to our current default lack of banner.\r\n * * It will not automatically set `persistentBanner == true`;\r\n * this must be set by the iOS app, and only under the following conditions:\r\n * * `keyman.isEmbedded == true`\r\n * * `device.OS == 'ios'`\r\n * * Keyman is being used as the system keyboard within an app that\r\n * needs to reserve this space (i.e: Keyman for iOS),\r\n * rather than as its standalone app.\r\n */\r\nexport default class BannerView implements OSKViewComponent {\r\n private bannerContainer: HTMLDivElement;\r\n private activeBanner: Banner;\r\n private _activeBannerHeight: number = Banner.DEFAULT_HEIGHT;\r\n\r\n public readonly events = new EventEmitter();\r\n\r\n constructor() {\r\n // Step 1 - establish the container element. Must come before this.setOptions.\r\n this.constructContainer();\r\n }\r\n\r\n /**\r\n * Constructs the
element used to contain hot-swapped `Banner` instances.\r\n */\r\n private constructContainer(): HTMLDivElement {\r\n let d = createUnselectableElement('div');\r\n d.id = \"keymanweb_banner_container\";\r\n d.className = \"kmw-banner-container\";\r\n return this.bannerContainer = d;\r\n }\r\n\r\n /**\r\n * Returns the `Banner`-containing div element used to facilitate hot-swapping.\r\n */\r\n public get element(): HTMLDivElement {\r\n return this.bannerContainer;\r\n }\r\n\r\n /**\r\n * Applies any stylesheets needed by specific `Banner` instances.\r\n */\r\n public appendStyles() {\r\n if(this.activeBanner) {\r\n this.activeBanner.appendStyleSheet();\r\n }\r\n }\r\n\r\n public get banner(): Banner {\r\n return this.activeBanner;\r\n }\r\n\r\n /**\r\n * Sets the active `Banner` to the specified type, regardless of\r\n * existing management logic settings.\r\n *\r\n * @param banner The `Banner` instance to set as active.\r\n */\r\n public set banner(banner: Banner) {\r\n if(this.activeBanner) {\r\n if(banner == this.activeBanner) {\r\n return;\r\n } else {\r\n let prevBanner = this.activeBanner;\r\n this.activeBanner = banner;\r\n this.bannerContainer.replaceChild(banner.getDiv(), prevBanner.getDiv());\r\n }\r\n } else {\r\n this.activeBanner = banner;\r\n if(banner) {\r\n this.bannerContainer.appendChild(banner.getDiv());\r\n }\r\n }\r\n\r\n if(!(banner instanceof BlankBanner)) {\r\n banner.height = this.activeBannerHeight;\r\n }\r\n\r\n this.events.emit('bannerchange');\r\n }\r\n\r\n /**\r\n * Gets the height (in pixels) of the active `Banner` instance.\r\n */\r\n public get height(): number {\r\n if(this.activeBanner) {\r\n return this.activeBanner.height;\r\n } else {\r\n return 0;\r\n }\r\n }\r\n\r\n public get activeBannerHeight(): number {\r\n return this._activeBannerHeight;\r\n }\r\n\r\n /**\r\n * Sets the height (in pixels) of the active 'Banner' instance.\r\n */\r\n public set activeBannerHeight(h: number) {\r\n this._activeBannerHeight = h;\r\n\r\n if (this.activeBanner && !(this.activeBanner instanceof BlankBanner)) {\r\n this.activeBanner.height = h;\r\n }\r\n }\r\n\r\n public get layoutHeight(): ParsedLengthStyle {\r\n return ParsedLengthStyle.inPixels(this.height);\r\n }\r\n\r\n public refreshLayout() {};\r\n}\r\n\r\nexport class BannerController {\r\n private _activeType: BannerType;\r\n private _options: BannerOptions = {};\r\n private container: BannerView;\r\n private alwaysShow: boolean;\r\n private imagePath?: string = \"\";\r\n\r\n private predictionContext?: PredictionContext;\r\n\r\n private readonly hostDevice: DeviceSpec;\r\n\r\n public static readonly DEFAULT_OPTIONS: BannerOptions = {\r\n alwaysShow: false,\r\n imagePath: \"\"\r\n }\r\n\r\n constructor(bannerView: BannerView, hostDevice: DeviceSpec, predictionContext?: PredictionContext) {\r\n // Step 1 - establish the container element. Must come before this.setOptions.\r\n this.hostDevice = hostDevice;\r\n this.container = bannerView;\r\n this.predictionContext = predictionContext;\r\n\r\n // Initialize with the default options - any 'manually set' options come post-construction.\r\n // This will also automatically set the default banner in place.\r\n this.setOptions(BannerController.DEFAULT_OPTIONS);\r\n }\r\n\r\n /**\r\n * This function corresponds to `keyman.osk.banner.getOptions`.\r\n *\r\n * Gets the current control settings in use by `BannerManager`.\r\n */\r\n public getOptions(): BannerOptions {\r\n let retObj = {};\r\n\r\n for(let key in this._options) {\r\n retObj[key] = this._options[key];\r\n }\r\n\r\n return retObj;\r\n }\r\n\r\n /**\r\n * This function corresponds to `keyman.osk.banner.setOptions`.\r\n *\r\n * Sets options used to tweak the automatic `Banner`\r\n * control logic used by `BannerManager`.\r\n * @param optionSpec An object specifying one or more of the following options:\r\n * * `persistentBanner` (boolean) When `true`, ensures that a `Banner`\r\n * is always displayed, even when no predictive model exists\r\n * for the active language.\r\n *\r\n * Default: `false`\r\n * * `imagePath` (URL string) Specifies the file path to use for an\r\n * `ImageBanner` when `persistentBanner` is `true` and no predictive model exists.\r\n *\r\n * Default: `''`.\r\n * * `enablePredictions` (boolean) Turns KMW predictions\r\n * on (when `true`) and off (when `false`).\r\n *\r\n * Default: `true`.\r\n */\r\n public setOptions(optionSpec: BannerOptions) {\r\n for(let key in optionSpec) {\r\n switch(key) {\r\n // Each defined option may require specialized handling.\r\n case 'alwaysShow':\r\n // Determines the banner type to activate.\r\n this.alwaysShow = optionSpec[key];\r\n break;\r\n case 'imagePath':\r\n // Determines the image file to use for ImageBanners.\r\n this.imagePath = optionSpec[key];\r\n break;\r\n default:\r\n // Invalid option specified!\r\n }\r\n this._options[key] = optionSpec[key];\r\n\r\n // If no banner instance exists yet, go with a safe, blank initialization.\r\n if(!this.container.banner) {\r\n this.selectBanner('inactive');\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Sets the active `Banner` to the specified type, regardless of\r\n * existing management logic settings.\r\n *\r\n * @param type `'blank' | 'image' | 'suggestion'` - A plain-text string\r\n * representing the type of `Banner` to set active.\r\n * @param height - Optional banner height in pixels.\r\n */\r\n public setBanner(type: BannerType) {\r\n var banner: Banner;\r\n\r\n let oldBanner = this.container.banner;\r\n if(oldBanner instanceof SuggestionBanner) {\r\n this.predictionContext.off('update', oldBanner.onSuggestionUpdate);\r\n }\r\n\r\n switch(type) {\r\n case 'blank':\r\n banner = new BlankBanner();\r\n break;\r\n case 'image':\r\n banner = new ImageBanner(this.imagePath, this.container.activeBannerHeight);\r\n break;\r\n case 'suggestion':\r\n let suggestBanner = banner = new SuggestionBanner(this.hostDevice, this.container.activeBannerHeight);\r\n suggestBanner.predictionContext = this.predictionContext;\r\n suggestBanner.events.on('apply', (selection) => this.predictionContext.accept(selection.suggestion));\r\n\r\n this.predictionContext.on('update', suggestBanner.onSuggestionUpdate);\r\n break;\r\n default:\r\n throw new Error(\"Invalid type specified for the banner!\");\r\n }\r\n\r\n this._activeType = type;\r\n\r\n if(banner) {\r\n this.container.banner = banner;\r\n }\r\n }\r\n\r\n /**\r\n * Handles `LanguageProcessor`'s `'statechange'` events,\r\n * allowing logic to automatically hot-swap `Banner`s as needed.\r\n * @param state\r\n */\r\n selectBanner(state: StateChangeEnum) {\r\n // Only display a SuggestionBanner when LanguageProcessor states it is active.\r\n if(state == 'active' || state == 'configured') {\r\n this.setBanner('suggestion');\r\n } else if(state == 'inactive') {\r\n if(this.alwaysShow) {\r\n this.setBanner('image');\r\n } else {\r\n this.setBanner('blank');\r\n }\r\n }\r\n }\r\n\r\n public get activeType(): BannerType {\r\n return this._activeType;\r\n }\r\n}", + "import EventEmitter from 'eventemitter3';\r\n\r\nimport { DeviceSpec } from '@keymanapp/web-utils';\r\nimport { Keyboard, KeyboardProperties } from '@keymanapp/keyboard-processor';\r\nimport { type PredictionContext } from '@keymanapp/input-processor';\r\nimport InputEventEngine, { InputEventEngineConfig } from '../input/event-interpreter/inputEventEngine.js';\r\nimport MouseEventEngine from '../input/event-interpreter/mouseEventEngine.js';\r\nimport TouchEventEngine from '../input/event-interpreter/touchEventEngine.js';\r\nimport UITouchHandlerBase from '../input/event-interpreter/uiTouchHandlerBase.js';\r\n\r\nimport { createUnselectableElement } from 'keyman/engine/dom-utils';\r\n\r\n// Base class for a banner above the keyboard in the OSK\r\n\r\nexport abstract class Banner {\r\n private _height: number; // pixels\r\n private div: HTMLDivElement;\r\n\r\n public static DEFAULT_HEIGHT: number = 37; // pixels; embedded apps can modify\r\n\r\n public static readonly BANNER_CLASS: string = 'kmw-banner-bar';\r\n public static readonly BANNER_ID: string = 'kmw-banner-bar';\r\n\r\n /**\r\n * Function height\r\n * Scope Public\r\n * @returns {number} height in pixels\r\n * Description Returns the height of the banner in pixels\r\n */\r\n public get height(): number {\r\n return this._height;\r\n }\r\n\r\n /**\r\n * Function height\r\n * Scope Public\r\n * @param {number} height the height in pixels\r\n * Description Sets the height of the banner in pixels. If a negative\r\n * height is given, set height to 0 pixels.\r\n * Also updates the banner styling.\r\n */\r\n public set height(height: number) {\r\n this._height = (height > 0) ? height : 0;\r\n this.update();\r\n }\r\n\r\n /**\r\n * Function update\r\n * @return {boolean} true if the banner styling changed\r\n * Description Update the height and display styling of the banner\r\n */\r\n private update() : boolean {\r\n let ds = this.div.style;\r\n let currentHeightStyle = ds.height;\r\n let currentDisplayStyle = ds.display;\r\n\r\n if (this._height > 0) {\r\n ds.height = this._height + 'px';\r\n ds.display = 'block';\r\n } else {\r\n ds.height = '0px';\r\n ds.display = 'none';\r\n }\r\n\r\n return (!(currentHeightStyle === ds.height) ||\r\n !(currentDisplayStyle === ds.display));\r\n }\r\n\r\n public constructor(height?: number) {\r\n let d = createUnselectableElement('div');\r\n d.id = Banner.BANNER_ID;\r\n d.className = Banner.BANNER_CLASS;\r\n this.div = d;\r\n\r\n this.height = height;\r\n this.update();\r\n }\r\n\r\n public appendStyleSheet() {\r\n // TODO: add stylesheets\r\n // See VisualKeyboard's method + 'addFontStyle' for current handling.\r\n }\r\n\r\n /**\r\n * Function getDiv\r\n * Scope Public\r\n * @returns {HTMLElement} Base element of the banner\r\n * Description Returns the HTMLElelemnt of the banner\r\n */\r\n public getDiv(): HTMLElement {\r\n return this.div;\r\n }\r\n\r\n /**\r\n * Allows banners to adapt based on the active keyboard and related properties, such as\r\n * associated fonts.\r\n * @param keyboard\r\n * @param keyboardProperties\r\n */\r\n public configureForKeyboard(keyboard: Keyboard, keyboardProperties: KeyboardProperties) { }\r\n}\r\n\r\n/**\r\n * Function BlankBanner\r\n * Description A banner of height 0 that should not be shown\r\n */\r\nexport class BlankBanner extends Banner {\r\n\r\n constructor() {\r\n super(0);\r\n }\r\n}\r\n\r\n/**\r\n * Function ImageBanner\r\n * @param {string} imagePath Path of image to display in the banner\r\n * @param {number} height If provided, the height of the banner in pixels\r\n * Description Display an image in the banner\r\n */\r\nexport class ImageBanner extends Banner {\r\n private img: HTMLElement;\r\n\r\n constructor(imagePath: string, height?: number) {\r\n if (imagePath.length > 0) {\r\n super();\r\n if (height) {\r\n this.height = height;\r\n }\r\n } else {\r\n super(0);\r\n }\r\n\r\n if(imagePath.indexOf('base64') >=0) {\r\n console.log(\"Loading img from base64 data\");\r\n } else {\r\n console.log(\"Loading img with src '\" + imagePath + \"'\");\r\n }\r\n this.img = document.createElement('img');\r\n this.img.setAttribute('src', imagePath);\r\n let ds = this.img.style;\r\n ds.width = '100%';\r\n ds.height = '100%';\r\n this.getDiv().appendChild(this.img);\r\n console.log(\"Image loaded.\");\r\n }\r\n\r\n /**\r\n * Function setImagePath\r\n * Scope Public\r\n * @param {string} imagePath Path of image to display in the banner\r\n * Description Update the image in the banner\r\n */\r\n public setImagePath(imagePath: string) {\r\n if (this.img) {\r\n this.img.setAttribute('src', imagePath);\r\n }\r\n }\r\n}\r\n\r\nexport class BannerSuggestion {\r\n div: HTMLDivElement;\r\n private display: HTMLSpanElement;\r\n private fontFamily?: string;\r\n private rtl: boolean = false;\r\n\r\n private _suggestion: Suggestion;\r\n\r\n private index: number;\r\n\r\n static readonly BASE_ID = 'kmw-suggestion-';\r\n\r\n constructor(index: number, isRTL: boolean) {\r\n this.index = index;\r\n this.rtl = isRTL;\r\n\r\n this.constructRoot();\r\n\r\n // Provides an empty, base SPAN for text display. We'll swap these out regularly;\r\n // `Suggestion`s will have varying length and may need different styling.\r\n let display = this.display = createUnselectableElement('span');\r\n this.div.appendChild(display);\r\n }\r\n\r\n private constructRoot() {\r\n // Add OSK suggestion labels\r\n let div = this.div = createUnselectableElement('div'), ds=div.style;\r\n div.className = \"kmw-suggest-option\";\r\n div.id = BannerSuggestion.BASE_ID + this.index;\r\n\r\n // Ensures that a reasonable width % is set.\r\n let usableWidth = 100 - SuggestionBanner.MARGIN * (SuggestionBanner.SUGGESTION_LIMIT - 1);\r\n let widthpc = usableWidth / SuggestionBanner.SUGGESTION_LIMIT;\r\n\r\n ds.width = widthpc + '%';\r\n\r\n this.div['suggestion'] = this;\r\n }\r\n\r\n public matchKeyboardProperties(keyboardProperties: KeyboardProperties) {\r\n const div = this.div;\r\n\r\n if(keyboardProperties) {\r\n if (keyboardProperties['KLC']) {\r\n div.lang = keyboardProperties['KLC'];\r\n }\r\n\r\n // Establish base font settings\r\n let font = keyboardProperties['KFont'];\r\n if(font && font.family && font.family != '') {\r\n div.style.fontFamily = this.fontFamily = font.family;\r\n }\r\n }\r\n }\r\n\r\n get suggestion(): Suggestion {\r\n return this._suggestion;\r\n }\r\n\r\n /**\r\n * Function update\r\n * @param {string} id Element ID for the suggestion span\r\n * @param {Suggestion} suggestion Suggestion from the lexical model\r\n * Description Update the ID and text of the BannerSuggestionSpec\r\n */\r\n public update(suggestion: Suggestion) {\r\n this._suggestion = suggestion;\r\n this.updateText();\r\n }\r\n\r\n private updateText() {\r\n let display = this.generateSuggestionText(this.rtl);\r\n this.div.replaceChild(display, this.display);\r\n this.display = display;\r\n }\r\n\r\n public isEmpty(): boolean {\r\n return !this._suggestion;\r\n }\r\n\r\n /**\r\n * Function generateSuggestionText\r\n * @return {HTMLSpanElement} Span element of the suggestion\r\n * Description Produces a HTMLSpanElement with the key's actual text.\r\n */\r\n //\r\n public generateSuggestionText(rtl: boolean): HTMLSpanElement {\r\n let suggestion = this._suggestion;\r\n var suggestionText: string;\r\n\r\n var s=createUnselectableElement('span');\r\n s.className = 'kmw-suggestion-text';\r\n\r\n if(suggestion == null) {\r\n return s;\r\n }\r\n\r\n if(suggestion.displayAs == null || suggestion.displayAs == '') {\r\n suggestionText = '\\xa0'; // default: nbsp.\r\n } else {\r\n // Default the LTR ordering to match that of the active keyboard.\r\n let orderCode = rtl ? 0x202e /* RTL */ : 0x202d /* LTR */;\r\n suggestionText = String.fromCharCode(orderCode) + suggestion.displayAs;\r\n }\r\n\r\n // TODO: Dynamic suggestion text resizing. (Refer to OSKKey.getTextWidth in visualKeyboard.ts.)\r\n\r\n // Finalize the suggestion text\r\n s.innerHTML = suggestionText;\r\n return s;\r\n }\r\n}\r\n\r\n/**\r\n * Function SuggestionBanner\r\n * Scope Public\r\n * @param {number} height - If provided, the height of the banner in pixels\r\n * Description Display lexical model suggestions in the banner\r\n */\r\nexport class SuggestionBanner extends Banner {\r\n public static readonly SUGGESTION_LIMIT: number = 3;\r\n public static readonly MARGIN = 1;\r\n\r\n public readonly events: EventEmitter;\r\n\r\n private currentSuggestions: Suggestion[] = [];\r\n\r\n private options : BannerSuggestion[] = [];\r\n private hostDevice: DeviceSpec;\r\n\r\n private manager: SuggestionInputManager;\r\n\r\n private _predictionContext: PredictionContext;\r\n\r\n static readonly TOUCHED_CLASS: string = 'kmw-suggest-touched';\r\n static readonly BANNER_CLASS: string = 'kmw-suggest-banner';\r\n\r\n constructor(hostDevice: DeviceSpec, height?: number) {\r\n super(height || Banner.DEFAULT_HEIGHT);\r\n this.hostDevice = hostDevice;\r\n\r\n this.getDiv().className = this.getDiv().className + ' ' + SuggestionBanner.BANNER_CLASS;\r\n\r\n this.buildInternals(false);\r\n\r\n this.manager = new SuggestionInputManager(this.getDiv());\r\n this.events = this.manager.events;\r\n\r\n this.setupInputHandling();\r\n }\r\n\r\n buildInternals(rtl: boolean) {\r\n if(this.options.length > 0) {\r\n this.options.splice(0, this.options.length); // Clear the array.\r\n }\r\n for (var i=0; i {\r\n const elem = suggestion.div;\r\n let classes = elem.className;\r\n let cs = ' ' + SuggestionBanner.TOUCHED_CLASS;\r\n\r\n if(on && classes.indexOf(cs) < 0) {\r\n elem.className=classes+cs;\r\n } else {\r\n elem.className=classes.replace(cs,'');\r\n }\r\n });\r\n\r\n this.manager.events.on('apply', (option) => {\r\n if(this.predictionContext) {\r\n this.predictionContext.accept(option.suggestion);\r\n }\r\n });\r\n }\r\n\r\n public configureForKeyboard(keyboard: Keyboard, keyboardProperties: KeyboardProperties) {\r\n const rtl = keyboard.isRTL;\r\n\r\n // Removes all previous children. (.replaceChildren requires Chrome for Android 86.)\r\n // Instantly replaces all children with an empty text node, bypassing the need to actually\r\n // parse incoming HTML.\r\n //\r\n // Just in case, alternative approaches: https://stackoverflow.com/a/3955238\r\n this.getDiv().textContent = '';\r\n\r\n // Builds new children to match needed RTL properties.\r\n this.buildInternals(rtl);\r\n\r\n this.options.forEach((option) => option.matchKeyboardProperties(keyboardProperties));\r\n this.onSuggestionUpdate(this.currentSuggestions); // restore suggestions\r\n }\r\n\r\n private get mouseEventConfig() {\r\n const config: InputEventEngineConfig = {\r\n targetRoot: this.getDiv(),\r\n // document.body is the event root b/c we need to track the mouse if it leaves\r\n // the VisualKeyboard's hierarchy.\r\n eventRoot: document.body,\r\n inputStartHandler: this.manager.touchStart.bind(this.manager),\r\n inputMoveHandler: this.manager.touchMove.bind(this.manager),\r\n inputEndHandler: this.manager.touchEnd.bind(this.manager),\r\n coordConstrainedWithinInteractiveBounds: function() { return true; }\r\n };\r\n\r\n return new MouseEventEngine(config);\r\n }\r\n\r\n private get touchEventConfig() {\r\n const config: InputEventEngineConfig = {\r\n targetRoot: this.getDiv(),\r\n // document.body is the event root b/c we need to track the mouse if it leaves\r\n // the VisualKeyboard's hierarchy.\r\n eventRoot: this.getDiv(),\r\n inputStartHandler: this.manager.touchStart.bind(this.manager),\r\n inputMoveHandler: this.manager.touchMove.bind(this.manager),\r\n inputEndHandler: this.manager.touchEnd.bind(this.manager),\r\n coordConstrainedWithinInteractiveBounds: function() { return true; }\r\n };\r\n\r\n return new TouchEventEngine(config);\r\n }\r\n\r\n public get predictionContext(): PredictionContext {\r\n return this._predictionContext;\r\n }\r\n\r\n public set predictionContext(context: PredictionContext) {\r\n if(this._predictionContext) {\r\n // disconnect the old one!\r\n this._predictionContext.off('update', this.onSuggestionUpdate);\r\n }\r\n\r\n // connect the new one!\r\n this._predictionContext = context;\r\n if(context) {\r\n context.on('update', this.onSuggestionUpdate);\r\n this.onSuggestionUpdate(context.currentSuggestions);\r\n }\r\n }\r\n\r\n public onSuggestionUpdate = (suggestions: Suggestion[]): void => {\r\n this.currentSuggestions = suggestions;\r\n\r\n this.options.forEach((option: BannerSuggestion, i: number) => {\r\n if(i < suggestions.length) {\r\n option.update(suggestions[i]);\r\n } else {\r\n option.update(null);\r\n }\r\n });\r\n }\r\n}\r\n\r\ninterface SuggestionInputEventMap {\r\n highlight: (bannerSuggestion: BannerSuggestion, state: boolean) => void,\r\n apply: (bannerSuggestion: BannerSuggestion) => void;\r\n hold: (bannerSuggestion: BannerSuggestion) => void;\r\n}\r\n\r\nclass SuggestionInputManager extends UITouchHandlerBase {\r\n public readonly events = new EventEmitter();\r\n\r\n private eventDisablePromise: Promise;\r\n\r\n platformHold: (suggestion: BannerSuggestion, isCustom: boolean) => void;\r\n\r\n //#region Touch handling implementation\r\n findTargetFrom(e: HTMLElement): HTMLDivElement {\r\n try {\r\n if(e) {\r\n if(e.classList.contains('kmw-suggest-option')) {\r\n return e as HTMLDivElement;\r\n }\r\n if(e.parentElement && e.parentElement.classList.contains('kmw-suggest-option')) {\r\n return e.parentElement as HTMLDivElement;\r\n }\r\n // if(e.firstChild && util.hasClass( e.firstChild,'kmw-suggest-option')) {\r\n // return e.firstChild as HTMLDivElement;\r\n // }\r\n }\r\n } catch(ex) {}\r\n return null;\r\n }\r\n\r\n protected highlight(t: HTMLDivElement, on: boolean): void {\r\n let suggestion = t['suggestion'] as BannerSuggestion;\r\n\r\n // Never highlight an empty suggestion button.\r\n if(suggestion.isEmpty()) {\r\n on = false;\r\n }\r\n\r\n this.events.emit('highlight', suggestion, on);\r\n }\r\n\r\n protected select(t: HTMLDivElement): void {\r\n this.events.emit('apply', t['suggestion'] as BannerSuggestion);\r\n }\r\n\r\n //#region Long-press support\r\n protected hold(t: HTMLDivElement): void {\r\n // let suggestionObj = t['suggestion'] as BannerSuggestion;\r\n //\r\n // // Is this the suggestion? It's never in this.currentSuggestions, so check against that.\r\n // let isCustom = this.currentSuggestions.indexOf(suggestionObj.suggestion) == -1;\r\n\r\n this.events.emit('hold', t['suggestion'] as BannerSuggestion);\r\n }\r\n protected clearHolds(): void {\r\n // Temp, pending implementation of suggestion longpress submenus\r\n // - nothing to clear without them -\r\n\r\n // only really used in native-KMW\r\n }\r\n\r\n protected hasModalPopup(): boolean {\r\n return this.eventsBlocked;\r\n }\r\n\r\n protected dealiasSubTarget(target: HTMLDivElement): HTMLDivElement {\r\n return target;\r\n }\r\n\r\n protected hasSubmenu(t: HTMLDivElement): boolean {\r\n // Temp, pending implementation of suggestion longpress submenus\r\n\r\n // Only really used by native-KMW - see kmwnative's highlightSubKeys func.\r\n return false;\r\n }\r\n\r\n protected isSubmenuActive(): boolean {\r\n // Temp, pending implementation of suggestion longpress submenus\r\n\r\n // Utilized only by native-KMW - it parallels hasModalPopup() in purpose.\r\n return false;\r\n }\r\n\r\n protected displaySubmenuFor(target: HTMLDivElement) {\r\n // Utilized only by native-KMW to show submenus.\r\n throw new Error(\"Method not implemented.\");\r\n }\r\n //#endregion\r\n //#endregion\r\n\r\n public get eventsBlocked(): boolean {\r\n return !!this.eventDisablePromise;\r\n }\r\n\r\n /**\r\n * Intended for use by the mobile apps, which sometimes 'takes over' touch handling.\r\n * For such cases, input should be blocked within KMW when the apps are managing an\r\n * ongoing touch-hold for any other interaction.\r\n *\r\n * Formerly:\r\n ```\r\n let keyman = com.keyman.singleton;\r\n return keyman['osk'].vkbd.subkeyGesture && keyman.isEmbedded;\r\n ```\r\n */\r\n public temporarilyBlockEvents(promise: Promise) { // TODO: ensure connection for embedded mode!\r\n this.eventDisablePromise = promise; // Will require routing; this class is not exported!\r\n promise.finally(() => {\r\n this.eventDisablePromise = null;\r\n })\r\n }\r\n\r\n constructor(div: HTMLElement) {\r\n // TODO: Determine appropriate CSS styling names, etc.\r\n super(div, Banner.BANNER_CLASS, SuggestionBanner.TOUCHED_CLASS);\r\n }\r\n}\r\n", + "import InputEventCoordinate from '../inputEventCoordinate.js';\r\n\r\nexport type InputHandler = (coord: InputEventCoordinate) => void;\r\n\r\nexport interface InputEventEngineConfig {\r\n /**\r\n * Specifies the element that input listeners should be attached to.\r\n */\r\n readonly eventRoot: HTMLElement;\r\n /**\r\n * Specifies the most specific common ancestor element of any event target\r\n * that the InputEventEngine should consider.\r\n */\r\n readonly targetRoot: HTMLElement;\r\n\r\n readonly coordConstrainedWithinInteractiveBounds: (coord: InputEventCoordinate) => boolean;\r\n\r\n readonly inputStartHandler?: InputHandler;\r\n readonly inputMoveHandler?: InputHandler;\r\n readonly inputMoveCancelHandler?: InputHandler;\r\n readonly inputEndHandler?: InputHandler;\r\n}\r\n\r\nexport default abstract class InputEventEngine {\r\n protected readonly config: InputEventEngineConfig;\r\n\r\n public constructor(config: InputEventEngineConfig) {\r\n this.config = config;\r\n }\r\n\r\n abstract registerEventHandlers();\r\n abstract unregisterEventHandlers();\r\n\r\n onInputStart(coord: InputEventCoordinate) {\r\n if(this.config.inputStartHandler) {\r\n this.config.inputStartHandler(coord);\r\n }\r\n }\r\n\r\n onInputMove(coord: InputEventCoordinate) {\r\n if(this.config.inputMoveHandler) {\r\n this.config.inputMoveHandler(coord);\r\n }\r\n }\r\n\r\n onInputMoveCancel(coord: InputEventCoordinate) {\r\n if(this.config.inputMoveCancelHandler) {\r\n this.config.inputMoveCancelHandler(coord);\r\n }\r\n }\r\n\r\n onInputEnd(coord: InputEventCoordinate) {\r\n if(this.config.inputEndHandler) {\r\n this.config.inputEndHandler(coord);\r\n }\r\n }\r\n}", + "/**\r\n * Represents the current location of the current cursor / touchpoint during\r\n * an ongoing OSK input event. This class standardizes to .pageX (document)\r\n * coordinates, rather than .clientX (viewport) coordinates.\r\n */\r\nexport default class InputEventCoordinate {\r\n public readonly x: number;\r\n public readonly y: number;\r\n\r\n private readonly source: MouseEvent | TouchEvent;\r\n\r\n public constructor(x: number, y: number, source?: MouseEvent | TouchEvent) {\r\n this.x = x;\r\n this.y = y;\r\n\r\n if(source) {\r\n this.source = source;\r\n }\r\n }\r\n\r\n // Converts a MouseEvent or TouchEvent into the base coordinates needed\r\n // by the mouse-dragging operations.\r\n public static fromEvent(e: MouseEvent | TouchEvent) {\r\n let coordSource: MouseEvent | Touch;\r\n\r\n // Desktop Safari versions as recent as 14.1 do not support TouchEvents.\r\n // So, just in case, a two-fold conditional check to avoid issues with a direct\r\n // 'instanceof' against the type.\r\n if(window['TouchEvent'] && e instanceof TouchEvent) {\r\n coordSource = e.changedTouches[0];\r\n } else if(e['changedTouches']) {\r\n coordSource = e['changedTouches'][0] as Touch;\r\n } else {\r\n coordSource = e as MouseEvent;\r\n }\r\n\r\n // For MouseEvents, .pageX is slightly less supported in older browsers when\r\n // compared to .clientX. They're about equally supported for TouchEvents.\r\n if (coordSource.pageX) {\r\n return new InputEventCoordinate(coordSource.pageX, coordSource.pageY, e);\r\n } else if (coordSource.clientX) {\r\n const x = coordSource.clientX + document.body.scrollLeft;\r\n const y = coordSource.clientY + document.body.scrollTop;\r\n\r\n return new InputEventCoordinate(x, y, e);\r\n } else {\r\n return new InputEventCoordinate(null, null, e);\r\n }\r\n }\r\n\r\n public get activeInputCount(): number {\r\n // May not be an ACTUAL touch event during unit tests.\r\n if(window['TouchEvent'] && this.source['touches'] !== undefined && this.source['touches'] !== null) {\r\n return this.source['touches'].length;\r\n } else {\r\n const event = this.source as MouseEvent;\r\n return event.buttons > 0 ? 1 : 0;\r\n }\r\n }\r\n\r\n public get target() {\r\n return this.source?.target;\r\n }\r\n\r\n public get isFromTouch(): boolean {\r\n return !this.isFromMouse;\r\n }\r\n\r\n public get isFromMouse(): boolean {\r\n return this.source instanceof MouseEvent;\r\n }\r\n}", + "import InputEventEngine, { InputEventEngineConfig } from './inputEventEngine.js';\r\nimport InputEventCoordinate from '../inputEventCoordinate.js';\r\n\r\nexport default class MouseEventEngine extends InputEventEngine {\r\n private readonly _mouseStart: typeof MouseEventEngine.prototype.onMouseStart;\r\n private readonly _mouseMove: typeof MouseEventEngine.prototype.onMouseMove;\r\n private readonly _mouseEnd: typeof MouseEventEngine.prototype.onMouseEnd;\r\n\r\n private hasActiveClick: boolean = false;\r\n private ignoreSequence: boolean = false;\r\n\r\n public constructor(config: InputEventEngineConfig) {\r\n super(config);\r\n\r\n this._mouseStart = this.onMouseStart.bind(this);\r\n this._mouseMove = this.onMouseMove.bind(this);\r\n this._mouseEnd = this.onMouseEnd.bind(this);\r\n }\r\n\r\n registerEventHandlers() {\r\n this.config.eventRoot.addEventListener('mousedown', this._mouseStart, true);\r\n this.config.eventRoot.addEventListener('mousemove', this._mouseMove, false);\r\n // The listener below fails to capture when performing automated testing checks in Chrome emulation unless 'true'.\r\n this.config.eventRoot.addEventListener('mouseup', this._mouseEnd, true);\r\n }\r\n\r\n unregisterEventHandlers() {\r\n this.config.eventRoot.removeEventListener('mousedown', this._mouseStart, true);\r\n this.config.eventRoot.removeEventListener('mousemove', this._mouseMove, false);\r\n this.config.eventRoot.removeEventListener('mouseup', this._mouseEnd, true);\r\n }\r\n\r\n private preventPropagation(e: MouseEvent) {\r\n // Standard event maintenance\r\n e.preventDefault();\r\n e.cancelBubble=true;\r\n e.returnValue=false; // I2409 - Avoid focus loss for visual keyboard events\r\n\r\n if(typeof e.stopImmediatePropagation == 'function') {\r\n e.stopImmediatePropagation();\r\n } else if(typeof e.stopPropagation == 'function') {\r\n e.stopPropagation();\r\n }\r\n }\r\n\r\n onMouseStart(event: MouseEvent) {\r\n if(!this.config.targetRoot.contains(event.target as Node)) {\r\n this.ignoreSequence = true;\r\n return;\r\n }\r\n\r\n this.preventPropagation(event);\r\n this.onInputStart(InputEventCoordinate.fromEvent(event));\r\n this.hasActiveClick = true;\r\n }\r\n\r\n onMouseMove(event: MouseEvent) {\r\n if(this.ignoreSequence) {\r\n return;\r\n }\r\n\r\n const coord = InputEventCoordinate.fromEvent(event);\r\n\r\n if(!event.buttons) {\r\n if(this.hasActiveClick) {\r\n this.hasActiveClick = false;\r\n this.onInputMoveCancel(coord);\r\n }\r\n return;\r\n } else if(!this.hasActiveClick) {\r\n // Can interfere with OSK drag-handlers (title bar, resize bar) otherwise.\r\n return;\r\n }\r\n\r\n this.preventPropagation(event);\r\n\r\n if(this.config.coordConstrainedWithinInteractiveBounds(coord)) {\r\n this.onInputMove(coord);\r\n } else {\r\n this.onInputMoveCancel(coord);\r\n }\r\n }\r\n\r\n onMouseEnd(event: MouseEvent) {\r\n if(this.ignoreSequence) {\r\n this.ignoreSequence = false;\r\n return;\r\n }\r\n\r\n if(!event.buttons) {\r\n this.hasActiveClick = false;\r\n }\r\n this.onInputEnd(InputEventCoordinate.fromEvent(event));\r\n }\r\n}", + "import InputEventEngine, { InputEventEngineConfig } from './inputEventEngine.js';\r\nimport InputEventCoordinate from '../inputEventCoordinate.js';\r\n\r\nexport default class TouchEventEngine extends InputEventEngine {\r\n private readonly _touchStart: typeof TouchEventEngine.prototype.onTouchStart;\r\n private readonly _touchMove: typeof TouchEventEngine.prototype.onTouchMove;\r\n private readonly _touchEnd: typeof TouchEventEngine.prototype.onTouchEnd;\r\n\r\n public constructor(config: InputEventEngineConfig) {\r\n super(config);\r\n\r\n this._touchStart = this.onTouchStart.bind(this);\r\n this._touchMove = this.onTouchMove.bind(this);\r\n this._touchEnd = this.onTouchEnd.bind(this);\r\n }\r\n\r\n registerEventHandlers() {\r\n this.config.eventRoot.addEventListener('touchstart', this._touchStart, true);\r\n this.config.eventRoot.addEventListener('touchmove', this._touchMove, false);\r\n // The listener below fails to capture when performing automated testing checks in Chrome emulation unless 'true'.\r\n this.config.eventRoot.addEventListener('touchend', this._touchEnd, true);\r\n }\r\n\r\n unregisterEventHandlers() {\r\n this.config.eventRoot.removeEventListener('touchstart', this._touchStart, true);\r\n this.config.eventRoot.removeEventListener('touchmove', this._touchMove, false);\r\n this.config.eventRoot.removeEventListener('touchend', this._touchEnd, true);\r\n }\r\n\r\n private preventPropagation(e: TouchEvent) {\r\n // Standard event maintenance\r\n e.preventDefault();\r\n e.cancelBubble=true;\r\n\r\n if(typeof e.stopImmediatePropagation == 'function') {\r\n e.stopImmediatePropagation();\r\n } else if(typeof e.stopPropagation == 'function') {\r\n e.stopPropagation();\r\n }\r\n }\r\n\r\n onTouchStart(event: TouchEvent) {\r\n this.onInputStart(InputEventCoordinate.fromEvent(event));\r\n }\r\n\r\n onTouchMove(event: TouchEvent) {\r\n this.preventPropagation(event);\r\n const coord = InputEventCoordinate.fromEvent(event);\r\n\r\n if(this.config.coordConstrainedWithinInteractiveBounds(coord)) {\r\n this.onInputMove(coord);\r\n } else {\r\n this.onInputMoveCancel(coord);\r\n }\r\n }\r\n\r\n onTouchEnd(event: TouchEvent) {\r\n this.onInputEnd(InputEventCoordinate.fromEvent(event));\r\n }\r\n}", + "import InputEventCoordinate from \"../inputEventCoordinate.js\";\r\nimport { getAbsoluteY } from 'keyman/engine/dom-utils';\r\n\r\n/**\r\n * This class was added to facilitate scroll handling for overflow-x elements, though it could\r\n * be extended in the future to accept overflow-y if needed.\r\n *\r\n * This is necessary because of the OSK's need to use `.preventDefault()` for stability; that\r\n * same method blocks native handling of overflow scrolling for touch browsers.\r\n */\r\nclass ScrollState {\r\n // While we don't currently track y-coordinates here, the class is designed\r\n // to permit tracking them with minimal extra effort if we ever decide to do so.\r\n x: number;\r\n totalLength = 0;\r\n\r\n // The amount of coordinate 'noise' allowed during a scroll-enabled touch allowed\r\n // before interpreting the currently-ongoing touch command as having scrolled.\r\n static readonly HAS_SCROLLED_FUDGE_FACTOR = 10;\r\n\r\n constructor(coord: InputEventCoordinate) {\r\n this.x = coord.x;\r\n\r\n this.totalLength = 0;\r\n }\r\n\r\n updateTo(coord: InputEventCoordinate): {deltaX: number} {\r\n let x = this.x;\r\n this.x = coord.x;\r\n\r\n let deltas = {deltaX: this.x - x};\r\n this.totalLength += Math.abs(deltas.deltaX);\r\n\r\n return deltas;\r\n }\r\n\r\n public get hasScrolled(): boolean {\r\n // Allow an accidental fudge-factor for overflow element noise during a touch, but not much.\r\n return this.totalLength > ScrollState.HAS_SCROLLED_FUDGE_FACTOR;\r\n }\r\n}\r\n\r\nexport default abstract class UITouchHandlerBase {\r\n private rowClassMatch: string;\r\n private selectedTargetMatch: string;\r\n private baseElement: HTMLElement;\r\n\r\n private touchX: number;\r\n private touchY: number;\r\n private touchCount: number;\r\n\r\n private currentTarget: Target;\r\n\r\n private scrollTouchState: ScrollState;\r\n private pendingTarget: Target;\r\n\r\n constructor(baseElement: HTMLElement, rowClassMatch: string, selectedTargetMatch: string) {\r\n this.baseElement = baseElement;\r\n this.rowClassMatch = rowClassMatch;\r\n this.selectedTargetMatch = selectedTargetMatch;\r\n }\r\n\r\n /**\r\n * Finds the internally-preferred target element or submenu target element.\r\n * @param e The DOM element that actually received the touch event.\r\n * May be parent, child, or the actually-desired element itself.\r\n */\r\n abstract findTargetFrom(e: HTMLElement): Target;\r\n\r\n /**\r\n * Highlights the target element as visual feedback representing\r\n * a pending touch.\r\n * @param t The `Target` to highlight\r\n * @param state `true` to apply highlighting, `false` to remove it.\r\n */\r\n protected abstract highlight(t: Target, state: boolean): void;\r\n\r\n /**\r\n * Called whenever the touch-handling analysis determines that the Target has been selected\r\n * @param t The `Target` to activate/execute.\r\n */\r\n protected abstract select(t: Target): void;\r\n\r\n /**\r\n * Requests info on whether or not the indicated `Target` has subkeys or a submenu.\r\n * @param t A `Target`.\r\n */\r\n protected abstract hasSubmenu(t: Target): boolean;\r\n\r\n /**\r\n * Indicates that the user is maintaining a `Touch` on the specified `Target`.\r\n * Popups and-or longpress menus may be appropriate.\r\n * @param t The `Target` being held.\r\n */\r\n protected abstract hold(t: Target): void;\r\n\r\n /**\r\n * Signals that any popup elements (previews, subkey views, etc) should be cancelled.\r\n */\r\n protected abstract clearHolds(): void;\r\n\r\n /**\r\n * Requests a boolean indicating whether or not the UI is currently displaying any input-blocking popup elements.\r\n * Embedded mode should return `true` when the app is displaying popup menus.\r\n */\r\n protected abstract hasModalPopup(): boolean;\r\n\r\n /**\r\n * Designed to support highlighting of prepended base keys on phone form-factor subkey menus.\r\n * @param target The base element with a potential subkey menu alias.\r\n * @returns The aliased submenu version of the `Target`, or the original `Target` if no alias exists.\r\n */\r\n protected abstract dealiasSubTarget(target: Target): Target;\r\n\r\n /**\r\n * Should return true whenever a 'native'-mode submenu (or subkey) display is active.\r\n */\r\n protected abstract isSubmenuActive(): boolean;\r\n\r\n /**\r\n * For 'native' mode - requests that the submenu for the indicated `Target` be instantly displayed.\r\n * @param target The base element with a potential submenu\r\n */\r\n protected abstract displaySubmenuFor(target: Target);\r\n\r\n /**\r\n * Identify the key nearest to (but NOT under) the touch point if at the end of a key row,\r\n * but return null more than about 0.6 key width from the nearest key.\r\n *\r\n * @param {Object} coord A pre-analyzed input coordinate\r\n * @param {Object} t HTML object at touch point\r\n * @param {boolean} omitCurrent Omits any target directly under the touch point.\r\n * @return {Object} nearest key to touch point\r\n *\r\n **/\r\n private findTargetFromTouch(coord: InputEventCoordinate, t: HTMLElement, forMove: boolean): Target {\r\n var x = coord.x;\r\n\r\n // Get the UI row beneath touch point (SuggestionBanner div, 'kmw-key-row' if OSK, ...)\r\n while(t && t.className !== undefined && t.className.indexOf(this.rowClassMatch) < 0) {\r\n t = t.parentNode;\r\n }\r\n if(!t) {\r\n return null;\r\n }\r\n\r\n // Find minimum distance from any key\r\n var k: number, bestMatch=0, dxMax=24, dxMin=100000, x1: number, x2: number;\r\n for(k = 0; k < t.childNodes.length; k++) {\r\n let childNode = t.childNodes[k] as HTMLElement;\r\n\r\n if(this.isInvalidTarget(this.findTargetFrom(childNode))) {\r\n continue;\r\n }\r\n\r\n x1 = childNode.offsetLeft;\r\n x2 = x1 + childNode.offsetWidth;\r\n\r\n // If it lies completely to the right and is the closest so far\r\n let dxRight = x1 - x;\r\n if(dxRight >= 0 && dxRight < dxMin) {\r\n bestMatch = k;\r\n dxMin = dxRight;\r\n }\r\n\r\n // If it lies completely to the left and is the closest so far\r\n let dxLeft = x - x2;\r\n if(dxLeft >= 0 && dxLeft < dxMin) {\r\n bestMatch = k;\r\n dxMin = dxLeft;\r\n }\r\n\r\n // If it is neither completely to the left nor completely to the right,\r\n // it's under the cursor. Stop the search!\r\n if(dxLeft < 0 && dxRight < 0) {\r\n return this.findTargetFrom(childNode);\r\n }\r\n }\r\n\r\n if(dxMin < 100000) {\r\n t = t.childNodes[bestMatch];\r\n x1 = t.offsetLeft;\r\n x2 = x1 + t.offsetWidth;\r\n\r\n // Limit extended touch area to the larger of 0.6 of the potential target's width and 24 px\r\n if(t.offsetWidth > 40) {\r\n dxMax = 0.6 * t.offsetWidth;\r\n }\r\n\r\n if(((x1 - x) >= 0 && (x1 - x) < dxMax) || ((x - x2) >= 0 && (x - x2) < dxMax)) {\r\n return this.findTargetFrom(t);\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n findBestTarget(coord: InputEventCoordinate, forMove?: boolean) {\r\n var eventTarget: HTMLElement;\r\n\r\n if(forMove) {\r\n const clientX = coord.x + document.body.scrollLeft;\r\n const clientY = coord.y + document.body.scrollTop;\r\n eventTarget = document.elementFromPoint(clientX, clientY) as HTMLElement;\r\n } else {\r\n eventTarget = coord.target as HTMLElement;\r\n }\r\n\r\n let target = this.findTargetFrom(eventTarget);\r\n\r\n // Should refactor this multi-check a bit for more overall reliability.\r\n if(!target) {\r\n // We didn't find a direct target, so we should look for the closest possible one.\r\n // Filters out invalid targets.\r\n target = this.findTargetFromTouch(coord, eventTarget, forMove);\r\n }\r\n\r\n return target;\r\n }\r\n\r\n /**\r\n * Reports whether or not a `Target` should be considered invalid. Needed by the OSK for\r\n * hidden keys.\r\n * @param target A `Target` element to be validated.\r\n */\r\n protected isInvalidTarget(target: Target): boolean {\r\n return false;\r\n }\r\n\r\n touchStart(coord: InputEventCoordinate) {\r\n // Determine the selected Target, manage state.\r\n this.currentTarget = this.findBestTarget(coord);\r\n this.touchX = coord.x;\r\n this.touchY = coord.y;\r\n\r\n // If popup stuff, immediately return.\r\n\r\n this.touchCount = coord.activeInputCount;\r\n\r\n if(!this.currentTarget) {\r\n return;\r\n }\r\n\r\n // Establish scroll tracking.\r\n let shouldScroll = (this.currentTarget.clientWidth < this.currentTarget.scrollWidth);\r\n this.scrollTouchState = shouldScroll ? new ScrollState(coord) : null;\r\n\r\n // Alright, Target acquired! Now to use it:\r\n\r\n // Highlight the touched key\r\n this.highlight(this.currentTarget,true);\r\n\r\n // If used by the OSK, the special function keys need immediate action\r\n // Add a `checkForImmediates()` to facilitate this.\r\n if(this.pendingTarget) {\r\n this.highlight(this.pendingTarget, false);\r\n this.select(this.pendingTarget);\r\n this.clearHolds();\r\n // Decrement the number of unreleased touch points to prevent\r\n // sending the keystroke again when the key is actually released\r\n this.touchCount--;\r\n } else {\r\n // If this key has subkey, start timer to display subkeys after delay, set up release\r\n this.hold(this.currentTarget);\r\n }\r\n this.pendingTarget = this.currentTarget;\r\n }\r\n\r\n touchEnd(coord: InputEventCoordinate): void {\r\n // Prevent incorrect multi-touch behaviour if native or device popup visible\r\n let t = this.currentTarget;\r\n\r\n if(this.isSubmenuActive() || this.hasModalPopup()) {\r\n // Ignore release if a multiple touch\r\n if(coord.activeInputCount > 0) {\r\n return;\r\n }\r\n\r\n // Cancel (but do not execute) pending key if neither a popup key or the base key\r\n if(t == null || t.id.indexOf('popup') < 0) {\r\n if (this.pendingTarget) {\r\n this.highlight(this.pendingTarget,false);\r\n }\r\n this.clearHolds();\r\n this.pendingTarget = null;\r\n }\r\n }\r\n\r\n // Test if moved off screen (effective release point must be corrected for touch point horizontal speed)\r\n // This is not completely effective and needs some tweaking, especially on Android\r\n var x = coord.x;\r\n var beyondEdge = ((x < 2 && this.touchX > 5) || (x > window.innerWidth - 2 && this.touchX < window.innerWidth - 5));\r\n\r\n if(this.scrollTouchState) {\r\n beyondEdge = beyondEdge || this.scrollTouchState.hasScrolled;\r\n }\r\n\r\n // Save then decrement current touch count\r\n var tc=this.touchCount;\r\n if(this.touchCount > 0) {\r\n this.touchCount--;\r\n }\r\n\r\n // Process and clear highlighting of pending target\r\n if(this.pendingTarget) {\r\n this.highlight(this.pendingTarget,false);\r\n\r\n // Output character unless moved off key\r\n if(this.pendingTarget.className.indexOf('hidden') < 0 && tc > 0 && !beyondEdge) {\r\n this.select(this.pendingTarget);\r\n }\r\n this.clearHolds();\r\n this.pendingTarget = null;\r\n // Always clear highlighting of current target on release (multi-touch)\r\n } else {\r\n t = this.findBestTarget(coord);\r\n\r\n if(t) {\r\n this.highlight(t,false);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * OSK touch move event handler\r\n *\r\n * @param {Object} coord A pre-analyzed input coordinate\r\n *\r\n **/\r\n touchMove(coord: InputEventCoordinate) : void {\r\n //let keyman = com.keyman.singleton;\r\n\r\n // Do not attempt to support reselection of target key for overlapped keystrokes\r\n if(coord.activeInputCount > 1 || this.touchCount == 0) {\r\n return;\r\n }\r\n\r\n if(this.currentTarget && this.scrollTouchState != null) {\r\n let deltaX = this.scrollTouchState.updateTo(coord).deltaX;\r\n this.currentTarget.scrollLeft -= window.devicePixelRatio * deltaX;\r\n\r\n return;\r\n }\r\n\r\n // Get touch position\r\n var y = coord.y;\r\n\r\n // Move target key and highlighting\r\n var key0 = this.pendingTarget,\r\n key1 = this.findBestTarget(coord, true); // For the OSK, this ALSO gets subkeys.\r\n\r\n // If option should not be selectable, how do we re-target?\r\n\r\n\r\n // Do not move over keys if device popup visible\r\n if(this.hasModalPopup()) {\r\n if(key0) {\r\n this.highlight(key0,false);\r\n }\r\n this.pendingTarget=null;\r\n return;\r\n }\r\n\r\n // Use the popup duplicate of the base key if a phone with a visible popup array\r\n key1 = this.dealiasSubTarget(key1);\r\n\r\n // Identify current touch position (to manage off-key release)\r\n this.currentTarget = key1;\r\n\r\n // Clear previous key highlighting\r\n if(key0 && key1 && key1 !== key0) {\r\n this.highlight(key0,false);\r\n }\r\n\r\n // Code below directly related to subkeys should only be triggered within 'native' mode.\r\n // The embedded version instead passes info to the apps to produce their own subkeys in-app.\r\n\r\n // If popup is visible, need to move over popup, not over main keyboard\r\n if(key1 && this.hasSubmenu(key1)) {\r\n //this.highlightSubKeys(key1,x,y);\r\n\r\n // Currently only used by the banner... which currently does not do submenus.\r\n // // Native-mode: show popup keys immediately if touch moved up towards key array (KMEW-100, Build 353)\r\n // if(!keyman.isEmbedded && (this.touchY-y > 5) && !this.isSubmenuActive()) {\r\n // // Instantly show the submenu.\r\n // this.displaySubmenuFor(key1);\r\n // }\r\n\r\n // Once a subkey array is displayed, do not allow changing the base key.\r\n // Keep that array visible and accept no other options until the touch ends.\r\n if(key1 && key1.id.indexOf('popup') < 0) { // TODO: reliant on 'popup' in .id\r\n return;\r\n }\r\n\r\n // Highlight the base key on devices that do not append it to the subkey array.\r\n if(key1 && key1.className.indexOf(this.selectedTargetMatch) < 0) {\r\n this.highlight(key1,true);\r\n }\r\n // Cancel touch if moved up and off keyboard, unless popup keys visible\r\n } else {\r\n let base = this.baseElement;\r\n let top = getAbsoluteY(base);\r\n let height = base.offsetHeight;\r\n let yMin = Math.max(5, top - 0.25 * height);\r\n let yMax = (top + height) + 0.25 * height;\r\n if(key0 && (coord.y < yMin || coord.y > yMax)) {\r\n this.highlight(key0,false);\r\n this.clearHolds();\r\n this.pendingTarget = null;\r\n }\r\n }\r\n\r\n // Replace the target key, if any, by the new target key\r\n // Do not replace a null target, as that indicates the key has already been released\r\n if(key1 && this.pendingTarget) {\r\n this.pendingTarget = key1;\r\n }\r\n\r\n if(this.pendingTarget) {\r\n if(key1 && (key0 != key1 || key1.className.indexOf(this.selectedTargetMatch) < 0)) {\r\n this.highlight(key1,true);\r\n }\r\n }\r\n\r\n if(key0 && key1 && (key1 != key0) && (key1.id != '')) {\r\n // Display the touch-hold keys (after a pause)\r\n this.hold(key1);\r\n }\r\n }\r\n}\r\n", + "export interface LengthStyle {\r\n val: number,\r\n absolute: boolean,\r\n special?: 'em' | 'rem';\r\n};\r\n\r\nexport class ParsedLengthStyle implements LengthStyle {\r\n public readonly val: number;\r\n public readonly absolute: boolean;\r\n public readonly special: 'em' | 'rem';\r\n\r\n public constructor(style: LengthStyle | string) {\r\n let parsed: LengthStyle = (typeof style == 'string') ? ParsedLengthStyle.parseLengthStyle(style) : style;\r\n\r\n // While Object.assign would be nice (and previously, was used), it will break\r\n // on old but still supported versions of Android if their Chrome isn't updated.\r\n // Requires mobile Chrome 45+, but API 21 (5.0) launches with an older browser.\r\n\r\n // Object.assign(this, parsed);\r\n this.val = parsed.val;\r\n this.absolute = parsed.absolute;\r\n if(parsed.special) {\r\n this.special = parsed.special;\r\n }\r\n }\r\n\r\n public get styleString(): string {\r\n if(this.absolute) {\r\n return this.val + 'px';\r\n } else if(this.special) {\r\n // Only 'em' and 'rem' are allowed, and both may be treated similarly.\r\n // Both relate to font sizes, though the path to the reference element\r\n // differs between them.\r\n return this.val + this.special;\r\n } else {\r\n return (this.val * 100) + '%';\r\n }\r\n }\r\n\r\n public scaledBy(scalar: number): ParsedLengthStyle {\r\n return new ParsedLengthStyle({\r\n val: scalar * this.val,\r\n absolute: this.absolute\r\n });\r\n }\r\n\r\n public static inPixels(val: number): ParsedLengthStyle {\r\n return new ParsedLengthStyle({val: val, absolute: true});\r\n }\r\n\r\n public static inPercent(val: number): ParsedLengthStyle {\r\n return new ParsedLengthStyle({val: val/100, absolute: false});\r\n }\r\n\r\n public static forScalar(val: number): ParsedLengthStyle {\r\n return new ParsedLengthStyle({val: val, absolute: false});\r\n }\r\n\r\n public static special(val: number, suffix: 'em' | 'rem'): ParsedLengthStyle {\r\n return new ParsedLengthStyle({val: val, absolute: false, special: suffix});\r\n }\r\n\r\n private static parseLengthStyle(spec: string): LengthStyle {\r\n const val = parseFloat(spec);\r\n\r\n if(isNaN(val)) {\r\n // Cannot parse.\r\n console.error(\"Could not properly parse specified length style info: '\" + spec + \"'.\");\r\n return null;\r\n }\r\n\r\n return spec.indexOf('px') != -1 ? {val: val, absolute: true} :\r\n // 16 px ~= 12 pt.\r\n // Reference: https://kyleschaeffer.com/css-font-size-em-vs-px-vs-pt-vs-percent\r\n spec.indexOf('pt') != -1 ? {val: (4 * val / 3), absolute: true} :\r\n spec.indexOf('%') != -1 ? {val: val/100, absolute: false} :\r\n spec.indexOf('rem') != -1 ? {val: val, absolute: false, special: 'rem'} :\r\n spec.indexOf('em') != -1 ? {val: val, absolute: false, special: 'em'} :\r\n // At this point, assuming either Number or number in a string without units\r\n // Note: this one is NOT natively handled by browsers!\r\n // We'll treat it as if it were 'pt', since that's likely the user's\r\n // most familiar font size unit.\r\n {val: (4 * val / 3), absolute: true};\r\n }\r\n}\r\n", + "import KeyboardView from \"./keyboardView.interface.js\";\r\nimport { ParsedLengthStyle } from \"../lengthStyle.js\";\r\n\r\nexport default class EmptyView implements KeyboardView {\r\n readonly element: HTMLDivElement;\r\n\r\n constructor() {\r\n let Ldiv = this.element = document.createElement('div');\r\n Ldiv.style.userSelect = 'none';\r\n Ldiv.className='kmw-osk-none';\r\n }\r\n\r\n // No operations needed; this is a stand-in for the desktop OSK when no keyboard is active.\r\n public postInsert() { }\r\n public updateState() { }\r\n\r\n public refreshLayout() { }\r\n\r\n public get layoutHeight(): ParsedLengthStyle {\r\n return ParsedLengthStyle.inPixels(0);\r\n }\r\n}", + "import { Keyboard } from '@keymanapp/keyboard-processor';\r\n\r\nimport KeyboardView from './keyboardView.interface.js';\r\nimport { ParsedLengthStyle } from \"../lengthStyle.js\";\r\n\r\nexport default class HelpPageView implements KeyboardView {\r\n private readonly kbd: Keyboard;\r\n public readonly element: HTMLDivElement;\r\n\r\n private static readonly ID = 'kmw-osk-help-page';\r\n\r\n constructor(keyboard: Keyboard) {\r\n this.kbd = keyboard;\r\n\r\n var Ldiv = this.element = document.createElement('div');\r\n Ldiv.style.userSelect = \"none\";\r\n Ldiv.className = 'kmw-osk-static';\r\n Ldiv.id = HelpPageView.ID;\r\n Ldiv.innerHTML = keyboard.helpText;\r\n }\r\n\r\n public postInsert() {\r\n if(!this.element.parentElement || !document.getElementById(HelpPageView.ID)) {\r\n throw new Error(\"The HelpPage root element has not yet been inserted into the DOM.\");\r\n }\r\n\r\n if(this.kbd.hasScript) {\r\n // .parentElement: ensure this matches the _Box element from OSKManager / OSKView\r\n // Not a hard requirement for any known keyboards, but is asserted by legacy docs.\r\n this.kbd.embedScript(this.element.parentElement);\r\n }\r\n }\r\n\r\n public updateState() { }\r\n public refreshLayout() { }\r\n\r\n public get layoutHeight(): ParsedLengthStyle {\r\n return ParsedLengthStyle.inPercent(100);\r\n }\r\n}", + "import EventEmitter from 'eventemitter3';\r\n\r\nimport {\r\n ActiveKey,\r\n ActiveLayout,\r\n ButtonClass,\r\n DeviceSpec,\r\n type InternalKeyboardFont,\r\n Keyboard,\r\n KeyboardProperties,\r\n KeyDistribution,\r\n KeyEvent,\r\n Layouts,\r\n StateKeyMap\r\n} from '@keymanapp/keyboard-processor';\r\n\r\nimport { createStyleSheet, getAbsoluteX, getAbsoluteY, StylesheetManager } from 'keyman/engine/dom-utils';\r\n\r\nimport GlobeHint from './globehint.interface.js';\r\nimport InputEventCoordinate from './input/inputEventCoordinate.js';\r\nimport InputEventEngine, { InputEventEngineConfig } from './input/event-interpreter/inputEventEngine.js';\r\nimport MouseEventEngine from './input/event-interpreter/mouseEventEngine.js';\r\nimport TouchEventEngine from './input/event-interpreter/touchEventEngine.js';\r\nimport KeyboardView from './components/keyboardView.interface.js';\r\nimport { type KeyElement, getKeyFrom } from './keyElement.js';\r\nimport KeyTip from './keytip.interface.js';\r\nimport OSKKey, { OSKKeySpec } from './keyboard-layout/oskKey.js';\r\nimport OSKLayer from './keyboard-layout/oskLayer.js';\r\nimport OSKLayerGroup from './keyboard-layout/oskLayerGroup.js';\r\nimport { LengthStyle, ParsedLengthStyle } from './lengthStyle.js';\r\nimport PendingGesture from './input/gestures/pendingGesture.interface.js';\r\nimport RealizedGesture from './input/gestures/realizedGesture.interface.js';\r\nimport { defaultFontSize, getFontSizeStyle } from './fontSizeUtils.js';\r\nimport PendingMultiTap, { PendingMultiTapState } from './input/gestures/browser/pendingMultiTap.js';\r\nimport InternalSubkeyPopup from './input/gestures/browser/subkeyPopup.js';\r\nimport InternalPendingLongpress from './input/gestures/browser/pendingLongpress.js';\r\nimport InternalKeyTip from './input/gestures/browser/keytip.js';\r\nimport CommonConfiguration from './config/commonConfiguration.js';\r\n\r\nimport { getViewportScale } from './screenUtils.js';\r\n\r\nexport interface VisualKeyboardConfiguration extends CommonConfiguration {\r\n /**\r\n * The Keyman keyboard on which to base the on-screen keyboard being represented.\r\n */\r\n keyboard: Keyboard,\r\n\r\n /**\r\n * Metadata about the keyboard, such as relevant fonts, display name, and language code.\r\n *\r\n * Designed for use with `KeyboardStub` objects, which are defined external to the\r\n * on-screen keyboard module.\r\n */\r\n keyboardMetadata: KeyboardProperties,\r\n\r\n /**\r\n * OSK-internal: the top-most element of the full on-screen keyboard element hierarchy.\r\n *\r\n * May be set to `null` if `isStatic` is `true`.\r\n */\r\n topContainer: HTMLElement,\r\n\r\n /**\r\n * Set to `true` for documentation keyboards, disabling all user-interactivity.\r\n */\r\n isStatic?: boolean,\r\n\r\n /**\r\n * Provide this field with the OSKView's stylesheet per-keyboard manager instance.\r\n *\r\n * Interim developer note: do NOT attach kmwosk.css using the same instance! We don't\r\n * want to remove that one when swapping keyboards.\r\n */\r\n styleSheetManager: StylesheetManager;\r\n}\r\n\r\ninterface BoundingRect {\r\n left: number,\r\n right: number,\r\n top: number,\r\n bottom: number\r\n};\r\n\r\ninterface EventMap {\r\n /**\r\n * Designed to pass key events off to any consuming modules/libraries.\r\n *\r\n * Note: the following code block was originally used to integrate with the keyboard & input\r\n * processors, but it requires entanglement with components external to this OSK module.\r\n */\r\n 'keyevent': (event: KeyEvent) => void,\r\n\r\n 'hiderequested': (keyElement: KeyElement) => void,\r\n\r\n 'globekey': (keyElement: KeyElement, on: boolean) => void\r\n}\r\n\r\nexport default class VisualKeyboard extends EventEmitter implements KeyboardView {\r\n // Legacy alias, maintaining a reference for code built against older\r\n // versions of KMW.\r\n static readonly specialCharacters = OSKKey.specialCharacters;\r\n\r\n /**\r\n * Contains layout properties corresponding to the OSK's layout. Needs to be public\r\n * so that its geometry may be updated on rotations and keyboard resize events, as\r\n * said geometry needs to be accurate for fat-finger probability calculations.\r\n */\r\n kbdLayout: ActiveLayout;\r\n layerGroup: OSKLayerGroup;\r\n\r\n readonly config: VisualKeyboardConfiguration;\r\n\r\n private _layerId: string = \"default\";\r\n layerIndex: number = 0; // the index of the default layer\r\n readonly isRTL: boolean;\r\n\r\n inputEngine: InputEventEngine;\r\n\r\n readonly isStatic: boolean = false;\r\n _fixedWidthScaling: boolean = false;\r\n _fixedHeightScaling: boolean = true;\r\n\r\n // Stores the base element for this instance of the visual keyboard.\r\n kbdDiv: HTMLDivElement;\r\n styleSheet: HTMLStyleElement;\r\n\r\n /**\r\n * The configured width for this VisualKeyboard. May be `undefined` or `null`\r\n * to allow automatic width scaling.\r\n */\r\n private _width: number;\r\n\r\n /**\r\n * The configured height for this VisualKeyboard. May be `undefined` or `null`\r\n * to allow automatic height scaling.\r\n */\r\n private _height: number;\r\n\r\n /**\r\n * The computed width for this VisualKeyboard. May be null if auto sizing\r\n * is allowed and the VisualKeyboard is not currently in the DOM hierarchy.\r\n */\r\n private _computedWidth: number;\r\n\r\n /**\r\n * The computed height for this VisualKeyboard. May be null if auto sizing\r\n * is allowed and the VisualKeyboard is not currently in the DOM hierarchy.\r\n */\r\n private _computedHeight: number;\r\n\r\n // Style-related properties\r\n fontFamily: string;\r\n private _fontSize: ParsedLengthStyle;\r\n // fontSize: string;\r\n\r\n // State-related properties\r\n keyPending: KeyElement;\r\n touchPending: InputEventCoordinate;\r\n deleteKey: KeyElement;\r\n deleting: number; // Tracks a timer id for repeated deletions.\r\n nextLayer: string;\r\n currentKey: string;\r\n stateKeys: StateKeyMap = {\r\n K_CAPS: false,\r\n K_NUMLOCK: false,\r\n K_SCROLL: false\r\n };\r\n\r\n // Touch-tracking properties\r\n initTouchCoord: InputEventCoordinate;\r\n touchCount: number;\r\n currentTarget: KeyElement;\r\n\r\n // Used by embedded-mode's globe key\r\n menuEvent: KeyElement; // Used by embedded-mode.\r\n\r\n // Popup key management\r\n keytip: KeyTip;\r\n globeHint: GlobeHint;\r\n pendingSubkey: PendingGesture;\r\n subkeyGesture: RealizedGesture;\r\n\r\n // Multi-tap gesture management\r\n pendingMultiTap: PendingMultiTap;\r\n\r\n // The keyboard object corresponding to this VisualKeyboard.\r\n public readonly layoutKeyboard: Keyboard;\r\n public readonly layoutKeyboardProperties: KeyboardProperties;\r\n\r\n get layerId(): string {\r\n return this._layerId;\r\n }\r\n\r\n set layerId(value: string) {\r\n const changedLayer = value != this._layerId;\r\n if(!this.layerGroup.layers[value]) {\r\n throw new Error(`Keyboard ${this.layoutKeyboard.id} does not have a layer with id ${value}`);\r\n } else {\r\n this._layerId = value;\r\n }\r\n\r\n if(changedLayer) {\r\n this.updateState();\r\n this.refreshLayout();\r\n }\r\n }\r\n\r\n get currentLayer(): OSKLayer {\r\n return this.layerId ? this.layerGroup?.layers[this.layerId] : null;\r\n }\r\n\r\n // Special keys (for the currently-visible layer)\r\n get lgKey(): KeyElement { // currently, must be visible for the touch language menu.\r\n return this.currentLayer?.globeKey?.btn;\r\n }\r\n\r\n private get hkKey(): KeyElement { // hide keyboard key\r\n return this.currentLayer?.hideKey?.btn;\r\n }\r\n\r\n public get spaceBar(): KeyElement { // also referenced by the touch language menu.\r\n return this.currentLayer?.spaceBarKey?.btn;\r\n }\r\n\r\n //#region OSK constructor and helpers\r\n\r\n /**\r\n * @param {Object} PVK Visual keyboard name\r\n * @param {Object} Lhelp true if OSK defined for this keyboard\r\n * @param {Object} layout0\r\n * @param {Number} kbdBitmask Keyboard modifier bitmask\r\n * Description Generates the base visual keyboard element, prepping for attachment to KMW\r\n */\r\n constructor(config: VisualKeyboardConfiguration) {\r\n super();\r\n\r\n this.config = config; // TODO: replace related parameters.\r\n\r\n this.config.device = config.device || config.hostDevice;\r\n this.config.isEmbedded = config.isEmbedded || false;\r\n\r\n if (config.isStatic) {\r\n this.isStatic = config.isStatic;\r\n }\r\n\r\n this._fixedWidthScaling = this.device.touchable && !this.isStatic;\r\n this._fixedHeightScaling = this.device.touchable && !this.isStatic;\r\n\r\n // Create the collection of HTML elements from the device-dependent layout object\r\n var Lkbd = document.createElement('div');\r\n this.config.styleSheetManager = config.styleSheetManager || new StylesheetManager(Lkbd);\r\n\r\n let layout: ActiveLayout;\r\n if (config.keyboard) {\r\n layout = this.kbdLayout = config.keyboard.layout(config.device.formFactor);\r\n this.layoutKeyboardProperties = config.keyboardMetadata;\r\n this.isRTL = config.keyboard.isRTL;\r\n } else {\r\n // This COULD be called with no backing keyboard; KMW will try to force-show the OSK even without\r\n // a backing keyboard on mobile, using the most generic default layout as the OSK's base.\r\n //\r\n // In KMW's current state, it'd take a major break, though - Processor always has an activeKeyboard,\r\n // even if it's \"hollow\".\r\n let rawLayout = Layouts.buildDefaultLayout(null, null, config.device.formFactor);\r\n layout = this.kbdLayout = ActiveLayout.polyfill(rawLayout, null, config.device.formFactor);\r\n // null will probably need to be replaced with a defined value.\r\n this.layoutKeyboardProperties = null;\r\n this.isRTL = false;\r\n }\r\n\r\n // Override font if specified by keyboard\r\n if ('font' in layout) {\r\n this.fontFamily = layout['font'];\r\n } else {\r\n this.fontFamily = '';\r\n }\r\n\r\n // Now to build the actual layout.\r\n const formFactor = config.device.formFactor;\r\n this.layoutKeyboard = config.keyboard;\r\n if (!this.layoutKeyboard) {\r\n // May occasionally be null in embedded contexts; have seen this when iOS engine sets\r\n // keyboard height during change of keyboards.\r\n this.layoutKeyboard = new Keyboard(null);\r\n }\r\n\r\n this.layerGroup = new OSKLayerGroup(this, this.layoutKeyboard, formFactor);\r\n\r\n // Now that we've properly processed the keyboard's layout, mark it as calibrated.\r\n // TODO: drop the whole 'calibration' thing. The newer layout system supersedes the\r\n // need for it. (Is no longer really used, so the drop ought be clean.)\r\n this.layoutKeyboard.markLayoutCalibrated(formFactor);\r\n\r\n // Append the OSK layer group container element to the containing element\r\n //osk.keyMap = divLayerContainer;\r\n Lkbd.appendChild(this.layerGroup.element);\r\n\r\n // Set base class - OS and keyboard added for Build 360\r\n this.kbdDiv = Lkbd;\r\n\r\n // For 'live' touch keyboards, attach touch-based event handling.\r\n // Needs to occur AFTER this.kbdDiv is initialized.\r\n if (!this.isStatic) {\r\n if (this.hostDevice.touchable) {\r\n this.inputEngine = this.touchInputConfiguration;\r\n } else {\r\n this.inputEngine = this.mouseInputConfiguration;\r\n }\r\n this.inputEngine.registerEventHandlers();\r\n }\r\n\r\n Lkbd.classList.add(config.device.formFactor, 'kmw-osk-inner-frame');\r\n\r\n // Tag the VisualKeyboard with a CSS class corresponding to its ID.\r\n let kbdID: string = this.layoutKeyboard?.id.replace('Keyboard_','') ?? '';\r\n\r\n const separatorIndex = kbdID.indexOf('::');\r\n if(separatorIndex != -1) { // We used to also test if we were in embedded mode, but... whatever.\r\n // De-namespaces the ID for use with CSS classes.\r\n // Assumes that keyboard IDs may not contain the ':' symbol.\r\n kbdID = kbdID.substring(separatorIndex + 2);\r\n }\r\n\r\n const kbdClassSuffix = 'kmw-keyboard-' + kbdID;\r\n this.element.classList.add(kbdClassSuffix);\r\n }\r\n\r\n private get mouseInputConfiguration() {\r\n const config: InputEventEngineConfig = {\r\n targetRoot: this.element,\r\n // document.body is the event root b/c we need to track the mouse if it leaves\r\n // the VisualKeyboard's hierarchy.\r\n eventRoot: document.body,\r\n inputStartHandler: this.touch.bind(this),\r\n inputMoveHandler: this.moveOver.bind(this),\r\n inputMoveCancelHandler: this.moveCancel.bind(this),\r\n inputEndHandler: this.release.bind(this),\r\n coordConstrainedWithinInteractiveBounds: this.detectWithinInteractiveBounds.bind(this)\r\n };\r\n\r\n return new MouseEventEngine(config);\r\n }\r\n\r\n private get touchInputConfiguration() {\r\n let config: InputEventEngineConfig = {\r\n targetRoot: this.element,\r\n eventRoot: this.element,\r\n inputStartHandler: this.touch.bind(this),\r\n inputMoveHandler: this.moveOver.bind(this),\r\n inputMoveCancelHandler: this.moveCancel.bind(this),\r\n inputEndHandler: this.release.bind(this),\r\n coordConstrainedWithinInteractiveBounds: this.detectWithinInteractiveBounds.bind(this)\r\n };\r\n\r\n return new TouchEventEngine(config);\r\n }\r\n\r\n public get element(): HTMLDivElement {\r\n return this.kbdDiv;\r\n }\r\n\r\n public get device(): DeviceSpec {\r\n return this.config.device;\r\n }\r\n\r\n public get hostDevice(): DeviceSpec {\r\n return this.config.hostDevice;\r\n }\r\n\r\n public get fontRootPath(): string {\r\n return this.config.pathConfig.fonts;\r\n }\r\n\r\n public get styleSheetManager(): StylesheetManager {\r\n return this.config.styleSheetManager;\r\n }\r\n\r\n public get topContainer(): HTMLElement {\r\n return this.config.topContainer;\r\n }\r\n\r\n public get isEmbedded(): boolean {\r\n return this.config.isEmbedded;\r\n }\r\n\r\n public postInsert(): void { }\r\n\r\n /**\r\n * The configured width for this VisualKeyboard. May be `undefined` or `null`\r\n * to allow automatic width scaling.\r\n */\r\n get width(): number {\r\n return this._width;\r\n }\r\n\r\n /**\r\n * The configured height for this VisualKeyboard. May be `undefined` or `null`\r\n * to allow automatic height scaling.\r\n */\r\n get height(): number {\r\n return this._height;\r\n }\r\n\r\n get layoutWidth(): ParsedLengthStyle {\r\n if (this.usesFixedWidthScaling) {\r\n let baseWidth = this.width;\r\n let cs = getComputedStyle(this.element);\r\n if (cs.border) {\r\n let borderWidth = new ParsedLengthStyle(cs.borderWidth).val;\r\n baseWidth -= borderWidth * 2;\r\n }\r\n return ParsedLengthStyle.inPixels(baseWidth);\r\n } else {\r\n return ParsedLengthStyle.forScalar(1);\r\n }\r\n }\r\n\r\n get layoutHeight(): ParsedLengthStyle {\r\n if (this.usesFixedHeightScaling) {\r\n let baseHeight = this.height;\r\n let cs = getComputedStyle(this.element);\r\n if (cs.border) {\r\n let borderHeight = new ParsedLengthStyle(cs.borderWidth).val;\r\n baseHeight -= borderHeight * 2;\r\n }\r\n return ParsedLengthStyle.inPixels(baseHeight);\r\n } else {\r\n return ParsedLengthStyle.forScalar(1);\r\n }\r\n }\r\n\r\n get internalHeight(): ParsedLengthStyle {\r\n if (this.usesFixedHeightScaling) {\r\n // Touch OSKs may apply internal padding to prevent row cropping at the edges.\r\n return ParsedLengthStyle.inPixels(this.layoutHeight.val - this.getVerticalLayerGroupPadding());\r\n } else {\r\n return ParsedLengthStyle.forScalar(1);\r\n }\r\n }\r\n\r\n get fontSize(): ParsedLengthStyle {\r\n if (!this._fontSize) {\r\n this._fontSize = new ParsedLengthStyle('1em');\r\n }\r\n return this._fontSize;\r\n }\r\n\r\n set fontSize(value: ParsedLengthStyle) {\r\n this._fontSize = value;\r\n this.kbdDiv.style.fontSize = value.styleString;\r\n }\r\n\r\n /**\r\n * Uses fixed scaling for widths of internal elements, rather than relative,\r\n * percent-based scaling.\r\n */\r\n public get usesFixedWidthScaling(): boolean {\r\n return this._fixedWidthScaling;\r\n }\r\n\r\n public set usesFixedWidthScaling(val: boolean) {\r\n this._fixedWidthScaling = val;\r\n }\r\n\r\n /**\r\n * Uses fixed scaling for heights of internal elements, rather than relative,\r\n * percent-based scaling.\r\n */\r\n public get usesFixedHeightScaling(): boolean {\r\n return this._fixedHeightScaling;\r\n }\r\n\r\n public set usesFixedHeightScaling(val: boolean) {\r\n this._fixedHeightScaling = val;\r\n }\r\n\r\n /**\r\n * Denotes if the VisualKeyboard or its containing OSKView / OSKManager uses\r\n * fixed positioning.\r\n */\r\n public get usesFixedPositioning(): boolean {\r\n let node: HTMLElement = this.element;\r\n while (node) {\r\n if (getComputedStyle(node).position == 'fixed') {\r\n return true;\r\n } else {\r\n node = node.offsetParent as HTMLElement;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Sets & tracks the size of the VisualKeyboard's primary element.\r\n * @param width\r\n * @param height\r\n * @param pending Set to `true` if called during a resizing interaction\r\n */\r\n public setSize(width?: number, height?: number, pending?: boolean) {\r\n this._width = width;\r\n this._height = height;\r\n\r\n if (this.kbdDiv) {\r\n this.kbdDiv.style.width = width ? this._width + 'px' : '';\r\n this.kbdDiv.style.height = height ? this._height + 'px' : '';\r\n\r\n if (!this.device.touchable && height) {\r\n this.fontSize = new ParsedLengthStyle((this._height / 8) + 'px');\r\n }\r\n\r\n if (!pending) {\r\n this.refreshLayout();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Returns the default properties for a key object, used to construct\r\n * both a base keyboard key and popup keys\r\n *\r\n * @return {Object} An object that contains default key properties\r\n */\r\n getDefaultKeyObject(): OSKKeySpec {\r\n return new OSKKeySpec(undefined, '', ActiveKey.DEFAULT_KEY.width, ActiveKey.DEFAULT_KEY.sp as ButtonClass,\r\n null, ActiveKey.DEFAULT_KEY.pad);\r\n };\r\n //#endregion\r\n\r\n //#region OSK touch handlers\r\n getTouchCoordinatesOnKeyboard(input: InputEventCoordinate) {\r\n // We need to compute the 'local', keyboard-based coordinates for the touch.\r\n let kbdCoords = {\r\n x: getAbsoluteX(this.kbdDiv),\r\n y: getAbsoluteY(this.kbdDiv)\r\n }\r\n let offsetCoords = { x: input.x - kbdCoords.x, y: input.y - kbdCoords.y };\r\n\r\n // The layer group's element always has the proper width setting, unlike kbdDiv itself.\r\n offsetCoords.x /= this.layerGroup.element.offsetWidth;\r\n offsetCoords.y /= this.kbdDiv.offsetHeight;\r\n\r\n return offsetCoords;\r\n }\r\n\r\n /**\r\n * Builds the fat-finger distribution used by predictive text as its source for likelihood\r\n * of alternate keystroke sequences.\r\n * @param input The input coordinate of the event that led to use of this function\r\n * @param keySpec The spec of the key directly triggered by the input event. May be for a subkey.\r\n * @returns\r\n */\r\n getTouchProbabilities(input: InputEventCoordinate, keySpec?: ActiveKey): KeyDistribution {\r\n // TODO: It'd be nice to optimize by keeping these off when unused, but the wiring\r\n // necessary would get in the way of modularization at the moment.\r\n // let keyman = com.keyman.singleton;\r\n // if (!keyman.core.languageProcessor.mayCorrect) {\r\n // return null;\r\n // }\r\n\r\n // Note: if subkeys are active, they will still be displayed at this time.\r\n // TODO: In such cases, we should build an ActiveLayout (of sorts) for subkey displays,\r\n // update their geometries to the actual display values, and use the results here.\r\n let touchKbdPos = this.getTouchCoordinatesOnKeyboard(input);\r\n let layerGroup = this.layerGroup.element; // Always has proper dimensions, unlike kbdDiv itself.\r\n let width = layerGroup.offsetWidth, height = this.kbdDiv.offsetHeight;\r\n // Prevent NaN breakages.\r\n if (!width || !height) {\r\n return null;\r\n }\r\n\r\n let kbdAspectRatio = layerGroup.offsetWidth / this.kbdDiv.offsetHeight;\r\n let baseKeyProbabilities = this.kbdLayout.getLayer(this.layerId).getTouchProbabilities(touchKbdPos, kbdAspectRatio);\r\n\r\n if (!keySpec || !this.subkeyGesture || !this.subkeyGesture.baseKey.key) {\r\n return baseKeyProbabilities;\r\n } else {\r\n // A temp-hack, as this was noted just before 14.0's release.\r\n // Since a more... comprehensive solution would be way too complex this late in the game,\r\n // this provides a half-decent stopgap measure.\r\n //\r\n // Will not correct to nearby subkeys; only includes the selected subkey and its base keys.\r\n // Still, better than ignoring them both for whatever base key is beneath the final cursor location.\r\n let baseMass = 1.0;\r\n\r\n let baseKeyMass = 1.0;\r\n let baseKeyID = this.subkeyGesture.baseKey.key.spec.coreID;\r\n\r\n let popupKeyMass = 0.0;\r\n let popupKeyID: string = null;\r\n\r\n popupKeyMass = 3.0;\r\n popupKeyID = keySpec.coreID;\r\n\r\n // If the base key appears in the subkey array and was selected, merge the probability masses.\r\n if (popupKeyID == baseKeyID) {\r\n baseKeyMass += popupKeyMass;\r\n popupKeyMass = 0;\r\n } else {\r\n // We namespace it so that lookup operations know to find it via its base key.\r\n popupKeyID = `${baseKeyID}::${popupKeyID}`;\r\n }\r\n\r\n // Compute the normalization factor\r\n let totalMass = baseMass + baseKeyMass + popupKeyMass;\r\n let scalar = 1.0 / totalMass;\r\n\r\n // Prevent duplicate entries in the final map & normalize the remaining entries!\r\n for (let i = 0; i < baseKeyProbabilities.length; i++) {\r\n let entry = baseKeyProbabilities[i];\r\n if (entry.keyId == baseKeyID) {\r\n baseKeyMass += entry.p * scalar;\r\n baseKeyProbabilities.splice(i, 1);\r\n i--;\r\n } else if (entry.keyId == popupKeyID) {\r\n popupKeyMass = + entry.p * scalar;\r\n baseKeyProbabilities.splice(i, 1);\r\n i--;\r\n } else {\r\n entry.p *= scalar;\r\n }\r\n }\r\n\r\n let finalArray: { keyId: string, p: number }[] = [];\r\n\r\n if (popupKeyMass > 0) {\r\n finalArray.push({ keyId: popupKeyID, p: popupKeyMass * scalar });\r\n }\r\n\r\n finalArray.push({ keyId: baseKeyID, p: baseKeyMass * scalar });\r\n\r\n finalArray = finalArray.concat(baseKeyProbabilities);\r\n return finalArray;\r\n }\r\n }\r\n\r\n //#region Input handling start\r\n\r\n /**\r\n * Determines a \"fuzzy boundary\" area around the OSK within which active mouse and\r\n * touch events will be maintained, even if their coordinates lie outside of the OSK's\r\n * true visual bounds.\r\n * @returns A `BoundingRect`, in `.pageX` / `.pageY` coordinates.\r\n */\r\n private getInteractiveBoundingRect(): BoundingRect {\r\n // Determine the important geometric values involved\r\n let oskX = getAbsoluteX(this.element);\r\n let oskY = getAbsoluteY(this.element);\r\n\r\n // Determine the out-of-bounds threshold at which touch-cancellation should automatically occur.\r\n // Assuming square key-squares, we'll use 1/3 the height of a row for bounds detection\r\n // for both dimensions.\r\n const rowCount = this.currentLayer.rows.length;\r\n const buffer = (0.333 * this.height / rowCount);\r\n\r\n // Determine the OSK's boundaries and the boundaries of the page / view.\r\n // These values are needed in .pageX / .pageY coordinates for the final calcs.\r\n let boundingRect: BoundingRect = {\r\n left: oskX - buffer,\r\n right: oskX + this.width + buffer,\r\n top: oskY - buffer,\r\n bottom: oskY + this.height + buffer\r\n };\r\n\r\n return boundingRect;\r\n }\r\n\r\n /**\r\n * Adjusts a potential \"interactive boundary\" definition by enforcing an\r\n * \"event cancellation zone\" near screen boundaries that are not directly adjacent\r\n * to the ongoing input event's initial coordinate.\r\n *\r\n * This facilitates modeling of conventional cancellation gestures where a user would\r\n * drag the mouse or touch point off the OSK, as mouse and touch event handlers receive\r\n * no input beyond screen boundaries.\r\n *\r\n * @param baseBounds The baseline interactive bounding area to be adjusted\r\n * @param startCoord The initial coordinate of a currently-ongoing input event\r\n * @returns\r\n */\r\n private applyScreenMarginBoundsThresholding(baseBounds: BoundingRect,\r\n startCoord: InputEventCoordinate): BoundingRect {\r\n // Determine the needed linear translation to screen coordinates.\r\n const xDelta = window.screenLeft - window.pageXOffset;\r\n const yDelta = window.screenTop - window.pageYOffset;\r\n\r\n let adjustedBounds: BoundingRect = { ...baseBounds };\r\n\r\n // Also translate the initial touch's screen coord, as it affects our bounding box logic.\r\n const initScreenCoord = new InputEventCoordinate(startCoord.x + xDelta, startCoord.y + yDelta);\r\n\r\n // Detection: is the OSK aligned with any screen boundaries?\r\n // If so, create a 'fuzzy' zone around the edges not near the initial touch point that allow\r\n // move-based cancellation.\r\n\r\n // If the initial input screen-coord is at least 5 pixels from the screen's left AND\r\n // the OSK's left boundary is within 2 pixels from the screen's left...\r\n if (initScreenCoord.x >= 5 && baseBounds.left + xDelta <= 2) {\r\n adjustedBounds.left = 2 - xDelta; // new `leftBound` is set to 2 pixels from the screen's left.\r\n }\r\n\r\n if (initScreenCoord.x <= screen.width - 5 && baseBounds.right + xDelta >= screen.width - 2) {\r\n adjustedBounds.right = (screen.width - 2) - xDelta; // new `rightBound` 2px from screen's right.\r\n }\r\n\r\n if (initScreenCoord.y >= 5 && baseBounds.top + yDelta <= 2) {\r\n adjustedBounds.top = 2 - yDelta;\r\n }\r\n\r\n if (initScreenCoord.y <= screen.height - 5 && baseBounds.bottom + yDelta >= screen.height - 2) {\r\n adjustedBounds.bottom = (screen.height - 2) - yDelta;\r\n }\r\n\r\n return adjustedBounds;\r\n }\r\n\r\n detectWithinInteractiveBounds(coord: InputEventCoordinate): boolean {\r\n // Shortcuts the method during unit testing, as we don't currently\r\n // provide coordinate values in its synthetic events.\r\n if (coord.x === null && coord.y === null) {\r\n return true;\r\n }\r\n\r\n const baseBoundingRect = this.getInteractiveBoundingRect();\r\n let adjustedBoundingRect = baseBoundingRect;\r\n if(this.initTouchCoord) {\r\n this.applyScreenMarginBoundsThresholding(baseBoundingRect, this.initTouchCoord);\r\n }\r\n\r\n // Now to check where the input coordinate lies in relation to the final bounding box!\r\n\r\n if (coord.x < adjustedBoundingRect.left || coord.x > adjustedBoundingRect.right) {\r\n return false;\r\n } else if (coord.y < adjustedBoundingRect.top || coord.y > adjustedBoundingRect.bottom) {\r\n return false;\r\n } else {\r\n return true;\r\n }\r\n }\r\n\r\n /**\r\n * The main OSK touch start event handler\r\n *\r\n * @param {Event} e touch start event object\r\n *\r\n */\r\n touch(input: InputEventCoordinate) {\r\n // Identify the key touched\r\n var t = input.target, key = this.keyTarget(t);\r\n\r\n // Save the touch point, which is used for quick-display of popup keys (defined in highlightSubKeys)\r\n this.initTouchCoord = input;\r\n\r\n // Set the key for the new touch point to be current target, if defined\r\n this.currentTarget = key;\r\n\r\n // Clear repeated backspace if active, preventing 'sticky' behavior.\r\n this.cancelDelete();\r\n\r\n // Prevent multi-touch if popup displayed\r\n if (this.subkeyGesture && this.subkeyGesture.isVisible()) {\r\n return;\r\n }\r\n\r\n // Keep track of number of active (unreleased) touch points\r\n this.touchCount = input.activeInputCount;\r\n\r\n // Get nearest key if touching a hidden key or the end of a key row\r\n if ((key && ((key.className.indexOf('key-hidden') >= 0) || (key.className.indexOf('key-blank') >= 0)))\r\n || t.className.indexOf('kmw-key-row') >= 0) {\r\n\r\n // Perform \"fudged\" selection ops if and only if we're not sure about the precision of the\r\n // input source. Mouse-based selection IS precise, so no need for \"fudging\" there.\r\n if (!input.isFromMouse) {\r\n key = this.findNearestKey(input, t);\r\n }\r\n }\r\n // Do not do anything if no key identified!\r\n if (key == null) {\r\n return;\r\n }\r\n\r\n // Get key name (K_...) from element ID\r\n let keyName = key['keyId'];\r\n\r\n // Highlight the touched key\r\n this.highlightKey(key, true);\r\n\r\n // Special function keys need immediate action\r\n if (keyName == 'K_LOPT' || keyName == 'K_ROPT') {\r\n window.setTimeout(function (this: VisualKeyboard) {\r\n this.modelKeyClick(key);\r\n // Because we immediately process the key, we need to re-highlight it after the click.\r\n this.highlightKey(key, true);\r\n // Highlighting'll be cleared automatically later.\r\n }.bind(this), 0);\r\n this.keyPending = null;\r\n this.touchPending = null;\r\n\r\n // Also backspace, to allow delete to repeat while key held\r\n } else if (keyName == 'K_BKSP') {\r\n // While we could inline the execution of the delete key here, we lose the ability to\r\n // record the backspace key if we do so.\r\n this.modelKeyClick(key, input);\r\n this.deleteKey = key;\r\n this.deleting = window.setTimeout(this.repeatDelete, 500);\r\n this.keyPending = null;\r\n this.touchPending = null;\r\n } else {\r\n if (this.keyPending) {\r\n this.highlightKey(this.keyPending, false);\r\n\r\n if (this.subkeyGesture && this.subkeyGesture instanceof InternalSubkeyPopup) {\r\n let subkeyPopup = this.subkeyGesture as InternalSubkeyPopup;\r\n subkeyPopup.updateTouch(input);\r\n subkeyPopup.finalize(input);\r\n } else {\r\n this.modelKeyClick(this.keyPending, this.touchPending);\r\n }\r\n // Decrement the number of unreleased touch points to prevent\r\n // sending the keystroke again when the key is actually released\r\n this.touchCount--;\r\n } else {\r\n this.initGestures(key, input);\r\n }\r\n this.keyPending = key;\r\n this.touchPending = input;\r\n }\r\n }\r\n\r\n /**\r\n * OSK touch release event handler\r\n *\r\n * @param {Event} e touch release event object\r\n *\r\n **/\r\n release(input: InputEventCoordinate): void {\r\n // Prevent incorrect multi-touch behaviour if native or device popup visible\r\n var t = this.currentTarget;\r\n\r\n // Clear repeated backspace if active, preventing 'sticky' behavior.\r\n this.cancelDelete();\r\n\r\n // Multi-Tap\r\n if (this.pendingMultiTap && this.pendingMultiTap.realized) {\r\n // Ignore pending key if we've just handled a multitap\r\n this.pendingMultiTap = null;\r\n\r\n this.highlightKey(this.keyPending, false);\r\n this.keyPending = null;\r\n this.touchPending = null;\r\n\r\n return;\r\n }\r\n\r\n if (this.pendingMultiTap && this.pendingMultiTap.cancelled) {\r\n this.pendingMultiTap = null;\r\n }\r\n\r\n // Longpress\r\n if ((this.subkeyGesture && this.subkeyGesture.isVisible())) {\r\n // Ignore release if a multiple touch\r\n if (input.activeInputCount > 0) {\r\n return;\r\n }\r\n\r\n if (this.subkeyGesture instanceof InternalSubkeyPopup) {\r\n let subkeyPopup = this.subkeyGesture as InternalSubkeyPopup;\r\n subkeyPopup.finalize(input);\r\n }\r\n this.highlightKey(this.keyPending, false);\r\n this.keyPending = null;\r\n this.touchPending = null;\r\n\r\n return;\r\n }\r\n\r\n // Handle menu key release event\r\n if (t && t.id) {\r\n this.optionKey(t, t.id, false);\r\n }\r\n\r\n // Test if moved off screen (effective release point must be corrected for touch point horizontal speed)\r\n // This is not completely effective and needs some tweaking, especially on Android\r\n if (!this.detectWithinInteractiveBounds(input)) {\r\n this.moveCancel(input);\r\n this.touchCount--;\r\n return;\r\n }\r\n\r\n // Save then decrement current touch count\r\n var tc = this.touchCount;\r\n if (this.touchCount > 0) {\r\n this.touchCount--;\r\n }\r\n\r\n // Process and clear highlighting of pending target\r\n if (this.keyPending) {\r\n this.highlightKey(this.keyPending, false);\r\n // Output character unless moved off key\r\n if (this.keyPending.className.indexOf('hidden') < 0 && tc > 0) {\r\n this.modelKeyClick(this.keyPending, input);\r\n }\r\n this.clearPopup();\r\n this.keyPending = null;\r\n this.touchPending = null;\r\n // Always clear highlighting of current target on release (multi-touch)\r\n } else {\r\n var tt = input;\r\n t = this.keyTarget(tt.target);\r\n if (!t) {\r\n // Operates relative to the viewport, not based on the actual coordinate on the page.\r\n var t1 = document.elementFromPoint(input.x - window.pageXOffset, input.y - window.pageYOffset);\r\n t = this.findNearestKey(input, t1);\r\n }\r\n\r\n this.highlightKey(t, false);\r\n }\r\n }\r\n\r\n moveCancel(input: InputEventCoordinate): void {\r\n // Do not attempt to support reselection of target key for overlapped keystrokes.\r\n // Perform _after_ ensuring possible sticky keys have been cancelled.\r\n if (input.activeInputCount > 1) {\r\n return;\r\n }\r\n\r\n // Update all gesture tracking. The function returns true if further input processing\r\n // should be blocked. (Keeps the subkey array operating when the input coordinate has\r\n // moved outside the OSK's boundaries.)\r\n if (this.updateGestures(null, this.keyPending, input)) {\r\n return;\r\n }\r\n\r\n this.cancelDelete();\r\n\r\n this.highlightKey(this.keyPending, false);\r\n this.showKeyTip(null, false);\r\n this.clearPopup();\r\n this.keyPending = null;\r\n this.touchPending = null;\r\n }\r\n\r\n /**\r\n * OSK touch move event handler\r\n *\r\n * @param {Event} e touch move event object\r\n *\r\n **/\r\n moveOver(input: InputEventCoordinate): void {\r\n // Shouldn't be possible, but just in case.\r\n if (this.touchCount == 0) {\r\n this.cancelDelete();\r\n return;\r\n }\r\n\r\n // Get touch position\r\n const x = input.x - window.pageXOffset;\r\n const y = input.y - window.pageYOffset;\r\n\r\n // Move target key and highlighting\r\n this.touchPending = input;\r\n // Operates on viewport-based coordinates, not page-based.\r\n var t1 = document.elementFromPoint(x, y);\r\n const key0 = this.keyPending;\r\n let key1 = this.keyTarget(t1); // Not only gets base keys, but also gets popup keys!\r\n\r\n // Find the nearest key to the touch point if not on a visible key\r\n if ((key1 && key1.className.indexOf('key-hidden') >= 0) ||\r\n (t1 && (!key1) && t1.className.indexOf('key-row') >= 0)) {\r\n key1 = this.findNearestKey(input, t1);\r\n }\r\n\r\n // Cancels BKSP if it's not the key. (Note... could also cancel BKSP if the ongoing\r\n // input is cancelled, regardless of key, just to be safe.)\r\n\r\n // Stop repeat if no longer on BKSP key\r\n if (key1 && (typeof key1.id == 'string') && (key1.id.indexOf('-K_BKSP') < 0)) {\r\n this.cancelDelete();\r\n }\r\n\r\n // Cancels if it's a multitouch attempt.\r\n\r\n // Do not attempt to support reselection of target key for overlapped keystrokes.\r\n // Perform _after_ ensuring possible sticky keys have been cancelled.\r\n if (input.activeInputCount > 1) {\r\n return;\r\n }\r\n\r\n // Gesture-updates should probably be a separate call from other touch-move aspects.\r\n\r\n // Update all gesture tracking. The function returns true if further input processing\r\n // should be blocked.\r\n if (this.updateGestures(key1, key0, input)) {\r\n return;\r\n }\r\n\r\n // Identify current touch position (to manage off-key release)\r\n this.currentTarget = key1;\r\n\r\n // Only NOW do we denote the newly-selected key as the currently-focused key.\r\n\r\n // Replace the target key, if any, by the new target key\r\n // Do not replace a null target, as that indicates the key has already been released\r\n if (key1 && this.keyPending) {\r\n this.highlightKey(key0, false);\r\n this.keyPending = key1;\r\n this.touchPending = input;\r\n }\r\n\r\n if (key0 && key1 && (key1 != key0) && (key1.id != '')) {\r\n // While there may not be an active subkey menu, we should probably update which base key\r\n // is being highlighted by the current touch & start a pending longpress for it.\r\n this.clearPopup();\r\n this.initGestures(key1, input);\r\n }\r\n\r\n if (this.keyPending) {\r\n if (key0 != key1 || key1.className.indexOf(OSKKey.HIGHLIGHT_CLASS) < 0) {\r\n this.highlightKey(key1, true);\r\n }\r\n }\r\n }\r\n\r\n //#endregion\r\n\r\n /**\r\n * Get the current key target from the touch point element within the key\r\n *\r\n * @param {Object} t element at touch point\r\n * @return {Object} the key element (or null)\r\n **/\r\n keyTarget(target: HTMLElement | EventTarget): KeyElement {\r\n let t = target;\r\n\r\n try {\r\n if (t) {\r\n if (t.classList.contains('kmw-key')) {\r\n return getKeyFrom(t);\r\n }\r\n if (t.parentNode && (t.parentNode as HTMLElement).classList.contains('kmw-key')) {\r\n return getKeyFrom(t.parentNode);\r\n }\r\n if (t.firstChild && (t.firstChild as HTMLElement).classList.contains('kmw-key')) {\r\n return getKeyFrom(t.firstChild);\r\n }\r\n }\r\n } catch (ex) { }\r\n return null;\r\n }\r\n\r\n /**\r\n * Identify the key nearest to the touch point if at the end of a key row,\r\n * but return null more than about 0.6 key width from the nearest key.\r\n *\r\n * @param {Event} e touch event\r\n * @param {Object} t HTML object at touch point\r\n * @return {Object} nearest key to touch point\r\n *\r\n **/\r\n findNearestKey(input: InputEventCoordinate, t: HTMLElement): KeyElement {\r\n if (!input) {\r\n return null;\r\n }\r\n\r\n // Get touch point on screen\r\n var x = input.x;\r\n\r\n // Get key-row beneath touch point\r\n while (t && t.className !== undefined && t.className.indexOf('key-row') < 0) {\r\n t = t.parentNode;\r\n }\r\n if (!t) {\r\n return null;\r\n }\r\n\r\n // Find minimum distance from any key\r\n var k, k0 = 0, dx, dxMax = 24, dxMin = 100000, x1, x2;\r\n for (k = 0; k < t.childNodes.length; k++) {\r\n let keySquare = t.childNodes[k] as HTMLElement; // gets the .kmw-key-square containing a key\r\n // Find the actual key element.\r\n let childNode = keySquare.firstChild ? keySquare.firstChild as HTMLElement : keySquare;\r\n\r\n if (childNode.className !== undefined\r\n && (childNode.className.indexOf('key-hidden') >= 0\r\n || childNode.className.indexOf('key-blank') >= 0)) {\r\n continue;\r\n }\r\n x1 = keySquare.offsetLeft;\r\n x2 = x1 + keySquare.offsetWidth;\r\n if (x >= x1 && x <= x2) {\r\n // Within the key square\r\n return childNode;\r\n }\r\n dx = x1 - x;\r\n if (dx >= 0 && dx < dxMin) {\r\n // To right of key\r\n k0 = k; dxMin = dx;\r\n }\r\n dx = x - x2;\r\n if (dx >= 0 && dx < dxMin) {\r\n // To left of key\r\n k0 = k; dxMin = dx;\r\n }\r\n }\r\n\r\n if (dxMin < 100000) {\r\n t = t.childNodes[k0];\r\n x1 = t.offsetLeft;\r\n x2 = x1 + t.offsetWidth;\r\n\r\n // Limit extended touch area to the larger of 0.6 of key width and 24 px\r\n if (t.offsetWidth > 40) {\r\n dxMax = 0.6 * t.offsetWidth;\r\n }\r\n\r\n if (((x1 - x) >= 0 && (x1 - x) < dxMax) || ((x - x2) >= 0 && (x - x2) < dxMax)) {\r\n return t.firstChild;\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n /**\r\n * Repeat backspace as long as the backspace key is held down\r\n **/\r\n repeatDelete: () => void = function (this: VisualKeyboard) {\r\n if (this.deleting) {\r\n this.modelKeyClick(this.deleteKey);\r\n this.deleting = window.setTimeout(this.repeatDelete, 100);\r\n }\r\n }.bind(this);\r\n\r\n /**\r\n * Cancels any active repeatDelete() timeouts, ensuring that\r\n * repeating backspace operations are properly terminated.\r\n */\r\n cancelDelete() {\r\n // Clears the delete-repeating timeout.\r\n if (this.deleting) {\r\n window.clearTimeout(this.deleting);\r\n }\r\n this.deleting = 0;\r\n }\r\n //#endregion\r\n\r\n modelKeyClick(e: KeyElement, input?: InputEventCoordinate) {\r\n let keyEvent = this.initKeyEvent(e, input);\r\n this.raiseKeyEvent(keyEvent, e);\r\n }\r\n\r\n initKeyEvent(e: KeyElement, input?: InputEventCoordinate) {\r\n // Turn off key highlighting (or preview)\r\n this.highlightKey(e, false);\r\n\r\n // Future note: we need to refactor osk.OSKKeySpec to instead be a 'tag field' for\r\n // keyboards.ActiveKey. (Prob with generics, allowing the Web-only parts to\r\n // be fully specified within the tag.)\r\n //\r\n // Would avoid the type shenanigans needed here because of our current type-abuse setup\r\n // for key spec tracking.\r\n let keySpec = (e['key'] ? e['key'].spec : null) as unknown as ActiveKey;\r\n if (!keySpec) {\r\n console.error(\"OSK key with ID '\" + e.id + \"', keyID '\" + e.keyId + \"' missing needed specification\");\r\n return null;\r\n }\r\n\r\n // Return the event object.\r\n return this.keyEventFromSpec(keySpec, input);\r\n }\r\n\r\n keyEventFromSpec(keySpec: ActiveKey, input?: InputEventCoordinate) {\r\n //let core = com.keyman.singleton.core; // only singleton-based ref currently needed here.\r\n\r\n // Start: mirrors _GetKeyEventProperties\r\n\r\n // First check the virtual key, and process shift, control, alt or function keys\r\n //let Lkc = keySpec.constructKeyEvent(core.keyboardProcessor, this.device);\r\n let Lkc = this.layoutKeyboard.constructKeyEvent(keySpec, this.device, this.stateKeys);\r\n\r\n /* In case of \"fun\" edge cases caused by JS's single-threadedness & event processing queue.\r\n *\r\n * Should a touch occur on an OSK key during active JS execution that results in a change\r\n * of the active keyboard, it's possible for an OSK key to be evaluated against an\r\n * unexpected, non-matching keyboard - one that could even be `null`!\r\n *\r\n * So, we mark the keyboard backing the OSK as the 'correct' keyboard for this key.\r\n */\r\n Lkc.srcKeyboard = this.layoutKeyboard;\r\n\r\n // End - mirrors _GetKeyEventProperties\r\n\r\n if (input) {\r\n Lkc.source = input;\r\n Lkc.keyDistribution = this.getTouchProbabilities(input, keySpec);\r\n }\r\n\r\n // Return the event object.\r\n return Lkc;\r\n }\r\n\r\n // cancel = function(e) {} //cancel event is never generated by iOS\r\n\r\n /**\r\n * Function _UpdateVKShiftStyle\r\n * Scope Private\r\n * @param {string=} layerId\r\n * Description Updates the OSK's visual style for any toggled state keys\r\n */\r\n _UpdateVKShiftStyle(layerId?: string) {\r\n var i;\r\n //let core = com.keyman.singleton.core;\r\n\r\n if (!layerId) {\r\n layerId = this.layerId;\r\n }\r\n\r\n const layer = this.layerGroup.layers[layerId];\r\n if (!layer) {\r\n return;\r\n }\r\n\r\n // So... through KMW 14, we actually never tracked the capsKey, numKey, and scrollKey\r\n // properly for keyboard-defined layouts - only _default_, desktop-style layouts.\r\n //\r\n // We _could_ remedy this, but then... touch keyboards like khmer_angkor actually\r\n // repurpose certain state keys, and in an inconsistent manner at that.\r\n // Considering the potential complexity of touch layouts, with multiple possible\r\n // layer-shift keys, it's likely best to just leave things as they are for now.\r\n if (!this.layoutKeyboard?.usesDesktopLayoutOnDevice(this.device)) {\r\n return;\r\n }\r\n\r\n // Set the on/off state of any visible state keys.\r\n const states = ['K_CAPS', 'K_NUMLOCK', 'K_SCROLL'];\r\n const keys = [layer.capsKey, layer.numKey, layer.scrollKey];\r\n\r\n for (i = 0; i < keys.length; i++) {\r\n // Skip any keys not in the OSK!\r\n if (keys[i] == null) {\r\n continue;\r\n }\r\n\r\n keys[i].setToggleState(this.stateKeys[states[i]]);\r\n }\r\n }\r\n\r\n updateStateKeys(stateKeys: StateKeyMap) {\r\n for(let key in this.stateKeys) {\r\n this.stateKeys[key] = stateKeys[key];\r\n }\r\n\r\n this._UpdateVKShiftStyle();\r\n }\r\n\r\n clearPopup() {\r\n // Remove the displayed subkey array, if any, and cancel popup request\r\n if (this.subkeyGesture) {\r\n this.subkeyGesture.clear();\r\n this.subkeyGesture = null;\r\n }\r\n\r\n if (this.pendingSubkey) {\r\n this.pendingSubkey.cancel();\r\n this.pendingSubkey = null;\r\n }\r\n }\r\n\r\n //#endregion\r\n\r\n /**\r\n * Indicate the current language and keyboard on the space bar\r\n **/\r\n showLanguage() {\r\n let activeStub = this.layoutKeyboardProperties;\r\n let displayName: string = activeStub?.displayName ?? '(System keyboard)';\r\n\r\n try {\r\n var t = this.spaceBar.key.label;\r\n let tParent = t.parentNode;\r\n if (typeof (tParent.className) == 'undefined' || tParent.className == '') {\r\n tParent.className = 'kmw-spacebar';\r\n } else if (tParent.className.indexOf('kmw-spacebar') == -1) {\r\n tParent.className += ' kmw-spacebar';\r\n }\r\n\r\n if (t.className != 'kmw-spacebar-caption') {\r\n t.className = 'kmw-spacebar-caption';\r\n }\r\n\r\n // It sounds redundant, but this dramatically cuts down on browser DOM processing;\r\n // but sometimes innerText is reported empty when it actually isn't, so set it\r\n // anyway in that case (Safari, iOS 14.4)\r\n if (t.innerText != displayName || displayName == '') {\r\n t.innerText = displayName;\r\n }\r\n\r\n this.spaceBar.key.refreshLayout(this);\r\n }\r\n catch (ex) { }\r\n }\r\n\r\n /**\r\n * Add or remove a class from a keyboard key (when touched or clicked)\r\n * or add a key preview for phone devices\r\n *\r\n * @param {Object} key key affected\r\n * @param {boolean} on add or remove highlighting\r\n **/\r\n highlightKey(key: KeyElement, on: boolean) {\r\n // Do not change element class unless a key\r\n if (!key || !key.key || (key.className == '') || (key.className.indexOf('kmw-key-row') >= 0)) return;\r\n\r\n // For phones, use key preview rather than highlighting the key,\r\n var usePreview = (this.keytip != null) && key.key.allowsKeyTip();\r\n\r\n if (usePreview) {\r\n this.showKeyTip(key, on);\r\n } else {\r\n // No key tip should be shown. In some cases (e.g. multitap), we\r\n // may still have a tip visible so let's always hide in that case\r\n this.showKeyTip(null, false);\r\n key.key.highlight(on);\r\n }\r\n }\r\n\r\n /**\r\n * Use of `getComputedStyle` is ideal, but in many of our use cases its preconditions are not met.\r\n * This function allows us to calculate the font size in those situations.\r\n */\r\n getKeyEmFontSize(): number {\r\n if (!this.fontSize) {\r\n return 0;\r\n }\r\n\r\n if (this.device.formFactor == 'desktop') {\r\n let keySquareScale = 0.8; // Set in kmwosk.css, is relative.\r\n return this.fontSize.scaledBy(keySquareScale).val;\r\n } else {\r\n let emSizeStr = getComputedStyle(document.body).fontSize;\r\n let emSize = getFontSizeStyle(emSizeStr).val;\r\n\r\n var emScale = 1;\r\n if (!this.isStatic) {\r\n // Double-check against the font scaling applied to the _Box element.\r\n if (this.fontSize.absolute) {\r\n return this.fontSize.val;\r\n } else {\r\n emScale = this.fontSize.val;\r\n }\r\n }\r\n return emSize * emScale;\r\n }\r\n }\r\n\r\n updateState() {\r\n // May happen for desktop-oriented keyboards that neglect to specify a touch layout.\r\n // See `test_chirality.js` from the unit-test keyboard suite, which tests keystrokes\r\n // using modifiers that lack corresponding visual-layout representation.\r\n if (!this.currentLayer) {\r\n return;\r\n }\r\n\r\n var n, b = this.kbdDiv.childNodes[0].childNodes;\r\n this.nextLayer = this.layerId;\r\n\r\n if (this.currentLayer.nextlayer) {\r\n this.nextLayer = this.currentLayer.nextlayer;\r\n }\r\n\r\n for (n = 0; n < b.length; n++) {\r\n let layerElement = b[n];\r\n if (layerElement['layer'] == this.layerId) {\r\n layerElement.style.display = 'block';\r\n //b[n].style.visibility='visible';\r\n\r\n // Most functions that call this one often indicate a change in modifier\r\n // or state key state. Keep it updated!\r\n this._UpdateVKShiftStyle();\r\n } else {\r\n layerElement.style.display = 'none';\r\n //layerElement.style.visibility='hidden';\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Used to refresh the VisualKeyboard's geometric layout and key sizes\r\n * when needed.\r\n */\r\n refreshLayout() {\r\n //let keyman = com.keyman.singleton;\r\n let device = this.device;\r\n\r\n var fs = 1.0;\r\n // TODO: Logically, this should be needed for Android, too - may need to be changed for the next version!\r\n if (device.OS == DeviceSpec.OperatingSystem.iOS && !this.isEmbedded) {\r\n fs = fs / getViewportScale();\r\n }\r\n\r\n let paddedHeight: number;\r\n if (this.height) {\r\n paddedHeight = this.computedAdjustedOskHeight(this.height);\r\n }\r\n\r\n let b = this.layerGroup.element as HTMLElement;\r\n let gs = this.kbdDiv.style;\r\n let bs = b.style;\r\n if (this.usesFixedHeightScaling && this.height) {\r\n // Sets the layer group to the correct height.\r\n gs.height = gs.maxHeight = this.height + 'px';\r\n }\r\n\r\n // The font-scaling applied on the layer group.\r\n gs.fontSize = this.fontSize.styleString;\r\n bs.fontSize = ParsedLengthStyle.forScalar(fs).styleString;\r\n\r\n // NEW CODE ------\r\n\r\n // Step 1: have the necessary conditions been met?\r\n const fixedSize = this.width && this.height;\r\n const computedStyle = getComputedStyle(this.kbdDiv);\r\n const isInDOM = computedStyle.height != '' && computedStyle.height != 'auto';\r\n\r\n // Step 2: determine basic layout geometry, refresh things that might update.\r\n this.showLanguage(); // In case the spacebar-text mode setting has changed.\r\n\r\n if (fixedSize) {\r\n this._computedWidth = this.width;\r\n this._computedHeight = this.height;\r\n } else if (isInDOM) {\r\n this._computedWidth = parseInt(computedStyle.width, 10);\r\n if (!this._computedWidth) {\r\n // For touch keyboards, the width _was_ specified on the layer group,\r\n // not the root element (`kbdDiv`).\r\n const groupStyle = getComputedStyle(this.kbdDiv.firstElementChild);\r\n this._computedWidth = parseInt(groupStyle.width, 10);\r\n }\r\n this._computedHeight = parseInt(computedStyle.height, 10);\r\n } else {\r\n // Cannot perform layout operations!\r\n return;\r\n }\r\n\r\n // Step 3: perform layout operations. (Handled by 'old code' section below.)\r\n\r\n // END NEW CODE -----------\r\n\r\n // Needs the refreshed layout info to work correctly.\r\n if(this.currentLayer) {\r\n this.currentLayer.refreshLayout(this, this._computedHeight - this.getVerticalLayerGroupPadding());\r\n }\r\n }\r\n\r\n private getVerticalLayerGroupPadding(): number {\r\n // For touch-based OSK layouts, kmwosk.css may include top & bottom padding on the layer-group element.\r\n const computedGroupStyle = getComputedStyle(this.layerGroup.element);\r\n\r\n // parseInt('') => NaN, which is falsy; we want to fallback to zero.\r\n let pt = parseInt(computedGroupStyle.paddingTop, 10) || 0;\r\n let pb = parseInt(computedGroupStyle.paddingBottom, 10) || 0;\r\n return pt + pb;\r\n }\r\n\r\n /*private*/ computedAdjustedOskHeight(allottedHeight: number): number {\r\n if (!this.layerGroup) {\r\n return allottedHeight;\r\n }\r\n\r\n const layers = this.layerGroup.layers;\r\n let oskHeight = 0;\r\n\r\n // In case the keyboard's layers have differing row counts, we check them all for the maximum needed oskHeight.\r\n for (const layerID in layers) {\r\n const layer = layers[layerID];\r\n let nRows = layer.rows.length;\r\n let rowHeight = Math.floor(allottedHeight / (nRows == 0 ? 1 : nRows));\r\n let layerHeight = nRows * rowHeight;\r\n\r\n if (layerHeight > oskHeight) {\r\n oskHeight = layerHeight;\r\n }\r\n }\r\n\r\n // This isn't set anywhere else; it's a legacy part of the original methods.\r\n const oskPad = 0;\r\n let oskPaddedHeight = oskHeight + oskPad;\r\n\r\n return oskPaddedHeight;\r\n }\r\n\r\n /**\r\n * Append a style sheet for the current keyboard if needed for specifying an embedded font\r\n * or to re-apply the default element font\r\n *\r\n **/\r\n appendStyleSheet() {\r\n //let util = com.keyman.singleton.util;\r\n\r\n var activeKeyboard = this.layoutKeyboard;\r\n var activeStub = this.layoutKeyboardProperties;\r\n\r\n // Do not do anything if a null stub\r\n if (activeStub == null) {\r\n return;\r\n }\r\n\r\n // First remove any existing keyboard style sheet\r\n if (this.styleSheet && this.styleSheet.parentNode) {\r\n this.styleSheet.parentNode.removeChild(this.styleSheet);\r\n }\r\n\r\n var kfd = activeStub.textFont, ofd = activeStub.oskFont;\r\n\r\n // Add and define style sheets for embedded fonts if necessary (each font-face style will only be added once)\r\n this.styleSheetManager.addStyleSheetForFont(kfd, this.fontRootPath, this.device.OS);\r\n this.styleSheetManager.addStyleSheetForFont(ofd, this.fontRootPath, this.device.OS);\r\n\r\n // Build the style string to USE the fonts and append (or replace) the font style sheet\r\n // Note: Some browsers do not download the font-face font until it is applied,\r\n // so must apply style before testing for font availability\r\n // Extended to allow keyboard-specific custom styles for Build 360\r\n var customStyle = this.addFontStyle(kfd, ofd);\r\n if (activeKeyboard != null && typeof (activeKeyboard.oskStyling) == 'string') // KMEW-129\r\n customStyle = customStyle + activeKeyboard.oskStyling;\r\n\r\n this.styleSheet = createStyleSheet(customStyle); //Build 360\r\n this.styleSheet.addEventListener('load', () => {\r\n // Once any related fonts are loaded, we can re-adjust key-cap scaling.\r\n this.refreshLayout();\r\n })\r\n this.styleSheetManager.linkStylesheet(this.styleSheet);\r\n }\r\n\r\n /**\r\n * Add or replace the style sheet used to set the font for input elements and OSK\r\n *\r\n * @param {Object} kfd KFont font descriptor\r\n * @param {Object} ofd OSK font descriptor (if any)\r\n * @return {string}\r\n *\r\n **/\r\n addFontStyle(kfd: InternalKeyboardFont, ofd: InternalKeyboardFont): string {\r\n let s: string = '';\r\n\r\n let family = (fd: InternalKeyboardFont) => fd.family.replace(/\\u0022/g, '').replace(/,/g, '\",\"');\r\n\r\n // Set font family for OSK text, suggestion text\r\n if (kfd || ofd) {\r\n s = `\r\n.kmw-key-text {\r\n font-family: \"${family(ofd || kfd)}\";\r\n}\r\n\r\n.kmw-suggestion-text {\r\n font-family: \"${family(kfd || ofd)}\";\r\n}\r\n`;\r\n }\r\n\r\n // Return the style string\r\n return s;\r\n }\r\n\r\n /**\r\n * Create copy of the OSK that can be used for embedding in documentation or help\r\n * The currently active keyboard will be returned if PInternalName is null\r\n *\r\n * @param {Object} PKbd the keyboard object to be displayed\r\n * @param {string=} argFormFactor layout form factor, defaulting to 'desktop'\r\n * @param {(string|number)=} argLayerId name or index of layer to show, defaulting to 'default'\r\n * @param {number} height Target height for the rendered keyboard\r\n * (currently required for legacy reasons)\r\n * @return {Object} DIV object with filled keyboard layer content\r\n */\r\n static buildDocumentationKeyboard(PKbd: Keyboard, kbdProperties: KeyboardProperties, fontRootPath: string, argFormFactor: DeviceSpec.FormFactor, argLayerId, height: number): HTMLElement { // I777\r\n if (!PKbd) {\r\n return null;\r\n }\r\n\r\n var formFactor = (typeof (argFormFactor) == 'undefined' ? 'desktop' : argFormFactor) as DeviceSpec.FormFactor,\r\n layerId = (typeof (argLayerId) == 'undefined' ? 'default' : argLayerId),\r\n device: {\r\n formFactor?: DeviceSpec.FormFactor,\r\n OS?: DeviceSpec.OperatingSystem,\r\n touchable?: boolean\r\n } = {};\r\n\r\n // Device emulation for target documentation.\r\n device.formFactor = formFactor;\r\n if (formFactor != 'desktop') {\r\n device.OS = DeviceSpec.OperatingSystem.iOS;\r\n device.touchable = true;\r\n } else {\r\n device.OS = DeviceSpec.OperatingSystem.Windows;\r\n device.touchable = false;\r\n }\r\n\r\n let layout = PKbd.layout(formFactor);\r\n\r\n const deviceSpec = new DeviceSpec('other', device.formFactor, device.OS, device.touchable);\r\n let kbdObj = new VisualKeyboard({\r\n keyboard: PKbd,\r\n keyboardMetadata: kbdProperties,\r\n hostDevice: deviceSpec,\r\n isStatic: true,\r\n topContainer: null,\r\n pathConfig: {\r\n fonts: fontRootPath,\r\n resources: '' // ignored\r\n },\r\n styleSheetManager: null\r\n }); //\r\n\r\n kbdObj.layerGroup.element.className = kbdObj.kbdDiv.className; // may contain multiple classes\r\n kbdObj.layerGroup.element.classList.add(device.formFactor + '-static');\r\n\r\n let kbd = kbdObj.kbdDiv.childNodes[0] as HTMLDivElement; // Gets the layer group.\r\n\r\n // Models CSS classes hosted on the OSKView in normal operation. We can't do this on the main\r\n // layer-group element because of the CSS rule structure for keyboard styling.\r\n //\r\n // For example, `.ios .kmw-keyboard-sil_cameroon_azerty` requires the element with the keyboard\r\n // ID to be in a child of an element with the .ios class.\r\n let classWrapper = document.createElement('div');\r\n classWrapper.classList.add(device.OS.toLowerCase(), device.formFactor);\r\n\r\n // Select the layer to display, and adjust sizes\r\n if (layout != null) {\r\n kbdObj.layerId = layerId;\r\n\r\n // This still feels fairly hacky... but something IS needed to constrain the height.\r\n // There are plans to address related concerns through some of the later aspects of\r\n // the Web OSK-Core design.\r\n kbdObj.setSize(800, height); // Probably need something for width, too, rather than\r\n kbdObj.fontSize = defaultFontSize(deviceSpec, height, false);\r\n\r\n // assuming 100%.\r\n kbdObj.refreshLayout(); // Necessary for the row heights to be properly set!\r\n // Relocates the font size definition from the main VisualKeyboard wrapper, since we don't return the whole thing.\r\n kbd.style.fontSize = kbdObj.kbdDiv.style.fontSize;\r\n kbd.style.height = kbdObj.kbdDiv.style.height;\r\n kbd.style.maxHeight = kbdObj.kbdDiv.style.maxHeight;\r\n } else {\r\n kbd.innerHTML = \"

No \" + formFactor + \" layout is defined for \" + PKbd.name + \".

\";\r\n }\r\n // Add a faint border\r\n kbd.style.border = '1px solid #ccc';\r\n\r\n // Once the element is inserted into the DOM, refresh the layout so that proper text scaling may apply.\r\n const detectAndHandleInsertion = () => {\r\n if(document.contains(kbd)) {\r\n // Yay, insertion!\r\n\r\n try {\r\n // Are there font-size attributes we may safely adjust? If so, do that!\r\n if(getComputedStyle(kbd) && kbd.style.fontSize) {\r\n kbdObj.fontSize = new ParsedLengthStyle(kbd.style.fontSize);\r\n }\r\n\r\n // Make sure that the stylesheet is attached, now that the keyboard-doc's been inserted.\r\n // The stylesheet is currently built + constructed in the same code that attaches it to\r\n // the page.\r\n kbdObj.appendStyleSheet();\r\n\r\n // Grab a reference to the stylesheet.\r\n const stylesheet = kbdObj.styleSheet;\r\n const stylesheetParentElement = stylesheet.parentElement;\r\n\r\n // Don't reset top-level stuff; just the visible layer.\r\n // kbdObj.currentLayer.refreshLayout(kbdObj, kbdObj.height);\r\n\r\n // We refresh the full layout so that font-size is properly detected & stored\r\n // on the documentation keyboard.\r\n kbdObj.refreshLayout();\r\n kbd.style.fontSize = kbdObj.kbdDiv.style.fontSize;\r\n\r\n // We no longer need a reference to the constructing VisualKeyboard, so we should let\r\n // it clean up its stylesheet links. This detaches the stylesheet, though.\r\n kbdObj.shutdown();\r\n\r\n // Now that shutdown is done, re-attach the stylesheet - but to the layer group.\r\n kbd.appendChild(stylesheet);\r\n } finally {\r\n insertionObserver.disconnect();\r\n }\r\n }\r\n }\r\n\r\n const insertionObserver = new MutationObserver(detectAndHandleInsertion);\r\n insertionObserver.observe(document.body, {\r\n childList: true,\r\n subtree: true\r\n });\r\n\r\n classWrapper.append(kbd);\r\n return classWrapper;\r\n }\r\n\r\n onHide() {\r\n // Remove highlighting from hide keyboard key, if applied\r\n if (this.hkKey) {\r\n this.highlightKey(this.hkKey, false);\r\n }\r\n }\r\n\r\n /**\r\n * Starts an implementation-specific longpress gesture. Separately implemented for\r\n * in-browser and embedded modes.\r\n * @param key The base key of the longpress.\r\n * @returns\r\n */\r\n startLongpress(key: KeyElement): PendingGesture {\r\n if(this.config.embeddedGestureConfig.startLongpress) {\r\n return this.config.embeddedGestureConfig.startLongpress(this, key);\r\n }\r\n\r\n // First-level object/Promise: will produce a subkey popup when the longpress gesture completes.\r\n // 'Returns' a second-level object/Promise: resolves when a subkey is selected or is cancelled.\r\n let pendingLongpress = new InternalPendingLongpress(this, key);\r\n pendingLongpress.promise.then((subkeyPopup) => {\r\n // In-browser-specific handling.\r\n if (subkeyPopup) {\r\n // Append the touch-hold (subkey) array to the OSK\r\n this.topContainer.appendChild(subkeyPopup.element);\r\n this.topContainer.appendChild(subkeyPopup.shim);\r\n\r\n // Must be placed after its `.element` has been inserted into the DOM.\r\n subkeyPopup.reposition(this);\r\n }\r\n });\r\n\r\n return pendingLongpress;\r\n }\r\n\r\n /**\r\n * Initializes all supported gestures given a base key and the triggering touch coordinates.\r\n * @param key The gesture's base key\r\n * @param touch The starting touch coordinates for the gesture\r\n * @returns\r\n */\r\n initGestures(key: KeyElement, input: InputEventCoordinate) {\r\n\r\n if (this.pendingMultiTap) {\r\n switch (this.pendingMultiTap.incrementTouch(key)) {\r\n case PendingMultiTapState.Cancelled:\r\n this.pendingMultiTap = null;\r\n break;\r\n case PendingMultiTapState.Realized:\r\n // Don't initialize any other gestures if the\r\n // multi tap is realized; we cleanup on touch\r\n // release because we need to cancel the base\r\n // key action\r\n return;\r\n }\r\n }\r\n\r\n if (!this.pendingMultiTap && PendingMultiTap.isValidTarget(this, key)) {\r\n // We are only going to support double-tap on Shift\r\n // in Keyman 15, so we pass in the constant count = 2\r\n this.pendingMultiTap = new PendingMultiTap(this, key, 2);\r\n this.pendingMultiTap.timeout.then(() => {\r\n this.pendingMultiTap = null;\r\n });\r\n }\r\n\r\n\r\n if (key['subKeys']) {\r\n let _this = this;\r\n\r\n let pendingLongpress = this.startLongpress(key);\r\n if (pendingLongpress == null) {\r\n return;\r\n }\r\n this.pendingSubkey = pendingLongpress;\r\n\r\n pendingLongpress.promise.then(function (subkeyPopup) {\r\n if (_this.pendingSubkey == pendingLongpress) {\r\n _this.pendingSubkey = null;\r\n }\r\n\r\n if (subkeyPopup) {\r\n // Clear key preview if any\r\n _this.showKeyTip(null, false);\r\n\r\n _this.subkeyGesture = subkeyPopup;\r\n subkeyPopup.promise.then(function (keyEvent: KeyEvent) {\r\n // Allow active cancellation, even if the source should allow passive.\r\n // It's an easy and cheap null guard.\r\n if (keyEvent) {\r\n _this.raiseKeyEvent(keyEvent, null);\r\n }\r\n _this.clearPopup();\r\n });\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Updates all currently-pending and activated gestures.\r\n *\r\n * @param currentKey The key currently underneath the most recent touch coordinate\r\n * @param previousKey The previously-selected key\r\n * @param input The current mouse or touch coordinate for the gesture\r\n * @returns true if should fully capture input, false if input should 'fall through'.\r\n */\r\n updateGestures(currentKey: KeyElement, previousKey: KeyElement, input: InputEventCoordinate): boolean {\r\n let key0 = previousKey;\r\n let key1 = currentKey;\r\n\r\n if(!currentKey && this.pendingMultiTap) {\r\n this.pendingMultiTap.cancel();\r\n this.pendingMultiTap = null;\r\n }\r\n\r\n // Clear previous key highlighting, allow subkey controller to highlight as appropriate.\r\n if (this.subkeyGesture) {\r\n if (key0) {\r\n key0.key.highlight(false);\r\n }\r\n this.subkeyGesture.updateTouch(input);\r\n\r\n this.keyPending = null;\r\n this.touchPending = null;\r\n\r\n return true;\r\n }\r\n\r\n this.currentTarget = null;\r\n\r\n // If popup is visible, need to move over popup, not over main keyboard\r\n // Could be turned into a browser-longpress specific implementation within browser.PendingLongpress?\r\n if (key1 && key1['subKeys'] != null && this.initTouchCoord) {\r\n if(this.pendingSubkey && this.pendingSubkey instanceof InternalPendingLongpress) {\r\n // Show popup keys immediately if touch moved up towards key array (KMEW-100, Build 353)\r\n if (this.initTouchCoord.y - input.y > this.getLongpressFlickThreshold()) {\r\n this.pendingSubkey.resolve();\r\n }\r\n }\r\n }\r\n\r\n // If there is an active popup menu (which can occur from the previous block),\r\n // a subkey popup exists; do not allow base key output.\r\n if (this.subkeyGesture || this.pendingSubkey) {\r\n return true;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n private getLongpressFlickThreshold(): number {\r\n const rowHeight = this.currentLayer.rowHeight;\r\n\r\n // If larger than 5 (and it likely is), new threshold = 1/4 the std. key height.\r\n const proportionalThreshold = rowHeight / 4;\r\n\r\n // 5 - the longpress-flick triggering threshold before 15.0.\r\n return Math.max(proportionalThreshold, 5);\r\n }\r\n\r\n optionKey(e: KeyElement, keyName: string, keyDown: boolean) {\r\n if (keyName.indexOf('K_LOPT') >= 0) {\r\n this.emit('globekey', e, keyDown);\r\n } else if (keyName.indexOf('K_ROPT') >= 0) {\r\n if (keyDown) {\r\n this.emit('hiderequested', e);\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Add (or remove) the keytip preview (if KeymanWeb on a phone device)\r\n *\r\n * @param {Object} key HTML key element\r\n * @param {boolean} on show or hide\r\n */\r\n showKeyTip(key: KeyElement, on: boolean) {\r\n var tip = this.keytip;\r\n\r\n if (tip == null) {\r\n return;\r\n }\r\n\r\n let sk = this.subkeyGesture;\r\n let popup = (sk && sk.isVisible());\r\n\r\n // If popup keys are active, do not show the key tip.\r\n on = popup ? false : on;\r\n\r\n tip.show(key, on, this);\r\n };\r\n\r\n /**\r\n * Create a key preview element for phone devices\r\n */\r\n createKeyTip() {\r\n if(this.config.embeddedGestureConfig.createKeyTip) {\r\n this.keytip = this.config.embeddedGestureConfig.createKeyTip(this);\r\n } else if (this.device.formFactor == 'phone') {\r\n if (this.keytip == null) {\r\n // For now, should only be true (in production) when keyman.isEmbedded == true.\r\n let constrainPopup = this.isEmbedded;\r\n this.keytip = new InternalKeyTip(constrainPopup);\r\n }\r\n }\r\n\r\n // Always append to _Box (since cleared during OSK Load)\r\n if (this.keytip && this.keytip.element) {\r\n this.topContainer.appendChild(this.keytip.element);\r\n }\r\n };\r\n\r\n createGlobeHint(): GlobeHint {\r\n if(this.config.embeddedGestureConfig.createGlobeHint) {\r\n return this.config.embeddedGestureConfig.createGlobeHint(this);\r\n } else {\r\n return null;\r\n }\r\n }\r\n\r\n shutdown() {\r\n // Prevents style-sheet pollution from multiple keyboard swaps.\r\n if(this.styleSheet && this.styleSheet.parentNode) {\r\n this.styleSheet.parentNode.removeChild(this.styleSheet);\r\n }\r\n\r\n if(this.inputEngine) {\r\n this.inputEngine.unregisterEventHandlers();\r\n }\r\n\r\n if(this.deleting) {\r\n window.clearTimeout(this.deleting);\r\n }\r\n\r\n this.keyPending = null;\r\n this.touchPending = null;\r\n\r\n this.keytip?.show(null, false, this);\r\n this.subkeyGesture?.clear();\r\n this.pendingMultiTap?.cancel();\r\n this.pendingSubkey?.cancel();\r\n }\r\n\r\n raiseKeyEvent(keyEvent: KeyEvent, e: KeyElement) {\r\n // Exclude menu and OSK hide keys from normal click processing\r\n if(keyEvent.kName == 'K_LOPT' || keyEvent.kName == 'K_ROPT') {\r\n this.optionKey(e, keyEvent.kName, true);\r\n return true;\r\n }\r\n\r\n this.emit('keyevent', keyEvent);\r\n }\r\n}\r\n", + "import { DeviceSpec } from \"@keymanapp/web-utils\";\r\nimport { ParsedLengthStyle } from \"./lengthStyle.js\";\r\n\r\nexport function getFontSizeStyle(e: HTMLElement|string): {val: number, absolute: boolean} {\r\n var fs: string;\r\n\r\n if(typeof e == 'string') {\r\n fs = e;\r\n } else {\r\n fs = e.style.fontSize;\r\n if(!fs) {\r\n fs = getComputedStyle(e).fontSize;\r\n }\r\n }\r\n\r\n return new ParsedLengthStyle(fs);\r\n}\r\n\r\nexport function defaultFontSize(device: DeviceSpec, computedHeight: number, isEmbedded: boolean): ParsedLengthStyle {\r\n if(device.touchable) {\r\n const fontScale = device.formFactor == 'phone'\r\n ? 1.6 * (isEmbedded ? 0.65 : 0.6) * 1.2 // Combines original scaling factor with one previously applied to the layer group.\r\n : 2; // iPad or Android tablet\r\n return ParsedLengthStyle.special(fontScale, 'em');\r\n } else {\r\n return computedHeight ? ParsedLengthStyle.inPixels(computedHeight / 8) : undefined;\r\n }\r\n}", + "// Defines the PUA code mapping for the various 'special' modifier/control keys on keyboards.\r\n// `specialCharacters` must be kept in sync with the same variable in builder.js. See also CompileKeymanWeb.pas: CSpecialText10\r\nlet specialCharacters = {\r\n '*Shift*': 8,\r\n '*Enter*': 5,\r\n '*Tab*': 6,\r\n '*BkSp*': 4,\r\n '*Menu*': 11,\r\n '*Hide*': 10,\r\n '*Alt*': 25,\r\n '*Ctrl*': 1,\r\n '*Caps*': 3,\r\n '*ABC*': 16,\r\n '*abc*': 17,\r\n '*123*': 19,\r\n '*Symbol*': 21,\r\n '*Currency*': 20,\r\n '*Shifted*': 9,\r\n '*AltGr*': 2,\r\n '*TabLeft*': 7,\r\n '*LAlt*': 0x56,\r\n '*RAlt*': 0x57,\r\n '*LCtrl*': 0x58,\r\n '*RCtrl*': 0x59,\r\n '*LAltCtrl*': 0x60,\r\n '*RAltCtrl*': 0x61,\r\n '*LAltCtrlShift*': 0x62,\r\n '*RAltCtrlShift*': 0x63,\r\n '*AltShift*': 0x64,\r\n '*CtrlShift*': 0x65,\r\n '*AltCtrlShift*': 0x66,\r\n '*LAltShift*': 0x67,\r\n '*RAltShift*': 0x68,\r\n '*LCtrlShift*': 0x69,\r\n '*RCtrlShift*': 0x70,\r\n // Added in Keyman 14.0.\r\n '*LTREnter*': 0x05, // Default alias of '*Enter*'.\r\n '*LTRBkSp*': 0x04, // Default alias of '*BkSp*'.\r\n '*RTLEnter*': 0x71,\r\n '*RTLBkSp*': 0x72,\r\n '*ShiftLock*': 0x73,\r\n '*ShiftedLock*': 0x74,\r\n '*ZWNJ*': 0x75, // If this one is specified, auto-detection will kick in.\r\n '*ZWNJiOS*': 0x75, // The iOS version will be used by default, but the\r\n '*ZWNJAndroid*': 0x76, // Android platform has its own default glyph.\r\n};\r\n\r\nexport default specialCharacters;", + "\r\n/**\r\n * Maps 'sp' properties on a touch-layout spec to their corresponding CSS class names.\r\n */\r\nlet BUTTON_CLASSES = [\r\n 'default',\r\n 'shift',\r\n 'shift-on',\r\n 'special',\r\n 'special-on',\r\n '', // Key classes 5 through 7 are reserved for future use.\r\n '',\r\n '',\r\n 'deadkey',\r\n 'blank',\r\n 'hidden'\r\n];\r\n\r\nexport default BUTTON_CLASSES;", + "import { ActiveKey, ButtonClass, DeviceSpec, LayoutKey } from '@keymanapp/keyboard-processor';\r\n// At present, we don't use @keymanapp/keyman. Just `keyman`. (Refer to /web/package.json.)\r\nimport { getAbsoluteX, getAbsoluteY } from 'keyman/engine/dom-utils';\r\n\r\nimport { getFontSizeStyle } from '../fontSizeUtils.js';\r\nimport InputEventCoordinate from '../input/inputEventCoordinate.js';\r\nimport specialChars from '../specialCharacters.js';\r\nimport buttonClassNames from '../buttonClassNames.js';\r\n\r\nimport { KeyElement } from '../keyElement.js';\r\nimport VisualKeyboard from '../visualKeyboard.js';\r\n\r\nexport class OSKKeySpec implements LayoutKey {\r\n id: string;\r\n\r\n // Only set (within @keymanapp/keyboard-processor) for keys actually specified in a loaded layout\r\n baseKeyID?: string;\r\n coreID?: string;\r\n elementID?: string;\r\n\r\n text?: string;\r\n sp?: ButtonClass;\r\n width: number;\r\n layer?: string; // The key will derive its base modifiers from this property - may not equal the layer on which it is displayed.\r\n nextlayer?: string;\r\n pad?: number;\r\n sk?: OSKKeySpec[];\r\n\r\n constructor(id: string, text?: string, width?: number, sp?: ButtonClass, nextlayer?: string, pad?: number) {\r\n this.id = id;\r\n this.text = text;\r\n this.width = width ? width : 50;\r\n this.sp = sp;\r\n this.nextlayer = nextlayer;\r\n this.pad = pad;\r\n }\r\n}\r\n\r\nexport default abstract class OSKKey {\r\n // Only set here to act as an alias for code built against legacy versions.\r\n static readonly specialCharacters = specialChars;\r\n\r\n static readonly BUTTON_CLASSES = buttonClassNames;\r\n\r\n static readonly HIGHLIGHT_CLASS = 'kmw-key-touched';\r\n readonly spec: OSKKeySpec;\r\n\r\n btn: KeyElement;\r\n label: HTMLSpanElement;\r\n square: HTMLDivElement;\r\n\r\n /**\r\n * The layer of the OSK on which the key is displayed.\r\n */\r\n readonly layer: string;\r\n\r\n constructor(spec: OSKKeySpec, layer: string) {\r\n this.spec = spec;\r\n this.layer = layer;\r\n }\r\n\r\n abstract getId(): string;\r\n\r\n /**\r\n * Attach appropriate class to each key button, according to the layout\r\n *\r\n * @param {Object=} layout source layout description (optional, sometimes)\r\n */\r\n public setButtonClass() {\r\n let key = this.spec;\r\n let btn = this.btn;\r\n\r\n var n=0;\r\n if(typeof key['dk'] == 'string' && key['dk'] == '1') {\r\n n=8;\r\n }\r\n\r\n n = key['sp'] ?? n;\r\n\r\n if(n < 0 || n > 10) {\r\n n=0;\r\n }\r\n\r\n btn.className='kmw-key kmw-key-'+ buttonClassNames[n];\r\n }\r\n\r\n /**\r\n * For keys with button classes that support toggle states, this method\r\n * may be used to toggle which state the key's button class is in.\r\n * - shift <=> shift-on\r\n * - special <=> special-on\r\n * @param {boolean=} flag The new toggle state\r\n */\r\n public setToggleState(flag?: boolean) {\r\n let btnClassId: number;\r\n\r\n btnClassId = this.spec['sp'];\r\n\r\n // 1 + 2: shift + shift-on\r\n // 3 + 4: special + special-on\r\n switch(buttonClassNames[btnClassId]) {\r\n case 'shift':\r\n case 'shift-on':\r\n if(flag === undefined) {\r\n flag = buttonClassNames[btnClassId] == 'shift';\r\n }\r\n\r\n this.spec['sp'] = 1 + (flag ? 1 : 0) as ButtonClass;\r\n break;\r\n // Added in 15.0: special key highlight toggling.\r\n // Was _intended_ in earlier versions, but not actually implemented.\r\n case 'special':\r\n case 'special-on':\r\n if(flag === undefined) {\r\n flag = buttonClassNames[btnClassId] == 'special';\r\n }\r\n\r\n this.spec['sp'] = 3 + (flag ? 1 : 0) as ButtonClass;\r\n break;\r\n default:\r\n return;\r\n }\r\n\r\n this.setButtonClass();\r\n }\r\n\r\n // \"Frame key\" - generally refers to non-linguistic keys on the keyboard\r\n public isFrameKey(): boolean {\r\n let classIndex = this.spec['sp'] || 0;\r\n switch(buttonClassNames[classIndex]) {\r\n case 'default':\r\n case 'deadkey':\r\n // Note: will (generally) include the spacebar.\r\n return false;\r\n default:\r\n return true;\r\n }\r\n }\r\n\r\n public allowsKeyTip(): boolean {\r\n if(this.isFrameKey()) {\r\n return false;\r\n } else {\r\n return !this.btn.classList.contains('kmw-spacebar');\r\n }\r\n }\r\n\r\n public highlight(on: boolean) {\r\n var classes=this.btn.classList;\r\n\r\n if(on) {\r\n if(!classes.contains(OSKKey.HIGHLIGHT_CLASS)) {\r\n classes.add(OSKKey.HIGHLIGHT_CLASS);\r\n }\r\n } else {\r\n classes.remove(OSKKey.HIGHLIGHT_CLASS);\r\n }\r\n }\r\n\r\n /**\r\n * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.\r\n *\r\n * @param {String} text The text to be rendered.\r\n * @param {String} style The CSSStyleDeclaration for an element to measure against, without modification.\r\n *\r\n * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393\r\n * This version has been substantially modified to work for this particular application.\r\n */\r\n static getTextMetrics(text: string, emScale: number, style: {fontFamily?: string, fontSize: string}): TextMetrics {\r\n // Since we may mutate the incoming style, let's make sure to copy it first.\r\n // Only the relevant properties, though.\r\n style = {\r\n fontFamily: style.fontFamily,\r\n fontSize: style.fontSize\r\n };\r\n\r\n // A final fallback - having the right font selected makes a world of difference.\r\n if(!style.fontFamily) {\r\n style.fontFamily = getComputedStyle(document.body).fontFamily;\r\n }\r\n\r\n if(!style.fontSize || style.fontSize == \"\") {\r\n style.fontSize = '1em';\r\n }\r\n\r\n let fontFamily = style.fontFamily;\r\n let fontSpec = getFontSizeStyle(style.fontSize);\r\n\r\n var fontSize: string;\r\n if(fontSpec.absolute) {\r\n // We've already got an exact size - use it!\r\n fontSize = fontSpec.val + 'px';\r\n } else {\r\n fontSize = fontSpec.val * emScale + 'px';\r\n }\r\n\r\n // re-use canvas object for better performance\r\n var canvas: HTMLCanvasElement = OSKKey.getTextMetrics['canvas'] ||\r\n (OSKKey.getTextMetrics['canvas'] = document.createElement(\"canvas\"));\r\n var context = canvas.getContext(\"2d\");\r\n context.font = fontSize + \" \" + fontFamily;\r\n var metrics = context.measureText(text);\r\n\r\n return metrics;\r\n }\r\n\r\n /**\r\n * Calculate the font size required for a key cap, scaling to fit longer text\r\n * @param vkbd\r\n * @param style specification for the desired base font size\r\n * @param override if true, don't use the font spec from the button, just use the passed in spec\r\n * @returns font size as a style string\r\n */\r\n getIdealFontSize(vkbd: VisualKeyboard, text: string, style: {height?: string, fontFamily?: string, fontSize: string}, override?: boolean): string {\r\n let buttonStyle = getComputedStyle(this.btn);\r\n let keyWidth = parseFloat(buttonStyle.width);\r\n let emScale = 1;\r\n\r\n const originalSize = getFontSizeStyle(style.fontSize || '1em');\r\n\r\n // Not yet available; it'll be handled in a later layout pass.\r\n if(!buttonStyle.fontSize) {\r\n // NOTE: preserves old behavior for use in documentation keyboards, for now.\r\n // Once we no longer need to maintain this code block, we can drop all current\r\n // method parameters safely.\r\n //\r\n // Recompute the new width for use in autoscaling calculations below, just in case.\r\n emScale = vkbd.getKeyEmFontSize();\r\n keyWidth = this.getKeyWidth(vkbd);\r\n } else if(!override) {\r\n // When available, just use computedStyle instead.\r\n style = buttonStyle;\r\n }\r\n\r\n let fontSpec = getFontSizeStyle(style.fontSize || '1em');\r\n let metrics = OSKKey.getTextMetrics(text, emScale, style);\r\n\r\n const MAX_X_PROPORTION = 0.90;\r\n const MAX_Y_PROPORTION = 0.90;\r\n const X_PADDING = 2;\r\n const Y_PADDING = 2;\r\n\r\n var fontHeight: number, keyHeight: number;\r\n if(metrics.fontBoundingBoxAscent) {\r\n fontHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;\r\n }\r\n\r\n let textHeight = fontHeight ? fontHeight + Y_PADDING : 0;\r\n if(style.height && style.height.indexOf('px') != -1) {\r\n keyHeight = Number.parseFloat(style.height.substring(0, style.height.indexOf('px')));\r\n }\r\n\r\n let xProportion = (keyWidth * MAX_X_PROPORTION) / (metrics.width + X_PADDING); // How much of the key does the text want to take?\r\n let yProportion = textHeight && keyHeight ? (keyHeight * MAX_Y_PROPORTION) / textHeight : undefined;\r\n\r\n var proportion: number = xProportion;\r\n if(yProportion && yProportion < xProportion) {\r\n proportion = yProportion;\r\n }\r\n\r\n // Never upscale keys past the default - only downscale them.\r\n // Proportion < 1: ratio of key width to (padded [loosely speaking]) text width\r\n // maxProportion determines the 'padding' involved.\r\n //\r\n if(proportion < 1) {\r\n if(originalSize.absolute) {\r\n return proportion * fontSpec.val + 'px';\r\n } else {\r\n return proportion * originalSize.val + 'em';\r\n }\r\n } else {\r\n if(originalSize.absolute) {\r\n return fontSpec.val + 'px';\r\n } else {\r\n return originalSize.val + 'em';\r\n }\r\n }\r\n }\r\n\r\n getKeyWidth(vkbd: VisualKeyboard): number {\r\n let key = this.spec as ActiveKey;\r\n return key.proportionalWidth * vkbd.width;\r\n }\r\n\r\n /**\r\n * Replace default key names by special font codes for modifier keys\r\n *\r\n * @param {string} oldText\r\n * @return {string}\r\n **/\r\n protected renameSpecialKey(oldText: string, vkbd: VisualKeyboard): string {\r\n // If a 'special key' mapping exists for the text, replace it with its corresponding special OSK character.\r\n switch(oldText) {\r\n case '*ZWNJ*':\r\n // Default ZWNJ symbol comes from iOS. We'd rather match the system defaults where\r\n // possible / available though, and there's a different standard symbol on Android.\r\n oldText = vkbd.device.OS == DeviceSpec.OperatingSystem.Android ?\r\n '*ZWNJAndroid*' :\r\n '*ZWNJiOS*';\r\n break;\r\n case '*Enter*':\r\n oldText = vkbd.isRTL ? '*RTLEnter*' : '*LTREnter*';\r\n break;\r\n case '*BkSp*':\r\n oldText = vkbd.isRTL ? '*RTLBkSp*' : '*LTRBkSp*';\r\n break;\r\n default:\r\n // do nothing.\r\n }\r\n\r\n let specialCodePUA = 0XE000 + specialChars[oldText];\r\n\r\n return specialChars[oldText] ?\r\n String.fromCharCode(specialCodePUA) :\r\n oldText;\r\n }\r\n\r\n public get keyText(): string {\r\n const spec = this.spec;\r\n const DEFAULT_BLANK = '\\xa0';\r\n\r\n // Add OSK key labels\r\n let keyText = null;\r\n if(spec['text'] == null || spec['text'] == '') {\r\n if(typeof spec['id'] == 'string') {\r\n // If the ID's Unicode-based, just use that code.\r\n keyText = ActiveKey.unicodeIDToText(spec['id']);\r\n }\r\n\r\n keyText = keyText || DEFAULT_BLANK;\r\n } else {\r\n keyText=spec['text'];\r\n\r\n // Unique layer-based transformation: SHIFT-TAB uses a different glyph.\r\n if(keyText == '*Tab*' && this.layer == 'shift') {\r\n keyText = '*TabLeft*';\r\n }\r\n }\r\n\r\n return keyText;\r\n }\r\n\r\n // Produces a HTMLSpanElement with the key's actual text.\r\n protected generateKeyText(vkbd: VisualKeyboard): HTMLSpanElement {\r\n const spec = this.spec;\r\n\r\n let t = document.createElement('span'), ts=t.style;\r\n t.className='kmw-key-text';\r\n\r\n // Add OSK key labels\r\n let keyText = this.keyText;\r\n let specialText = this.renameSpecialKey(keyText, vkbd);\r\n if(specialText != keyText) {\r\n // The keyboard wants to use the code for a special glyph defined by the SpecialOSK font.\r\n keyText = specialText;\r\n spec['font'] = \"SpecialOSK\";\r\n }\r\n\r\n //Override font spec if set for this key in the layout\r\n if(typeof spec['font'] == 'string' && spec['font'] != '') {\r\n ts.fontFamily=spec['font'];\r\n }\r\n\r\n if(typeof spec['fontsize'] == 'string' && spec['fontsize'] != '') {\r\n ts.fontSize=spec['fontsize'];\r\n }\r\n\r\n // For some reason, fonts will sometimes 'bug out' for the embedded iOS page if we\r\n // instead assign fontFamily to the existing style 'ts'. (Occurs in iOS 12.)\r\n let styleSpec: {fontFamily?: string, fontSize: string} = {fontSize: ts.fontSize};\r\n\r\n if(ts.fontFamily) {\r\n styleSpec.fontFamily = ts.fontFamily;\r\n } else {\r\n styleSpec.fontFamily = vkbd.fontFamily; // Helps with style sheet calculations.\r\n }\r\n\r\n // Check the key's display width - does the key visualize well?\r\n let emScale = vkbd.getKeyEmFontSize();\r\n var width: number = OSKKey.getTextMetrics(keyText, emScale, styleSpec).width;\r\n if(width == 0 && keyText != '' && keyText != '\\xa0') {\r\n // Add the Unicode 'empty circle' as a base support for needy diacritics.\r\n\r\n // Disabled by mcdurdin 2020-10-19; dotted circle display is inconsistent on iOS/Safari\r\n // at least and doesn't combine with diacritic marks. For consistent display, it may be\r\n // necessary to build a custom font that does not depend on renderer choices for base\r\n // mark display -- e.g. create marks with custom base included, potentially even on PUA\r\n // code points and use those in rendering the OSK. See #3039 for more details.\r\n // keyText = '\\u25cc' + keyText;\r\n\r\n if(vkbd.isRTL) {\r\n // Add the RTL marker to ensure it displays properly.\r\n keyText = '\\u200f' + keyText;\r\n }\r\n }\r\n\r\n ts.fontSize = this.getIdealFontSize(vkbd, keyText, styleSpec);\r\n\r\n // Finalize the key's text.\r\n t.innerText = keyText;\r\n\r\n return t;\r\n }\r\n\r\n public isUnderTouch(input: InputEventCoordinate): boolean {\r\n let x = input.x;\r\n let y = input.y;\r\n\r\n let btn = this.btn;\r\n let x0 = getAbsoluteX(btn);\r\n let y0 = getAbsoluteY(btn);\r\n let x1 = x0 + btn.offsetWidth;\r\n let y1 = y0 + btn.offsetHeight;\r\n\r\n return (x > x0 && x < x1 && y > y0 && y < y1);\r\n }\r\n\r\n public refreshLayout(vkbd: VisualKeyboard) {\r\n // space bar may not define the text span!\r\n if(this.label) {\r\n if(!this.label.classList.contains('kmw-spacebar-caption')) {\r\n this.label.style.fontSize = this.getIdealFontSize(vkbd, this.keyText, this.btn.style);\r\n } else {\r\n // Remove any custom setting placed on it before recomputing its inherited style info.\r\n this.label.style.fontSize = '';\r\n const fontSize = this.getIdealFontSize(vkbd, this.label.textContent, getComputedStyle(this.label), true);\r\n\r\n // Since the kmw-spacebar-caption version uses !important, we must specify\r\n // it directly on the element too; otherwise, scaling gets ignored.\r\n this.label.style.setProperty(\"font-size\", fontSize, \"important\");\r\n }\r\n }\r\n }\r\n}\r\n", + "import OSKKey, { OSKKeySpec } from \"./keyboard-layout/oskKey.js\";\r\n\r\nexport class KeyData {\r\n ['key']: OSKKey;\r\n ['keyId']: string;\r\n ['subKeys']?: OSKKeySpec[];\r\n\r\n constructor(keyData: OSKKey, keyId: string) {\r\n this['key'] = keyData;\r\n this['keyId'] = keyId;\r\n }\r\n}\r\n\r\nexport type KeyElement = HTMLDivElement & KeyData;\r\n\r\n// Many thanks to https://www.typescriptlang.org/docs/handbook/advanced-types.html for this.\r\nexport function link(elem: HTMLDivElement, data: KeyData): KeyElement {\r\n let e = elem;\r\n\r\n // Merges all properties and methods of KeyData onto the underlying HTMLDivElement, creating a merged class.\r\n for(let id in data) {\r\n if(!e.hasOwnProperty(id)) {\r\n (e)[id] = (data)[id];\r\n }\r\n }\r\n\r\n return e;\r\n}\r\n\r\nexport function isKey(elem: Node): boolean {\r\n return elem && ('key' in elem) && (( elem['key']) instanceof OSKKey);\r\n}\r\n\r\nexport function getKeyFrom(elem: Node): KeyElement {\r\n if(isKey(elem)) {\r\n return elem;\r\n } else {\r\n return null;\r\n }\r\n}", + "import { ActiveKey, Codes, DeviceSpec } from '@keymanapp/keyboard-processor';\r\nimport { landscapeView } from 'keyman/engine/dom-utils';\r\n\r\nimport OSKKey, { OSKKeySpec } from './oskKey.js';\r\nimport { KeyData, KeyElement, link } from '../keyElement.js';\r\nimport OSKRow from './oskRow.js';\r\nimport VisualKeyboard from '../visualKeyboard.js';\r\n\r\n\r\nexport default class OSKBaseKey extends OSKKey {\r\n private capLabel: HTMLDivElement;\r\n public readonly row: OSKRow;\r\n\r\n constructor(spec: OSKKeySpec, layer: string, row: OSKRow) {\r\n super(spec, layer);\r\n this.row = row;\r\n }\r\n\r\n getId(): string {\r\n // Define each key element id by layer id and key id (duplicate possible for SHIFT - does it matter?)\r\n return this.spec.elementID;\r\n }\r\n\r\n getCoreId(): string {\r\n return this.spec.coreID;\r\n }\r\n\r\n getBaseId(): string {\r\n return this.spec.baseKeyID;\r\n }\r\n\r\n // Produces a small reference label for the corresponding physical key on a US keyboard.\r\n private generateKeyCapLabel(): HTMLDivElement {\r\n // Create the default key cap labels (letter keys, etc.)\r\n var x = Codes.keyCodes[this.spec.baseKeyID];\r\n switch(x) {\r\n // Converts the keyman key id code for common symbol keys into its representative ASCII code.\r\n // K_COLON -> K_BKQUOTE\r\n case 186: x=59; break;\r\n case 187: x=61; break;\r\n case 188: x=44; break;\r\n case 189: x=45; break;\r\n case 190: x=46; break;\r\n case 191: x=47; break;\r\n case 192: x=96; break;\r\n // K_LBRKT -> K_QUOTE\r\n case 219: x=91; break;\r\n case 220: x=92; break;\r\n case 221: x=93; break;\r\n case 222: x=39; break;\r\n default:\r\n // No other symbol character represents a base key on the standard QWERTY English layout.\r\n if(x < 48 || x > 90) {\r\n x=0;\r\n }\r\n }\r\n\r\n let q = document.createElement('div');\r\n q.className='kmw-key-label';\r\n if(x > 0) {\r\n q.innerText=String.fromCharCode(x);\r\n } else {\r\n // Keyman-only virtual keys have no corresponding physical key.\r\n // So, no text for the key-cap.\r\n }\r\n return q;\r\n }\r\n\r\n private processSubkeys(btn: KeyElement, vkbd: VisualKeyboard) {\r\n // Add reference to subkey array if defined\r\n var bsn: number, bsk=btn['subKeys'] = this.spec['sk'];\r\n // Transform any special keys into their PUA representations.\r\n for(bsn=0; bsn 0) {\r\n return this.keys[0].displaysKeyCap;\r\n } else {\r\n return undefined;\r\n }\r\n }\r\n\r\n public set displaysKeyCaps(flag: boolean) {\r\n for(const key of this.keys) {\r\n key.displaysKeyCap = flag;\r\n }\r\n }\r\n\r\n public refreshLayout(vkbd: VisualKeyboard) {\r\n const rs = this.element.style;\r\n\r\n const rowHeight = vkbd.internalHeight.scaledBy(this.heightFraction);\r\n rs.maxHeight=rs.lineHeight=rs.height=rowHeight.styleString;\r\n\r\n // Only used for fixed-height scales at present.\r\n const padRatio = 0.15;\r\n\r\n const keyHeightBase = vkbd.usesFixedHeightScaling ? rowHeight : ParsedLengthStyle.forScalar(1);\r\n const padTop = keyHeightBase.scaledBy(padRatio / 2);\r\n const keyHeight = keyHeightBase.scaledBy(1 - padRatio);\r\n\r\n for(const key of this.keys) {\r\n const keySquare = key.btn.parentElement;\r\n const keyElement = key.btn;\r\n\r\n // Set the kmw-key-square position\r\n const kss = keySquare.style;\r\n kss.height=kss.minHeight=keyHeightBase.styleString;\r\n\r\n const kes = keyElement.style;\r\n kes.top = padTop.styleString;\r\n kes.height=kes.lineHeight=kes.minHeight=keyHeight.styleString;\r\n\r\n if(keyElement.key) {\r\n keyElement.key.refreshLayout(vkbd);\r\n }\r\n }\r\n }\r\n}", + "import { ActiveLayer, ActiveLayout } from '@keymanapp/keyboard-processor';\r\n\r\nimport OSKRow from './oskRow.js';\r\nimport OSKBaseKey from './oskBaseKey.js';\r\nimport VisualKeyboard from '../visualKeyboard.js';\r\n\r\nexport default class OSKLayer {\r\n public readonly element: HTMLDivElement;\r\n public readonly rows: OSKRow[];\r\n public readonly spec: ActiveLayer;\r\n public readonly nextlayer: string;\r\n\r\n public readonly globeKey: OSKBaseKey;\r\n public readonly spaceBarKey: OSKBaseKey;\r\n public readonly hideKey: OSKBaseKey;\r\n public readonly capsKey: OSKBaseKey;\r\n public readonly numKey: OSKBaseKey;\r\n public readonly scrollKey: OSKBaseKey;\r\n\r\n private _rowHeight: number;\r\n\r\n public get rowHeight(): number {\r\n return this._rowHeight;\r\n }\r\n\r\n public get id(): string {\r\n return this.spec.id;\r\n }\r\n\r\n public constructor(vkbd: VisualKeyboard,\r\n layout: ActiveLayout,\r\n layer: ActiveLayer) {\r\n this.spec = layer;\r\n\r\n const gDiv = this.element = document.createElement('div');\r\n const gs=gDiv.style;\r\n gDiv.className='kmw-key-layer';\r\n\r\n var nRows=layer['row'].length;\r\n if(nRows > 4 && vkbd.device.formFactor == 'phone') {\r\n gDiv.className = gDiv.className + ' kmw-5rows';\r\n }\r\n\r\n // Set font for layer if defined in layout\r\n gs.fontFamily = 'font' in layout ? layout['font'] : '';\r\n\r\n this.nextlayer = gDiv['layer'] = layer['id'];\r\n if(typeof layer['nextlayer'] == 'string') {\r\n // The gDiv['nextLayer'] is no longer referenced in KMW 15.0+, but is\r\n // maintained for partial back-compat in case any site devs actually\r\n // relied on its value from prior versions.\r\n //\r\n // We won't pay attention to any mutations to the gDiv copy, though.\r\n gDiv['nextLayer'] = this.nextlayer = layer['nextlayer'];\r\n }\r\n\r\n // Create a DIV for each row of the group\r\n let rows=layer['row'];\r\n this.rows = [];\r\n\r\n for(let i=0; i;\r\n private cancelDelayFactor = 125; // 125msec * count\r\n private _destinationLayerId;\r\n\r\n public get timeout() {\r\n return this._timeout;\r\n }\r\n public get realized() {\r\n return this._state == PendingMultiTapState.Realized;\r\n }\r\n public get cancelled() {\r\n return this._state == PendingMultiTapState.Cancelled;\r\n }\r\n\r\n /**\r\n * Construct a record of a potential multitap gesture\r\n * @param vkbd\r\n * @param baseKey key which is being tapped\r\n * @param count number of taps required to finalize this gesture\r\n */\r\n constructor(vkbd: VisualKeyboard, baseKey: KeyElement, count: number) {\r\n this.vkbd = vkbd;\r\n this.count = count;\r\n this.baseKey = baseKey;\r\n\r\n this._destinationLayerId = 'caps';\r\n let multitap = baseKey?.key?.spec?.['multitap'];\r\n if(multitap?.length && multitap[0]?.['nextlayer']) {\r\n this._destinationLayerId = multitap[0]['nextlayer'];\r\n }\r\n\r\n const _this = this;\r\n this._timeout = new Promise(function(resolve) {\r\n // If multiple taps do not occur within the timeout window,\r\n // then we will abandon the gesture\r\n _this.timerId = window.setTimeout(() => {\r\n _this.cancel();\r\n resolve();\r\n }, _this.cancelDelayFactor * _this.count);\r\n });\r\n }\r\n\r\n public static isValidTarget(vkbd: VisualKeyboard, baseKey: KeyElement) {\r\n // Could use String.includes, but Chrome for Android must be version 41+.\r\n // We support down to version 37.\r\n return (\r\n baseKey['keyId'].indexOf('K_SHIFT') >= 0 &&\r\n vkbd.layerGroup.layers['caps'] &&\r\n !baseKey['subKeys'] &&\r\n vkbd.touchCount == 1\r\n );\r\n }\r\n\r\n private cleanup(): void {\r\n if(this.timerId) {\r\n window.clearTimeout(this.timerId);\r\n }\r\n this.timerId = null;\r\n }\r\n\r\n /**\r\n * Cancel a pending multitap gesture\r\n */\r\n public cancel(): void {\r\n this._state = PendingMultiTapState.Cancelled;\r\n this.cleanup();\r\n }\r\n\r\n /**\r\n * Increments the touch counter for the gesture, and\r\n * if the touch count is reached, realize the gesture\r\n * @returns new state of the gesture\r\n */\r\n public incrementTouch(newKey: KeyElement): PendingMultiTapState {\r\n // TODO: support for any key\r\n if(this._state == PendingMultiTapState.Waiting) {\r\n if(!newKey?.['keyId']?.includes('K_SHIFT')) {\r\n this.cancel();\r\n }\r\n else if(++this._touches == this.count) {\r\n this.realize();\r\n }\r\n }\r\n return this._state;\r\n }\r\n\r\n /**\r\n * Realize the gesture. In Keyman 15, this supports only\r\n * the Caps double-tap gesture on the Shift key.\r\n */\r\n public realize(): void {\r\n if(this._state != PendingMultiTapState.Waiting) {\r\n return;\r\n }\r\n this._state = PendingMultiTapState.Realized;\r\n this.cleanup();\r\n\r\n // In Keyman 15, only the K_SHIFT key supports multi-tap, so we can hack\r\n // in the switch to the caps layer.\r\n //\r\n // TODO: generalize this with double-tap key properties in touch layout\r\n // description.\r\n let e = KeyEvent.constructNullKeyEvent(this.vkbd.device);\r\n e.kNextLayer = this._destinationLayerId;\r\n e.Lstates = Codes.stateBitmasks.CAPS;\r\n e.LmodifierChange = true;\r\n this.vkbd.raiseKeyEvent(e, null);\r\n }\r\n}", + "import OSKKey, { OSKKeySpec } from '../../../keyboard-layout/oskKey.js';\r\nimport { KeyData, KeyElement, link } from '../../../keyElement.js';\r\nimport VisualKeyboard from '../../../visualKeyboard.js';\r\n\r\nexport default class OSKSubKey extends OSKKey {\r\n constructor(spec: OSKKeySpec, layer: string) {\r\n if(typeof(layer) != 'string' || layer == '') {\r\n throw \"The 'layer' parameter for subkey construction must be properly defined.\";\r\n }\r\n\r\n super(spec, layer);\r\n }\r\n\r\n getId(): string {\r\n // Create (temporarily) unique ID by prefixing 'popup-' to actual key ID\r\n return 'popup-'+this.layer+'-'+this.spec['id'];\r\n }\r\n\r\n construct(osk: VisualKeyboard, baseKey: KeyElement, topMargin: boolean): HTMLDivElement {\r\n let spec = this.spec;\r\n\r\n let kDiv=document.createElement('div');\r\n let tKey = osk.getDefaultKeyObject();\r\n let ks=kDiv.style;\r\n\r\n for(var tp in tKey) {\r\n if(typeof spec[tp] != 'string') {\r\n spec[tp]=tKey[tp];\r\n }\r\n }\r\n\r\n kDiv.className='kmw-key-square-ex';\r\n if(topMargin) {\r\n ks.marginTop='5px';\r\n }\r\n\r\n if(typeof spec['width'] != 'undefined') {\r\n ks.width=(spec['width']*baseKey.offsetWidth/100)+'px';\r\n } else {\r\n ks.width=baseKey.offsetWidth+'px';\r\n }\r\n ks.height=baseKey.offsetHeight+'px';\r\n\r\n let btnEle=document.createElement('div');\r\n let btn = this.btn = link(btnEle, new KeyData(this, spec['id']));\r\n\r\n this.setButtonClass();\r\n btn.id = this.getId();\r\n\r\n // Must set button size (in px) dynamically, not from CSS\r\n let bs=btn.style;\r\n bs.height=ks.height;\r\n bs.lineHeight=baseKey.style.lineHeight;\r\n bs.width=ks.width;\r\n\r\n // Must set position explicitly, at least for Android\r\n bs.position='absolute';\r\n\r\n btn.appendChild(this.label = this.generateKeyText(osk));\r\n kDiv.appendChild(btn);\r\n\r\n return this.square = kDiv;\r\n }\r\n\r\n public allowsKeyTip(): boolean {\r\n return false;\r\n }\r\n}", + "import OSKSubKey from './oskSubKey.js';\r\nimport RealizedGesture from '../realizedGesture.interface.js';\r\nimport { OSKKeySpec } from '../../../keyboard-layout/oskKey.js';\r\nimport { type KeyElement } from '../../../keyElement.js';\r\nimport OSKBaseKey from '../../../keyboard-layout/oskBaseKey.js';\r\nimport VisualKeyboard from '../../../visualKeyboard.js';\r\nimport InputEventCoordinate from '../../../input/inputEventCoordinate.js';\r\n\r\nimport { DeviceSpec, KeyEvent } from '@keymanapp/keyboard-processor';\r\n\r\n/**\r\n * Represents a 'realized' longpress gesture's default implementation\r\n * within KeymanWeb. Once a touch sequence has been confirmed to\r\n * correspond to a longpress gesture, implementations of this class\r\n * provide the following:\r\n * * The UI needed to present a subkey menu\r\n * * The state management needed to present feedback about the\r\n * currently-selected subkey to the user\r\n * * A `Promise` that will resolve to the user's selected subkey\r\n * once the longpress operation is complete.\r\n *\r\n * As selection of the subkey occurs after the subkey popup is\r\n * displayed, selection of the subkey is inherently asynchronous.\r\n * The `Promise` may also resolve to `null` if the user indicates\r\n * the desire to cancel subkey selection.\r\n */\r\nexport default class SubkeyPopup implements RealizedGesture {\r\n public readonly element: HTMLDivElement;\r\n public readonly shim: HTMLDivElement;\r\n\r\n private vkbd: VisualKeyboard;\r\n private currentSelection: KeyElement;\r\n\r\n private callout: HTMLDivElement;\r\n\r\n public readonly baseKey: KeyElement;\r\n public readonly promise: Promise;\r\n\r\n // Resolves the promise that generated this SubkeyPopup.\r\n private resolver: (keyEvent: KeyEvent) => void;\r\n\r\n constructor(vkbd: VisualKeyboard, e: KeyElement) {\r\n let _this = this;\r\n\r\n this.promise = new Promise(function(resolve) {\r\n _this.resolver = resolve;\r\n })\r\n\r\n this.vkbd = vkbd;\r\n this.baseKey = e;\r\n\r\n // If the user doesn't move their finger and releases, we'll output the base key\r\n // by default.\r\n this.currentSelection = e;\r\n e.key.highlight(true);\r\n\r\n // A tag we directly set on a key element during its construction.\r\n let subKeySpec: OSKKeySpec[] = e['subKeys'];\r\n\r\n // The holder is position:fixed, but the keys do not need to be, as no scrolling\r\n // is possible while the array is visible. So it is simplest to let the keys have\r\n // position:static and display:inline-block\r\n var subKeys = this.element = document.createElement('div');\r\n\r\n var i;\r\n subKeys.id='kmw-popup-keys';\r\n\r\n // #3718: No longer prepend base key to popup array\r\n\r\n // Must set position dynamically, not in CSS\r\n var ss=subKeys.style;\r\n\r\n // Set key font according to layout, or defaulting to OSK font\r\n // (copied, not inherited, since OSK is not a parent of popup keys)\r\n ss.fontFamily=vkbd.fontFamily;\r\n\r\n // Copy the font size from the parent key, allowing for style inheritance\r\n const computedStyle = getComputedStyle(e);\r\n ss.fontSize=computedStyle.fontSize;\r\n ss.visibility='hidden';\r\n\r\n var nKeys=subKeySpec.length,nRows,nCols;\r\n nRows=Math.min(Math.ceil(nKeys/9),2);\r\n nCols=Math.ceil(nKeys/nRows);\r\n ss.width=(nCols*e.offsetWidth+nCols*5)+'px';\r\n\r\n // Add nested button elements for each sub-key\r\n for(i=0; i 1 && nRow > 0) {\r\n needsTopMargin = true;\r\n }\r\n\r\n let layer = e['key'].layer;\r\n if(typeof(layer) != 'string' || layer == '') {\r\n // Use the currently-active layer.\r\n layer = vkbd.layerId;\r\n }\r\n let keyGenerator = new OSKSubKey(subKeySpec[i], layer);\r\n let kDiv = keyGenerator.construct(vkbd, e, needsTopMargin);\r\n\r\n subKeys.appendChild(kDiv);\r\n }\r\n\r\n // And add a filter to fade main keyboard\r\n this.shim = document.createElement('div');\r\n this.shim.id = 'kmw-popup-shim';\r\n\r\n // Highlight the duplicated base key or ideal subkey (if a phone)\r\n if(vkbd.device.formFactor == DeviceSpec.FormFactor.Phone) {\r\n this.selectDefaultSubkey(vkbd, e, subKeys /* == this.element */);\r\n }\r\n }\r\n\r\n finalize(input: InputEventCoordinate) {\r\n if(this.resolver) {\r\n let keyEvent: KeyEvent = null;\r\n if(this.currentSelection) {\r\n keyEvent = this.vkbd.initKeyEvent(this.currentSelection, input);\r\n this.currentSelection.key.highlight(false);\r\n }\r\n this.resolver(keyEvent);\r\n }\r\n this.resolver = null;\r\n }\r\n\r\n reposition(vkbd: VisualKeyboard) {\r\n let subKeys = this.element;\r\n let e = this.baseKey;\r\n\r\n // And correct its position with respect to that element\r\n const _Box = vkbd.topContainer;\r\n let rowElement = (e.key as OSKBaseKey).row.element;\r\n let ss=subKeys.style;\r\n var x = e.offsetLeft + (e.offsetParent).offsetLeft + 0.5*(e.offsetWidth-subKeys.offsetWidth);\r\n var xMax = vkbd.width - subKeys.offsetWidth;\r\n\r\n if(x > xMax) {\r\n x=xMax;\r\n }\r\n if(x < 0) {\r\n x=0;\r\n }\r\n ss.left=x+'px';\r\n\r\n let _BoxRect = _Box.getBoundingClientRect();\r\n let rowElementRect = rowElement.getBoundingClientRect();\r\n ss.top = (rowElementRect.top - _BoxRect.top - subKeys.offsetHeight - 3) + 'px';\r\n\r\n // Make the popup keys visible\r\n ss.visibility='visible';\r\n\r\n // For now, should only be true (in production) when keyman.isEmbedded == true.\r\n let constrainPopup = vkbd.isEmbedded;\r\n\r\n let cs = getComputedStyle(subKeys);\r\n let topY = parseFloat(cs.top);\r\n\r\n // Adjust the vertical position of the popup to keep it within the\r\n // bounds of the keyboard rectangle, when on iPhone (system keyboard)\r\n const topOffset = 0; // Set this when testing constrainPopup, e.g. to -80px\r\n let delta = 0;\r\n if(topY < topOffset && constrainPopup) {\r\n delta = topOffset - topY;\r\n ss.top = topOffset + 'px';\r\n }\r\n\r\n // Add the callout\r\n if(vkbd.device.formFactor == DeviceSpec.FormFactor.Phone && vkbd.device.OS == DeviceSpec.OperatingSystem.iOS) {\r\n this.callout = this.addCallout(e, delta);\r\n }\r\n }\r\n\r\n /**\r\n * Add a callout for popup keys (if KeymanWeb on a phone device)\r\n *\r\n * @param {Object} key HTML key element\r\n * @return {Object} callout object\r\n */\r\n addCallout(key: KeyElement, delta?: number): HTMLDivElement {\r\n const _Box = this.vkbd.topContainer;\r\n\r\n delta = delta || 0;\r\n\r\n let calloutHeight = key.offsetHeight - delta + 6;\r\n\r\n if(calloutHeight > 0) {\r\n var cc = document.createElement('div'), ccs = cc.style;\r\n cc.id = 'kmw-popup-callout';\r\n _Box.appendChild(cc);\r\n\r\n // Create the callout\r\n let keyRect = key.getBoundingClientRect();\r\n let _BoxRect = _Box.getBoundingClientRect();\r\n\r\n // Set position and style\r\n // We're going to adjust the top of the box to ensure it stays\r\n // pixel aligned, otherwise we can get antialiasing artifacts\r\n // that look ugly\r\n let top = Math.floor(keyRect.top - _BoxRect.top - 9 + delta);\r\n ccs.top = top + 'px';\r\n ccs.left = (keyRect.left - _BoxRect.left) + 'px';\r\n ccs.width = keyRect.width + 'px';\r\n ccs.height = (keyRect.bottom - _BoxRect.top - top - 1) + 'px'; //(height - 1) + 'px';\r\n\r\n // Return callout element, to allow removal later\r\n return cc;\r\n } else {\r\n return null;\r\n }\r\n }\r\n\r\n selectDefaultSubkey(vkbd: VisualKeyboard, baseKey: KeyElement, popupBase: HTMLElement) {\r\n var bk: KeyElement;\r\n let subkeys = baseKey['subKeys'];\r\n for(let i=0; i < subkeys.length; i++) {\r\n let skSpec = subkeys[i];\r\n let skElement = popupBase.childNodes[i].firstChild;\r\n\r\n // Preference order:\r\n // #1: if a default subkey has been specified, select it. (pending, for 15.0+)\r\n // #2: if no default subkey is specified, default to a subkey with the same\r\n // key ID and layer / modifier spec.\r\n //if(skSpec.isDefault) { TODO for 15.0\r\n // bk = skElement;\r\n // break;\r\n //} else\r\n if(!baseKey.key || !baseKey.key.spec) {\r\n continue;\r\n }\r\n\r\n if(skSpec.elementID == baseKey.key.spec.elementID) {\r\n bk = skElement;\r\n break; // Best possible match has been found. (Disable 'break' once above block is implemented.)\r\n }\r\n }\r\n\r\n if(bk) {\r\n vkbd.keyPending = bk;\r\n // Subkeys never get key previews, so we can directly highlight the subkey.\r\n bk.key.highlight(true);\r\n }\r\n }\r\n\r\n isVisible(): boolean {\r\n return this.element.style.visibility == 'visible';\r\n }\r\n\r\n clear() {\r\n // Discard the reference to the Promise's resolve method, allowing\r\n // GC to clean it up. The corresponding Promise's contract allows\r\n // passive cancellation.\r\n this.resolver = null;\r\n\r\n // Remove the displayed subkey array, if any\r\n if(this.element.parentNode) {\r\n this.element.parentNode.removeChild(this.element);\r\n }\r\n\r\n if(this.shim.parentNode) {\r\n this.shim.parentNode.removeChild(this.shim);\r\n }\r\n\r\n if(this.callout && this.callout.parentNode) {\r\n this.callout.parentNode.removeChild(this.callout);\r\n }\r\n }\r\n\r\n updateTouch(input: InputEventCoordinate) {\r\n this.currentSelection = null;\r\n this.baseKey.key.highlight(false);\r\n\r\n for(let i=0; i < this.baseKey['subKeys'].length; i++) {\r\n try {\r\n let sk = this.element.childNodes[i].firstChild as KeyElement;\r\n\r\n let onKey = sk.key.isUnderTouch(input);\r\n if(onKey) {\r\n this.currentSelection = sk;\r\n }\r\n sk.key.highlight(onKey);\r\n } catch(ex) {\r\n if(ex.message) {\r\n console.error(\"Unexpected error when attempting to update selected subkey:\" + ex.message);\r\n } else {\r\n console.error(\"Unexpected error (and error type) when attempting to update selected subkey.\");\r\n }\r\n }\r\n }\r\n\r\n // Use the popup duplicate of the base key if a phone with a visible popup array\r\n if(!this.currentSelection && this.baseKey.key.isUnderTouch(input)) {\r\n this.baseKey.key.highlight(true);\r\n this.currentSelection = this.baseKey;\r\n }\r\n }\r\n}\r\n", + "import { type KeyElement } from '../../../keyElement.js';\r\nimport VisualKeyboard from '../../../visualKeyboard.js';\r\nimport PendingGesture from '../pendingGesture.interface.js';\r\nimport SubkeyPopup from './subkeyPopup.js';\r\n\r\n/**\r\n * (Conceptually) represents a finite-state-machine that determines\r\n * whether or not a series of touch events corresponds to a longpress\r\n * touch input. The `resolve` method may be used to trigger the\r\n * subkey menu early, as with the upward quick-display shortcut.\r\n *\r\n * This is the default implementation of longpress behavior for KMW.\r\n * Alterate implementations are modeled through the `embedded`\r\n * namespace's equivalent, which is designed to facilitate custom\r\n * modeling for such gestures.\r\n *\r\n * Once the conditions to recognize a longpress gesture have been\r\n * fulfilled, this class's `promise` will resolve with a `SubkeyPopup`\r\n * matching the gesture's 'base' key, which itself provides a\r\n * `promise` field that will resolve to a `KeyEvent` once the touch\r\n * sequence is completed.\r\n */\r\nexport default class PendingLongpress implements PendingGesture {\r\n public readonly baseKey: KeyElement;\r\n public readonly promise: Promise;\r\n\r\n public readonly subkeyUI: SubkeyPopup;\r\n\r\n private readonly vkbd: VisualKeyboard;\r\n private resolver: (subkeyPopup: SubkeyPopup) => void;\r\n\r\n private timerId: number;\r\n private popupDelay: number = 500;\r\n\r\n constructor(vkbd: VisualKeyboard, baseKey: KeyElement) {\r\n this.vkbd = vkbd;\r\n this.baseKey = baseKey;\r\n\r\n let _this = this;\r\n this.promise = new Promise(function(resolve, reject) {\r\n _this.resolver = resolve;\r\n // After the timeout, it's no longer deferred; it's being fulfilled.\r\n // Even if the actual subkey itself is still async.\r\n _this.timerId = window.setTimeout(_this.resolve.bind(_this), _this.popupDelay);\r\n });\r\n }\r\n\r\n public cancel() {\r\n if(this.timerId) {\r\n window.clearTimeout(this.timerId);\r\n this.timerId = null;\r\n }\r\n\r\n if(this.resolver) {\r\n this.resolver(null);\r\n this.resolver = null;\r\n }\r\n }\r\n\r\n public resolve() {\r\n // User has flicked up to get to the longpress, before\r\n // the timeout has expired. We need to cancel the timeout.\r\n // See #5950\r\n if(this.timerId) {\r\n window.clearTimeout(this.timerId);\r\n this.timerId = null;\r\n }\r\n\r\n if(this.resolver) {\r\n this.resolver(new SubkeyPopup(this.vkbd, this.baseKey));\r\n }\r\n }\r\n}\r\n", + "import OSKBaseKey from '../../../keyboard-layout/oskBaseKey.js';\r\nimport { KeyElement } from '../../../keyElement.js';\r\nimport KeyTipInterface from '../../../keytip.interface.js';\r\nimport VisualKeyboard from '../../../visualKeyboard.js';\r\n\r\nexport default class KeyTip implements KeyTipInterface {\r\n public readonly element: HTMLDivElement;\r\n public key: KeyElement;\r\n public state: boolean = false;\r\n\r\n // -----\r\n // | | <-- tip\r\n // | x | <-- label\r\n // |_ _|\r\n // | |\r\n // | | <-- cap\r\n // |___|\r\n\r\n private readonly cap: HTMLDivElement;\r\n private readonly tip: HTMLDivElement;\r\n private readonly label: HTMLSpanElement;\r\n\r\n private readonly constrain: boolean;\r\n\r\n /**\r\n *\r\n * @param constrain keep the keytip within the bounds of the overall OSK.\r\n * Will probably be handled via function in a later pass.\r\n */\r\n constructor(constrain: boolean) {\r\n let tipElement = this.element=document.createElement('div');\r\n tipElement.className='kmw-keytip';\r\n tipElement.id = 'kmw-keytip';\r\n\r\n // The following style is critical, so do not rely on external CSS\r\n tipElement.style.pointerEvents='none';\r\n tipElement.style.display='none';\r\n\r\n tipElement.appendChild(this.tip = document.createElement('div'));\r\n tipElement.appendChild(this.cap = document.createElement('div'));\r\n this.tip.appendChild(this.label = document.createElement('span'));\r\n\r\n this.tip.className = 'kmw-keytip-tip';\r\n this.cap.className = 'kmw-keytip-cap';\r\n this.label.className = 'kmw-keytip-label';\r\n\r\n this.constrain = constrain;\r\n }\r\n\r\n show(key: KeyElement, on: boolean, vkbd: VisualKeyboard) {\r\n // Create and display the preview\r\n // If !key.offsetParent, the OSK is probably hidden. Either way, it's a half-\r\n // decent null-guard check.\r\n if(on && key.offsetParent) {\r\n // The key element is positioned relative to its key-square, which is,\r\n // in turn, relative to its row. Rows take 100% width, so this is sufficient.\r\n //\r\n let rowElement = (key.key as OSKBaseKey).row.element;\r\n\r\n // May need adjustment for borders if ever enabled for the desktop form-factor target.\r\n let rkey = key.getClientRects()[0], rrow = rowElement.getClientRects()[0];\r\n let xLeft = rkey.left - rrow.left,\r\n xWidth = rkey.width,\r\n xHeight = rkey.height,\r\n kc = key.key.label,\r\n previewFontScale = 1.8;\r\n\r\n let kts = this.element.style;\r\n\r\n // Roughly matches how the subkey positioning is set.\r\n const _Box = vkbd.topContainer as HTMLDivElement;\r\n const _BoxRect = _Box.getBoundingClientRect();\r\n const keyRect = key.getBoundingClientRect();\r\n let y = (keyRect.bottom - _BoxRect.top + 1);\r\n let ySubPixelPadding = y - Math.floor(y);\r\n\r\n // Canvas dimensions must be set explicitly to prevent clipping\r\n // This gives us exactly the same number of pixels on left and right\r\n let canvasWidth = xWidth + Math.ceil(xWidth * 0.3) * 2;\r\n let canvasHeight = Math.ceil(2.3 * xHeight) + (ySubPixelPadding); //\r\n\r\n kts.top = 'auto';\r\n kts.bottom = Math.floor(_BoxRect.height - y) + 'px';\r\n kts.textAlign = 'center';\r\n kts.overflow = 'visible';\r\n kts.width = canvasWidth+'px';\r\n kts.height = canvasHeight+'px';\r\n\r\n const ckts = getComputedStyle(vkbd.element);\r\n kts.fontFamily = ckts.fontFamily;\r\n\r\n var px=parseInt(ckts.fontSize,10);\r\n if(px == Number.NaN) {\r\n px = 0;\r\n }\r\n\r\n if(px != 0) {\r\n let popupFS = previewFontScale * px;\r\n let scaleStyle = {\r\n fontFamily: kts.fontFamily,\r\n fontSize: popupFS + 'px',\r\n height: 1.6 * xHeight + 'px' // as opposed to the canvas height of 2.3 * xHeight.\r\n };\r\n\r\n kts.fontSize = key.key.getIdealFontSize(vkbd, key.key.keyText, scaleStyle, true);\r\n }\r\n\r\n this.label.textContent = kc.textContent;\r\n\r\n // Adjust shape if at edges\r\n var xOverflow = (canvasWidth - xWidth) / 2;\r\n if(xLeft < xOverflow) {\r\n this.cap.style.left = '1px';\r\n xLeft += xOverflow - 1;\r\n } else if(xLeft > window.innerWidth - xWidth - xOverflow) {\r\n this.cap.style.left = (canvasWidth - xWidth - 1) + 'px';\r\n xLeft -= xOverflow - 1;\r\n } else {\r\n this.cap.style.left = xOverflow + 'px';\r\n }\r\n\r\n kts.left=(xLeft - xOverflow) + 'px';\r\n\r\n let cs = getComputedStyle(this.element);\r\n let oskHeight = _BoxRect.height;\r\n let bottomY = parseFloat(cs.bottom);\r\n let tipHeight = parseFloat(cs.height);\r\n let halfHeight = Math.ceil(canvasHeight / 2);\r\n\r\n this.cap.style.width = xWidth + 'px';\r\n this.tip.style.height = halfHeight + 'px';\r\n\r\n this.cap.style.top = (halfHeight - 3) + 'px';\r\n this.cap.style.height = (keyRect.bottom - _BoxRect.top - Math.floor(y - canvasHeight) - (halfHeight)) + 'px'; //(halfHeight + 3 + ySubPixelPadding) + 'px';\r\n\r\n if(this.constrain && tipHeight + bottomY > oskHeight) {\r\n const delta = tipHeight + bottomY - oskHeight;\r\n kts.height = (canvasHeight-delta) + 'px';\r\n const hx = Math.max(0, (canvasHeight-delta)-(canvasHeight/2) + 2);\r\n this.cap.style.height = hx + 'px';\r\n }\r\n\r\n kts.display = 'block';\r\n } else { // Hide the key preview\r\n this.element.style.display = 'none';\r\n }\r\n\r\n // Save the key preview state\r\n this.key = key;\r\n this.state = on;\r\n }\r\n}", + "import { landscapeView } from \"keyman/engine/dom-utils\";\r\n\r\n/**\r\n * Get viewport scale factor for this document\r\n *\r\n * @return {number}\r\n */\r\nexport function getViewportScale(): number {\r\n // This can sometimes fail with some browsers if called before document defined,\r\n // so catch the exception\r\n try {\r\n // For emulation of iOS on a desktop device, use a default value\r\n if(this.device.formFactor == 'desktop') {\r\n return 1;\r\n }\r\n\r\n // Get viewport width\r\n var viewportWidth = document.documentElement.clientWidth;\r\n\r\n // Return a default value if screen width is greater than the viewport width (not fullscreen).\r\n if(screen.width > viewportWidth) {\r\n return 1;\r\n }\r\n\r\n // Get the orientation corrected screen width\r\n var screenWidth = screen.width;\r\n if(landscapeView()) {\r\n // Take larger of the two dimensions\r\n if(screen.width < screen.height) {\r\n screenWidth = screen.height;\r\n }\r\n } else {\r\n // Take smaller of the two dimensions\r\n if(screen.width > screen.height) {\r\n screenWidth = screen.height;\r\n }\r\n }\r\n // Calculate viewport scale\r\n return Math.round(100*screenWidth / viewportWidth)/100;\r\n } catch(ex) {\r\n return 1;\r\n }\r\n}", + "import EventEmitter from 'eventemitter3';\r\n\r\ninterface EventMap {\r\n activate: (flag: boolean) => void;\r\n}\r\n\r\n/**\r\n * Used to encapsulate activation logic for the on-screen keyboadr, conditionally activating\r\n * and deactivating it based on specified conditions.\r\n */\r\nexport default abstract class Activator extends EventEmitter {\r\n /**\r\n * For certain sub-types, this may be set to `false` to \"turn activation off\", putting\r\n * the `Activator` in a state that ignores changes to any other conditions.\r\n */\r\n abstract get enabled(): boolean;\r\n\r\n abstract set enabled(flag: boolean);\r\n\r\n /**\r\n * When `true`, indicates that the listener should activate / become visible.\r\n */\r\n abstract get activate(): boolean;\r\n\r\n /**\r\n * When `true` and `activate` is `false`, indicates that changing the value of `enabled`\r\n * will result in activation.\r\n */\r\n abstract get conditionsMet(): boolean;\r\n}\r\n\r\nexport class StaticActivator extends Activator {\r\n get enabled(): boolean {\r\n return true;\r\n }\r\n\r\n get activate(): boolean {\r\n return true;\r\n }\r\n\r\n get conditionsMet(): boolean {\r\n return true;\r\n }\r\n}", + "import { ManagedPromise } from \"@keymanapp/web-utils\";\r\n\r\nexport default class TouchEventPromiseMap {\r\n private map: Record> = {};\r\n\r\n // Used to\r\n public promiseForTouchpoint(id: number): ManagedPromise {\r\n if(!this.map[id]) {\r\n this.map[id] = new ManagedPromise();\r\n }\r\n\r\n return this.map[id]; // touchpoint identifiers are unique during a page's lifetime.\r\n }\r\n\r\n public maintainTouches(list: TouchList) {\r\n let keys = Object.keys(this.map);\r\n\r\n for(let i=0; i < list.length; i++) {\r\n let pos = keys.indexOf('' + list.item(i).identifier);\r\n if(pos != -1) {\r\n keys.splice(pos, 1);\r\n }\r\n }\r\n\r\n // Any remaining entries of `keys` are no longer in the map!\r\n for(let endedKey of keys) {\r\n (this.map[endedKey] as ManagedPromise).resolve();\r\n delete this.map[endedKey];\r\n }\r\n }\r\n}", + "import EventEmitter from 'eventemitter3';\r\n\r\nimport { Keyboard } from '@keymanapp/keyboard-processor';\r\n\r\nimport OSKViewComponent from './oskViewComponent.interface.js';\r\nimport { ParsedLengthStyle } from '../lengthStyle.js';\r\nimport MouseDragOperation from '../input/mouseDragOperation.js';\r\n\r\nimport { createUnselectableElement } from 'keyman/engine/dom-utils';\r\n\r\ninterface EventMap {\r\n /**\r\n * The close button (to request that the OSK hide) has been clicked.\r\n */\r\n close: () => void,\r\n\r\n /**\r\n * The config button has been clicked.\r\n */\r\n config: () => void,\r\n\r\n /**\r\n * The help button has been clicked.\r\n */\r\n help: () => void,\r\n\r\n /**\r\n * The pin button was visible and has been clicked.\r\n */\r\n unpin: () => void\r\n}\r\n\r\nexport default class TitleBar extends EventEmitter implements OSKViewComponent {\r\n private _element: HTMLDivElement;\r\n private _unpinButton: HTMLDivElement;\r\n private _closeButton: HTMLDivElement;\r\n private _helpButton: HTMLDivElement;\r\n private _configButton: HTMLDivElement;\r\n private _caption: HTMLSpanElement;\r\n\r\n private _helpEnabled: boolean;\r\n private _configEnabled: boolean;\r\n\r\n public get helpEnabled(): boolean {\r\n return this._helpEnabled;\r\n }\r\n\r\n public set helpEnabled(val) {\r\n this._helpEnabled = val;\r\n\r\n this._helpButton.style.display = val ? 'inline' : 'none';\r\n }\r\n\r\n public get configEnabled(): boolean {\r\n return this._configEnabled;\r\n }\r\n\r\n public set configEnabled(val) {\r\n this._configEnabled = val;\r\n\r\n this._configButton.style.display = val ? 'inline' : 'none';\r\n }\r\n\r\n private static readonly DISPLAY_HEIGHT = ParsedLengthStyle.inPixels(20); // As set in kmwosk.css\r\n\r\n public constructor(dragHandler?: MouseDragOperation) {\r\n super();\r\n\r\n this._element = this.buildTitleBar();\r\n\r\n this.helpEnabled = false;\r\n this.configEnabled = false;\r\n\r\n if(dragHandler) {\r\n this.element.onmousedown = dragHandler.mouseDownHandler;\r\n }\r\n }\r\n\r\n public get layoutHeight(): ParsedLengthStyle {\r\n return TitleBar.DISPLAY_HEIGHT;\r\n }\r\n\r\n private mouseCancellingHandler: (ev: MouseEvent) => boolean = function(ev: MouseEvent) {\r\n ev.preventDefault();\r\n ev.cancelBubble = true;\r\n return false;\r\n };\r\n\r\n public get element(): HTMLDivElement {\r\n return this._element;\r\n }\r\n\r\n public setPinCJKOffset() {\r\n this._unpinButton.style.left = '15px';\r\n }\r\n\r\n public showPin(visible: boolean) {\r\n this._unpinButton.style.display = visible ? 'block' : 'none';\r\n }\r\n\r\n public setTitle(str: string) {\r\n this._caption.innerHTML = str;\r\n }\r\n\r\n public setTitleFromKeyboard(keyboard: Keyboard) {\r\n let title = \"\" + keyboard?.name + ''; // I1972 // I2186\r\n this._caption.innerHTML = title;\r\n }\r\n\r\n /**\r\n * Create a control bar with title and buttons for the desktop OSK\r\n */\r\n buildTitleBar(): HTMLDivElement {\r\n let bar = createUnselectableElement('div');\r\n bar.id='keymanweb_title_bar';\r\n bar.className='kmw-title-bar';\r\n\r\n var Ltitle = this._caption = createUnselectableElement('span');\r\n Ltitle.className='kmw-title-bar-caption';\r\n Ltitle.style.color='#fff';\r\n bar.appendChild(Ltitle);\r\n\r\n var Limg = this._closeButton = this.buildCloseButton();\r\n this._closeButton.onclick = () => {\r\n this.emit('close');\r\n return false;\r\n };\r\n bar.appendChild(Limg);\r\n\r\n Limg = this._helpButton = this.buildHelpButton()\r\n this._helpButton.onclick = () => {\r\n this.emit('help');\r\n return false;\r\n }\r\n bar.appendChild(Limg);\r\n\r\n Limg = this._configButton = this.buildConfigButton();\r\n this._configButton.onclick = () => {\r\n this.emit('config');\r\n return false;\r\n }\r\n bar.appendChild(Limg);\r\n\r\n Limg = this._unpinButton = this.buildUnpinButton();\r\n this._unpinButton.onclick = () => {\r\n this.emit('unpin');\r\n return false;\r\n }\r\n bar.appendChild(Limg);\r\n\r\n return bar;\r\n }\r\n\r\n private buildCloseButton(): HTMLDivElement {\r\n var Limg = createUnselectableElement('div');\r\n\r\n Limg.id='kmw-close-button';\r\n Limg.className='kmw-title-bar-image';\r\n Limg.onmousedown = this.mouseCancellingHandler;\r\n\r\n return Limg;\r\n }\r\n\r\n private buildHelpButton(): HTMLDivElement {\r\n let Limg = createUnselectableElement('div');\r\n Limg.id='kmw-help-image';\r\n Limg.className='kmw-title-bar-image';\r\n Limg.title='KeymanWeb Help';\r\n Limg.onmousedown = this.mouseCancellingHandler;\r\n return Limg;\r\n }\r\n\r\n private buildConfigButton(): HTMLDivElement {\r\n let Limg = createUnselectableElement('div');\r\n\r\n Limg.id='kmw-config-image';\r\n Limg.className='kmw-title-bar-image';\r\n Limg.title='KeymanWeb Configuration Options';\r\n Limg.onmousedown = this.mouseCancellingHandler;\r\n\r\n return Limg;\r\n }\r\n\r\n /**\r\n * Builds an 'unpin' button for restoring OSK to default location, handle mousedown and click events\r\n */\r\n private buildUnpinButton(): HTMLDivElement {\r\n let Limg = createUnselectableElement('div'); //I2186\r\n\r\n Limg.id = 'kmw-pin-image';\r\n Limg.className = 'kmw-title-bar-image';\r\n Limg.title='Pin the On Screen Keyboard to its default location on the active text box';\r\n\r\n Limg.onmousedown = this.mouseCancellingHandler;\r\n\r\n return Limg;\r\n }\r\n\r\n public refreshLayout() {\r\n // The title bar is adaptable as it is and needs no adjustments.\r\n }\r\n}", + "import EventEmitter from 'eventemitter3';\r\n\r\nimport OSKViewComponent from './oskViewComponent.interface.js';\r\nimport { ParsedLengthStyle } from '../lengthStyle.js';\r\nimport MouseDragOperation from '../input/mouseDragOperation.js';\r\n\r\nimport { createUnselectableElement } from 'keyman/engine/dom-utils';\r\n\r\ninterface EventMap {\r\n /**\r\n * Triggered when the user inputs a special command to show the engine's current version number.\r\n */\r\n showbuild: () => void;\r\n}\r\n\r\nexport default class ResizeBar extends EventEmitter implements OSKViewComponent {\r\n private _element: HTMLDivElement;\r\n private _resizeHandle: HTMLDivElement;\r\n\r\n private static readonly DISPLAY_HEIGHT = ParsedLengthStyle.inPixels(16); // As set in kmwosk.css\r\n\r\n private mouseCancellingHandler: (ev: MouseEvent) => boolean = function(ev: MouseEvent) {\r\n ev.preventDefault();\r\n ev.cancelBubble = true;\r\n return false;\r\n };\r\n\r\n public constructor(dragHandler?: MouseDragOperation) {\r\n super();\r\n this._element = this.buildResizeBar();\r\n\r\n if(dragHandler) {\r\n this._resizeHandle.onmousedown = dragHandler.mouseDownHandler;\r\n }\r\n }\r\n\r\n public get layoutHeight(): ParsedLengthStyle {\r\n return ResizeBar.DISPLAY_HEIGHT;\r\n }\r\n\r\n public get element(): HTMLDivElement {\r\n return this._element;\r\n }\r\n\r\n public get handle(): HTMLDivElement {\r\n return this._resizeHandle;\r\n }\r\n\r\n public allowResizing(flag: boolean) {\r\n this._resizeHandle.style.display = flag ? 'block' : 'none';\r\n }\r\n\r\n /**\r\n * Create a bottom bar with a resizing icon for the desktop OSK\r\n */\r\n buildResizeBar(): HTMLDivElement {\r\n var bar = createUnselectableElement('div');\r\n bar.className='kmw-footer';\r\n bar.onmousedown = this.mouseCancellingHandler;\r\n\r\n // Add caption\r\n var Ltitle=createUnselectableElement('div');\r\n Ltitle.className='kmw-footer-caption';\r\n Ltitle.innerHTML='KeymanWeb';\r\n Ltitle.id='keymanweb-osk-footer-caption';\r\n\r\n // Display build number on shift+double click\r\n Ltitle.addEventListener('dblclick', (e) => {\r\n this.emit('showbuild');\r\n\r\n return false;\r\n }, false);\r\n\r\n bar.appendChild(Ltitle);\r\n\r\n var Limg = createUnselectableElement('div');\r\n Limg.className='kmw-footer-resize';\r\n bar.appendChild(Limg);\r\n this._resizeHandle=Limg;\r\n\r\n return bar;\r\n }\r\n\r\n public refreshLayout() {\r\n // The title bar is adaptable as it is and needs no adjustments.\r\n }\r\n}", + "import InputEventCoordinate from './inputEventCoordinate.js';\r\n\r\ntype MouseHandler = (this: GlobalEventHandlers, ev: MouseEvent) => any;\r\n\r\n/**\r\n * Used to store the page's original mouse handlers and properties\r\n * when temporarily overridden by OSK moving or resizing handlers due\r\n * to user interaction.\r\n */\r\nclass MouseStartSnapshot {\r\n private readonly _VPreviousMouseMove: MouseHandler;\r\n private readonly _VPreviousMouseUp: MouseHandler;\r\n private readonly _VPreviousCursor: string;\r\n private readonly _VPreviousMouseButton: number;\r\n\r\n constructor(e: MouseEvent) {\r\n this._VPreviousMouseMove = document.onmousemove;\r\n this._VPreviousMouseUp = document.onmouseup;\r\n\r\n this._VPreviousCursor = document.body.style.cursor;\r\n this._VPreviousMouseButton = (typeof(e.which)=='undefined' ? e.button : e.which);\r\n }\r\n\r\n restore() {\r\n document.onmousemove = this._VPreviousMouseMove;\r\n document.onmouseup = this._VPreviousMouseUp;\r\n\r\n if(document.body.style.cursor) {\r\n document.body.style.cursor = this._VPreviousCursor;\r\n }\r\n }\r\n\r\n matchesCausingClick(e: MouseEvent): boolean {\r\n return this._VPreviousMouseButton == (typeof(e.which)=='undefined' ? e.button : e.which);\r\n }\r\n}\r\n\r\nexport default abstract class MouseDragOperation {\r\n private _enabled: boolean;\r\n private _startCoord: InputEventCoordinate;\r\n private _mouseStartSnapshot: MouseStartSnapshot;\r\n\r\n private startHandler: (e: MouseEvent) => void;\r\n private cursorType: string;\r\n\r\n public constructor(cursorType?: string) {\r\n this.startHandler = this._VMoveMouseDown.bind(this);\r\n this.cursorType = cursorType;\r\n }\r\n\r\n /**\r\n * Denotes whether or not this object should handle incoming events.\r\n */\r\n public get enabled(): boolean {\r\n return this._enabled;\r\n }\r\n\r\n public set enabled(flag: boolean) {\r\n this._enabled = flag;\r\n }\r\n\r\n /**\r\n * Denotes whether or not this object is currently handling an ongoing drag event.\r\n */\r\n public get isActive(): boolean {\r\n return !!this._mouseStartSnapshot;\r\n }\r\n\r\n public get mouseDownHandler(): (e: MouseEvent) => void {\r\n return this.startHandler;\r\n }\r\n\r\n /**\r\n * Function _VMoveMouseDown\r\n * Scope Private\r\n * @param {Object} e event\r\n * Description Process mouse down on OSK\r\n */\r\n private _VMoveMouseDown(e: MouseEvent) {\r\n if(!e) {\r\n return true;\r\n }\r\n\r\n if(!this._enabled) {\r\n return true;\r\n }\r\n\r\n if(!this._mouseStartSnapshot) { // I1472 - Dragging off edge of browser window causes muckup\r\n this._mouseStartSnapshot = new MouseStartSnapshot(e);\r\n }\r\n\r\n this._startCoord = InputEventCoordinate.fromEvent(e);\r\n\r\n document.onmousemove = this._VMoveMouseMove.bind(this);\r\n document.onmouseup = this._VMoveMouseUp.bind(this);\r\n if(document.body.style.cursor) {\r\n document.body.style.cursor = this.cursorType;\r\n }\r\n\r\n e.preventDefault();\r\n e.cancelBubble = true;\r\n\r\n this.onDragStart();\r\n return false;\r\n }\r\n\r\n protected abstract onDragStart();\r\n\r\n /**\r\n * Process mouse drag on OSK\r\n *\r\n * @param {Object} e event\r\n */\r\n private _VMoveMouseMove(e: MouseEvent) {\r\n if(!e) {\r\n return true;\r\n }\r\n\r\n if(!this.enabled) {\r\n return true;\r\n }\r\n\r\n e.preventDefault();\r\n e.cancelBubble = true;\r\n\r\n if(!this._mouseStartSnapshot.matchesCausingClick(e)) { // I1472 - Dragging off edge of browser window causes muckup\r\n return this._VMoveMouseUp(e);\r\n } else {\r\n const coord = InputEventCoordinate.fromEvent(e);\r\n const deltaX = coord.x - this._startCoord.x;\r\n const deltaY = coord.y - this._startCoord.y;\r\n\r\n this.onDragMove(deltaX, deltaY);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param deltaX The total horizontal distance moved, in pixels, since the start of the drag\r\n * @param deltaY The total vertical distance moved, in pixels, since the start of the drag\r\n */\r\n protected abstract onDragMove(deltaX: number, deltaY: number);\r\n\r\n /**\r\n * Function _VMoveMouseUp\r\n * Scope Private\r\n * @param {Object} e event\r\n * Description Process mouse up during movement of KMW OSK UI\r\n */\r\n private _VMoveMouseUp(e: MouseEvent) {\r\n if(!e) {\r\n return true;\r\n }\r\n\r\n this._mouseStartSnapshot.restore();\r\n this._mouseStartSnapshot = null;\r\n\r\n e.preventDefault();\r\n e.cancelBubble = true;\r\n\r\n this.onDragRelease();\r\n return false;\r\n }\r\n\r\n protected abstract onDragRelease();\r\n}", + "import Activator from './activator.js';\r\n\r\ninterface TriggerEventMap {\r\n triggerchange: (trigger: Type) => void;\r\n}\r\n\r\nexport default class TwoStateActivator extends Activator> {\r\n private _enabled: boolean = true;\r\n private actValue: Type = null;\r\n\r\n get activate(): boolean {\r\n return this._enabled && !!this.actValue;\r\n }\r\n\r\n private checkState(oldValue: boolean) {\r\n if(this.activate != oldValue) {\r\n this.emit('activate', this.activate);\r\n }\r\n }\r\n\r\n get enabled(): boolean {\r\n return this._enabled;\r\n }\r\n\r\n set enabled(flag: boolean) {\r\n const oldState = this.activate;\r\n this._enabled = flag; // may change this.value!\r\n\r\n this.checkState(oldState);\r\n }\r\n\r\n get activationTrigger(): Type {\r\n return this.actValue;\r\n }\r\n\r\n set activationTrigger(value: Type) {\r\n const oldState = this.activate;\r\n const oldValue = this.actValue;\r\n this.actValue = value; // may change this.value!\r\n\r\n this.checkState(oldState);\r\n if(oldValue != value) {\r\n this.emit('triggerchange', value);\r\n }\r\n }\r\n\r\n get conditionsMet(): boolean {\r\n return !!this.activationTrigger;\r\n }\r\n}", + "import { CookieSerializer } from 'keyman/engine/dom-utils';\r\n\r\nexport interface FloatingOSKCookie {\r\n /**\r\n * Notes whether or not the OSK was hidden at the end of the previous session.\r\n */\r\n visible: 0 | 1;\r\n\r\n /**\r\n * Notes whether or not the OSK was pinned (located by the user) at the end\r\n * of the previous session.\r\n */\r\n userSet: 0 | 1;\r\n\r\n /**\r\n * Denotes the left-position of the OSK at the end of the previous session if pinned.\r\n * Defaults to -1 if the value was undefined.\r\n */\r\n left: number;\r\n\r\n /**\r\n * Denotes the left-position of the OSK at the end of the previous session if pinned.\r\n * Defaults to -1 if the value was undefined.\r\n */\r\n top: number;\r\n\r\n /**\r\n * The previously-set OSK width.\r\n */\r\n width?: number;\r\n\r\n /**\r\n * The previously-set OSK height.\r\n */\r\n height?: number;\r\n\r\n /**\r\n * The version of KeymanWeb active when this cookie was generated.\r\n */\r\n _version: string;\r\n}\r\n\r\nexport class FloatingOSKCookieSerializer extends CookieSerializer> {\r\n constructor() {\r\n super('KeymanWeb_OnScreenKeyboard');\r\n }\r\n\r\n loadWithDefaults(defaults: Required) {\r\n return {...defaults, ...this.load()};\r\n }\r\n\r\n load() {\r\n const cookie = super.load((value, key) => {\r\n switch(key) {\r\n case 'version':\r\n return value;\r\n default:\r\n return Number.parseInt(value, 10);\r\n }\r\n });\r\n\r\n if(!cookie.width) {\r\n delete cookie.width; // in case of a '' entry.\r\n }\r\n if(!cookie.height) {\r\n delete cookie.height; // in case of a '' entry.\r\n }\r\n\r\n return cookie;\r\n }\r\n\r\n save(cookie: Required) {\r\n super.save(cookie);\r\n }\r\n}", + "import { Codes, DeviceSpec, ManagedPromise, Version } from '@keymanapp/keyboard-processor';\r\nimport { getAbsoluteX, getAbsoluteY, landscapeView } from 'keyman/engine/dom-utils';\r\nimport { EmitterListenerSpy, LegacyEventMap } from 'keyman/engine/events';\r\n\r\nimport OSKView, { EventMap, type LegacyOSKEventMap, OSKPos, OSKRect } from './oskView.js';\r\nimport TitleBar from '../components/titleBar.js';\r\nimport ResizeBar from '../components/resizeBar.js';\r\n\r\nimport MouseDragOperation from '../input/mouseDragOperation.js';\r\nimport { getViewportScale } from '../screenUtils.js';\r\nimport Configuration from '../config/viewConfiguration.js';\r\nimport TwoStateActivator from './twoStateActivator.js';\r\nimport { FloatingOSKCookie, FloatingOSKCookieSerializer } from './floatingOskCookie.js';\r\n\r\n/***\r\n KeymanWeb 10.0\r\n Copyright 2017 SIL International\r\n***/\r\n\r\nexport interface FloatingOSKViewConfiguration extends Configuration {\r\n activator?: TwoStateActivator;\r\n}\r\n\r\nexport default class FloatingOSKView extends OSKView {\r\n // OSK positioning fields\r\n userPositioned: boolean = false;\r\n specifiedPosition: boolean = false;\r\n x: number;\r\n y: number;\r\n noDrag: boolean = false;\r\n dfltX: string;\r\n dfltY: string;\r\n\r\n layoutSerializer = new FloatingOSKCookieSerializer();\r\n\r\n private titleBar: TitleBar;\r\n private resizeBar: ResizeBar;\r\n\r\n // Encapsulations of the drag behaviors for OSK movement & resizing\r\n private _moveHandler: MouseDragOperation;\r\n private _resizeHandler: MouseDragOperation;\r\n\r\n public constructor(config: FloatingOSKViewConfiguration) {\r\n config.activator = config.activator || new TwoStateActivator();\r\n\r\n super(config);\r\n\r\n this.typedActivationModel.on('triggerchange', () => this.setDisplayPositioning());\r\n\r\n document.body.appendChild(this._Box);\r\n\r\n // Add header element to OSK only for desktop browsers\r\n this.titleBar = new TitleBar(this.titleDragHandler);\r\n this.titleBar.on('help', () => {\r\n this.legacyEvents.callEvent('helpclick', {});\r\n });\r\n this.titleBar.on('config', () => {\r\n this.legacyEvents.callEvent('configclick', {});\r\n });\r\n this.titleBar.on('close', () => this.startHide(true));\r\n this.titleBar.on('unpin', () => this.restorePosition(true));\r\n\r\n this.resizeBar = new ResizeBar(this.resizeDragHandler);\r\n this.resizeBar.on('showbuild', () => this.emit('showbuild'));\r\n\r\n this.headerView = this.titleBar;\r\n\r\n const onListenedEvent = (eventName: keyof EventMap | keyof LegacyOSKEventMap) => {\r\n // As the following title bar buttons (for desktop / FloatingOSKView) do nothing unless a site\r\n // designer uses these events, we disable / hide them unless an event-handler is attached.\r\n let titleBar = this.headerView;\r\n if(titleBar && titleBar instanceof TitleBar) {\r\n switch(eventName) {\r\n case 'configclick':\r\n titleBar.configEnabled = this.legacyEvents.listenerCount('configclick') > 0;\r\n break;\r\n case 'helpclick':\r\n titleBar.helpEnabled = this.legacyEvents.listenerCount('helpclick') > 0;\r\n break;\r\n default:\r\n return;\r\n }\r\n }\r\n }\r\n\r\n const listenerSpyNew = new EmitterListenerSpy(this);\r\n const listenerSpyOld = new EmitterListenerSpy(this.legacyEvents);\r\n for(let listenerSpy of [listenerSpyNew, listenerSpyOld]) {\r\n listenerSpy.on('listeneradded', onListenedEvent);\r\n listenerSpy.on('listenerremoved', onListenedEvent);\r\n }\r\n\r\n this.loadPersistedLayout();\r\n }\r\n\r\n private get typedActivationModel(): TwoStateActivator {\r\n return this.activationModel as TwoStateActivator;\r\n }\r\n\r\n /**\r\n * Function _Unload\r\n * Scope Private\r\n * Description Clears OSK variables prior to exit (JMD 1.9.1 - relocation of local variables 3/9/10)\r\n */\r\n _Unload() {\r\n this.keyboardView = null;\r\n this.bannerView = null;\r\n this._Box = null;\r\n }\r\n\r\n protected setBoxStyling() {\r\n const s = this._Box.style;\r\n\r\n s.zIndex = '9999';\r\n s.display = 'none';\r\n s.width = 'auto';\r\n s.position = 'absolute';\r\n }\r\n\r\n protected postKeyboardAdjustments() {\r\n // Add header element to OSK only for desktop browsers\r\n this.enableMoveResizeHandlers();\r\n if(this.activeKeyboard) {\r\n this.titleBar.setTitleFromKeyboard(this.activeKeyboard.keyboard);\r\n }\r\n\r\n if(this.vkbd) {\r\n this.footerView = this.resizeBar;\r\n this._Box.appendChild(this.footerView.element);\r\n } else {\r\n if(this.footerView) {\r\n this._Box.removeChild(this.footerView.element);\r\n }\r\n this.footerView = null;\r\n }\r\n\r\n this.loadPersistedLayout();\r\n this.setNeedsLayout();\r\n }\r\n\r\n /**\r\n * Function restorePosition\r\n * Scope Public\r\n * @param {boolean?} keepDefaultPosition If true, does not reset the default x,y set by `setRect`.\r\n * If false or omitted, resets the default x,y as well.\r\n * Description Move OSK back to default position, floating under active input element\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/osk/restorePosition\r\n */\r\n public restorePosition: (keepDefaultPosition?: boolean) => void = function(this: FloatingOSKView, keepDefaultPosition?: boolean) {\r\n let isVisible = this._Visible;\r\n\r\n let dragPromise = new ManagedPromise();\r\n this.emit('dragmove', dragPromise.corePromise);\r\n\r\n this.loadPersistedLayout();\r\n this.userPositioned=false;\r\n if(!keepDefaultPosition) {\r\n delete this.dfltX;\r\n delete this.dfltY;\r\n }\r\n this.savePersistedLayout();\r\n\r\n if(isVisible) {\r\n this.present();\r\n }\r\n\r\n this.titleBar.showPin(false);\r\n dragPromise.resolve();\r\n this.doResizeMove(); //allow the UI to respond to OSK movements\r\n }.bind(this);\r\n\r\n /**\r\n * Function enabled\r\n * Scope Public\r\n * @return {boolean|number} True if KMW OSK enabled\r\n * Description Test if KMW OSK is enabled\r\n */\r\n ['isEnabled'](): boolean {\r\n return this.displayIfActive;\r\n }\r\n\r\n /**\r\n * Function isVisible\r\n * Scope Public\r\n * @return {boolean|number} True if KMW OSK visible\r\n * Description Test if KMW OSK is actually visible\r\n * Note that this will usually return false after any UI event that results in (temporary) loss of input focus\r\n */\r\n ['isVisible'](): boolean {\r\n return this._Visible;\r\n }\r\n\r\n /**\r\n * Save size, position, font size and visibility of OSK\r\n */\r\n private savePersistedLayout() {\r\n var p = this.getPos();\r\n\r\n const c: FloatingOSKCookie = {\r\n visible: this.displayIfActive ? 1 : 0,\r\n userSet: this.userPositioned ? 1 : 0,\r\n left: p.left,\r\n top: p.top,\r\n _version: Version.CURRENT.toString()\r\n }\r\n\r\n if(this.vkbd) {\r\n c.width = this.width.val;\r\n c.height = this.height.val;\r\n }\r\n\r\n this.layoutSerializer.save(c as Required);\r\n }\r\n\r\n /**\r\n * Restore size, position, font size and visibility of desktop OSK\r\n *\r\n * @return {boolean}\r\n */\r\n private loadPersistedLayout(): void {\r\n let c = this.layoutSerializer.loadWithDefaults({\r\n visible: 1,\r\n userSet: 0,\r\n left: -1,\r\n top: -1,\r\n _version: undefined,\r\n width: 0.3*screen.width,\r\n height: 0.15*screen.height\r\n });\r\n\r\n this.activationModel.enabled = c.visible == 1;\r\n this.userPositioned = c.userSet == 1;\r\n this.x = c.left;\r\n this.y = c.top;\r\n const cookieVersionString = c._version;\r\n\r\n // Restore OSK size - font size now fixed in relation to OSK height, unless overridden (in em) by keyboard\r\n const isNewCookie = cookieVersionString === undefined;\r\n let newWidth = c.width;\r\n let newHeight = c.height;\r\n\r\n // Limit the OSK dimensions to reasonable values\r\n if(newWidth < 0.2*screen.width) {\r\n newWidth = 0.2*screen.width;\r\n }\r\n if(newHeight < 0.1*screen.height) {\r\n newHeight = 0.1*screen.height;\r\n }\r\n if(newWidth > 0.9*screen.width) {\r\n newWidth=0.9*screen.width;\r\n }\r\n if(newHeight > 0.5*screen.height) {\r\n newHeight=0.5*screen.height;\r\n }\r\n\r\n // if(!cookieVersionString) - this component was not tracked until 15.0.\r\n // Before that point, the OSK's title bar and resize bar heights were not included\r\n // in the OSK's cookie-persisted height.\r\n if(isNewCookie || !cookieVersionString) {\r\n // Adds some space to account for the OSK's header and footer, should they exist.\r\n if(this.headerView && this.headerView.layoutHeight.absolute) {\r\n newHeight += this.headerView.layoutHeight.val;\r\n }\r\n\r\n if(this.footerView && this.footerView.layoutHeight.absolute) {\r\n newHeight += this.footerView.layoutHeight.val;\r\n }\r\n }\r\n\r\n this.setSize(newWidth, newHeight);\r\n\r\n // and OSK position if user located\r\n if(this.x == -1 || this.y == -1 || (!this._Box)) {\r\n this.userPositioned = false;\r\n }\r\n\r\n if(this.x < window.pageXOffset-0.8*newWidth) {\r\n this.x=window.pageXOffset-0.8*newWidth;\r\n }\r\n if(this.y < 0) {\r\n this.x=-1;\r\n this.y=-1;\r\n this.userPositioned=false;\r\n }\r\n\r\n if(this.userPositioned && this._Box) {\r\n this.setPos({'left': this.x, 'top': this.y});\r\n }\r\n }\r\n\r\n /**\r\n * Get the wanted height of the OSK for touch devices (does not include banner height)\r\n * @return {number} height in pixels\r\n **/\r\n getDefaultKeyboardHeight(): number {\r\n // KeymanTouch - get OSK height from device\r\n if(this.configuration.heightOverride) {\r\n return this.configuration.heightOverride();\r\n }\r\n\r\n var oskHeightLandscapeView=Math.floor(Math.min(screen.availHeight,screen.availWidth)/2),\r\n height=oskHeightLandscapeView;\r\n\r\n if(this.targetDevice.formFactor == 'phone') {\r\n var sx=Math.min(screen.height,screen.width),\r\n sy=Math.max(screen.height,screen.width);\r\n\r\n if(!landscapeView())\r\n height=Math.floor(Math.max(screen.availHeight,screen.availWidth)/3);\r\n else\r\n height=height*(sy/sx)/1.6; //adjust for aspect ratio, increase slightly for iPhone 5\r\n }\r\n\r\n // Correct for viewport scaling (iOS - Android 4.2 does not want this, at least on Galaxy Tab 3))\r\n if(this.targetDevice.OS == DeviceSpec.OperatingSystem.iOS) {\r\n height=height/getViewportScale();\r\n }\r\n\r\n return height;\r\n }\r\n\r\n /**\r\n * Get the wanted width of the OSK for touch devices\r\n *\r\n * @return {number} height in pixels\r\n **/\r\n getDefaultWidth(): number {\r\n // KeymanTouch - get OSK height from device\r\n if(this.configuration.widthOverride) {\r\n return this.configuration.widthOverride();\r\n }\r\n\r\n var width: number;\r\n if(this.targetDevice.OS == DeviceSpec.OperatingSystem.iOS) {\r\n // iOS does not interchange these values when the orientation changes!\r\n //width = util.portraitView() ? screen.width : screen.height;\r\n width = window.innerWidth;\r\n } else if(this.targetDevice.OS == DeviceSpec.OperatingSystem.Android) {\r\n try {\r\n width=document.documentElement.clientWidth;\r\n } catch(ex) {\r\n width=screen.availWidth;\r\n }\r\n } else {\r\n width=screen.width;\r\n }\r\n\r\n return width;\r\n }\r\n\r\n /**\r\n * Allow UI to update OSK position and properties\r\n *\r\n * @param {Object=} p object with coordinates and userdefined flag\r\n *\r\n */\r\n doResizeMove(p?) {\r\n this.legacyEvents.callEvent('resizemove', p);\r\n }\r\n\r\n /**\r\n * Allow the UI or page to set the position and size of the OSK\r\n * and (optionally) override user repositioning or sizing\r\n *\r\n * @param {Object.} p Array object with position and size of OSK container\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/osk/setRect\r\n **/\r\n public setRect(p: OSKRect) {\r\n if(this._Box == null || this.targetDevice.formFactor != 'desktop') {\r\n return;\r\n }\r\n\r\n var b = this._Box, bs = b.style;\r\n if('left' in p) {\r\n this.x = p['left'] - getAbsoluteX(b) + b.offsetLeft;\r\n bs.left= this.x + 'px';\r\n this.dfltX=bs.left;\r\n }\r\n\r\n if('top' in p) {\r\n this.y = p['top'] - getAbsoluteY(b) + b.offsetTop;\r\n bs.top = this.y + 'px';\r\n this.dfltY=bs.top;\r\n }\r\n\r\n //Do not allow user resizing for non-standard keyboards (e.g. EuroLatin)\r\n if(this.vkbd != null) {\r\n var d=this.vkbd.kbdDiv, ds=d.style;\r\n\r\n // Set width, but limit to reasonable value\r\n if('width' in p) {\r\n var w=(p['width']-(b.offsetWidth-d.offsetWidth));\r\n if(w < 0.2*screen.width) {\r\n w=0.2*screen.width;\r\n }\r\n if(w > 0.9*screen.width) {\r\n w=0.9*screen.width;\r\n }\r\n ds.width=w+'px';\r\n // Use of the `computed` variant is here temporary.\r\n // Shouldn't use `setSize` for this in the long-term.\r\n this.setSize(w, this.computedHeight, true);\r\n }\r\n\r\n // Set height, but limit to reasonable value\r\n // This sets the default font size for the OSK in px, but that\r\n // can be modified at the key text level by setting\r\n // the font size in em in the kmw-key-text class\r\n if('height' in p) {\r\n var h=(p['height']-(b.offsetHeight-d.offsetHeight));\r\n if(h < 0.1*screen.height) {\r\n h=0.1*screen.height;\r\n }\r\n if(h > 0.5*screen.height) {\r\n h=0.5*screen.height;\r\n }\r\n ds.height=h+'px'; ds.fontSize=(h/8)+'px';\r\n // Use of the `computed` variant is here temporary.\r\n // Shouldn't use `setSize` for this in the long-term.\r\n this.setSize(this.computedWidth, h, true);\r\n }\r\n\r\n // Fix or release user resizing\r\n if('nosize' in p) {\r\n this.resizingEnabled = !p['nosize'];\r\n }\r\n\r\n }\r\n // Fix or release user dragging\r\n if('nomove' in p) {\r\n this.noDrag=p['nomove'];\r\n this.movementEnabled = !this.noDrag;\r\n }\r\n // Save the user-defined OSK size\r\n this.savePersistedLayout();\r\n }\r\n\r\n /**\r\n * Get position of OSK window\r\n *\r\n * @return {Object.} Array object with OSK window position\r\n **/\r\n getPos(): OSKPos {\r\n var Lkbd=this._Box, p={\r\n left: this._Visible ? Lkbd.offsetLeft : this.x,\r\n top: this._Visible ? Lkbd.offsetTop : this.y\r\n };\r\n\r\n return p;\r\n }\r\n\r\n /**\r\n * Function setPos\r\n * Scope Private\r\n * @param {Object.} p Array object with OSK left, top\r\n * Description Set position of OSK window, but limit to screen\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/osk/setPos\r\n */\r\n public setPos(p: OSKPos) {\r\n if(typeof(this._Box) == 'undefined') {\r\n return; // I3363 (Build 301)\r\n }\r\n\r\n if(this.userPositioned) {\r\n var Px=p['left'], Py=p['top'];\r\n\r\n if(typeof(Px) != 'undefined') {\r\n if(Px < -0.8*this._Box.offsetWidth) {\r\n Px = -0.8*this._Box.offsetWidth;\r\n }\r\n if(this.userPositioned) {\r\n this._Box.style.left=Px+'px';\r\n this.x = Px;\r\n }\r\n }\r\n // May not be needed - vertical positioning is handled differently and defaults to input field if off screen\r\n if(typeof(Py) != 'undefined') {\r\n if(Py < 0) {\r\n Py = 0;\r\n }\r\n\r\n if(this.userPositioned) {\r\n this._Box.style.top=Py+'px';\r\n this.y = Py;\r\n }\r\n }\r\n }\r\n\r\n this.titleBar.showPin(this.userPositioned);\r\n }\r\n\r\n public setDisplayPositioning() {\r\n var Ls = this._Box.style;\r\n\r\n Ls.position='absolute';\r\n // Keep it hidden if not currently displayed.\r\n if(this.activationModel.activate) {\r\n Ls.display='block'; //Ls.visibility='visible';\r\n }\r\n Ls.left='0px';\r\n if(this.specifiedPosition || this.userPositioned) {\r\n Ls.left = this.x+'px';\r\n Ls.top = this.y+'px';\r\n } else {\r\n let el: HTMLElement = this.typedActivationModel.activationTrigger || null;\r\n\r\n if(this.dfltX) {\r\n Ls.left=this.dfltX;\r\n } else if(typeof el != 'undefined' && el != null) {\r\n Ls.left=getAbsoluteX(el) + 'px';\r\n }\r\n\r\n if(this.dfltY) {\r\n Ls.top=this.dfltY;\r\n } else if(typeof el != 'undefined' && el != null) {\r\n Ls.top=(getAbsoluteY(el) + el.offsetHeight)+'px';\r\n }\r\n }\r\n\r\n // Unset the flag, keeping 'specified position' specific to single\r\n // presentAtPosition calls.\r\n this.specifiedPosition = false;\r\n }\r\n\r\n /**\r\n * Display KMW OSK at specified position (returns nothing)\r\n *\r\n * @param {number=} Px x-coordinate for OSK rectangle\r\n * @param {number=} Py y-coordinate for OSK rectangle\r\n */\r\n presentAtPosition(Px?: number, Py?: number) {\r\n if(!this.mayShow()) {\r\n return;\r\n }\r\n\r\n this.specifiedPosition = Px >= 0 || Py >= 0; //probably never happens, legacy support only\r\n if(this.specifiedPosition) {\r\n this.x = Px;\r\n this.y = Py;\r\n }\r\n\r\n // Combines the two paths with set positioning.\r\n this.specifiedPosition = this.specifiedPosition || this.userPositioned;\r\n\r\n this.present();\r\n }\r\n\r\n present() {\r\n if(!this.mayShow()) {\r\n return;\r\n }\r\n\r\n this.titleBar.showPin(this.userPositioned);\r\n\r\n super.present();\r\n\r\n // Allow desktop UI to execute code when showing the OSK\r\n var Lpos={};\r\n Lpos['x']=this._Box.offsetLeft;\r\n Lpos['y']=this._Box.offsetTop;\r\n Lpos['userLocated']=this.userPositioned;\r\n this.doShow(Lpos);\r\n }\r\n\r\n public startHide(hiddenByUser: boolean) {\r\n super.startHide(hiddenByUser);\r\n\r\n if(hiddenByUser) {\r\n this.savePersistedLayout(); // Save current OSK state, size and position (desktop only)\r\n }\r\n }\r\n\r\n ['show'](bShow?: boolean) {\r\n if(bShow !== undefined) {\r\n super['show'](bShow);\r\n } else {\r\n super['show']();\r\n }\r\n this.savePersistedLayout();\r\n }\r\n\r\n /**\r\n * Function userPositioned\r\n * Scope Public\r\n * @return {(boolean|number)} true if user located\r\n * Description Test if OSK window has been repositioned by user\r\n *\r\n * See https://help.keyman.com/developer/engine/web/current-version/reference/osk/userLocated\r\n */\r\n public userLocated() {\r\n return this.userPositioned;\r\n }\r\n\r\n public get movementEnabled(): boolean {\r\n return this.titleDragHandler.enabled;\r\n }\r\n\r\n public set movementEnabled(flag: boolean) {\r\n this.titleDragHandler.enabled = flag;\r\n this.titleBar.showPin(flag && this.userPositioned);\r\n }\r\n\r\n public get resizingEnabled(): boolean {\r\n return this.resizeDragHandler.enabled;\r\n }\r\n\r\n public set resizingEnabled(flag: boolean) {\r\n this.resizeDragHandler.enabled = flag;\r\n this.resizeBar.allowResizing(flag);\r\n }\r\n\r\n public get isBeingMoved(): boolean {\r\n return this.titleDragHandler.isActive;\r\n }\r\n\r\n public get isBeingResized(): boolean {\r\n return this.resizeDragHandler.isActive;\r\n }\r\n\r\n private enableMoveResizeHandlers() {\r\n this.titleDragHandler.enabled = !this.noDrag;\r\n this.resizeDragHandler.enabled = true; // by default.\r\n }\r\n\r\n private get titleDragHandler(): MouseDragOperation {\r\n const _this = this;\r\n\r\n if(this._moveHandler) {\r\n return this._moveHandler;\r\n }\r\n\r\n this._moveHandler = new class extends MouseDragOperation {\r\n startX: number;\r\n startY: number;\r\n\r\n dragPromise: ManagedPromise;\r\n\r\n constructor() {\r\n super('move'); // The type of cursor to use while 'active'.\r\n }\r\n\r\n onDragStart() {\r\n this.startX = _this._Box.offsetLeft;\r\n this.startY = _this._Box.offsetTop;\r\n\r\n if(_this.activeKeyboard.keyboard.isCJK) {\r\n _this.titleBar.setPinCJKOffset();\r\n }\r\n\r\n if(this.dragPromise) {\r\n // We got interrupted during the previous one; allow it to reset, at least!\r\n this.dragPromise.resolve();\r\n }\r\n\r\n this.dragPromise = new ManagedPromise();\r\n _this.emit('dragmove', this.dragPromise.corePromise);\r\n }\r\n\r\n onDragMove(cumulativeX: number, cumulativeY: number) {\r\n _this.titleBar.showPin(true);\r\n _this.userPositioned = true;\r\n\r\n _this._Box.style.left = (this.startX + cumulativeX) + 'px';\r\n _this._Box.style.top = (this.startY + cumulativeY) + 'px';\r\n\r\n var r=_this.getRect();\r\n _this.setSize(r.width, r.height, true);\r\n _this.x = r.left;\r\n _this.y = r.top;\r\n }\r\n\r\n onDragRelease() {\r\n if(_this.vkbd) {\r\n _this.vkbd.currentKey=null;\r\n }\r\n\r\n this.dragPromise.resolve();\r\n\r\n // Remainder should be done after anything else pending on the Promise.\r\n this.dragPromise.then(() => {\r\n _this.userPositioned = true;\r\n _this.doResizeMove();\r\n _this.savePersistedLayout();\r\n });\r\n this.dragPromise = null;\r\n }\r\n }\r\n\r\n return this._moveHandler;\r\n }\r\n\r\n private get resizeDragHandler(): MouseDragOperation {\r\n const _this = this;\r\n\r\n if(this._resizeHandler) {\r\n return this._resizeHandler;\r\n }\r\n\r\n this._resizeHandler = new class extends MouseDragOperation {\r\n startWidth: number;\r\n startHeight: number;\r\n\r\n dragPromise: ManagedPromise;\r\n\r\n constructor() {\r\n super('se-resize'); // The type of cursor to use while 'active'.\r\n }\r\n\r\n onDragStart() {\r\n this.startWidth = _this.computedWidth;\r\n this.startHeight = _this.computedHeight;\r\n\r\n if(this.dragPromise) {\r\n // We got interrupted during the previous one; allow it to reset, at least!\r\n this.dragPromise.resolve();\r\n }\r\n\r\n this.dragPromise = new ManagedPromise();\r\n _this.emit('resizemove', this.dragPromise.corePromise);\r\n }\r\n\r\n onDragMove(cumulativeX: number, cumulativeY: number) {\r\n let newWidth = this.startWidth + cumulativeX;\r\n let newHeight = this.startHeight + cumulativeY;\r\n\r\n // Set the smallest and largest OSK size\r\n if(newWidth < 0.2*screen.width) {\r\n newWidth = 0.2*screen.width;\r\n }\r\n if(newHeight < 0.1*screen.height) {\r\n newHeight = 0.1*screen.height;\r\n }\r\n if(newWidth > 0.9*screen.width) {\r\n newWidth = 0.9*screen.width;\r\n }\r\n if(newHeight > 0.5*screen.height) {\r\n newHeight = 0.5*screen.height;\r\n }\r\n\r\n // Explicitly set OSK width, height, and font size - cannot safely rely on scaling from font\r\n _this.setSize(newWidth, newHeight, true);\r\n }\r\n\r\n onDragRelease() {\r\n if(_this.vkbd) {\r\n _this.vkbd.currentKey=null;\r\n }\r\n\r\n if(_this.vkbd) {\r\n this.startWidth = _this.computedWidth;\r\n this.startHeight = _this.computedHeight;\r\n }\r\n\r\n _this.refreshLayout(); // Finalize the resize.\r\n\r\n this.dragPromise.resolve();\r\n\r\n // Remainder should be done after anything else pending on the Promise.\r\n this.dragPromise.then(() => {\r\n _this.doResizeMove();\r\n _this.savePersistedLayout();\r\n });\r\n this.dragPromise = null;\r\n }\r\n }\r\n\r\n return this._resizeHandler;\r\n }\r\n}\r\n", + "import { Codes, DeviceSpec } from '@keymanapp/keyboard-processor';\r\nimport { landscapeView } from 'keyman/engine/dom-utils';\r\n\r\nimport OSKView, { OSKPos, OSKRect } from './oskView.js';\r\nimport { getViewportScale } from '../screenUtils.js';\r\nimport Configuration from '../config/viewConfiguration.js';\r\nimport { StaticActivator } from './activator.js';\r\nimport TwoStateActivator from './twoStateActivator.js';\r\n\r\n/***\r\n KeymanWeb 10.0\r\n Copyright 2017 SIL International\r\n***/\r\n\r\nexport default class AnchoredOSKView extends OSKView {\r\n\r\n // OSK positioning fields\r\n x: number;\r\n y: number;\r\n\r\n private isResizing: boolean = false;\r\n\r\n public constructor(config: Configuration) {\r\n if(config.isEmbedded) {\r\n config.activator = config.activator || new StaticActivator();\r\n } else {\r\n config.activator = config.activator || new TwoStateActivator();\r\n }\r\n super(config);\r\n\r\n document.body.appendChild(this._Box);\r\n\r\n }\r\n\r\n /**\r\n * Function _Unload\r\n * Scope Private\r\n * Description Clears OSK variables prior to exit (JMD 1.9.1 - relocation of local variables 3/9/10)\r\n */\r\n _Unload() {\r\n this.keyboardView = null;\r\n this.bannerView = null;\r\n this._Box = null;\r\n }\r\n\r\n protected setBoxStyling() {\r\n const s = this._Box.style;\r\n\r\n s.zIndex = '9999';\r\n s.display = 'none';\r\n s.width = '100%';\r\n s.position = 'fixed';\r\n }\r\n\r\n /**\r\n * @override\r\n */\r\n public refreshLayout(pending?: boolean): void {\r\n // This function is generally triggered whenever the OSK's dimensions change, among other\r\n // things.\r\n if(this.isResizing) {\r\n return;\r\n }\r\n\r\n try {\r\n this.isResizing = true;\r\n // This resizes the OSK to what is appropriate for the device's current orientation,\r\n // which will often trigger a resize event... which in turn triggers a layout refresh.\r\n //\r\n // So, we mark and unmark the `isResizing` flag to prevent triggering a circular\r\n // call-stack chain from this call.\r\n this.doResize();\r\n } finally {\r\n this.isResizing = false;\r\n }\r\n super.refreshLayout(pending);\r\n }\r\n\r\n protected doResize() {\r\n if(this.vkbd) {\r\n let targetOSKHeight = this.getDefaultKeyboardHeight();\r\n this.setSize(this.getDefaultWidth(), targetOSKHeight + this.banner.height);\r\n }\r\n }\r\n\r\n protected postKeyboardAdjustments() {\r\n // Initializes the size of a touch keyboard.\r\n this.doResize();\r\n }\r\n\r\n /**\r\n * Function restorePosition\r\n * Scope Public\r\n * @param {boolean?} keepDefaultPosition If true, does not reset the default x,y set by `setRect`.\r\n * If false or omitted, resets the default x,y as well.\r\n * Description Move OSK back to default position, floating under active input element\r\n */\r\n ['restorePosition']: (keepDefaultPosition?: boolean) => void = function(this: AnchoredOSKView, keepDefaultPosition?: boolean) {\r\n return;\r\n }.bind(this);\r\n\r\n /**\r\n * Get the wanted height of the OSK for touch devices (does not include banner height)\r\n * @return {number} height in pixels\r\n **/\r\n getDefaultKeyboardHeight(): number {\r\n let device = this.targetDevice;\r\n\r\n // KeymanTouch - get OSK height from device\r\n if(this.configuration.heightOverride) {\r\n return this.configuration.heightOverride();\r\n }\r\n\r\n /*\r\n * We've noticed some fairly inconsistent behavior in the past when attempting to base\r\n * this logic on window.innerWidth/Height, as there can be very unexpected behavior\r\n * on mobile devices during and after rotation.\r\n *\r\n * Online forums (such as https://stackoverflow.com/a/54812656) seem to indicate that\r\n * document.documentElement.clientWidth/Height seem to be the most stable analogues\r\n * to a window's size in the situations where it matters for Keyman Engine for Web.\r\n *\r\n * That said, an important note: this gets the dimensions of the _document element_,\r\n * not the screen or even the window.\r\n */\r\n let baseWidth = document?.documentElement?.clientWidth;\r\n let baseHeight = document?.documentElement?.clientHeight;\r\n if(typeof baseWidth == 'undefined') {\r\n /*\r\n * Fallback logic. We _shouldn't_ need this, but it's best to have _something_\r\n * for the sake of robustness.\r\n */\r\n baseWidth = Math.min(screen.height, screen.width);\r\n baseHeight = Math.max(screen.height, screen.width);\r\n\r\n if(landscapeView()) {\r\n let temp = baseWidth;\r\n baseWidth = baseHeight;\r\n baseHeight = temp;\r\n }\r\n }\r\n\r\n var oskHeightLandscapeView=Math.floor(Math.min(baseHeight, baseWidth)/2),\r\n height=oskHeightLandscapeView;\r\n\r\n if(device.formFactor == 'phone') {\r\n /**\r\n * Assuming the first-pass detection of width and height work correctly, note\r\n * that these calculations are based on the document's size, not the device's\r\n * resolution. This _particularly_ matters for height.\r\n *\r\n * - Is the mobile-device browser showing a URL bar? That's not included.\r\n * - The standard signal-strength, battery-strength, etc device status bar?\r\n * Also not included.\r\n */\r\n if(!landscapeView())\r\n height=Math.floor(baseHeight/2.4);\r\n else\r\n height=Math.floor(baseHeight/1.6); //adjust for aspect ratio, increase slightly for iPhone 5\r\n }\r\n\r\n // Correct for viewport scaling (iOS - Android 4.2 does not want this, at least on Galaxy Tab 3))\r\n if(this.targetDevice.OS == DeviceSpec.OperatingSystem.iOS) {\r\n height=height/getViewportScale();\r\n }\r\n\r\n return height;\r\n }\r\n\r\n /**\r\n * Get the wanted width of the OSK for touch devices\r\n *\r\n * @return {number} height in pixels\r\n **/\r\n getDefaultWidth(): number {\r\n let device = this.targetDevice;\r\n\r\n // KeymanTouch - get OSK height from device\r\n if(this.configuration.widthOverride) {\r\n return this.configuration.widthOverride();\r\n }\r\n\r\n var width: number;\r\n\r\n width = document?.documentElement?.clientWidth;\r\n if(typeof width == 'undefined') {\r\n if(this.targetDevice.OS == DeviceSpec.OperatingSystem.iOS) {\r\n width = window.innerWidth;\r\n } else if(device.OS == DeviceSpec.OperatingSystem.Android) {\r\n width=screen.availWidth;\r\n } else {\r\n width=screen.width;\r\n }\r\n }\r\n\r\n return width;\r\n }\r\n\r\n /**\r\n * Allow the UI or page to set the position and size of the OSK\r\n * and (optionally) override user repositioning or sizing\r\n *\r\n * @param {Object.} p Array object with position and size of OSK container\r\n **/\r\n ['setRect'](p: OSKRect) {\r\n return;\r\n }\r\n\r\n /**\r\n * Get position of OSK window\r\n *\r\n * @return {Object.} Array object with OSK window position\r\n **/\r\n getPos(): OSKPos {\r\n var Lkbd=this._Box, p={\r\n left: this._Visible ? Lkbd.offsetLeft : this.x,\r\n top: this._Visible ? Lkbd.offsetTop : this.y\r\n };\r\n\r\n return p;\r\n }\r\n\r\n /**\r\n * Function setPos\r\n * Scope Private\r\n * @param {Object.} p Array object with OSK left, top\r\n * Description Set position of OSK window, but limit to screen, and ignore if a touch input device\r\n */\r\n ['setPos'](p: OSKPos) {\r\n return; // I3363 (Build 301)\r\n }\r\n\r\n protected setDisplayPositioning() {\r\n let Ls = this._Box.style;\r\n\r\n // The following code will always be executed except for externally created OSK such as EuroLatin\r\n if(this.vkbd) {\r\n Ls.position='fixed';\r\n Ls.left=Ls.bottom='0px';\r\n Ls.border='none';\r\n Ls.borderTop='1px solid gray';\r\n }\r\n }\r\n\r\n public present() {\r\n super.present();\r\n this.legacyEvents.callEvent('show', {});\r\n }\r\n}\r\n", + "import Activator from './activator.js';\r\n\r\nexport default class SimpleActivator extends Activator {\r\n private flag: boolean = true;\r\n\r\n get enabled(): boolean {\r\n return this.flag;\r\n }\r\n\r\n set enabled(value: boolean) {\r\n // Enabled + activated are the same thing for this class.\r\n this.activate = value;\r\n }\r\n\r\n get activate(): boolean {\r\n return this.flag;\r\n }\r\n\r\n set activate(value: boolean) {\r\n if(this.flag != value) {\r\n this.flag = value;\r\n this.emit('activate', value);\r\n }\r\n }\r\n\r\n get conditionsMet(): boolean {\r\n return true;\r\n }\r\n}", + "import { Codes, DeviceSpec } from '@keymanapp/keyboard-processor';\r\n\r\nimport OSKView, { OSKPos, OSKRect } from './oskView.js';\r\nimport VisualKeyboard from '../visualKeyboard.js';\r\nimport Configuration from '../config/viewConfiguration.js';\r\nimport SimpleActivator from './simpleActivator.js';\r\n\r\n/*\r\n * Keyman is copyright (c) SIL International. MIT License.\r\n */\r\n\r\n/**\r\n * Defines a version of the OSK that produces an element designed for site-controlled\r\n * insertion into the DOM. Rather than \"floating\" over the page, this version is inlined\r\n * as part of the host page's layout.\r\n */\r\nexport default class InlinedOSKView extends OSKView {\r\n public constructor(config: Configuration) {\r\n config.activator = config.activator || new SimpleActivator();\r\n super(config);\r\n }\r\n\r\n public get element(): HTMLDivElement {\r\n return this._Box;\r\n }\r\n\r\n /**\r\n * Clears OSK variables prior to exit (JMD 1.9.1 - relocation of local variables 3/9/10)\r\n *\r\n * This should probably be merged or incorporated into the `shutdown` method at some point.\r\n */\r\n _Unload() {\r\n this.keyboardView = null;\r\n this.bannerView = null;\r\n this._Box = null;\r\n }\r\n\r\n protected setBoxStyling() {\r\n const s = this._Box.style;\r\n s.display = 'none';\r\n // Positioned with no relative offset from its default position.\r\n // This allows _Box to still serve as an offsetParent for keytip & subkey menu positioning.\r\n s.position = 'relative';\r\n }\r\n\r\n protected postKeyboardAdjustments() {\r\n }\r\n\r\n /**\r\n * Moves the OSK back to default position, floating under active input element\r\n *\r\n * Is a long-published API intended solely for use with the FloatingOSKView use pattern.\r\n * @param keepDefaultPosition If true, does not reset the default x,y set by `setRect`.\r\n * If false or omitted, resets the default x,y as well.\r\n */\r\n ['restorePosition']: (keepDefaultPosition?: boolean) => void = function(this: InlinedOSKView, keepDefaultPosition?: boolean) {\r\n return;\r\n }.bind(this);\r\n\r\n /**\r\n * Get the default height for the OSK\r\n * @return height in pixels\r\n **/\r\n getDefaultKeyboardHeight(): number {\r\n if(this.keyboardView instanceof VisualKeyboard) {\r\n return this.keyboardView.height;\r\n } else {\r\n // Should probably refine, but it's a decent stopgap.\r\n return this.computedHeight;\r\n }\r\n }\r\n\r\n /**\r\n * Get the default width for the OSK\r\n * @return width in pixels\r\n **/\r\n getDefaultWidth(): number {\r\n return this.computedWidth;\r\n }\r\n\r\n /**\r\n * Allow the UI or page to set the position and size of the OSK\r\n * and (optionally) override user repositioning or sizing\r\n *\r\n * Designed solely for use with the FloatingOSKView use pattern, but is a\r\n * long-standing API endpoint that needs preservation.\r\n *\r\n * @param p Array object with position and size of OSK container\r\n **/\r\n ['setRect'](p: OSKRect) {\r\n return;\r\n }\r\n\r\n /**\r\n * Get position of OSK window\r\n *\r\n * @return Array object with OSK window position\r\n **/\r\n getPos(): OSKPos {\r\n var Lkbd=this._Box, p={\r\n left: this._Visible ? Lkbd.offsetLeft : undefined,\r\n top: this._Visible ? Lkbd.offsetTop : undefined\r\n };\r\n\r\n return p;\r\n }\r\n\r\n /**\r\n * Set position of OSK window, but limited to the screen.\r\n *\r\n * Designed solely for use with the FloatingOSKView use pattern, but is a\r\n * long-standing API endpoint that needs preservation.\r\n * @param p Array object with OSK left, top\r\n */\r\n ['setPos'](p: OSKPos) {\r\n return; // I3363 (Build 301)\r\n }\r\n\r\n public present() {\r\n super.present();\r\n\r\n this.legacyEvents.callEvent('show', {});\r\n }\r\n\r\n protected setDisplayPositioning() {\r\n // no-op; an inlined OSK cannot control its own positioning.\r\n }\r\n\r\n protected allowsDeviceChange(newSpec: DeviceSpec): boolean {\r\n return true;\r\n }\r\n}\r\n", + "import {\r\n ViewConfiguration,\r\n AnchoredOSKView,\r\n FloatingOSKView,\r\n FloatingOSKViewConfiguration,\r\n InlinedOSKView\r\n} from \"keyman/engine/osk\";\r\nimport KeymanEngine from \"./keymanEngine.js\";\r\n\r\nfunction buildBaseOskConfiguration(engine: KeymanEngine) {\r\n return {\r\n hostDevice: engine.config.hostDevice,\r\n pathConfig: engine.config.paths,\r\n predictionContextManager: engine.contextManager.predictionContext,\r\n isEmbedded: false\r\n };\r\n};\r\n\r\nclass PublishedAnchoredOSKView extends AnchoredOSKView {\r\n constructor(engine: KeymanEngine, config?: ViewConfiguration) {\r\n let finalConfig = {\r\n ...buildBaseOskConfiguration(engine),\r\n ...(config || {})\r\n };\r\n\r\n super(finalConfig);\r\n }\r\n}\r\n\r\nclass PublishedFloatingOSKView extends FloatingOSKView {\r\n constructor(engine: KeymanEngine, config?: FloatingOSKViewConfiguration) {\r\n let finalConfig: FloatingOSKViewConfiguration = {\r\n ...buildBaseOskConfiguration(engine),\r\n ...(config || {})\r\n };\r\n\r\n super(finalConfig);\r\n }\r\n}\r\n\r\nclass PublishedInlineOSKView extends InlinedOSKView {\r\n constructor(engine: KeymanEngine, config?: ViewConfiguration) {\r\n let finalConfig: ViewConfiguration = {\r\n ...buildBaseOskConfiguration(engine),\r\n ...(config || {})\r\n };\r\n\r\n super(finalConfig);\r\n }\r\n}\r\n\r\nexport { PublishedAnchoredOSKView as AnchoredOSKView };\r\nexport { PublishedFloatingOSKView as FloatingOSKView };\r\nexport { PublishedInlineOSKView as InlinedOSKView };\r\n\r\n", + "import { OutputTarget as OutputTargetBase } from \"@keymanapp/keyboard-processor\";\r\nimport EventEmitter from 'eventemitter3';\r\n\r\nexport default abstract class OutputTarget extends OutputTargetBase {\r\n // JS/TS can't do true multiple inheritance, so we maintain class events on a readonly field.\r\n public readonly events: EventEmitter = new EventEmitter();\r\n\r\n /**\r\n * A field that may be used to track whether or not the represented context has changed over an\r\n * arbitrary period of time.\r\n */\r\n public changed = false;\r\n\r\n /**\r\n * Returns the underlying element / document modeled by the wrapper.\r\n */\r\n abstract getElement(): HTMLElement;\r\n\r\n public focus(): void {\r\n const ele = this.getElement();\r\n if(ele.focus) {\r\n ele.focus();\r\n }\r\n }\r\n\r\n /**\r\n * Denotes when the represented element is forcing a text scroll via focus manipulation.\r\n * As the intent is not to change the focused element, but just to have the browser update\r\n * the scroll location, standard focus handlers (for updating the active context) should\r\n * not deactivate the element while this state is active.\r\n */\r\n isForcingScroll(): boolean {\r\n return false;\r\n }\r\n\r\n /**\r\n * A helper method for doInputEvent; creates a simple common event and default dispatching.\r\n * @param elem\r\n */\r\n protected dispatchInputEventOn(elem: HTMLElement) {\r\n let event: InputEvent;\r\n\r\n // `undefined` in pre-Chrome Edge and Chrome for Android before version 60.\r\n if(window['InputEvent']) { // can't condition on the type directly; TS optimizes that out.\r\n event = new InputEvent('input', {\"bubbles\": true, \"cancelable\": false});\r\n }\r\n\r\n if(elem && event) {\r\n elem.dispatchEvent(event);\r\n }\r\n }\r\n}", + "import OutputTarget from './outputTarget.js';\r\n\r\ninterface EventMap {\r\n /**\r\n * This event will be raised when a newline is received by wrapped elements not of\r\n * the 'search' or 'submit' types.\r\n *\r\n * Original code this is replacing:\r\n ```\r\n // Allows compiling this separately from the main body of KMW.\r\n // TODO: rework class to accept a class-static 'callback' from the DOM module that this can call.\r\n // Would eliminate the need for this 'static' reference.\r\n // Only strongly matters once we better modularize KMW, with web-dom vs web-dom-targets vs web-core, etc.\r\n if(com.keyman[\"singleton\"]) {\r\n com.keyman[\"singleton\"].domManager.moveToNext(false);\r\n }\r\n ```\r\n * This does not belong in a modularized version of this class; it must be supplied\r\n * by the consuming top-level products instead.\r\n */\r\n 'unhandlednewline': (element: HTMLInputElement) => void\r\n}\r\n\r\nexport default class Input extends OutputTarget {\r\n root: HTMLInputElement;\r\n\r\n /**\r\n * Tracks the most recently-cached selection start index.\r\n */\r\n private _cachedSelectionStart: number\r\n\r\n /**\r\n * Tracks the most recently processed, extended-string-based selection start index.\r\n * When the element's selectionStart value changes, this should be invalidated.\r\n */\r\n private processedSelectionStart: number;\r\n\r\n /**\r\n * Tracks the most recently processed, extended-string-based selection end index.\r\n * When the element's selectionEnd value changes, this should be invalidated.\r\n */\r\n private processedSelectionEnd: number;\r\n\r\n /**\r\n * Set, then unset within the `forceScroll` method in order to facilitate the\r\n * `isForcingScroll` flag.\r\n */\r\n private _activeForcedScroll: boolean;\r\n\r\n constructor(ele: HTMLInputElement) {\r\n super();\r\n\r\n this.root = ele;\r\n this._cachedSelectionStart = -1;\r\n }\r\n\r\n get isSynthetic(): boolean {\r\n return false;\r\n }\r\n\r\n getElement(): HTMLInputElement {\r\n return this.root;\r\n }\r\n\r\n clearSelection(): void {\r\n // Processes our codepoint-based variants of selectionStart and selectionEnd.\r\n this.getCaret(); // updates processedSelectionStart if required\r\n this.root.value = this.root.value._kmwSubstring(0, this.processedSelectionStart) + this.root.value._kmwSubstring(this.processedSelectionEnd); //I3319\r\n\r\n this.setCaret(this.processedSelectionStart);\r\n }\r\n\r\n isSelectionEmpty(): boolean {\r\n return this.root.selectionStart == this.root.selectionEnd;\r\n }\r\n\r\n hasSelection(): boolean {\r\n return true;\r\n }\r\n\r\n invalidateSelection() {\r\n // Since .selectionStart will never return this value, we use it to indicate\r\n // the need to refresh our processed indices.\r\n this._cachedSelectionStart = -1;\r\n }\r\n\r\n getCaret(): number {\r\n if(this.root.selectionStart != this._cachedSelectionStart) {\r\n this._cachedSelectionStart = this.root.selectionStart; // KMW-1\r\n this.processedSelectionStart = this.root.value._kmwCodeUnitToCodePoint(this.root.selectionStart); // I3319\r\n this.processedSelectionEnd = this.root.value._kmwCodeUnitToCodePoint(this.root.selectionEnd); // I3319\r\n }\r\n return this.root.selectionDirection == 'forward' ? this.processedSelectionEnd : this.processedSelectionStart;\r\n }\r\n\r\n getDeadkeyCaret(): number {\r\n return this.getCaret();\r\n }\r\n\r\n setCaret(caret: number) {\r\n this.setSelection(caret, caret, \"none\");\r\n }\r\n\r\n setSelection(start: number, end: number, direction: \"forward\" | \"backward\" | \"none\") {\r\n let domStart = this.root.value._kmwCodePointToCodeUnit(start);\r\n let domEnd = this.root.value._kmwCodePointToCodeUnit(end);\r\n this.root.setSelectionRange(domStart, domEnd, direction);\r\n\r\n this.processedSelectionStart = start;\r\n this.processedSelectionEnd = end;\r\n\r\n this.forceScroll();\r\n\r\n this.root.setSelectionRange(domStart, domEnd, direction);\r\n }\r\n\r\n forceScroll() {\r\n // Only executes when com.keyman.DOMEventHandlers is defined.\r\n //\r\n // We bypass this whenever operating in the embedded format.\r\n const element = this.getElement();\r\n\r\n let selectionStart = element.selectionStart;\r\n let selectionEnd = element.selectionEnd;\r\n\r\n this._activeForcedScroll = true;\r\n\r\n try {\r\n //Forces scrolling; the re-focus triggers the scroll, at least.\r\n element.blur();\r\n element.focus();\r\n } finally {\r\n // On Edge, it appears that the blur/focus combination will reset the caret position\r\n // under certain scenarios during unit tests. So, we re-set it afterward.\r\n element.selectionStart = selectionStart;\r\n element.selectionEnd = selectionEnd;\r\n this._activeForcedScroll = false;\r\n }\r\n }\r\n\r\n isForcingScroll(): boolean {\r\n return this._activeForcedScroll;\r\n }\r\n\r\n getSelectionDirection(): \"forward\" | \"backward\" | \"none\" {\r\n return this.root.selectionDirection;\r\n }\r\n\r\n getTextBeforeCaret(): string {\r\n this.getCaret();\r\n return this.getText()._kmwSubstring(0, this.processedSelectionStart);\r\n }\r\n\r\n getSelectedText(): string {\r\n this.getCaret();\r\n return this.getText()._kmwSubstring(this.processedSelectionStart, this.processedSelectionEnd);\r\n }\r\n\r\n setTextBeforeCaret(text: string) {\r\n this.getCaret();\r\n let selectionLength = this.processedSelectionEnd - this.processedSelectionStart;\r\n let direction = this.getSelectionDirection();\r\n let newCaret = text._kmwLength();\r\n this.root.value = text + this.getText()._kmwSubstring(this.processedSelectionStart);\r\n\r\n this.setSelection(newCaret, newCaret + selectionLength, direction);\r\n }\r\n\r\n protected setTextAfterCaret(s: string) {\r\n let c = this.getCaret();\r\n let direction = this.getSelectionDirection();\r\n\r\n this.root.value = this.getTextBeforeCaret() + s;\r\n this.setSelection(this.processedSelectionStart, this.processedSelectionEnd, direction);\r\n }\r\n\r\n getTextAfterCaret(): string {\r\n this.getCaret();\r\n return this.getText()._kmwSubstring(this.processedSelectionEnd);\r\n }\r\n\r\n getText(): string {\r\n return this.root.value;\r\n }\r\n\r\n deleteCharsBeforeCaret(dn: number) {\r\n if(dn > 0) {\r\n let curText = this.getTextBeforeCaret();\r\n let caret = this.processedSelectionStart;\r\n\r\n if(dn > caret) {\r\n dn = caret;\r\n }\r\n\r\n this.adjustDeadkeys(-dn);\r\n this.setTextBeforeCaret(curText.kmwSubstring(0, caret - dn));\r\n this.setCaret(caret - dn);\r\n }\r\n }\r\n\r\n insertTextBeforeCaret(s: string) {\r\n if(!s) {\r\n return;\r\n }\r\n\r\n let caret = this.getCaret();\r\n let front = this.getTextBeforeCaret();\r\n let back = this.getText()._kmwSubstring(this.processedSelectionStart);\r\n\r\n this.adjustDeadkeys(s._kmwLength());\r\n this.root.value = front + s + back;\r\n this.setCaret(caret + s._kmwLength());\r\n }\r\n\r\n handleNewlineAtCaret(): void {\r\n const inputEle = this.root;\r\n // Can't occur for Mocks - just Input types.\r\n if (inputEle && (inputEle.type == 'search' || inputEle.type == 'submit')) {\r\n inputEle.disabled=false;\r\n inputEle.form.submit();\r\n } else {\r\n this.events.emit('unhandlednewline', inputEle);\r\n }\r\n }\r\n\r\n doInputEvent() {\r\n this.dispatchInputEventOn(this.root);\r\n }\r\n}", + "import OutputTarget from './outputTarget.js';\r\n\r\nexport default class TextArea extends OutputTarget<{}> {\r\n root: HTMLTextAreaElement;\r\n\r\n /**\r\n * Tracks the most recently-cached selection start index.\r\n */\r\n private _cachedSelectionStart: number\r\n\r\n /**\r\n * Tracks the most recently processed, extended-string-based selection start index.\r\n * When the element's selectionStart value changes, this should be invalidated.\r\n */\r\n private processedSelectionStart: number;\r\n\r\n /**\r\n * Tracks the most recently processed, extended-string-based selection end index.\r\n * When the element's selectionEnd value changes, this should be invalidated.\r\n */\r\n private processedSelectionEnd: number;\r\n\r\n /**\r\n * Set, then unset within the `forceScroll` method in order to facilitate the\r\n * `isForcingScroll` flag.\r\n */\r\n private _activeForcedScroll: boolean;\r\n\r\n constructor(ele: HTMLTextAreaElement) {\r\n super();\r\n\r\n this.root = ele;\r\n this._cachedSelectionStart = -1;\r\n }\r\n\r\n get isSynthetic(): boolean {\r\n return false;\r\n }\r\n\r\n getElement(): HTMLTextAreaElement {\r\n return this.root;\r\n }\r\n\r\n clearSelection(): void {\r\n // Processes our codepoint-based variants of selectionStart and selectionEnd.\r\n this.getCaret(); // updates processedSelectionStart if required\r\n this.root.value = this.root.value._kmwSubstring(0, this.processedSelectionStart) + this.root.value._kmwSubstring(this.processedSelectionEnd); //I3319\r\n\r\n this.setCaret(this.processedSelectionStart);\r\n }\r\n\r\n isSelectionEmpty(): boolean {\r\n return this.root.selectionStart == this.root.selectionEnd;\r\n }\r\n\r\n hasSelection(): boolean {\r\n return true;\r\n }\r\n\r\n invalidateSelection() {\r\n // Since .selectionStart will never return this value, we use it to indicate\r\n // the need to refresh our processed indices.\r\n this._cachedSelectionStart = -1;\r\n }\r\n\r\n getCaret(): number {\r\n if(this.root.selectionStart != this._cachedSelectionStart) {\r\n this._cachedSelectionStart = this.root.selectionStart; // KMW-1\r\n this.processedSelectionStart = this.root.value._kmwCodeUnitToCodePoint(this.root.selectionStart); // I3319\r\n this.processedSelectionEnd = this.root.value._kmwCodeUnitToCodePoint(this.root.selectionEnd); // I3319\r\n }\r\n return this.root.selectionDirection == 'forward' ? this.processedSelectionEnd : this.processedSelectionStart;\r\n }\r\n\r\n getDeadkeyCaret(): number {\r\n return this.getCaret();\r\n }\r\n\r\n setCaret(caret: number) {\r\n this.setSelection(caret, caret, \"none\");\r\n }\r\n\r\n setSelection(start: number, end: number, direction: \"forward\" | \"backward\" | \"none\") {\r\n let domStart = this.root.value._kmwCodePointToCodeUnit(start);\r\n let domEnd = this.root.value._kmwCodePointToCodeUnit(end);\r\n this.root.setSelectionRange(domStart, domEnd, direction);\r\n\r\n this.processedSelectionStart = start;\r\n this.processedSelectionEnd = end;\r\n\r\n this.forceScroll();\r\n\r\n this.root.setSelectionRange(domStart, domEnd, direction);\r\n }\r\n\r\n forceScroll() {\r\n // Only executes when com.keyman.DOMEventHandlers is defined.\r\n //\r\n // We bypass this whenever operating in the embedded format.\r\n const element = this.getElement();\r\n\r\n let selectionStart = element.selectionStart;\r\n let selectionEnd = element.selectionEnd;\r\n\r\n this._activeForcedScroll = true;\r\n\r\n try {\r\n //Forces scrolling; the re-focus triggers the scroll, at least.\r\n element.blur();\r\n element.focus();\r\n } finally {\r\n // On Edge, it appears that the blur/focus combination will reset the caret position\r\n // under certain scenarios during unit tests. So, we re-set it afterward.\r\n element.selectionStart = selectionStart;\r\n element.selectionEnd = selectionEnd;\r\n this._activeForcedScroll = false;\r\n }\r\n }\r\n\r\n isForcingScroll(): boolean {\r\n return this._activeForcedScroll;\r\n }\r\n\r\n getSelectionDirection(): \"forward\" | \"backward\" | \"none\" {\r\n return this.root.selectionDirection;\r\n }\r\n\r\n getTextBeforeCaret(): string {\r\n this.getCaret();\r\n return this.getText()._kmwSubstring(0, this.processedSelectionStart);\r\n }\r\n\r\n setTextBeforeCaret(text: string) {\r\n this.getCaret();\r\n let selectionLength = this.processedSelectionEnd - this.processedSelectionStart;\r\n let direction = this.getSelectionDirection();\r\n let newCaret = text._kmwLength();\r\n this.root.value = text + this.getText()._kmwSubstring(this.processedSelectionStart);\r\n\r\n this.setSelection(newCaret, newCaret + selectionLength, direction);\r\n }\r\n\r\n protected setTextAfterCaret(s: string) {\r\n let c = this.getCaret();\r\n let direction = this.getSelectionDirection();\r\n\r\n this.root.value = this.getTextBeforeCaret() + s;\r\n this.setSelection(this.processedSelectionStart, this.processedSelectionEnd, direction);\r\n }\r\n\r\n getTextAfterCaret(): string {\r\n this.getCaret();\r\n return this.getText()._kmwSubstring(this.processedSelectionEnd);\r\n }\r\n\r\n getSelectedText(): string {\r\n this.getCaret();\r\n return this.getText()._kmwSubstring(this.processedSelectionStart, this.processedSelectionEnd);\r\n }\r\n\r\n getText(): string {\r\n return this.root.value;\r\n }\r\n\r\n deleteCharsBeforeCaret(dn: number) {\r\n if(dn > 0) {\r\n let curText = this.getTextBeforeCaret();\r\n let caret = this.processedSelectionStart;\r\n\r\n if(dn > caret) {\r\n dn = caret;\r\n }\r\n\r\n this.adjustDeadkeys(-dn);\r\n this.setTextBeforeCaret(curText.kmwSubstring(0, caret - dn));\r\n this.setCaret(caret - dn);\r\n }\r\n }\r\n\r\n insertTextBeforeCaret(s: string) {\r\n if(!s) {\r\n return;\r\n }\r\n\r\n let caret = this.getCaret();\r\n let front = this.getTextBeforeCaret();\r\n let back = this.getText()._kmwSubstring(this.processedSelectionStart);\r\n\r\n this.adjustDeadkeys(s._kmwLength());\r\n this.root.value = front + s + back;\r\n this.setCaret(caret + s._kmwLength());\r\n }\r\n\r\n handleNewlineAtCaret(): void {\r\n this.insertTextBeforeCaret('\\n');\r\n }\r\n\r\n doInputEvent() {\r\n this.dispatchInputEventOn(this.root);\r\n }\r\n}", + "import OutputTarget from './outputTarget.js';\r\n\r\nclass SelectionCaret {\r\n node: Node;\r\n offset: number;\r\n\r\n constructor(node, offset) {\r\n this.node = node;\r\n this.offset = offset;\r\n }\r\n}\r\n\r\nclass SelectionRange {\r\n start: SelectionCaret;\r\n end: SelectionCaret;\r\n\r\n constructor(start, end) {\r\n this.start = start;\r\n this.end = end;\r\n }\r\n}\r\n\r\nclass StyleCommand {\r\n cmd: string;\r\n stateType: number;\r\n cache: string|boolean;\r\n\r\n constructor(c: string, s:number) {\r\n this.cmd = c;\r\n this.stateType = s;\r\n }\r\n}\r\n\r\nexport default class DesignIFrame extends OutputTarget<{}> {\r\n root: HTMLIFrameElement;\r\n doc: Document;\r\n docRoot: HTMLElement;\r\n\r\n commandCache: StyleCommand[];\r\n\r\n constructor(ele: HTMLIFrameElement) {\r\n super();\r\n this.root = ele;\r\n\r\n if(ele.contentWindow && ele.contentWindow.document && ele.contentWindow.document.designMode == 'on') {\r\n this.doc = ele.contentWindow.document;\r\n this.docRoot = ele.contentWindow.document.documentElement;\r\n } else {\r\n throw \"Specified IFrame is not in design-mode!\";\r\n }\r\n }\r\n\r\n get isSynthetic(): boolean {\r\n return false;\r\n }\r\n\r\n getElement(): HTMLIFrameElement {\r\n return this.root;\r\n }\r\n\r\n focus(): void {\r\n this.doc.defaultView.focus(); // I3363 (Build 301)\r\n }\r\n\r\n isSelectionEmpty(): boolean {\r\n if(!this.hasSelection()) {\r\n return true;\r\n }\r\n\r\n return this.doc.getSelection().isCollapsed;\r\n }\r\n\r\n hasSelection(): boolean {\r\n let Lsel = this.doc.getSelection();\r\n let outerSel = document.getSelection();\r\n\r\n // If the outer doc's selection matches, we're active.\r\n if(outerSel.anchorNode == Lsel.anchorNode && outerSel.focusNode == Lsel.focusNode) {\r\n return true;\r\n } else {\r\n // Problem: for testing, we can't enforce the ideal (ie: first) condition.\r\n // Technically, the IFrame _will_ always have its own internal selection, though... so... it kinda works?\r\n return true;\r\n }\r\n }\r\n\r\n clearSelection(): void {\r\n if(this.hasSelection()) {\r\n let Lsel = this.doc.getSelection();\r\n\r\n if(!Lsel.isCollapsed) {\r\n Lsel.deleteFromDocument(); // I2134, I2192\r\n }\r\n } else {\r\n console.warn(\"Attempted to clear an unowned Selection!\");\r\n }\r\n }\r\n\r\n invalidateSelection(): void { /* No cache maintenance needed here, partly because\r\n * it's impossible to cache a Selection; it mutates.\r\n */ }\r\n\r\n getCarets(): SelectionRange {\r\n let Lsel = this.doc.getSelection();\r\n let code = Lsel.anchorNode.compareDocumentPosition(Lsel.focusNode);\r\n\r\n if(Lsel.isCollapsed) {\r\n let caret = new SelectionCaret(Lsel.anchorNode, Lsel.anchorOffset);\r\n return new SelectionRange(caret, caret);\r\n } else {\r\n let anchor = new SelectionCaret(Lsel.anchorNode, Lsel.anchorOffset);\r\n let focus = new SelectionCaret(Lsel.focusNode, Lsel.focusOffset);\r\n\r\n if(anchor.node == focus.node) {\r\n code = (focus.offset - anchor.offset > 0) ? 2 : 4;\r\n }\r\n\r\n if(code & 2) {\r\n return new SelectionRange(anchor, focus);\r\n } else { // Default\r\n // can test against code & 4 to ensure Focus is before anchor, though.\r\n return new SelectionRange(focus, anchor);\r\n }\r\n }\r\n }\r\n\r\n getDeadkeyCaret(): number {\r\n return this.getTextBeforeCaret().kmwLength();\r\n }\r\n\r\n getTextBeforeCaret(): string {\r\n if(!this.hasSelection()) {\r\n return this.getText();\r\n }\r\n\r\n let caret = this.getCarets().start;\r\n\r\n if(caret.node.nodeType != 3) {\r\n return ''; // Must be a text node to provide a context.\r\n }\r\n\r\n return caret.node.textContent.substr(0, caret.offset);\r\n }\r\n\r\n getSelectedText(): string {\r\n // TODO: figure out the proper implementation.\r\n // KMW 16 and before behavior may be maintained by just returning the empty string.\r\n return '';\r\n }\r\n\r\n getTextAfterCaret(): string {\r\n if(!this.hasSelection()) {\r\n return '';\r\n }\r\n\r\n let caret = this.getCarets().end;\r\n\r\n if(caret.node.nodeType != 3) {\r\n return ''; // Must be a text node to provide a context.\r\n }\r\n\r\n return caret.node.textContent.substr(caret.offset);\r\n }\r\n\r\n getText(): string {\r\n return this.docRoot.innerText;\r\n }\r\n\r\n deleteCharsBeforeCaret(dn: number) {\r\n if(!this.hasSelection() || dn <= 0) {\r\n return;\r\n }\r\n\r\n let start = this.getCarets().start;\r\n\r\n // Bounds-check on the number of chars to delete.\r\n if(dn > start.offset) {\r\n dn = start.offset;\r\n }\r\n\r\n if(start.node.nodeType != 3) {\r\n console.warn(\"Deletion of characters requested without available context!\");\r\n return; // No context to delete characters from.\r\n }\r\n\r\n let range = this.doc.createRange();\r\n let dnOffset = start.offset - start.node.nodeValue.substr(0, start.offset)._kmwSubstr(-dn).length;\r\n\r\n range.setStart(start.node, dnOffset);\r\n range.setEnd(start.node, start.offset);\r\n\r\n this.adjustDeadkeys(-dn);\r\n range.deleteContents();\r\n // No need to reposition the caret - the DOM will auto-move the selection accordingly, since\r\n // we didn't use the selection to delete anything.\r\n }\r\n\r\n insertTextBeforeCaret(s: string) {\r\n if(!this.hasSelection()) {\r\n return;\r\n }\r\n\r\n let start = this.getCarets().start;\r\n let delta = s._kmwLength();\r\n let Lsel = this.doc.getSelection();\r\n\r\n if(delta == 0) {\r\n return;\r\n }\r\n\r\n this.adjustDeadkeys(delta);\r\n\r\n // While Selection.extend() was really nice for this, IE didn't support it whatsoever.\r\n // However, IE (11, at least) DID support setting selections via ranges, so we were still\r\n // able to manage the caret properly.\r\n //\r\n // TODO: double-check that it was only IE-motivated, re-implement with Selection.extend().\r\n let finalCaret = this.root.ownerDocument.createRange();\r\n\r\n if(start.node.nodeType == 3) {\r\n let textStart = start.node;\r\n textStart.insertData(start.offset, s);\r\n finalCaret.setStart(textStart, start.offset + s.length);\r\n } else {\r\n // Create a new text node - empty control\r\n var n = this.doc.createTextNode(s);\r\n\r\n let range = this.doc.createRange();\r\n range.setStart(start.node, start.offset);\r\n range.collapse(true);\r\n range.insertNode(n);\r\n finalCaret.setStart(n, s.length);\r\n }\r\n\r\n finalCaret.collapse(true);\r\n Lsel.removeAllRanges();\r\n try {\r\n Lsel.addRange(finalCaret);\r\n } catch(e) {\r\n // Chrome (through 4.0 at least) throws an exception because it has not synchronised its content with the selection.\r\n // scrollIntoView synchronises the content for selection\r\n start.node.parentElement.scrollIntoView();\r\n Lsel.addRange(finalCaret);\r\n }\r\n Lsel.collapseToEnd();\r\n }\r\n\r\n handleNewlineAtCaret(): void {\r\n // TODO: Implement.\r\n //\r\n // As it turns out, we never had an implementation for handling newline inputs from the OSK for this element type.\r\n // At least this way, it's more explicit.\r\n //\r\n // Note: consult \"// Create a new text node - empty control\" case in insertTextBeforeCaret -\r\n // this helps to handle the browser-default implementation of newline handling. In particular,\r\n // entry of the first character after a newline.\r\n //\r\n // If raw newlines are entered into the HTML, but as with usual HTML, they're interpreted as excess whitespace and\r\n // have no effect. We need to add DOM elements for a functional newline.\r\n }\r\n\r\n protected setTextAfterCaret(s: string) {\r\n if(!this.hasSelection()) {\r\n return;\r\n }\r\n\r\n let caret = this.getCarets().end;\r\n let delta = s._kmwLength();\r\n let Lsel = this.doc.getSelection();\r\n\r\n if(delta == 0) {\r\n return;\r\n }\r\n\r\n // This is designed explicitly for use in direct-setting operations; deadkeys\r\n // will be handled after this method.\r\n\r\n if(caret.node.nodeType == 3) {\r\n let textStart = caret.node;\r\n textStart.replaceData(caret.offset, textStart.length, s);\r\n } else {\r\n // Create a new text node - empty control\r\n var n = caret.node.ownerDocument.createTextNode(s);\r\n\r\n let range = this.root.ownerDocument.createRange();\r\n range.setStart(caret.node, caret.offset);\r\n range.collapse(true);\r\n range.insertNode(n);\r\n }\r\n }\r\n\r\n /**\r\n * Function saveProperties\r\n * Scope Private\r\n * Description Build and create list of styles that can be applied in iframes\r\n */\r\n saveProperties() {\r\n // Formerly _CacheCommands.\r\n var _CacheableCommands=[\r\n new StyleCommand('backcolor',1), new StyleCommand('fontname',1), new StyleCommand('fontsize',1),\r\n new StyleCommand('forecolor',1), new StyleCommand('bold',0), new StyleCommand('italic',0),\r\n new StyleCommand('strikethrough',0), new StyleCommand('subscript',0),\r\n new StyleCommand('superscript',0), new StyleCommand('underline',0)\r\n ];\r\n\r\n if(this.doc.defaultView) {\r\n _CacheableCommands.push(new StyleCommand('hilitecolor',1));\r\n }\r\n\r\n for(var n=0; n < _CacheableCommands.length; n++) { // I1511 - array prototype extended\r\n let cmd = _CacheableCommands[n];\r\n //KeymanWeb._Debug('Command:'+_CacheableCommands[n][0]);\r\n if(cmd.stateType == 1) {\r\n cmd.cache = this.doc.queryCommandValue(cmd.cmd);\r\n } else {\r\n cmd.cache = this.doc.queryCommandState(cmd.cmd);\r\n }\r\n }\r\n this.commandCache = _CacheableCommands;\r\n }\r\n\r\n /**\r\n * Function restoreProperties\r\n * Scope Private\r\n * Description Restore styles in IFRAMEs (??)\r\n */\r\n restoreProperties(_func?: () => void): void {\r\n // Formerly _CacheCommandsReset.\r\n if(!this.commandCache) {\r\n console.error(\"No command cache exists to restore!\");\r\n }\r\n\r\n for(var n=0; n < this.commandCache.length; n++) { // I1511 - array prototype extended\r\n let cmd = this.commandCache[n];\r\n\r\n //KeymanWeb._Debug('ResetCacheCommand:'+_CacheableCommands[n][0]+'='+_CacheableCommands[n][2]);\r\n if(cmd.stateType == 1) {\r\n if(this.doc.queryCommandValue(cmd.cmd) != cmd.cache) {\r\n if(_func) {\r\n _func();\r\n }\r\n this.doc.execCommand(cmd.cmd, false, cmd.cache);\r\n }\r\n } else if(this.doc.queryCommandState(cmd.cmd) != cmd.cache) {\r\n if(_func) {\r\n _func();\r\n }\r\n //KeymanWeb._Debug('executing command '+_CacheableCommand[n][0]);\r\n this.doc.execCommand(cmd.cmd, false, null);\r\n }\r\n }\r\n }\r\n\r\n doInputEvent() {\r\n // Root = the iframe, the outermost component and the one we were originally told to attach to.\r\n this.dispatchInputEventOn(this.root);\r\n }\r\n}", + "import OutputTarget from './outputTarget.js';\r\n\r\nclass SelectionCaret {\r\n node: Node;\r\n offset: number;\r\n\r\n constructor(node, offset) {\r\n this.node = node;\r\n this.offset = offset;\r\n }\r\n}\r\n\r\nclass SelectionRange {\r\n start: SelectionCaret;\r\n end: SelectionCaret;\r\n\r\n constructor(start, end) {\r\n this.start = start;\r\n this.end = end;\r\n }\r\n}\r\n\r\nexport default class ContentEditable extends OutputTarget<{}> {\r\n root: HTMLElement;\r\n\r\n constructor(ele: HTMLElement) {\r\n if(ele.isContentEditable) {\r\n super();\r\n this.root = ele;\r\n } else {\r\n throw \"Specified element is not already content-editable!\";\r\n }\r\n }\r\n\r\n get isSynthetic(): boolean {\r\n return false;\r\n }\r\n\r\n getElement(): HTMLElement {\r\n return this.root;\r\n }\r\n\r\n isSelectionEmpty(): boolean {\r\n if(!this.hasSelection()) {\r\n return true;\r\n }\r\n\r\n return this.root.ownerDocument.getSelection().isCollapsed;\r\n }\r\n\r\n hasSelection(): boolean {\r\n let Lsel = this.root.ownerDocument.getSelection();\r\n\r\n if(this.root != Lsel.anchorNode && !this.root.contains(Lsel.anchorNode)) {\r\n return false;\r\n }\r\n\r\n if(this.root != Lsel.focusNode && !this.root.contains(Lsel.focusNode)) {\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n clearSelection(): void {\r\n if(this.hasSelection()) {\r\n let Lsel = this.root.ownerDocument.getSelection();\r\n\r\n if(!Lsel.isCollapsed) {\r\n Lsel.deleteFromDocument(); // I2134, I2192\r\n }\r\n } else {\r\n console.warn(\"Attempted to clear an unowned Selection!\");\r\n }\r\n }\r\n\r\n invalidateSelection(): void { /* No cache maintenance needed here, partly because\r\n * it's impossible to cache a Selection; it mutates.\r\n */ }\r\n\r\n getCarets(): SelectionRange {\r\n let Lsel = this.root.ownerDocument.getSelection();\r\n let code = Lsel.anchorNode.compareDocumentPosition(Lsel.focusNode);\r\n\r\n if(Lsel.isCollapsed) {\r\n let caret = new SelectionCaret(Lsel.anchorNode, Lsel.anchorOffset);\r\n return new SelectionRange(caret, caret);\r\n } else {\r\n let anchor = new SelectionCaret(Lsel.anchorNode, Lsel.anchorOffset);\r\n let focus = new SelectionCaret(Lsel.focusNode, Lsel.focusOffset);\r\n\r\n if(anchor.node == focus.node) {\r\n code = (focus.offset - anchor.offset > 0) ? 2 : 4;\r\n }\r\n\r\n if(code & 2) {\r\n return new SelectionRange(anchor, focus);\r\n } else { // Default\r\n // can test against code & 4 to ensure Focus is before anchor, though.\r\n return new SelectionRange(focus, anchor);\r\n }\r\n }\r\n }\r\n\r\n getDeadkeyCaret(): number {\r\n return this.getTextBeforeCaret().kmwLength();\r\n }\r\n\r\n getTextBeforeCaret(): string {\r\n if(!this.hasSelection()) {\r\n return this.getText();\r\n }\r\n\r\n let caret = this.getCarets().start;\r\n\r\n if(caret.node.nodeType != 3) {\r\n return ''; // Must be a text node to provide a context.\r\n }\r\n\r\n return caret.node.textContent.substr(0, caret.offset);\r\n }\r\n\r\n getSelectedText(): string {\r\n // TODO: figure out the proper implementation.\r\n // KMW 16 and before behavior may be maintained by just returning the empty string.\r\n return '';\r\n }\r\n\r\n getTextAfterCaret(): string {\r\n if(!this.hasSelection()) {\r\n return '';\r\n }\r\n\r\n let caret = this.getCarets().end;\r\n\r\n if(caret.node.nodeType != 3) {\r\n return ''; // Must be a text node to provide a context.\r\n }\r\n\r\n return caret.node.textContent.substr(caret.offset);\r\n }\r\n\r\n getText(): string {\r\n return this.root.innerText;\r\n }\r\n\r\n deleteCharsBeforeCaret(dn: number) {\r\n if(!this.hasSelection() || dn <= 0) {\r\n return;\r\n }\r\n\r\n let start = this.getCarets().start;\r\n\r\n // Bounds-check on the number of chars to delete.\r\n if(dn > start.offset) {\r\n dn = start.offset;\r\n }\r\n\r\n if(start.node.nodeType != 3) {\r\n console.warn(\"Deletion of characters requested without available context!\");\r\n return; // No context to delete characters from.\r\n }\r\n\r\n let range = this.root.ownerDocument.createRange();\r\n let dnOffset = start.offset - start.node.nodeValue.substr(0, start.offset)._kmwSubstr(-dn).length;\r\n\r\n range.setStart(start.node, dnOffset);\r\n range.setEnd(start.node, start.offset);\r\n\r\n this.adjustDeadkeys(-dn);\r\n range.deleteContents();\r\n // No need to reposition the caret - the DOM will auto-move the selection accordingly, since\r\n // we didn't use the selection to delete anything.\r\n }\r\n\r\n insertTextBeforeCaret(s: string) {\r\n if(!this.hasSelection()) {\r\n return;\r\n }\r\n\r\n let start = this.getCarets().start;\r\n let delta = s._kmwLength();\r\n let Lsel = this.root.ownerDocument.getSelection();\r\n\r\n if(delta == 0) {\r\n return;\r\n }\r\n\r\n this.adjustDeadkeys(delta);\r\n\r\n // While Selection.extend() was really nice for this, IE didn't support it whatsoever.\r\n // However, IE (11, at least) DID support setting selections via ranges, so we were still\r\n // able to manage the caret properly.\r\n //\r\n // TODO: double-check that it was only IE-motivated, re-implement with Selection.extend().\r\n let finalCaret = this.root.ownerDocument.createRange();\r\n\r\n if(start.node.nodeType == 3) {\r\n let textStart = start.node;\r\n textStart.insertData(start.offset, s);\r\n finalCaret.setStart(textStart, start.offset + s.length);\r\n } else {\r\n // Create a new text node - empty control\r\n var n = start.node.ownerDocument.createTextNode(s);\r\n\r\n let range = this.root.ownerDocument.createRange();\r\n range.setStart(start.node, start.offset);\r\n range.collapse(true);\r\n range.insertNode(n);\r\n finalCaret.setStart(n, s.length);\r\n }\r\n\r\n finalCaret.collapse(true);\r\n Lsel.removeAllRanges();\r\n try {\r\n Lsel.addRange(finalCaret);\r\n } catch(e) {\r\n // Chrome (through 4.0 at least) throws an exception because it has not synchronised its content with the selection.\r\n // scrollIntoView synchronises the content for selection\r\n start.node.parentElement.scrollIntoView();\r\n Lsel.addRange(finalCaret);\r\n }\r\n Lsel.collapseToEnd();\r\n }\r\n\r\n handleNewlineAtCaret(): void {\r\n // TODO: Implement.\r\n //\r\n // As it turns out, we never had an implementation for handling newline inputs from the OSK for this element type.\r\n // At least this way, it's more explicit.\r\n //\r\n // Note: consult \"// Create a new text node - empty control\" case in insertTextBeforeCaret -\r\n // this helps to handle the browser-default implementation of newline handling. In particular,\r\n // entry of the first character after a newline.\r\n //\r\n // If raw newlines are entered into the HTML, but as with usual HTML, they're interpreted as excess whitespace and\r\n // have no effect. We need to add DOM elements for a functional newline.\r\n }\r\n\r\n protected setTextAfterCaret(s: string) {\r\n if(!this.hasSelection()) {\r\n return;\r\n }\r\n\r\n let caret = this.getCarets().end;\r\n let delta = s._kmwLength();\r\n let Lsel = this.root.ownerDocument.getSelection();\r\n\r\n if(delta == 0) {\r\n return;\r\n }\r\n\r\n // This is designed explicitly for use in direct-setting operations; deadkeys\r\n // will be handled after this method.\r\n\r\n if(caret.node.nodeType == 3) {\r\n let textStart = caret.node;\r\n textStart.replaceData(caret.offset, textStart.length, s);\r\n } else {\r\n // Create a new text node - empty control\r\n var n = caret.node.ownerDocument.createTextNode(s);\r\n\r\n let range = this.root.ownerDocument.createRange();\r\n range.setStart(caret.node, caret.offset);\r\n range.collapse(true);\r\n range.insertNode(n);\r\n }\r\n }\r\n\r\n doInputEvent() {\r\n this.dispatchInputEventOn(this.root);\r\n }\r\n}", + "/**\r\n * Checks the type of an input DOM-related object while ensuring that it is checked against the correct prototype,\r\n * as class prototypes are (by specification) scoped upon the owning Window.\r\n *\r\n * See https://stackoverflow.com/questions/43587286/why-does-instanceof-return-false-on-chrome-safari-and-edge-and-true-on-firefox\r\n * for more details.\r\n *\r\n * @param {Element|Event} Pelem An element of the web page or one of its IFrame-based subdocuments.\r\n * @param {string} className The plain-text name of the expected Element type.\r\n * @return {boolean}\r\n */\r\nexport function nestedInstanceOf(Pelem: Event|EventTarget, className: string): boolean {\r\n var scopedClass;\r\n\r\n if(!Pelem) {\r\n // If we're bothering to check something's type, null references don't match\r\n // what we're looking for.\r\n return false;\r\n }\r\n if (Pelem['Window']) { // Window objects contain the class definitions for types held within them. So, we can check for those.\r\n return className == 'Window';\r\n } else if (Pelem['defaultView']) { // Covers Document.\r\n scopedClass = Pelem['defaultView'][className];\r\n } else if(Pelem['ownerDocument']) {\r\n scopedClass = (Pelem as Node).ownerDocument.defaultView[className];\r\n } else if(Pelem['target']) {\r\n var event = Pelem as Event;\r\n\r\n if(this.instanceof(event.target, 'Window')) {\r\n scopedClass = event.target[className];\r\n } else if(this.instanceof(event.target, 'Document')) {\r\n scopedClass = (event.target as Document).defaultView[className];\r\n } else if(this.instanceof(event.target, 'HTMLElement')) {\r\n scopedClass = (event.target as HTMLElement).ownerDocument.defaultView[className];\r\n }\r\n }\r\n\r\n if(scopedClass) {\r\n return Pelem instanceof scopedClass;\r\n } else {\r\n return false;\r\n }\r\n}", + "import type OutputTarget from './outputTarget.js';\r\nimport Input from './input.js';\r\nimport TextArea from './textarea.js';\r\nimport DesignIFrame from './designIFrame.js';\r\nimport ContentEditable from './contentEditable.js';\r\nimport { nestedInstanceOf } from './utils.js';\r\n\r\nexport default function wrapElement(e: HTMLElement): OutputTarget {\r\n // Complex type scoping is implemented here so that kmwutils.ts is not a dependency for test compilations.\r\n\r\n if(nestedInstanceOf(e, \"HTMLInputElement\")) {\r\n return new Input( e);\r\n } else if(nestedInstanceOf(e, \"HTMLTextAreaElement\")) {\r\n return new TextArea( e);\r\n } else if(nestedInstanceOf(e, \"HTMLIFrameElement\")) {\r\n let iframe = e;\r\n\r\n if(iframe.contentWindow && iframe.contentWindow.document && iframe.contentWindow.document.designMode == \"on\") {\r\n return new DesignIFrame(iframe);\r\n } else if (e.isContentEditable) {\r\n // Do content-editable