diff --git a/common/predictive-text/unit_tests/in_browser/manual.conf.cjs b/common/predictive-text/unit_tests/in_browser/manual.conf.cjs index 9f94634473..738f2b7713 100644 --- a/common/predictive-text/unit_tests/in_browser/manual.conf.cjs +++ b/common/predictive-text/unit_tests/in_browser/manual.conf.cjs @@ -19,7 +19,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. + // Can't set in base.conf.cjs b/c of constant's definition style. logLevel: config.LOG_INFO }; diff --git a/common/web/keyboard-processor/tests/dom/CI.conf.cjs b/common/web/keyboard-processor/tests/dom/CI.conf.cjs index c7c9813df7..a96ffebbed 100644 --- a/common/web/keyboard-processor/tests/dom/CI.conf.cjs +++ b/common/web/keyboard-processor/tests/dom/CI.conf.cjs @@ -18,7 +18,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. + // Can't set in base.conf.cjs b/c of constant's definition style. logLevel: config.LOG_INFO }; diff --git a/common/web/keyboard-processor/tests/dom/manual.conf.cjs b/common/web/keyboard-processor/tests/dom/manual.conf.cjs index 88e20d283c..37d982e9b5 100644 --- a/common/web/keyboard-processor/tests/dom/manual.conf.cjs +++ b/common/web/keyboard-processor/tests/dom/manual.conf.cjs @@ -18,7 +18,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. + // Can't set in base.conf.cjs b/c of constant's definition style. logLevel: config.LOG_INFO }; diff --git a/web/package.json b/web/package.json index 0f7559e0c9..9c06e3d5b0 100644 --- a/web/package.json +++ b/web/package.json @@ -72,7 +72,6 @@ "@keymanapp/web-sentry-manager": "*", "@sentry/cli": "2.2.0", "chai": "^4.3.4", - "google-closure-compiler-java": "^20200224.0.0", "karma": "^6.4.1", "karma-browserstack-launcher": "^1.6.0", "karma-chai": "^0.1.0", diff --git a/web/src/app/browser/src/utilApiEndpoint.ts b/web/src/app/browser/src/utilApiEndpoint.ts index b622d68f0c..04e2670b32 100644 --- a/web/src/app/browser/src/utilApiEndpoint.ts +++ b/web/src/app/browser/src/utilApiEndpoint.ts @@ -8,6 +8,7 @@ import { } from "keyman/engine/dom-utils"; import { DomEventTracker } from "keyman/engine/events"; import { BrowserConfiguration, BrowserInitOptionSpec } from "./configuration.js"; +import { getStyleValue } from "./utils/getStyleValue.js"; /** * Calls document.createElement for the specified node type and also applies @@ -205,6 +206,8 @@ export class UtilApiEndpoint { this.domEventTracker.detachDOMEvent(Pelem as any, Peventname as any, Phandler, PuseCapture); } + getStyleValue = getStyleValue; + shutdown() { this.stylesheetManager?.unlinkAll(); this.domEventTracker?.shutdown(); diff --git a/web/src/app/ui/build-bundler.js b/web/src/app/ui/build-bundler.js index bfc57b20b0..a09c61bf7f 100644 --- a/web/src/app/ui/build-bundler.js +++ b/web/src/app/ui/build-bundler.js @@ -30,7 +30,7 @@ let es5ClassAnnotationAsPurePlugin = { } } -const modules = ['kmwuitoggle', 'kmwuitoolbar']; +const modules = ['kmwuibutton', 'kmwuifloat', 'kmwuitoggle', 'kmwuitoolbar']; for(let module of modules) { await esbuild.build({ diff --git a/web/src/app/ui/kmwuibutton.ts b/web/src/app/ui/kmwuibutton.ts index 1205c7b6e4..d81bc11b67 100644 --- a/web/src/app/ui/kmwuibutton.ts +++ b/web/src/app/ui/kmwuibutton.ts @@ -1,12 +1,14 @@ /*** - KeymanWeb 11.0 - Copyright 2019 SIL International + KeymanWeb 17.0 + Copyright 2019-2023 SIL International ***/ -// If a UI module has been loaded, we can rely on the publically-published 'name' property -// having been set as a way to short-out a UI reload. Its parent object always exists by -// this point in the build process. -if(!window['keyman']['ui']['name']) { +import type { KeymanEngine, KeyboardCookie, UIModule } from 'keyman/app/browser'; +import type { FloatingOSKViewCookie } from 'keyman/engine/osk'; + +declare var keyman: KeymanEngine + +if(!keyman?.ui?.name) { /********************************/ /* */ @@ -25,390 +27,635 @@ if(!window['keyman']['ui']['name']) { try { // Declare KeymanWeb, OnScreen keyboard and Util objects - var keymanweb=window['keyman'], - util=keymanweb['util'],dbg=keymanweb['debug']; + const keymanweb = keyman; + const util=keymanweb.util; + // var dbg=keymanweb['debug']; // Disable UI for touch devices - if(util['isTouchDevice']()) throw ''; - - // User interface global and variables - keymanweb['ui'] = {}; - var ui=keymanweb['ui']; - ui['name'] = 'button'; - - ui.init = false; - ui.initTimer = null; - ui.KeyboardSelector = null; - - ui.KeymanWeb_DefaultKeyboardHelp='KeymanWeb is not running. Choose a keyboard from the list'; - ui._KeymanWeb_KbdList = null; - ui._KMWSel = null; - ui._IsHelpVisible = false; - ui._DefaultKeyboardID = ''; - - ui.updateTimer = null; - ui.updateList = true; - - /** - * Highlight the currently active keyboard in the list of keyboards - **/ - ui._ShowSelected = function() - { - var _rv,kbd=keymanweb['getActiveKeyboard'](),lgc=keymanweb['getActiveLanguage'](), - kList = ui._KeymanWeb_KbdList.childNodes, - _r = /^KMWSel_(.*)\$(.*)$/; - - for(var i=1; i= kList.length) i=1; - kList[i].childNodes[0].className = 'selected'; + if(util.isTouchDevice()) { + throw ''; } - /** - * Select keyboard by id - * - * @param {Event} _id keyboard selection event - * @return {boolean} - */ - ui._SelectKeyboard = function(_id) - { - if(typeof(_id) == 'object') - { - var t=null; - if((typeof(_id.target) != 'undefined') && _id.target) t=_id.target; - else if((typeof(_id.srcElement) != 'undefined') && _id.srcElement) t=_id.srcElement; - if(t) _id=t.id; - } + class UIButton implements UIModule { + // User interface global and variables + readonly name = 'button'; - var _r=/^KMWSel_(.*)\$(.*)$/; - var _rv=_r.exec(_id),_lgc='',_name=''; - if(_rv !== null) - { - _name = _rv[1].split('$')[0]; //new code - _lgc = _id.split('$')[1]; - if(ui._KMWSel != null) ui._KMWSel.className = ''; - var _k = document.getElementById(_id); - if(_k) _k.className='selected'; - ui._KMWSel = _k; - keymanweb['setActiveKeyboard'](_name,_lgc); - } - else - _name=null; + init = false; - keymanweb['focusLastActiveElement'](); - let osk = keymanweb.osk; - if(osk && osk['isEnabled']()) osk['show'](true); - - ui._ShowKeyboardButton(_name); - return false; - } - - /** - * Set KMW UI activation state on mouse click - * - * @param {Event} e event - */ - ui._SelectorMouseDown = function(e) - { - var x=keymanweb['getLastActiveElement'](); - - // Set the focus to an input field, to get correct OSK display behaviour - if(!x) ui._FocusFirstInput(); else keymanweb['focusLastActiveElement'](); - - if(keymanweb['activatingUI']) keymanweb['activatingUI'](1); - } - - /** - * Set focus on mouse up - * - * @param {Event} e event - */ - ui._SelectorMouseUp = function(e) - { - var x=keymanweb['getLastActiveElement'](); - - // Set the focus to an input field, to get correct OSK display behaviour - if(!x) ui._FocusFirstInput(); else keymanweb['focusLastActiveElement'](); - } - - /** - * Set KMW UI activation state on mouse over - * - * @param {Event} e event - */ - ui._SelectorMouseOver = function(e) - { - // highlight the currently active keyboard - ui._ShowSelected(); - - if(keymanweb['activatingUI']) keymanweb['activatingUI'](1); - document.getElementById("kmwico_li").className="sfhover"; - - // Conditionally display keyboard button - ui._ShowKeyboardButton(); - } - - /** - * Sets the focus to the first input or textarea found on the current page - * to ensure consistent keyboard selection and OSK display behaviour - */ - ui._FocusFirstInput = function() - { - var i,ip=null,tp=null, - iList=document.getElementsByTagName("input"), - tList=document.getElementsByTagName("textarea"); - - for(i=0; i 0) tp = tList[0]; - - if((!ip) && (!tp)) - return; - else if(ip && !tp) - ip.focus(); - else if(tp && !ip) - tp.focus(); - else if(ip.offsetTop < tp.offsetTop) - ip.focus(); - else if(ip.offsetTop > tp.offsetTop) - tp.focus(); - else if(ip.offsetLeft < tp.offsetLeft) - ip.focus(); - else - tp.focus(); - } - - /** - * Clear KMW UI activation state on mouse out - * - * @param {Event} e event - */ - ui._SelectorMouseOut = function(e) - { - if(keymanweb['activatingUI']) keymanweb['activatingUI'](0); - document.getElementById("kmwico_li").className="sfunhover"; - } - - /** - * Disable the button to show/hide the OSK if no active keyboard or active keyboard is CJK (user cannot hide) - * - * @param {?string=} _name current keyboard name - */ - ui._ShowKeyboardButton = function(_name) - { - var kbdName = keymanweb['getActiveKeyboard'](), kbdId=document.getElementById("KMW_Keyboard"); - if(arguments.length > 0) kbdName = _name; - if(kbdId) - { - if((kbdName == '') || keymanweb['isCJK']()) - { - kbdId.className='kmw_disabled'; - } - else - { - let osk = keymanweb.osk; - kbdId.className = osk && osk['isEnabled']() ? 'kmw_show' : 'kmw_hide'; - } - } - } - - ui.registerEvents = function() { - let osk = keymanweb.osk; - if(!osk) { - return; - } /** - * UI Functions called by KeymanWeb or OSK + * Initialization timeout handle. */ - osk['addEventListener']('show', function(oskPosition) { - var t=keymanweb['getLastActiveElement'](); - if(t) - { - if(!oskPosition['userLocated']) - { - oskPosition['x'] = util['getAbsoluteX'](t); - oskPosition['y'] = util['getAbsoluteY'](t)+t.offsetHeight; + private initTimer: number = null; + private keyboardSelector = null; // not referenced + + // not referenced + private KeymanWeb_DefaultKeyboardHelp='KeymanWeb is not running. Choose a keyboard from the list'; + + + private _KeymanWeb_KbdList: HTMLUListElement = null; + + /** + * Tracks the element corresponding to the active keyboard on the keyboard list. + * + * Is also set when activating it due to user interaction. + */ + private _KMWSel: HTMLElement = null; + private _IsHelpVisible = false; + private _DefaultKeyboardID = ''; + + updateTimer: number = 0; + updateList = true; + + /** + * If no id == 'KeymanWebControl' element exists during init, this UI module + * will make its own, auto-insert it, and track the element here. + */ + private _insertedElem: HTMLElement = null; + + /** + * Highlight the currently active keyboard in the list of keyboards + **/ + private _ShowSelected() { + let kbd=keymanweb.getActiveKeyboard(); + let lgc=keymanweb.getActiveLanguage(); + let kList = this._KeymanWeb_KbdList.childNodes; + let _r = /^KMWSel_(.*)\$(.*)$/; + + for(let i=1; i= kList.length) { + i=1; + } + (kList[i].childNodes[0] as HTMLElement).className = 'selected'; + } + + /** + * Select keyboard by id + * + * @param {Event} _id keyboard selection event + * @return {boolean} + */ + readonly _SelectKeyboard = (_id: Event) => { + let id: string = ''; + if(typeof(_id) == 'object') { + let t: HTMLElement = null; + if((typeof(_id.target) != 'undefined') && _id.target) { + t=_id.target as HTMLElement; + } else if((typeof(_id.srcElement) != 'undefined') && _id.srcElement) { + t=_id.srcElement as HTMLElement; + } + + if(t) { + id=t.id; } } - ui._ShowKeyboardButton(); - return oskPosition; - }); - - /* TODO: why is this still needed??? Does it actually do anything?? */ - osk['addEventListener']('hide', function(hiddenByUser) { - if((arguments.length > 0) && hiddenByUser) - { - var _a = document.getElementById('KMW_Keyboard'); - if(_a) _a.className = 'kmw_hide'; + let _r=/^KMWSel_(.*)\$(.*)$/; + let _rv=_r.exec(id),_lgc='',_name=''; + if(_rv !== null) { + _name = _rv[1].split('$')[0]; //new code + _lgc = id.split('$')[1]; + if(this._KMWSel != null) { + this._KMWSel.className = ''; + } + let _k = document.getElementById(id); + if(_k) { + _k.className='selected'; + } + this._KMWSel = _k; + keymanweb.setActiveKeyboard(_name,_lgc); + } else { + _name=null; } - }); - } - /** - * Show or hide the OSK (always visible for CJK keyboards) - * - * @param {Object} _anchor anchor element (?) - * @return {boolean} - **/ - ui._ShowKeymanWebKeyboard = function(_anchor) - { - var kbdId=document.getElementById("KMW_Keyboard"); - let osk = keymanweb.osk; + keymanweb.focusLastActiveElement(); + let osk = keymanweb.osk; + if(osk && osk.isEnabled()) { + osk.show(true); + } - if((kbdId.className!='kmw_disabled') && osk && osk['show']) - { - if(osk['isEnabled']()) osk['hide'](); else osk['show'](true); - } - if(window.event) window.event.returnValue=false; - - keymanweb['focusLastActiveElement'](); - return false; - } - - /** - * Initialize Button User Interface - **/ - ui['initialize'] = ui.Initialize = function() { - if(ui.initTimer) { - window.clearTimeout(ui.initTimer); - ui.initTimer = null; + this._ShowKeyboardButton(_name); + return false; } - //Never initialize UI before KMW (parameters will be undefined) - if(!keymanweb['initialized']) { - ui.initTimer = window.setTimeout(ui.Initialize, 50); - return; + /** + * Set KMW UI activation state on mouse click + * + * @param {Event} e event + */ + readonly _SelectorMouseDown = (e: MouseEvent) => { + const x=keymanweb.getLastActiveElement(); + + // Set the focus to an input field, to get correct OSK display behaviour + if(!x) { + this._FocusFirstInput(); + } else { + keymanweb.focusLastActiveElement(); + } + + if(keymanweb.activatingUI) { + keymanweb.activatingUI(1); + } } - if(ui.init || util['isTouchDevice']()) { - return; + /** + * Set focus on mouse up + * + * @param {Event} e event + */ + readonly _SelectorMouseUp = (e: MouseEvent) => { + const x=keymanweb.getLastActiveElement(); + + // Set the focus to an input field, to get correct OSK display behaviour + if(!x) { + this._FocusFirstInput(); + } else { + keymanweb.focusLastActiveElement(); + } } - ui.init = true; + /** + * Set KMW UI activation state on mouse over + * + * @param {Event} e event + */ + readonly _SelectorMouseOver = (e: MouseEvent) => { + // highlight the currently active keyboard + this._ShowSelected(); - util['addStyleSheet'](ui._Appearance); + if(keymanweb.activatingUI) { + keymanweb.activatingUI(1); + } - ui._KeymanWeb_KbdList = util['createElement']('ul'); - ui._KeymanWeb_KbdList.id = 'KeymanWeb_KbdList'; + document.getElementById("kmwico_li").className="sfhover"; - var _elem = document.getElementById('KeymanWebControl'); - if(!_elem) - { - var _elems = document.getElementsByTagName('div'); - for(var _i = 0; _i < _elems.length; _i++) - { - if(_elems[_i].className == 'KeymanWebControl') - { - _elem = _elems[_i]; break; + // Conditionally display keyboard button + this._ShowKeyboardButton(); + } + + /** + * Sets the focus to the first input or textarea found on the current page + * to ensure consistent keyboard selection and OSK display behaviour + */ + private _FocusFirstInput() { + let ip: HTMLInputElement = null; + let tp: HTMLTextAreaElement = null; + const iList=document.getElementsByTagName("input"); + const tList=document.getElementsByTagName("textarea"); + + let i: number; + for(i=0; i 0) { + tp = tList[0]; + } + + if((!ip) && (!tp)) { + return; + } else if(ip && !tp) { + ip.focus(); + } else if(tp && !ip) { + tp.focus(); + } else if(ip.offsetTop < tp.offsetTop) { + ip.focus(); + } else if(ip.offsetTop > tp.offsetTop) { + tp.focus(); + } else if(ip.offsetLeft < tp.offsetLeft) { + ip.focus(); + } else { + tp.focus(); + } + } + + /** + * Clear KMW UI activation state on mouse out + * + * @param {Event} e event + */ + readonly _SelectorMouseOut = (e: MouseEvent) => { + if(keymanweb.activatingUI) { + keymanweb.activatingUI(0); + } + document.getElementById("kmwico_li").className="sfunhover"; + } + + /** + * Disable the button to show/hide the OSK if no active keyboard or active keyboard is CJK (user cannot hide) + * + * @param {?string=} _name current keyboard name + */ + private _ShowKeyboardButton(_name?: string) { + let kbdName = keymanweb.getActiveKeyboard(); + let kbdId = document.getElementById("KMW_Keyboard"); + if(arguments.length > 0) { + kbdName = _name; + } + + if(kbdId) { + if((kbdName == '') || keymanweb.isCJK()) { + kbdId.className='kmw_disabled'; + } else { + let osk = keymanweb.osk; + kbdId.className = osk && osk.isEnabled() ? 'kmw_show' : 'kmw_hide'; } } } - // Insert as first child of body if not defined by user - if(!_elem && (document.body != null)) - { - _elem=document.createElement('DIV'); - _elem.id='KeymanWebControl'; - document.body.insertBefore(_elem,document.body.firstChild); - ui._insertedElem = _elem; + registerEvents() { + let osk = keymanweb.osk; + if(!osk) { + return; + } + /** + * UI Functions called by KeymanWeb or OSK + */ + osk.addEventListener('show', (oskPosition) => { + const t=keymanweb.getLastActiveElement(); + if(t) { + if(!oskPosition['userLocated']) { + oskPosition['x'] = util.getAbsoluteX(t); + oskPosition['y'] = util.getAbsoluteY(t)+t.offsetHeight; + } + } + + this._ShowKeyboardButton(); + return oskPosition; + }); + + /* TODO: why is this still needed??? Does it actually do anything?? */ + osk.addEventListener('hide', function(obj) { + if((arguments.length > 0) && obj['hiddenByUser']) { + let _a = document.getElementById('KMW_Keyboard'); + if(_a) { + _a.className = 'kmw_hide'; + } + } + }); } + /** + * Show or hide the OSK (always visible for CJK keyboards) + * + * @param {Object} _anchor anchor element (?) + * @return {boolean} + **/ + readonly _ShowKeymanWebKeyboard = () => { + const kbdId=document.getElementById("KMW_Keyboard"); + let osk = keymanweb.osk; - var imgPath=util['getOption']('resources')+'ui/button/'; - if(_elem) - { - // Append another DIV to follow the main control with clear:both to prevent selection over entire width of window - var dx=document.createElement('DIV'),ds=dx.style; - ds.clear='both'; - _elem.parentNode.insertBefore(dx,_elem.nextSibling); + if((kbdId.className!='kmw_disabled') && osk && osk.show) { + if(osk.isEnabled()) { + osk.hide(); + } else { + osk.show(true); + } + } + if(window.event) { + window.event.returnValue=false; + } - var _btn=util['createElement']('img'), _ul=util['createElement']('ul'),_li0=util['createElement']('li'); - _btn.id = 'kmwico_a'; - _btn.src = imgPath+'kmw_button.gif'; - _btn.onclick = function(){return false;} //may want to use this in iOS ***** - _li0.appendChild(_btn); - _li0.id = 'kmwico_li'; - _ul.appendChild(_li0); - _ul.id = 'kmwico'; - _ul.style.display = 'block'; - _elem.appendChild(_ul); - } - // Do not define any UI behaviour if no controller element can be found - else return; - - if(!keymanweb['iOS']) - { - var _li = util['createElement']('li'); - var _a = util['createElement']('a'); - var _img = util['createElement']('img'); - _img.src = imgPath+'kbdicon.gif'; - _a.appendChild(_img); - - var _txt1 = document.createTextNode(' Hide Keyboard'); - var _txt2 = document.createTextNode(' Show Keyboard'); - var _sp1 = util['createElement']('span'); - _sp1.id = 'KMW_KbdVisibleMsg'; - _sp1.appendChild(_txt1); - _a.appendChild(_sp1); - - var _sp2 = util['createElement']('span'); - _sp2.id = 'KMW_KbdHiddenMsg'; - _sp2.appendChild(_txt2); - _a.appendChild(_sp2); - _a.onmousedown = ui._ShowKeymanWebKeyboard; - _a.href = '#'; - _a.id = 'KMW_Keyboard'; - _li.id = 'KMW_ButtonUI_KbdIcon'; - _li.appendChild(_a); - ui._KMWSel = _a; - ui._KeymanWeb_KbdList.appendChild(_li); + keymanweb.focusLastActiveElement(); + return false; } - var _li1 = util['createElement']('li'); - _li1.id = 'KMW_ButtonUI_KbdList'; - var _a1 = util['createElement']('a'); - _a1.appendChild(document.createTextNode('(System keyboard)')); + /** + * Initialize Button User Interface + **/ + readonly initialize = () => { + if(this.initTimer) { + window.clearTimeout(this.initTimer); + this.initTimer = null; + } - _a1.onclick = ui._SelectKeyboard; - _a1.href = '#'; - _a1.id='KMWSel_$'; - _a1.className='selected'; - _li1.appendChild(_a1); - ui._KMWSel = _a1; - ui._KeymanWeb_KbdList.appendChild(_li1); + //Never initialize UI before KMW (parameters will be undefined) + if(!keymanweb.initialized) { + this.initTimer = window.setTimeout(this.initialize, 50); + return; + } - var _kbds = keymanweb['getKeyboards'](), _added = []; + if(this.init || util.isTouchDevice()) { + return; + } - ui.updateKeyboardList(); + this.init = true; - document.getElementById('kmwico_li').appendChild(ui._KeymanWeb_KbdList); + util.addStyleSheet(this._Appearance); - var _sfEl = document.getElementById("kmwico_li"); - util['attachDOMEvent'](_sfEl,'mousedown',ui._SelectorMouseDown); - util['attachDOMEvent'](_sfEl,'mouseover',ui._SelectorMouseOver); - util['attachDOMEvent'](_sfEl,'mouseout',ui._SelectorMouseOut); - util['attachDOMEvent'](_sfEl,'mouseup',ui._SelectorMouseUp); + this._KeymanWeb_KbdList = util.createElement('ul'); + this._KeymanWeb_KbdList.id = 'KeymanWeb_KbdList'; - ui.registerEvents(); - keymanweb['focusLastActiveElement'](); //TODO: this needs to be extended - if no element is active, try and identify an enabled input element + let _elem = document.getElementById('KeymanWebControl'); + if(!_elem) { + const _elems = document.getElementsByTagName('div'); + for(let _i = 0; _i < _elems.length; _i++) { + if(_elems[_i].className == 'KeymanWebControl') { + _elem = _elems[_i]; { + break; + } + } + } + } + + // Insert as first child of body if not defined by user + if(!_elem && (document.body != null)) { + _elem=document.createElement('DIV'); + _elem.id='KeymanWebControl'; + document.body.insertBefore(_elem,document.body.firstChild); + this._insertedElem = _elem; + } + + + let imgPath=util.getOption('resources')+'ui/button/'; + if(_elem) { + // Append another DIV to follow the main control with clear:both to prevent selection over entire width of window + const dx=document.createElement('DIV') + const ds=dx.style; + ds.clear='both'; + _elem.parentNode.insertBefore(dx,_elem.nextSibling); + + const _btn=util.createElement('img'); + const _ul=util.createElement('ul'); + const _li0=util.createElement('li'); + + _btn.id = 'kmwico_a'; + _btn.src = imgPath+'kmw_button.gif'; + _btn.onclick = function() { + return false; + } //may want to use this in iOS ***** + _li0.appendChild(_btn); + _li0.id = 'kmwico_li'; + _ul.appendChild(_li0); + _ul.id = 'kmwico'; + _ul.style.display = 'block'; + _elem.appendChild(_ul); + } else { + // Do not define any UI behaviour if no controller element can be found + return; + } + + // ... wait, what? I can't find any evidence of this reference point existing. + // Even tag `release-web-stable-2.0` turns up results only for this specific sourcefile. + // Thus, in essence: if(true) { /* ... */ } + if(!keymanweb['iOS']) { + var _li = util.createElement('li'); + var _a = util.createElement('a'); + var _img = util.createElement('img'); + _img.src = imgPath+'kbdicon.gif'; + _a.appendChild(_img); + + const _txt1 = document.createTextNode(' Hide Keyboard'); + const _txt2 = document.createTextNode(' Show Keyboard'); + const _sp1 = util.createElement('span'); + _sp1.id = 'KMW_KbdVisibleMsg'; + _sp1.appendChild(_txt1); + _a.appendChild(_sp1); + + var _sp2 = util.createElement('span'); + _sp2.id = 'KMW_KbdHiddenMsg'; + _sp2.appendChild(_txt2); + _a.appendChild(_sp2); + _a.onmousedown = this._ShowKeymanWebKeyboard; + _a.href = '#'; + _a.id = 'KMW_Keyboard'; + _li.id = 'KMW_ButtonUI_KbdIcon'; + _li.appendChild(_a); + this._KMWSel = _a; + this._KeymanWeb_KbdList.appendChild(_li); + } + + var _li1 = util.createElement('li'); + _li1.id = 'KMW_ButtonUI_KbdList'; + var _a1 = util.createElement('a'); + _a1.appendChild(document.createTextNode('(System keyboard)')); + + _a1.onclick = this._SelectKeyboard; + _a1.href = '#'; + _a1.id='KMWSel_$'; + _a1.className='selected'; + _li1.appendChild(_a1); + this._KMWSel = _a1; + this._KeymanWeb_KbdList.appendChild(_li1); + + this.updateKeyboardList(); + + document.getElementById('kmwico_li').appendChild(this._KeymanWeb_KbdList); + + var _sfEl = document.getElementById("kmwico_li"); + util.attachDOMEvent(_sfEl,'mousedown',this._SelectorMouseDown); + util.attachDOMEvent(_sfEl,'mouseover',this._SelectorMouseOver); + util.attachDOMEvent(_sfEl,'mouseout',this._SelectorMouseOut); + util.attachDOMEvent(_sfEl,'mouseup',this._SelectorMouseUp); + + this.registerEvents(); + keymanweb.focusLastActiveElement(); //TODO: this needs to be extended - if no element is active, try and identify an enabled input element + } + + shutdown() { + const root = this._insertedElem; + if(root) { + root.parentNode.removeChild(root); + } + } + + /** + * Update the entire menu when keyboards are registered or deregistered + **/ + readonly updateKeyboardList = () => { + this.updateList = false; + + if(!this.init) { + return; + } + + // Clear existing list first (first two nodes must be preserved) + for(let i = this._KeymanWeb_KbdList.childNodes.length; i>2; i--) { + this._KeymanWeb_KbdList.removeChild(this._KeymanWeb_KbdList.childNodes[i-1]); + } + + const kbds=keymanweb.getKeyboards(); + if(kbds.length > 0) { + for(var i:number=0; i 26) { + _t=_t.substr(0,24)+'\u2026'; + } + _a2.appendChild(document.createTextNode(_t)); + _a2.onclick = this._SelectKeyboard; + _a2.href = '#'; + _a2.id='KMWSel_'+Lki+'$'+Lklc; + _li2.appendChild(_a2); + this._KeymanWeb_KbdList.appendChild(_li2); + } + + // Define appearance of this interface + private _Appearance = ` +#kmwico, #kmwkbd { + vertical-align: middle; +} + +#KeymanWebControl { + float:left; +} + +#KeymanWebControl * { + letter-spacing: 0px !important; + line-height: 1li !important; + white-space: nowrap !important; +} + +#KeymanWebControl #kmwico img { + vertical-align: top; + padding: 0; + margin: 0; + border: none; +} + +#KeymanWebControl #kmwico, #kmwico ul { + padding: 0; + margin: 0; + list-style: none; +} + +#KeymanWebControl #kmwico_a { + display: block; + /* border: none !important; */ + width: 22px; height: 23px; /* sizes needed for kmw_button.gif */ +} + +#KeymanWebControl #kmwico li { + text-align: left; +} + +#KeymanWebControl #kmwico li ul { + display: block; + position: absolute; + left: -5999px; + border: solid 2px #ad4a28; + background: white; + border-radius: 4px; + box-shadow: 4px 4px 2px #666; + z-index: 10011; /* above the osk */ +} + +#KeymanWebControl #kmwico li.sfunhover ul { + display: none; left: -5999px; +} + +#KeymanWebControl #kmwico li:hover ul, #kmwico li.sfhover ul { + display: block; + left: auto; +} + +#KeymanWebControl #kmwico ul li { + float: none; + padding: 0 !important; + margin: 0 !important; + width: 136px !important; +} + +#KeymanWebControl #KMW_LanguageName { + font-weight: bold; +} + +#KeymanWebControl #kmwico ul li a, #kmwico ul li a:visited { + display: block; + padding: 2px 4px !important; + border: none !important; + /* width: auto; */ + color: #404040; + font-family: Tahoma,Verdana,Arial,sans-serif; + font-size: 8pt; + text-decoration: none; +} + +#KeymanWebControl #kmwico ul li a.selected { + font-weight: bold; + color: black; +} + +#KeymanWebControl #kmwico ul li a:hover { + color: white; + background-color: #ad4a28; +} + +#KeymanWebControl #kmwico ul li a.kmw_disabled, #KeymanWebControl #kmwico ul li a.kmw_disabled:hover { + color: #c0c0c0; cursor: default; + background-color: white; +} + +#KeymanWebControl #kmwico ul li a.kmw_show span#KMW_KbdHiddenMsg, #KeymanWebControl #kmwico ul li a.kmw_disabled span#KMW_KbdVisibleMsg { + display: none; +} + +#KeymanWebControl #kmwico ul li a.kmw_show span#KMW_KbdVisibleMsg { + display: inline; +} + +#KeymanWebControl #kmwico ul li a.kmw_hide span#KMW_KbdHiddenMsg { + display: inline; +} + +#KeymanWebControl #kmwico ul li a.kmw_hide span#KMW_KbdVisibleMsg { + display: none; +} + +#KeymanWebControl #kmwico ul li#KMW_ButtonUI_KbdIcon { + border-bottom: solid 1px #ad4a28; +} +` } - ui.shutdown = function() { - var root = ui._insertedElem; - if(root) { - root.parentNode.removeChild(root); - } - } + const ui = keymanweb.ui = new UIButton(); /** * Keyboard registration event handler @@ -416,182 +663,15 @@ if(!window['keyman']['ui']['name']) { * Set a timer to update the UI keyboard list on timeout after each keyboard is registered, * thus updating only once when only if multiple keyboards are registered together */ - keymanweb['addEventListener']('keyboardregistered', - function(p) - { - ui.updateList = true; - if(ui.updateTimer) clearTimeout(ui.updateTimer); - ui.updateTimer = setTimeout(ui.updateKeyboardList,20); - }); - - - /** - * Update the entire menu when keyboards are registered or deregistered - **/ - ui.updateKeyboardList = function() - { - ui.updateList = false; - - if(!ui.init) return; - - // Clear existing list first (first two nodes must be preserved) - for(var i:number=ui._KeymanWeb_KbdList.childNodes.length; i>2; i--) - ui._KeymanWeb_KbdList.removeChild(ui._KeymanWeb_KbdList.childNodes[i-1]); - - var kbds=keymanweb['getKeyboards'](); - if(kbds.length > 0) - { - for(var i:number=0; i 26) _t=_t.substr(0,24)+'\u2026'; - _a2.appendChild(document.createTextNode(_t)); - _a2.onclick = ui._SelectKeyboard; - _a2.href = '#'; - _a2.id='KMWSel_'+Lki+'$'+Lklc; - _li2.appendChild(_a2); - ui._KeymanWeb_KbdList.appendChild(_li2); - } - - // Define appearance of this interface - ui._Appearance = - "#kmwico, #kmwkbd {"+ - "vertical-align: middle;"+ - "} "+ - - "#KeymanWebControl {float:left;} "+ - - "#KeymanWebControl * "+ - "{"+ - "letter-spacing: 0px !important;"+ - "line-height: 1li !important;"+ - "white-space: nowrap !important;"+ - "} "+ - - "#KeymanWebControl #kmwico img {"+ - "vertical-align: top;"+ - "padding: 0;"+ - "margin: 0;"+ - "border: none;"+ - "} "+ - - "#KeymanWebControl #kmwico, #kmwico ul {"+ - "padding: 0;"+ - "margin: 0;"+ - "list-style: none;"+ - "} "+ - - "#KeymanWebControl #kmwico_a {"+ - "display: block;"+ - //"border: none !important;"+ - "width: 22px; height: 23px; "+ /* sizes needed for kmw_button.gif */ - "} "+ - - "#KeymanWebControl #kmwico li { "+ - "text-align: left;"+ - "} "+ - - "#KeymanWebControl #kmwico li ul {"+ - "display: block;"+ - "position: absolute;"+ - "left: -5999px;"+ - "border: solid 2px #ad4a28;"+ - "background: white;"+ - "border-radius: 4px;"+ - "box-shadow: 4px 4px 2px #666;"+ - "z-index: 10011;"+ /* above the osk */ - "} "+ - - "#KeymanWebControl #kmwico li.sfunhover ul {"+ - "display: none; left: -5999px;"+ - "} "+ - - "#KeymanWebControl #kmwico li:hover ul, #kmwico li.sfhover ul {"+ - "display: block;"+ - "left: auto;"+ - "} "+ - - "#KeymanWebControl #kmwico ul li {"+ - "float: none;"+ - "padding: 0 !important;"+ - "margin: 0 !important;"+ - "width: 136px !important;"+ - "} "+ - - "#KeymanWebControl #KMW_LanguageName {"+ - "font-weight: bold;"+ - "} "+ - - "#KeymanWebControl #kmwico ul li a, #kmwico ul li a:visited {"+ - "display: block;"+ - "padding: 2px 4px !important;"+ - "border: none !important;"+ - // "width: auto;"+ - "color: #404040;"+ - "font-family: Tahoma,Verdana,Arial,sans-serif;"+ - "font-size: 8pt;"+ - "text-decoration: none;"+ - "} "+ - - "#KeymanWebControl #kmwico ul li a.selected {"+ - "font-weight: bold;"+ - "color: black;"+ - "} "+ - - "#KeymanWebControl #kmwico ul li a:hover {"+ - "color: white;"+ - "background-color: #ad4a28;"+ - "} "+ - - "#KeymanWebControl #kmwico ul li a.kmw_disabled, #KeymanWebControl #kmwico ul li a.kmw_disabled:hover {"+ - "color: #c0c0c0; cursor: default;"+ - "background-color: white;"+ - "} "+ - - "#KeymanWebControl #kmwico ul li a.kmw_show span#KMW_KbdHiddenMsg, #KeymanWebControl #kmwico ul li a.kmw_disabled span#KMW_KbdVisibleMsg {"+ - "display: none;"+ - "} "+ - - "#KeymanWebControl #kmwico ul li a.kmw_show span#KMW_KbdVisibleMsg {"+ - "display: inline;"+ - "} "+ - - "#KeymanWebControl #kmwico ul li a.kmw_hide span#KMW_KbdHiddenMsg {"+ - "display: inline;"+ - "} "+ - - "#KeymanWebControl #kmwico ul li a.kmw_hide span#KMW_KbdVisibleMsg {"+ - "display: none;"+ - "} "+ - - "#KeymanWebControl #kmwico ul li#KMW_ButtonUI_KbdIcon {"+ - "border-bottom: solid 1px #ad4a28;"+ - "} "; + ui.updateTimer = window.setTimeout(ui.updateKeyboardList, 20); + }); // but also call initialization when script loaded, which is after KMW initialization for asynchronous script loading - ui.Initialize(); + ui.initialize(); } catch(err){} } \ No newline at end of file diff --git a/web/src/app/ui/kmwuifloat.ts b/web/src/app/ui/kmwuifloat.ts index b9313f6e2f..0d15a3d649 100644 --- a/web/src/app/ui/kmwuifloat.ts +++ b/web/src/app/ui/kmwuifloat.ts @@ -1,12 +1,17 @@ /*** - KeymanWeb 11.0 - Copyright 2019 SIL International + KeymanWeb 17.0 + Copyright 2019-2023 SIL International ***/ +import type { KeymanEngine, KeyboardCookie, UIModule } from 'keyman/app/browser'; +import type { FloatingOSKViewCookie } from 'keyman/engine/osk'; + +declare var keyman: KeymanEngine + // If a UI module has been loaded, we can rely on the publically-published 'name' property // having been set as a way to short-out a UI reload. Its parent object always exists by // this point in the build process. -if(!window['keyman']['ui']['name']) { +if(!keyman?.ui?.name) { /********************************/ /* */ /* Floating User Interface */ @@ -25,531 +30,573 @@ if(!window['keyman']['ui']['name']) { try { // Declare KeymanWeb, OnScreen keyboard and Util objects - var keymanweb=window['keyman']; - var util=keymanweb['util']; + const keymanweb = keyman; + const util=keymanweb.util; + var dbg=keymanweb['debug']; // Disable UI for touch devices - if(util['isTouchDevice']()) throw ''; - - // User interface global and local variables - keymanweb['ui'] = {}; - var ui=keymanweb['ui']; - ui['name'] = 'float'; - - ui.KeyboardSelector = null; - - ui.outerDiv = null; // replaces DivKeymanWeb - ui.innerDiv = null; // replaces Lkdiv - ui.oskButton = null; // toggles OSK on or off - ui.kbdIcon = null; // keyboard icon within OSK button - ui.selecting = false; // control focus behaviour during selection - ui.updateList = true; // control keyboard list updating - ui.updateTimer = null; // prevent unnecessary list refreshing - ui.floatRight = false; // align left by default - ui.initialized = false; // initialization flag - ui.initTimer = null; - - /** - * Display or hide the OSK from the OSK icon link - */ - ui.toggleOSK = function() - { - keymanweb['activatingUI'](true); - let osk = keymanweb['osk']; - if(osk && osk['show']) - { - if(osk['isEnabled']()) osk['hide'](); else osk['show'](true); - } - if(window.event) window.event.returnValue=false; - keymanweb['focusLastActiveElement'](); - keymanweb['activatingUI'](false); - return false; + if(util.isTouchDevice()) { + throw ''; } - /** - * Function Initialize - * Scope Private - * Description UI Initialization - **/ - ui['initialize'] = ui.Initialize = function() { - if(ui.initTimer) { - window.clearTimeout(ui.initTimer); - ui.initTimer = null; + class UIFloat implements UIModule { + // User interface global and local variables + readonly name = 'float'; + + /** + * The top-level element for the keyboard-selection drop menu + */ + KeyboardSelector: HTMLSelectElement = null; + + /** + * Container for all Float UI elements (visible when KeymanWeb is active) + */ + outerDiv: HTMLDivElement = null; // replaces DivKeymanWeb + + innerDiv: HTMLDivElement = null; // replaces Lkdiv + + /** + * The button element allowing users to toggle the OSK on or off + */ + oskButton: HTMLDivElement = null; + + /** + * keyboard icon within the OSK button + */ + kbdIcon: HTMLImageElement = null; + + /** + * Tracks ongoing user interactions with the Float UI. This is used to help + * control focus-related behaviors during selection so that KMW may more easily + * maintain the user's intended focus & expected context. + */ + selecting = false; + + updateList = true; // control keyboard list updating + updateTimer: number = 0; // prevent unnecessary list refreshing + floatRight = false; // align left by default + initialized = false; // initialization flag + initTimer: number = 0; + + /** + * Display or hide the OSK from the OSK icon link + */ + readonly toggleOSK = () => { + keymanweb.activatingUI(true); + let osk = keymanweb.osk; + if(osk && osk.show) { + if(osk.isEnabled()) { + osk.hide(); + } else { + osk.show(true); + } + } + if(window.event) { + window.event.returnValue=false; + } + keymanweb.focusLastActiveElement(); + keymanweb.activatingUI(false); + return false; } - // Must always initialize after keymanWeb itself, otherwise options are undefined - if(!keymanweb['initialized']) { - ui.initTimer = window.setTimeout(ui.Initialize, 50); - return; - } - - if(ui.initialized || util['isTouchDevice']()) { - return; - } - - var imgPath=util['getOption']('resources')+"ui/float/"; - - ui.outerDiv = util['createElement']('DIV'); // Container for UI (visible when KeymanWeb is active) - ui.innerDiv = util['createElement']('DIV'); // inner div for UI - ui.kbdIcon = util['createElement']('IMG'); - ui.outerDiv.innerHTML = "" - + "KeymanWeb"; /* I2081 */ - - var s=ui.outerDiv.style; - s.backgroundColor='white'; s.border='solid 1px black'; s.position='absolute'; s.height='18px'; - s.font='bold 8pt sans-serif'; s.display='none'; s.textAlign='left';s.overflow='hidden'; - - util['attachDOMEvent'](ui.outerDiv,'mousedown',ui._SelectorMouseDown); - util['attachDOMEvent'](ui.outerDiv,'mouseover',ui._SelectorMouseOver); - util['attachDOMEvent'](ui.outerDiv,'mouseout',ui._SelectorMouseOut); - - // Register keymanweb events - ui.registerEvents(); - - ui.kbdIcon.src = imgPath+'kbdicon.gif'; - ui.kbdIcon.title = 'Display visual keyboard'; - - // Set initial OSK button style (off by default) - ui.oskButtonState(false); - - var Lhdiv = util['createElement']('DIV'); - ui.oskButton = Lhdiv; - Lhdiv.onclick = ui.toggleOSK; - Lhdiv.appendChild(ui.kbdIcon); - ui.innerDiv.appendChild(Lhdiv); - ui.outerDiv.appendChild(ui.innerDiv); - document.body.appendChild(ui.outerDiv); - - ui.KeyboardSelector = util['createElement']('SELECT'); // ControlSelector - KeymanWeb keyboard selector - - s=ui.KeyboardSelector.style; - s.font='8pt sans-serif'; s.backgroundColor='#f3e5de'; s.border='solid 1px #7B9EBD'; s.height='16px';s.margin='1px 2px 0px 2px'; - s.left='20px'; s.top='0px'; s.position='absolute'; s.maxWidth='150px'; - - util['attachDOMEvent'](ui.KeyboardSelector,'change',ui.SelectKeyboardChange); - util['attachDOMEvent'](ui.KeyboardSelector,'blur',ui.SelectBlur); - - ui.innerDiv.appendChild(ui.KeyboardSelector); //this may need to be moved up.... - - // Check required interface alignment and default keyboard - var opt=util['getOption']('ui'),dfltKeyboard='(System keyboard)'; - if(opt && typeof(opt) == 'object') - { - if(typeof(opt['position']) == 'string' && opt['position'] == 'right') - ui.floatRight = true; - if(typeof(opt['default']) == 'string') - dfltKeyboard = opt['default']; - } - - var Lopt = util['createElement']('OPTION'); - Lopt.value = '-'; - Lopt.innerHTML = dfltKeyboard; - ui.KeyboardSelector.appendChild(Lopt); - Lopt = null; - - // This must be set before updating the keyboard list to prevent recursion! - ui.initialized = true; - - // Update the keyboard list if required - ui.updateKeyboardList(); - - //may also want to initialize style sheet here ?? - } - - ui._UnloadUserInterface = function() { - ui.KeyboardSelector = ui.innerDiv = ui.outerDiv = ui.kbdIcon = null; - }; - /** - * UI removal - resource cleanup - */ - keymanweb['addEventListener']('unloaduserinterface', ui._UnloadUserInterface); - - - ui.shutdown = function() { - var root = ui.outerDiv; - if(root) { - root.parentNode.removeChild(root); - } - - ui._UnloadUserInterface(); - - if(window.removeEventListener) { - window.removeEventListener('resize', ui._Resize, false); - } - } - - /** - * Update list of keyboards shown by UI - */ - ui.updateKeyboardList = function() - { - // Do nothing unless list requires updating - if(ui.updateList) - { - if(!ui.initialized) ui.Initialize(); - - // Remove current list (except for default element) - var i,opts=ui.KeyboardSelector.getElementsByTagName('OPTION'); - for(i=opts.length; i>1; i--) - { - ui.KeyboardSelector.removeChild(opts[i-1]); + /** + * Function Initialize + * Scope Private + * Description UI Initialization + **/ + readonly initialize = () => { + if(this.initTimer) { + window.clearTimeout(this.initTimer); + this.initTimer = null; } - // Loop over installed keyboards and add to selection list - var Ln,Lopt,Lkbds=keymanweb['getKeyboards'](); + // Must always initialize after keymanWeb itself, otherwise options are undefined + if(!keymanweb.initialized) { + this.initTimer = window.setTimeout(this.initialize, 50); + return; + } - for(Ln=0; Ln"; /* I2081 */ + + var s=this.outerDiv.style; + s.backgroundColor='white'; s.border='solid 1px black'; s.position='absolute'; s.height='18px'; + s.font='bold 8pt sans-serif'; s.display='none'; s.textAlign='left';s.overflow='hidden'; + + util.attachDOMEvent(this.outerDiv, 'mousedown', this._SelectorMouseDown); + util.attachDOMEvent(this.outerDiv, 'mouseover', this._SelectorMouseOver); + util.attachDOMEvent(this.outerDiv, 'mouseout', this._SelectorMouseOut); + + // Register keymanweb events + this.registerEvents(); + + this.kbdIcon.src = imgPath+'kbdicon.gif'; + this.kbdIcon.title = 'Display visual keyboard'; + + // Set initial OSK button style (off by default) + this.oskButtonState(false); + + var Lhdiv = util.createElement('div'); + this.oskButton = Lhdiv; + Lhdiv.onclick = this.toggleOSK; + Lhdiv.appendChild(this.kbdIcon); + this.innerDiv.appendChild(Lhdiv); + this.outerDiv.appendChild(this.innerDiv); + document.body.appendChild(this.outerDiv); + + this.KeyboardSelector = util.createElement('select'); // ControlSelector - KeymanWeb keyboard selector + + s=this.KeyboardSelector.style; + s.font='8pt sans-serif'; + s.backgroundColor='#f3e5de'; + s.border='solid 1px #7B9EBD'; + s.height='16px'; + s.margin='1px 2px 0px 2px'; + s.left='20px'; + s.top='0px'; + s.position='absolute'; + s.maxWidth='150px'; + + util.attachDOMEvent(this.KeyboardSelector, 'change', this.SelectKeyboardChange); + util.attachDOMEvent(this.KeyboardSelector, 'blur', this.SelectBlur); + + this.innerDiv.appendChild(this.KeyboardSelector); //this may need to be moved up.... + + // Check required interface alignment and default keyboard + var opt=util.getOption('ui'), dfltKeyboard='(System keyboard)'; + if(opt && typeof(opt) == 'object') { + if(typeof(opt['position']) == 'string' && opt['position'] == 'right') { + this.floatRight = true; + } if(typeof(opt['default']) == 'string') { + dfltKeyboard = opt['default']; + } + } + + var Lopt = util.createElement('option'); + Lopt.value = '-'; + Lopt.innerHTML = dfltKeyboard; + this.KeyboardSelector.appendChild(Lopt); + Lopt = null; + + // This must be set before updating the keyboard list to prevent recursion! + this.initialized = true; + + // Update the keyboard list if required + this.updateKeyboardList(); + + //may also want to initialize style sheet here ?? + } + + readonly _UnloadUserInterface = () => { + this.KeyboardSelector = this.innerDiv = this.outerDiv = this.kbdIcon = null; + }; + + /** + * UI removal - resource cleanup + */ + shutdown() { + var root = this.outerDiv; + if(root) { + root.parentNode.removeChild(root); + } + + this._UnloadUserInterface(); + + if(window.removeEventListener) { + window.removeEventListener('resize', this._Resize, false); + } + } + + /** + * Update list of keyboards shown by UI + */ + readonly updateKeyboardList = () => { + // Do nothing unless list requires updating + if(this.updateList) { + if(!this.initialized) { + this.initialize(); } - ui.KeyboardSelector.appendChild(Lopt); - Lopt = null; + // Remove current list (except for default element) + const opts=this.KeyboardSelector.getElementsByTagName('OPTION'); + for(let i=opts.length; i>1; i--) { + this.KeyboardSelector.removeChild(opts[i-1]); + } + + // Loop over installed keyboards and add to selection list + var Lkbds=keymanweb.getKeyboards(); + + for(let Ln=0; Ln { + var i=0; - // Redisplay the UI to correct width for any new language entries - if(keymanweb['getLastActiveElement']()) - { - ui.HideInterface(); - ui.ShowInterface(); - } - } + // This can be called during startup before fully initialized - ignore if so + if(!this.initialized) { + return; + } - /** - * Function updateMenu - * Scope Private - * @param {string} kbd - * @param {string} lg - * Description Update the UI menu selection - * Separate arguments passed to allow better control of selection when a keyboard - * is listed more than once for different language codes - */ - ui.updateMenu = function(kbd,lg) - { - var i=0; + var match = kbd; + if(lg != '') { + match += ':' + lg; + } - // This can be called during startup before fully initialized - ignore if so - if(!ui.initialized) return; + if(kbd == '') { + this.KeyboardSelector.selectedIndex=0; + } else { + const opt=this.KeyboardSelector.getElementsByTagName('option'); + for(i=0; i { + var s = this.kbdIcon.style; + s.width='24px'; + s.height='13px'; + s.top='1px'; + s.verticalAlign='bottom'; + s.padding='2px 2px 1px 2px'; + s.position='absolute'; + s.border=oskEnabled ? 'inset 1px #808080' : 'none'; + s.background=oskEnabled ? '#f7e7de' : 'white'; + s.display = 'block'; + if(this.initialized) { + this.oskButton.style.display = 'block'; + } + } + + /** + * Register all keymanweb and OSK events only after keymanweb is fully initialized * - * Set a timer to update the UI keyboard list on timeout after each keyboard is registered, - * thus updating only once when only if multiple keyboards are registered together - */ - keymanweb['addEventListener']('keyboardregistered', - function(p) - { - ui.updateList = true; - if(ui.updateTimer) clearTimeout(ui.updateTimer); - ui.updateTimer = setTimeout(ui.updateKeyboardList,200); + **/ + registerEvents() { + /** + * Keyboard registration event handler + * + * Set a timer to update the UI keyboard list on timeout after each keyboard is registered, + * thus updating only once when only if multiple keyboards are registered together + */ + keymanweb.addEventListener('keyboardregistered', (p) => { + this.updateList = true; + if(this.updateTimer) { + clearTimeout(this.updateTimer); + } + this.updateTimer = window.setTimeout(this.updateKeyboardList, 200); }); - /** - * Keyboard load event handler - * - * Set the menu selector to the currently saved keyboard when a keyboard is loaded - * - * Note: Cannot simply set it to the loaded keyboard, - * as more than one language may be supported by that keyboard. - */ - keymanweb['addEventListener']('keyboardloaded', - function(p) { - var sk = keymanweb['getSavedKeyboard']().split(':'); - if(sk.length > 1) ui.updateMenu(sk[0],sk[1]); + /** + * Keyboard load event handler + * + * Set the menu selector to the currently saved keyboard when a keyboard is loaded + * + * Note: Cannot simply set it to the loaded keyboard, + * as more than one language may be supported by that keyboard. + */ + keymanweb.addEventListener('keyboardloaded', (p) => { + const sk = keymanweb.getSavedKeyboard().split(':'); + if(sk.length > 1) { + this.updateMenu(sk[0],sk[1]); + } }); - /** - * Keyboard change event handler - * - * Update menu selection and control OSK display appropriately - */ - keymanweb['addEventListener']('keyboardchange', - function(p) - { + /** + * Keyboard change event handler + * + * Update menu selection and control OSK display appropriately + */ + keymanweb.addEventListener('keyboardchange', (p) => { // Update the keyboard selector whenever a keyboard is loaded - ui.updateMenu(p['internalName'],p['languageCode']); + this.updateMenu(p.internalName, p.languageCode); // (Conditionally) display the OSK button, and set the style - ui.addButtonOSK(); + this.addButtonOSK(); }); - let osk = keymanweb['osk']; - if(!osk) { - return; - } - /** - * Show OSK event handler: show or hide the OSK button (never display if a CJK keyboard) - */ - osk['addEventListener']('show', - function(oskPosition) - { - ui.addButtonOSK(); + let osk = keymanweb.osk; + if(!osk) { + return; + } + + /** + * Show OSK event handler: show or hide the OSK button (never display if a CJK keyboard) + */ + osk.addEventListener('show', (oskPosition) => { + this.addButtonOSK(); return oskPosition; }); - /** - * Hide OSK event handler - */ + /** + * Hide OSK event handler + */ - osk['addEventListener']('hide', - function(hiddenByUser) - { - if(ui.initialized) ui.oskButtonState(false); - }); - } - - /** - * Function _SelectorMouseDown - * Scope Private - * @param {Object} e event - * Description Set KMW UI activation state on mouse click - */ - ui._SelectorMouseDown = function(e) - { - if(keymanweb['activatingUI']) keymanweb['activatingUI'](1); - } - - /** - * Function _SelectorMouseOver - * Scope Private - * @param {Object} e event - * Description Set KMW UI activation state on mouse over - */ - ui._SelectorMouseOver = function(e) - { - if(keymanweb['activatingUI']) keymanweb['activatingUI'](1); - } - - /** - * Function _SelectorMouseOut - * Scope Private - * @param {Object} e event - * Description Clear KMW UI activation state on mouse out - */ - ui._SelectorMouseOut = function(e) - { - if(keymanweb['activatingUI']) keymanweb['activatingUI'](0); - } - - /** - * Function _SelectKeyboardChange - * Scope Private - * @param {Object} e event - * Description Change active keyboard in response to user selection event - */ - ui.SelectKeyboardChange = function(e) - { - keymanweb['activatingUI'](true); - if(ui.KeyboardSelector.value != '-') - { - var i=ui.KeyboardSelector.selectedIndex; - var t=ui.KeyboardSelector.options[i].value.split(':'); - keymanweb['setActiveKeyboard'](t[0],t[1]); - } - else - keymanweb['setActiveKeyboard'](''); - - //if(osk['show']) osk['show'](osk['isEnabled']()); handled by keyboard change event??? - keymanweb['focusLastActiveElement'](); - keymanweb['activatingUI'](false); - ui.selecting = true; - } - - /** - * Function _SelectBlur - * Scope Private - * @param {Object} e event - * Description Ensure OSK is hidden when moving focus after reselecting a keyboard - */ - ui.SelectBlur = function(e) - { - if(!ui.selecting) keymanweb['focusLastActiveElement'](); - ui.selecting = false; - } - - /** - * Function ShowInterface - * Scope Private - * @param {number=} Px x-position for KMW window - * @param {number=} Py y-position for KMW window - * Description Display KMW window at specified position - */ - ui.ShowInterface = function(Px, Py) - { - if(!ui.initialized) return; - - var Ls = ui.outerDiv.style; - - if(Px && Py) { - Ls.left = Px + 'px'; - Ls.top = Py + 'px'; - } - Ls.display = 'block'; - - ui.kbdIcon.style.left = ui.KeyboardSelector.offsetWidth + 24 + 'px'; - - // (Conditionally) display the OSK button - ui.addButtonOSK(); - - // Set the language selection to the currently active keyboard, if listed - ui.updateMenu(keymanweb['getActiveKeyboard'](),keymanweb['getActiveLanguage']()); - } - - /** - * Function HideInterface - * Scope Private - * Description Completely hide KMW window - */ - ui.HideInterface = function() - { - if(!ui.initialized) return; - - ui.outerDiv.style.display = 'none'; - } - - - /** - * Function addButtonOSK - * Scope Private - * Description Display the OSK button unless a CJK keyboard (or English) - */ - ui.addButtonOSK = function() - { - if(ui.oskButton != null) - { - if(keymanweb['isCJK']() || (ui.KeyboardSelector.selectedIndex==0)) - { - ui.oskButton.style.display = 'none'; - ui.outerDiv.style.width = ui.KeyboardSelector.offsetWidth+30+'px'; - } - else - { - ui.oskButton.style.display = 'block'; - let osk = keymanweb['osk']; - if(osk) { - ui.oskButtonState(osk['isEnabled']()); - } else { - ui.oskButtonState(false); + osk.addEventListener('hide', (hiddenByUser) => { + if(this.initialized) { + this.oskButtonState(false); } - ui.outerDiv.style.width = ui.KeyboardSelector.offsetWidth+56+'px'; + }); + } + + /** + * Function _SelectorMouseDown + * Scope Private + * @param {Object} e event + * Description Set KMW UI activation state on mouse click + */ + readonly _SelectorMouseDown = (e: MouseEvent) => { + if(keymanweb.activatingUI) { + keymanweb.activatingUI(1); } } - } - //TODO: had to expose properties of params - what does that do? (focus event doesn't normally include these properties?) - keymanweb['addEventListener']('controlfocused', - function(params) - { - if(params['activeControl'] == null || params['activeControl']['_kmwAttachment']) - { - /*if(keymanweb.domManager._IsEditableIframe(Ltarg)) - Ltarg = Ltarg.defaultView.frameElement;*/ - if(ui.floatRight) // I1296 - ui.ShowInterface(util['getAbsoluteX'](params.target) + params.target.offsetWidth + 1, util['getAbsoluteY'](params.target) + 1); - else - ui.ShowInterface(util['getAbsoluteX'](params.target), util['getAbsoluteY'](params.target) - - parseInt(util['getStyleValue'](ui.outerDiv,'height'),10) - 2); + + /** + * Function _SelectorMouseOver + * Scope Private + * @param {Object} e event + * Description Set KMW UI activation state on mouse over + */ + readonly _SelectorMouseOver = (e: MouseEvent) => { + if(keymanweb.activatingUI) { + keymanweb.activatingUI(1); + } + } + + /** + * Function _SelectorMouseOut + * Scope Private + * @param {Object} e event + * Description Clear KMW UI activation state on mouse out + */ + readonly _SelectorMouseOut = (e: MouseEvent) => { + if(keymanweb.activatingUI) { + keymanweb.activatingUI(0); + } + } + + /** + * Function _SelectKeyboardChange + * Scope Private + * @param {Object} e event + * Description Change active keyboard in response to user selection event + */ + readonly SelectKeyboardChange = (e) => { + keymanweb.activatingUI(true); + + if(this.KeyboardSelector.value != '-') { + var i=this.KeyboardSelector.selectedIndex; + var t=this.KeyboardSelector.options[i].value.split(':'); + keymanweb.setActiveKeyboard(t[0],t[1]); + } else { + keymanweb.setActiveKeyboard(''); + } + + //if(osk['show']) osk['show'](osk['isEnabled']()); handled by keyboard change event??? + keymanweb.focusLastActiveElement(); + keymanweb.activatingUI(false); + this.selecting = true; + } + + /** + * Function _SelectBlur + * Scope Private + * @param {Object} e event + * Description Ensure OSK is hidden when moving focus after reselecting a keyboard + */ + readonly SelectBlur = (e) => { + if(!this.selecting) { + keymanweb.focusLastActiveElement(); + } + this.selecting = false; + } + + /** + * Function ShowInterface + * Scope Private + * @param {number=} Px x-position for KMW window + * @param {number=} Py y-position for KMW window + * Description Display KMW window at specified position + */ + readonly ShowInterface = (Px?: number, Py?: number) => { + if(!this.initialized) return; + + var Ls = this.outerDiv.style; + + if(Px && Py) { + Ls.left = Px + 'px'; + Ls.top = Py + 'px'; + } + Ls.display = 'block'; + + this.kbdIcon.style.left = this.KeyboardSelector.offsetWidth + 24 + 'px'; + + // (Conditionally) display the OSK button + this.addButtonOSK(); + + // Set the language selection to the currently active keyboard, if listed + this.updateMenu(keymanweb.getActiveKeyboard(), keymanweb.getActiveLanguage()); + } + + /** + * Function HideInterface + * Scope Private + * Description Completely hide KMW window + */ + readonly HideInterface = () => { + if(!this.initialized) { + return; + } + + this.outerDiv.style.display = 'none'; + } + + /** + * Function addButtonOSK + * Scope Private + * Description Display the OSK button unless a CJK keyboard (or English) + */ + readonly addButtonOSK = () => { + if(this.oskButton != null) { + if(keymanweb.isCJK() || (this.KeyboardSelector.selectedIndex==0)) { + this.oskButton.style.display = 'none'; + this.outerDiv.style.width = this.KeyboardSelector.offsetWidth+30+'px'; + } else { + this.oskButton.style.display = 'block'; + let osk = keymanweb.osk; + if(osk) { + this.oskButtonState(osk.isEnabled()); + } else { + this.oskButtonState(false); + } + this.outerDiv.style.width = this.KeyboardSelector.offsetWidth+56+'px'; + } + } + } + + /** + * Function _Resize + * Scope Private + * @param {Object} e event object + * @return {boolean} + * Description Display KMW OSK at specified position (returns nothing) + */ + readonly _Resize = (e) => { + if(this.outerDiv.style.display =='block') { + var elem = keymanweb.getLastActiveElement(); + if(this.floatRight) { // I1296 + this.ShowInterface(util.getAbsoluteX(elem) + elem.offsetWidth + 1, util.getAbsoluteY(elem) + 1); + } else { + this.ShowInterface(util.getAbsoluteX(elem) + 1, util.getAbsoluteY(elem) + elem.offsetHeight + 1); + } } return true; - }); + } + } - keymanweb['addEventListener']('controlblurred', - function(params) - { - if(!params['event']) return true; // I2404 - Manage IE events in IFRAMEs + const ui=keymanweb.ui = new UIFloat(); - if(!params['isActivating']) ui.HideInterface(); - - return true; - }); - - /** - * Function _Resize - * Scope Private - * @param {Object} e event object - * @return {boolean} - * Description Display KMW OSK at specified position (returns nothing) - */ - ui._Resize = function(e) - { - if(ui.outerDiv.style.display =='block') - { - var elem = keymanweb['getLastActiveElement'](); - if(ui.floatRight) // I1296 - ui.ShowInterface(util['getAbsoluteX'](elem) + elem.offsetWidth + 1, util['getAbsoluteY'](elem) + 1); - else - ui.ShowInterface(util['getAbsoluteX'](elem) + 1, util['getAbsoluteY'](elem) + elem.offsetHeight + 1); + //TODO: had to expose properties of params - what does that do? (focus event doesn't normally include these properties?) + keymanweb.addEventListener('controlfocused', (params) => { + // ... this check shouldn't need to check _kmwAttachment directly. + if(params.activeControl == null || params.activeControl['_kmwAttachment']) { + /*if(keymanweb.domManager._IsEditableIframe(Ltarg)) + Ltarg = Ltarg.defaultView.frameElement;*/ + if(ui.floatRight) { // I1296 + ui.ShowInterface(util.getAbsoluteX(params.target) + params.target.offsetWidth + 1, util.getAbsoluteY(params.target) + 1); + } else { + ui.ShowInterface(util.getAbsoluteX(params.target), util.getAbsoluteY(params.target) + - parseInt(util.getStyleValue(ui.outerDiv,'height'),10) - 2); + } } return true; + }); + + keymanweb.addEventListener('controlblurred', (params) => { + if(!params.event) { + return true; // I2404 - Manage IE events in IFRAMEs + } + + if(!params.isActivating) { + ui.HideInterface(); + } + + return true; + }); + + if(window.addEventListener) { + window.addEventListener('resize', ui._Resize, false); } - if(window.addEventListener) - window.addEventListener('resize', ui._Resize, false); - // but also call initialization when script loaded, which is after KMW initialization for asynchronous script loading - ui.Initialize(); + ui.initialize(); + + // is/was never actually raised. Note that the `shutdown` method likely fulfills a similar role. + // keymanweb.addEventListener('unloaduserinterface', ui._UnloadUserInterface); } catch(err){} diff --git a/web/src/app/ui/tsconfig.json b/web/src/app/ui/tsconfig.json index 84f3b46025..83eef7b7a0 100644 --- a/web/src/app/ui/tsconfig.json +++ b/web/src/app/ui/tsconfig.json @@ -9,8 +9,8 @@ }, "extends": "../../tsconfig.base.json", "files": [ - // "kmwuibutton.ts", - // "kmwuifloat.ts", + "kmwuibutton.ts", + "kmwuifloat.ts", "kmwuitoggle.ts", "kmwuitoolbar.ts" ], diff --git a/web/src/test/auto/dom/CI.conf.cjs b/web/src/test/auto/dom/CI.conf.cjs index c7c9813df7..a96ffebbed 100644 --- a/web/src/test/auto/dom/CI.conf.cjs +++ b/web/src/test/auto/dom/CI.conf.cjs @@ -18,7 +18,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. + // Can't set in base.conf.cjs b/c of constant's definition style. logLevel: config.LOG_INFO }; diff --git a/web/src/test/auto/dom/manual.conf.cjs b/web/src/test/auto/dom/manual.conf.cjs index 88e20d283c..37d982e9b5 100644 --- a/web/src/test/auto/dom/manual.conf.cjs +++ b/web/src/test/auto/dom/manual.conf.cjs @@ -18,7 +18,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. + // Can't set in base.conf.cjs b/c of constant's definition style. logLevel: config.LOG_INFO }; diff --git a/web/src/test/auto/integrated/CI.conf.cjs b/web/src/test/auto/integrated/CI.conf.cjs index 38ee2c97a0..94e94a0fc8 100644 --- a/web/src/test/auto/integrated/CI.conf.cjs +++ b/web/src/test/auto/integrated/CI.conf.cjs @@ -1,4 +1,4 @@ -var BASE_CONFIG = require("./base.conf.js"); +var BASE_CONFIG = require("./base.conf.cjs"); var ci_config_adapter = require("../../../../../common/test/resources/karma-browserstack-config.cjs"); module.exports = ci_config_adapter(BASE_CONFIG, "full"); \ No newline at end of file diff --git a/web/src/test/auto/integrated/cases/basics.js b/web/src/test/auto/integrated/cases/basics.js index ac99e9f830..c06ce73d38 100644 --- a/web/src/test/auto/integrated/cases/basics.js +++ b/web/src/test/auto/integrated/cases/basics.js @@ -73,60 +73,60 @@ Modernizr.on('touchevents', function(result) { }) }); -// describe('Basic Button UI', function() { + describe('Basic Button UI', function() { -// beforeEach(function() { -// this.timeout(testconfig.timeouts.uiLoad); -// fixture.setBase('fixtures'); -// fixture.load('singleInput.html'); + beforeEach(function() { + this.timeout(testconfig.timeouts.uiLoad); + fixture.setBase('fixtures'); + fixture.load('singleInput.html'); -// // Loads two scripts in parallel, but just in case, 2x timeout. -// return setupKMW('button', testconfig.timeouts.uiLoad); -// }); + // Loads two scripts in parallel, but just in case, 2x timeout. + return setupKMW('button', testconfig.timeouts.uiLoad); + }); -// afterEach(function() { -// fixture.cleanup(); -// teardownKMW(); -// }); + afterEach(function() { + fixture.cleanup(); + teardownKMW(); + }); -// it('The Button UI initializes correctly.', function() { -// assert(keyman.ui.init, 'Initialization flag is set to false!'); -// }) -// }); + it('The Button UI initializes correctly.', function() { + assert(keyman.ui.init, 'Initialization flag is set to false!'); + }) + }); -// describe('Basic Float UI', function() { + describe('Basic Float UI', function() { -// beforeEach(function() { -// this.timeout(testconfig.timeouts.uiLoad); -// fixture.setBase('fixtures'); -// fixture.load('singleInput.html'); + beforeEach(function() { + this.timeout(testconfig.timeouts.uiLoad); + fixture.setBase('fixtures'); + fixture.load('singleInput.html'); -// // Loads two scripts in parallel, but just in case, 2x timeout. -// return setupKMW('float', testconfig.timeouts.uiLoad); -// }); + // Loads two scripts in parallel, but just in case, 2x timeout. + return setupKMW('float', testconfig.timeouts.uiLoad); + }); -// afterEach(function() { -// fixture.cleanup(); -// teardownKMW(); -// }); + afterEach(function() { + fixture.cleanup(); + teardownKMW(); + }); -// it('The Float UI initializes correctly.', function() { -// assert(keyman.ui.initialized, 'Initialization flag is set to false!'); + it('The Float UI initializes correctly.', function() { + assert(keyman.ui.initialized, 'Initialization flag is set to false!'); -// assert.isNotNull(keyman.ui.outerDiv, 'Failed to create the floating controller element!'); + assert.isNotNull(keyman.ui.outerDiv, 'Failed to create the floating controller element!'); -// var divs = document.getElementsByTagName("div"); -// var match = false; + var divs = document.getElementsByTagName("div"); + var match = false; -// for(var i=0; i < divs.length; i++) { -// if(divs[i] == keyman.ui.outerDiv) { -// match = true; -// } -// } + for(var i=0; i < divs.length; i++) { + if(divs[i] == keyman.ui.outerDiv) { + match = true; + } + } -// assert(match, 'Floating controller element has not been added to the page!'); -// }) -// }); + assert(match, 'Floating controller element has not been added to the page!'); + }) + }); describe('Basic Toolbar UI', function() { diff --git a/web/src/test/auto/integrated/manual.conf.cjs b/web/src/test/auto/integrated/manual.conf.cjs index 2c9e939f08..1969c40050 100644 --- a/web/src/test/auto/integrated/manual.conf.cjs +++ b/web/src/test/auto/integrated/manual.conf.cjs @@ -18,7 +18,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. + // Can't set in base.conf.cjs b/c of constant's definition style. logLevel: config.LOG_INFO }; diff --git a/web/src/test/manual/web/desktop-ui/button.html b/web/src/test/manual/web/desktop-ui/button.html index 73f058e494..a52dbfc0c5 100644 --- a/web/src/test/manual/web/desktop-ui/button.html +++ b/web/src/test/manual/web/desktop-ui/button.html @@ -23,7 +23,7 @@ - + - +