import { Atom } from '../core/atom' ; import { Box , makeStruts , coalesce , adjustInterAtomSpacing } from '../core/box' ; import { parseLatex } from '../core/parser' ; import { BACKGROUND_COLORS , FOREGROUND_COLORS } from '../core/color' ; import { l10n as l10nOptions , localize as l10n } from './l10n' ; import { attachButtonHandlers , ButtonHandlers , } from '../editor-mathfield/buttons' ; import { inject as injectStylesheet , Stylesheet } from '../common/stylesheet' ; import VIRTUAL_KEYBOARD_STYLESHEET from '../../css/virtual-keyboard.less' ; import CORE_STYLESHEET from '../../css/core.less' ; import { CoreOptions , VirtualKeyboardDefinition , VirtualKeyboardKeycap , VirtualKeyboardLayer , VirtualKeyboardOptions , } from '../public/options' ; import { Mathfield , VirtualKeyboardInterface } from '../public/mathfield' ; import { getActiveKeyboardLayout } from './keyboard-layout' ; import { loadFonts } from '../core/fonts' ; import { isArray } from '../common/types' ; import { COMMANDS , SelectorPrivate } from './commands' ; import { getMacros } from '../core-definitions/definitions' ; import { Scrim } from './scrim' ; import { Context } from '../core/context' ; import { DEFAULT_FONT_SIZE } from '../core/font-metrics' ; import { typeset } from '../core/typeset' ; import { getDefaultRegisters } from '../core/registers' ; import { throwIfNotInBrowser } from '../common/capabilities' ; import { hashCode } from '../common/hash-code' ; import { Selector } from '../public/commands' ; import { MathfieldPrivate } from './mathfield' ; let gScrim = null ; let VIRTUAL_KEYBOARD_STYLESHEET_HASH = undefined ; export function showAlternateKeys ( keyboard , altKeysetId ) { throwIfNotInBrowser ( ) ; const altKeys = ALT_KEYS [ altKeysetId ] ; const altContainer = document . createElement ( 'div' ) ; altContainer . setAttribute ( 'aria-hidden' , 'true' ) ; altContainer . className = 'ML__keyboard alternate-keys' + ( keyboard . element ! . classList . contains ( 'material' ) ? ' material' : '' ) ; altContainer . id = 'mathlive-alternate-keys-panel' ; if ( altKeys . length >= 14 ) { altContainer . style . width = '236px' ; } else if ( altKeys . length >= 7 ) { altContainer . style . width = '286px' ; } else if ( altKeys . length === 4 || altKeys . length === 2 ) { altContainer . style . width = '146px' ; } else if ( altKeys . length === 1 ) { altContainer . style . width = '86px' ; } else { altContainer . style . width = '146px' ; } altContainer . style . height = 'auto' ; let markup = '' ; for ( const altKey of altKeys ) { markup += '<li' ; if ( typeof altKey === 'string' ) { markup += ' data-latex="' + altKey . replace ( /"/g , '&quot;' ) + '"' ; } else { if ( altKey . latex ) { markup += ' data-latex="' + altKey . latex . replace ( /"/g , '&quot;' ) + '"' ; } if ( altKey . content ) { markup += ' data-content="' + altKey . content . replace ( /"/g , '&quot;' ) + '"' ; } if ( altKey . insert ) { markup += ' data-insert="' + altKey . insert . replace ( /"/g , '&quot;' ) + '"' ; } if ( altKey . command ) { if ( typeof altKey . command === 'string' ) { markup += ` data-command="${ altKey . command . replace ( /"/g , '&quot;' ) }"` ; } else { markup += " data-command='" + JSON . stringify ( altKey . command ) . replace ( /"/g , '&quot;' ) + "'" ; } } if ( altKey . aside ) { markup += ` data-aside="${ altKey . aside . replace ( /"/g , '&quot;' ) }"` ; } if ( altKey . class ) { markup += ` data-classes="${ altKey . class }"` ; } } markup += '>' ; markup += typeof altKey === 'string' ? altKey : altKey . label ?? '' ; markup += '</li>' ; } markup = '<ul>' + markup + '</ul>' ; altContainer . innerHTML = keyboard . options . createHTML ( markup ) ; makeKeycap ( keyboard , [ ... altContainer . querySelectorAll ( 'li' ) ] , 'performAlternateKeys' ) ; if ( gScrim === null ) gScrim = new Scrim ( ) ; gScrim . open ( { root : keyboard . options . virtualKeyboardContainer , child : altContainer , } ) ; const keycapElement = keyboard ?. element ! . querySelector ( 'div.keyboard-layer.is-visible div.rows ul li[data-alt-keys="' + altKeysetId + '"]' ) ; const position = keycapElement ?. getBoundingClientRect ( ) ; if ( position ) { if ( position . top - altContainer . clientHeight < 0 ) { altContainer . style . width = 'auto' ; if ( altKeys . length <= 6 ) { altContainer . style . height = '56px' ; } else if ( altKeys . length <= 12 ) { altContainer . style . height = '108px' ; } else if ( altKeys . length <= 18 ) { altContainer . style . height = '205px' ; } else { altContainer . classList . add ( 'compact' ) ; } } const top = ( position . top - altContainer . clientHeight + 5 ) . toString ( ) + 'px' ; const left = Math . max ( 0 , Math . min ( window . innerWidth - altContainer . offsetWidth , ( position . left + position . right - altContainer . offsetWidth ) / 2 ) ) + 'px' ; altContainer . style . transform = 'translate(' + left + ',' + top + ')' ; altContainer . classList . add ( 'is-visible' ) ; } return false ; } export function hideAlternateKeys ( ) { throwIfNotInBrowser ( ) ; const altContainer = document . querySelector < HTMLElement > ( '#mathlive-alternate-keys-panel' ) ; if ( altContainer ) { altContainer . classList . remove ( 'is-visible' ) ; altContainer . innerHTML = '' ; } gScrim ?. close ( ) ; return false ; } export class VirtualKeyboard implements VirtualKeyboardInterface { options : VirtualKeyboardOptions & CoreOptions ; _visible : boolean ; _element ? : HTMLDivElement ; private readonly _mathfield ? : MathfieldPrivate ; coreStylesheet : Stylesheet | null ; virtualKeyboardStylesheet : Stylesheet | null ; constructor ( options , mathfield ? ) { this . options = options ; this . visible = false ; this . _mathfield = mathfield as MathfieldPrivate ; this . coreStylesheet = null ; this . virtualKeyboardStylesheet = null ; } setOptions ( options ) { this . visible = false ; this . _element ?. remove ( ) ; this . _element = undefined ; this . options = options ; } get element ( ) : HTMLDivElement | undefined { return this . _element ; } set element ( val ) { this . _element ?. remove ( ) ; this . _element = val ; } get visible ( ) : boolean { return this . _visible ; } set visible ( val ) { this . _visible = val ; } get height ( ) : number { return this . element ?. offsetHeight ?? 0 ; } handleEvent ( evt ) { if ( ! this . element ) { return ; } switch ( evt . type ) { case 'mouseup' : case 'blur' : case 'touchend' : case 'touchcancel' : document . body . style . userSelect = '' ; unshiftKeyboardLayer ( this ) ; break ; } } focusMathfield ( ) { this . _mathfield ?. focus ?. ( ) ; } blurMathfield ( ) { this . _mathfield ?. blur ?. ( ) ; } stateChanged ( ) { this . _mathfield ?. element ?. dispatchEvent ( new Event ( 'virtual-keyboard-toggle' , { bubbles : true , cancelable : false , composed : true , } ) ) ; } executeCommand ( command ) { let selector ; let args = [ ] ; if ( isArray ( command ) ) { selector = command [ 0 ] ; args = command . slice ( 1 ) ; } else { selector = command ; } selector = selector . replace ( /-\w/g , ( m ) => m [ 1 ] . toUpperCase ( ) ) as SelectorPrivate ; if ( COMMANDS [ selector ] ?. target === 'virtual-keyboard' ) { return COMMANDS [ selector ] ! . fn ( this , ... args ) ; } return ( this . _mathfield ?. executeCommand ( command as Selector | [ Selector , ... any [ ] ] ) ?? false ) ; } create ( ) { if ( ! VIRTUAL_KEYBOARD_STYLESHEET_HASH ) { VIRTUAL_KEYBOARD_STYLESHEET_HASH = hashCode ( VIRTUAL_KEYBOARD_STYLESHEET ) . toString ( 36 ) ; } this . virtualKeyboardStylesheet = this . virtualKeyboardStylesheet = injectStylesheet ( null , VIRTUAL_KEYBOARD_STYLESHEET , VIRTUAL_KEYBOARD_STYLESHEET_HASH ) ; this . coreStylesheet = injectStylesheet ( null , CORE_STYLESHEET , hashCode ( CORE_STYLESHEET ) . toString ( 36 ) ) ; void loadFonts ( this . options . fontsDirectory ) ; } enable ( ) { window . addEventListener ( 'mouseup' , this ) ; window . addEventListener ( 'blur' , this ) ; window . addEventListener ( 'touchend' , this ) ; window . addEventListener ( 'touchcancel' , this ) ; } disable ( ) { window . removeEventListener ( 'mouseup' , this ) ; window . removeEventListener ( 'blur' , this ) ; window . removeEventListener ( 'touchend' , this ) ; window . removeEventListener ( 'touchcancel' , this ) ; hideAlternateKeys ( ) ; this . visible = false ; this . coreStylesheet ?. release ( ) ; this . coreStylesheet = null ; this . virtualKeyboardStylesheet ?. release ( ) ; this . virtualKeyboardStylesheet = null ; this . _element ?. remove ( ) ; this . _element = undefined ; } dispose ( ) { } } const KEYBOARDS = { numeric : { tooltip : 'keyboard.tooltip.numeric' , layer : 'math' , label : '123' , layers : [ 'math' ] , } , roman : { tooltip : 'keyboard.tooltip.roman' , layer : 'lower-roman' , label : 'ABC' , layers : [ 'lower-roman' , 'upper-roman' ] , } , greek : { tooltip : 'keyboard.tooltip.greek' , layer : 'lower-greek' , label : '&alpha;&beta;&gamma;' , classes : 'tex-math' , layers : [ 'lower-greek' , 'upper-greek' ] , } , functions : { tooltip : 'keyboard.tooltip.functions' , layer : 'functions' , label : '<i>f</i>&thinsp;()' , classes : 'tex' , layers : [ 'functions' ] , } , symbols : { tooltip : 'keyboard.tooltip.symbols' , layer : 'symbols' , label : '&infin;≠∈' , classes : 'tex' , layers : [ 'symbols' ] , } , latex : { tooltip : 'keyboard.tooltip.command' , command : [ 'switchMode' , 'latex' ] , label : `<svg class="svg-glyph"><use xlink:href='#svg-command' /></svg>` , layers : [ 'latex-lower' , 'latex-upper' , 'latex-symbols' ] , } , style : { tooltip : 'keyboard.tooltip.style' , layer : 'style' , label : '<b>b</b><i>i</i>𝔹' , } , } ; const SHIFTED_KEYS = { '\\varphi ' : [ '&Phi;' , '\\Phi ' ] , '\\varsigma ' : [ '&Sigma;' , '\\Sigma ' ] , '\\epsilon ' : [ '&#x0190;' , '{\\char"0190}' ] , '\\rho ' : [ '&#x3A1' , '{\\char"3A1}' ] , '\\tau ' : [ '&#x3A4;' , '{\\char"3A4}' ] , '\\upsilon ' : [ '&Upsilon;' , '\\Upsilon ' ] , '\\theta ' : [ '&Theta;' , '\\Theta ' ] , '\\iota ' : [ '&Iota;' , '{\\char"399}' ] , '\\omicron ' : [ '&#x039F;' , '{\\char"39F}' ] , '\\pi ' : [ '&Pi;' , '\\Pi ' ] , '\\alpha ' : [ '&Alpha;' , '{\\char"391}' ] , '\\sigma ' : [ '&Sigma;' , '\\Sigma ' ] , '\\delta ' : [ '&Delta;' , '\\Delta ' ] , '\\phi ' : [ '&#x03a6;' , '\\Phi ' ] , '\\gamma ' : [ '&Gamma;' , '\\Gamma ' ] , '\\eta ' : [ '&Eta;' , '{\\char"397}' ] , '\\xi ' : [ '&Xi;' , '\\Xi ' ] , '\\kappa ' : [ '&Kappa;' , '{\\char"39A}' ] , '\\lambda ' : [ '&Lambda;' , '\\Lambda ' ] , '\\zeta ' : [ '&Zeta;' , '{\\char"396}' ] , '\\chi ' : [ '&Chi;' , '{\\char"3A7}' ] , '\\psi ' : [ '&Psi;' , '\\Psi ' ] , '\\omega ' : [ '&Omega;' , '\\Omega ' ] , '\\beta ' : [ '&Beta;' , '{\\char"392}' ] , '\\nu ' : [ '&Nu;' , '{\\char"39D}' ] , '\\mu ' : [ '&Mu;' , '{\\char"39C}' ] , } ; const ALT_KEYS_BASE = { '0' : [ '\\emptyset' , '\\varnothing' , '\\infty' , { latex : '#?_0' , insert : '#@_0' } , '\\circ' , '\\bigcirc' , '\\bullet' , ] , '2' : [ '\\frac{1}{2}' , { latex : '#?^2' , insert : '#@^2' } ] , '3' : [ '\\frac{1}{3}' , { latex : '#?^3' , insert : '#@^3' } ] , '.' : [ ',' , ';' , '\\colon' , { latex : ':' , aside : 'ratio' } , { latex : '\\cdotp' , aside : 'center dot' , class : 'box' } , { latex : '\\cdots' , aside : 'center ellipsis' , class : 'box' } , { latex : '\\ldotp' , aside : 'low dot' , class : 'box' } , { latex : '\\ldots' , aside : 'low ellipsis' , class : 'box' } , { latex : '\\vdots' , aside : '' , class : 'box' } , { latex : '\\ddots' , aside : '' , class : 'box' } , '\\odot' , '\\oslash' , '\\circledcirc' , ] , '*' : [ '\\cdot' , '\\ast' , '\\star' , '\\bigstar' , '\\ltimes' , '\\rtimes' , '\\rightthreetimes' , '\\leftthreetimes' , '\\intercal' , '\\prod' , { latex : '\\prod_{n\\mathop=0}^{\\infty}' , class : 'small' } , ] , '+' : [ '\\pm' , '\\mp' , '\\sum' , { latex : '\\sum_{n\\mathop=0}^{\\infty}' , class : 'small' } , '\\dotplus' , '\\oplus' , ] , '-' : [ '\\pm' , '\\mp' , '\\ominus' , '\\vert #0  \\vert' ] , '/' : [ '\\divideontimes' , '/' , '\\div' , '\\%' ] , '(' : [ '\\left( #0\\right)' , '\\left[ #0\\right]' , '\\left\\{ #0\\right\\}' , '\\left\\langle #0\\right\\rangle' , '\\lfloor' , '\\llcorner' , '(' , '\\lbrack' , '\\lvert' , '\\lVert' , '\\lgroup' , '\\langle' , '\\lceil' , '\\ulcorner' , '\\lmoustache' , '\\lbrace' , ] , ')' : [ '\\rfloor' , '\\lrcorner' , ')' , '\\rbrack' , '\\rvert' , '\\rVert' , '\\rgroup' , '\\rangle' , '\\rceil' , '\\urcorner' , '\\rmoustache' , '\\rbrace' , ] , '=' : [ '\\cong' , '\\asymp' , '\\equiv' , '\\differencedelta' , '\\varpropto' , '\\thickapprox' , '\\approxeq' , '\\thicksim' , '\\backsim' , '\\eqsim' , '\\simeq' , '\\Bumpeq' , '\\bumpeq' , '\\doteq' , '\\Doteq' , '\\fallingdotseq' , '\\risingdotseq' , '\\coloneq' , '\\eqcirc' , '\\circeq' , '\\triangleq' , '\\between' , ] , '!=' : [ '\\neq' , '\\ncong' , '' , '\\nsim' ] , '<' : [ '\\leq' , '\\leqq' , '\\lneqq' , '\\ll' , '\\lessgtr' , '\\nless' , '\\nleq' , '\\lesssim' , '\\precsim' , '\\prec' , '\\nprec' , '\\preccurlyeq' , '\\lessdot' , ] , '>' : [ '\\geq' , '\\geqq' , '\\gneqq' , '\\gg' , '\\gtrless' , '\\ngtr' , '\\ngeq' , '\\gtrsim' , '\\succsim' , '\\succ' , '\\nsucc' , '\\succcurlyeq' , '\\gtrdot' , ] , 'in' : [ '\\owns' ] , '!in' : [ '\\backepsilon' ] , 'subset' : [ '\\subseteq' , '\\nsubset' , '\\nsubseteq' ] , 'superset' : [ '\\supseteq' , '\\nsupset' , '\\nsupseteq' ] , 'infinity' : [ '\\aleph_0' , '\\aleph_1' , '\\omega' , '\\mathfrak{m}' ] , 'numeric-pi' : [ '\\prod' , '\\theta' , '\\rho' , '\\sin' , '\\cos' , '\\tan' ] , 'ee' : [ '\\times 10^{#?}' , '\\ln' , '\\ln_{10}' , '\\log' ] , '^' : [ '_{#?}' ] , 'int' : [ { latex : '\\int_{#?}^{#?}' , class : 'small' } , { latex : '\\int' , class : 'small' } , { latex : '\\smallint' , class : 'small' } , { latex : '\\iint' , class : 'small' } , { latex : '\\iiint' , class : 'small' } , { latex : '\\oint' , class : 'small' } , { latex : '\\dfrac{\\rd}{\\rd x}' , class : 'small' } , { latex : '\\frac{\\partial}{\\partial x}' , class : 'small' } , '\\capitalDifferentialD' , '\\rd' , '\\partial' , ] , 'nabla' : [ '\\nabla\\times' , '\\nabla\\cdot' , '\\nabla^{2}' ] , '!' : [ '!!' , '\\Gamma' , '\\Pi' ] , 'accents' : [ '\\bar{#@}' , '\\vec{#@}' , '\\hat{#@}' , '\\check{#@}' , '\\dot{#@}' , '\\ddot{#@}' , '\\mathring{#@}' , '\\breve{#@}' , '\\acute{#@}' , '\\tilde{#@}' , '\\grave{#@}' , ] , 'underline' : [ '\\underbrace{#@}' , '\\underlinesegment{#@}' , '\\underleftrightarrow{#@}' , '\\underrightarrow{#@}' , '\\underleftarrow{#@}' , '\\undergroup{#@}' , ] , 'overline' : [ '\\overbrace{#@}' , '\\overlinesegment{#@}' , '\\overleftrightarrow{#@}' , '\\overrightarrow{#@}' , '\\overleftarrow{#@}' , '\\overgroup{#@}' , ] , 'xleftarrows' : [ '\\xlongequal{}' , '\\xleftrightarrow{}' , '\\xLeftrightarrow{}' , '\\xleftrightharpoons{}' , '\\xLeftarrow{}' , '\\xleftharpoonup{}' , '\\xleftharpoondown{}' , '\\xtwoheadleftarrow{}' , '\\xhookleftarrow{}' , '\\xtofrom{}' , '\\xleftequilibrium{}' , '\\xrightleftarrows{}' , ] , 'xrightarrows' : [ '\\xlongequal{}' , '\\xleftrightarrow{}' , '\\xLeftrightarrow{}' , '\\xleftrightharpoons{}' , '\\xRightarrow{}' , '\\xrightharpoonup{}' , '\\xrightharpoondown{}' , '\\xtwoheadrightarrow{}' , '\\xrightleftharpoons{}' , '\\xhookrightarrow{}' , '\\xmapsto{}' , '\\xrightequilibrium{}' , '\\xrightleftarrows{}' , ] , 'A' : [ { latex : '\\aleph' , aside : 'aleph' } , { latex : '\\forall' , aside : 'for all' } , ] , 'a' : [ { latex : '\\aleph' , aside : 'aleph' } , { latex : '\\forall' , aside : 'for all' } , ] , 'b' : [ { latex : '\\beth' , aside : 'beth' } ] , 'B' : [ { latex : '\\beth' , aside : 'beth' } ] , 'c' : [ { latex : '\\C' , aside : 'set of complex numbers' } ] , 'd' : [ { latex : '\\daleth' , aside : 'daleth' } ] , 'D' : [ { latex : '\\daleth' , aside : 'daleth' } ] , 'e' : [ { latex : '\\exponentialE' , aside : 'exponential e' } , { latex : '\\exists' , aside : 'there is' } , { latex : '\\nexists' , aside : 'there isn’t' } , ] , 'g' : [ { latex : '\\gimel' , aside : 'gimel' } ] , 'G' : [ { latex : '\\gimel' , aside : 'gimel' } ] , 'h' : [ { latex : '\\hbar' , aside : 'h bar' } , { latex : '\\hslash' , aside : 'h slash' } , ] , 'i' : [ { latex : '\\imaginaryI' , aside : 'imaginary i' } ] , 'j' : [ { latex : '\\imaginaryJ' , aside : 'imaginary j' } ] , 'l' : [ { latex : '\\ell' , aside : 'ell' } ] , 'n' : [ { latex : '\\N' , aside : 'set of natural numbers' } ] , 'p' : [ { latex : '\\P' , aside : 'set of primes' } ] , 'q' : [ { latex : '\\Q' , aside : 'set of rational numbers' } ] , 'r' : [ { latex : '\\R' , aside : 'set of real numbers' } ] , 'z' : [ { latex : '\\Z' , aside : 'set of integers' } ] , 'x-var' : [ 'y' , 'z' , 't' , 'r' , { latex : 'f(#?)' , class : 'small' } , { latex : 'g(#?)' , class : 'small' } , 'x^2' , 'x^n' , 'x_n' , 'x_{n+1}' , 'x_i' , 'x_{i+1}' , ] , 'n-var' : [ 'i' , 'j' , 'p' , 'k' , 'a' , 'u' ] , 'ii' : [ '\\Re' , '\\Im' , '\\imaginaryJ' , '\\Vert #0 \\Vert' ] , 'logic' : [ { latex : '\\exists' , aside : 'there is' } , { latex : '\\nexists' , aside : 'there isn’t' } , { latex : '\\ni' , aside : 'such that' } , { latex : '\\Colon' , aside : 'such that' } , { latex : '\\implies' , aside : 'implies' } , { latex : '\\impliedby' , aside : 'implied by' } , { latex : '\\iff' , aside : 'if and only if' } , { latex : '\\land' , aside : 'and' } , { latex : '\\lor' , aside : 'or' } , { latex : '\\oplus' , aside : 'xor' } , { latex : '\\lnot' , aside : 'not' } , { latex : '\\downarrow' , aside : 'nor' } , { latex : '\\uparrow' , aside : 'nand' } , { latex : '\\curlywedge' , aside : 'nor' } , { latex : '\\bar\\curlywedge' , aside : 'nand' } , { latex : '\\therefore' , aside : 'therefore' } , { latex : '\\because' , aside : 'because' } , { latex : '^\\biconditional' , aside : 'biconditional' } , '\\leftrightarrow' , '\\Leftrightarrow' , '\\to' , '\\models' , '\\vdash' , '\\gets' , '\\dashv' , '\\roundimplies' , ] , 'set-operators' : [ '\\cap' , '\\cup' , '\\setminus' , '\\smallsetminus' , '\\complement' , ] , 'set-relations' : [ '\\in' , '\\notin' , '\\ni' , '\\owns' , '\\subset' , '\\supset' , '\\subseteq' , '\\supseteq' , '\\subsetneq' , '\\supsetneq' , '\\varsubsetneq' , '\\subsetneqq' , '\\nsubset' , '\\nsupset' , '\\nsubseteq' , '\\nsupseteq' , ] , 'space' : [ { latex : '\\char"203A\\!\\char"2039' , insert : '\\!' , aside : 'negative thin space<br>⁻³⧸₁₈ em' , } , { latex : '\\unicode{"203A}\\,\\unicode{"2039}' , insert : '\\,' , aside : 'thin space<br>³⧸₁₈ em' , } , { latex : '\\unicode{"203A}\\:\\unicode{"2039}' , insert : '\\:' , aside : 'medium space<br>⁴⧸₁₈ em' , } , { latex : '\\unicode{"203A}\\;\\unicode{"2039}' , insert : '\\;' , aside : 'thick space<br>⁵⧸₁₈ em' , } , { latex : '\\unicode{"203A}\\ \\unicode{"2039}' , insert : '\\ ' , aside : '⅓ em' , } , { latex : '\\unicode{"203A}\\enspace\\unicode{"2039}' , insert : '\\enspace' , aside : '½ em' , } , { latex : '\\unicode{"203A}\\quad\\unicode{"2039}' , insert : '\\quad' , aside : '1 em' , } , { latex : '\\unicode{"203A}\\qquad\\unicode{"2039}' , insert : '\\qquad' , aside : '2 em' , } , ] , 'delete' : [ { label : '<span class="warning"><svg class="svg-glyph"><use xlink:href="#svg-trash" /></svg></span>' , command : 'deleteAll' , } , ] , '->|' : [ ] , } ; let ALT_KEYS = { } ; const LAYERS = { 'math' : `
        <div class='rows'>
            <ul>
                <li class='keycap tex' data-alt-keys='x-var'><i>x</i></li>
                <li class='keycap tex' data-alt-keys='n-var'><i>n</i></li>
                <li class='separator w5'></li>
                <row name='numpad-1'/>
                <li class='separator w5'></li>
                <li class='keycap tex' data-latex='\\exponentialE' data-alt-keys='ee'>e</li>
                <li class='keycap tex' data-latex='\\imaginaryI' data-alt-keys='ii'>i</li>
                <li class='keycap tex' data-latex='\\pi' data-alt-keys='numeric-pi'></li>
            </ul>
            <ul>
                <li class='keycap tex' data-key='<' data-alt-keys='<'>&lt;</li>
                <li class='keycap tex' data-key='>' data-alt-keys='>'>&gt;</li>
                <li class='separator w5'></li>
                <row name='numpad-2'/>
                <li class='separator w5'></li>
                <li class='keycap tex' data-latex='#@^{2}' data-latex='x^2'></li>
                <li class='keycap tex' data-alt-keys='^' data-insert='#@^{#?}' data-latex='x^\\placeholder'></li>
                <li class='keycap tex small' data-insert='\\sqrt{#0}' data-latex='\\sqrt{#0}'></li>
            </ul>
            <ul>
                <li class='keycap tex' data-alt-keys='(' >(</li>
                <li class='keycap tex' data-alt-keys=')' >)</li>
                <li class='separator w5'></li>
                <row name='numpad-3'/>
                <li class='separator w5'></li>
                <li class='keycap small' data-alt-keys='int' data-latex='\\int_0^\\infty'></li>
                <li class='keycap' data-latex='\\forall' data-alt-keys='logic' ></li>
                <li class='action font-glyph bottom right' data-alt-keys='delete' data-command='["performWithFeedback","deleteBackward"]'>&#x232b;</li></ul>
            </ul>
            <ul>
                <li class='keycap' data-alt-keys='foreground-color' data-command='["applyStyle",{"color":"red"}]'><span style='border-radius: 50%;width:22px;height:22px; border: 3px solid #cc2428; box-sizing: border-box'></span></li>
                <li class='keycap' data-alt-keys='background-color' data-command='["applyStyle",{"backgroundColor":"yellow"}]'><span style='border-radius: 50%;width:22px;height:22px; background:#fff590; box-sizing: border-box'></span></li>
                <li class='separator w5'></li>
                <row name='numpad-4'/>
                <li class='separator w5'></li>
                <arrows/>
            </ul>
        </div>
    ` , 'lower-roman' : `
        <div class='rows'>
            <ul>
                <row name='numpad-1' class='if-wide'/>
                <row name='lower-1' shift-layer='upper-roman'/>
            </ul>
            <ul>
                <row name='numpad-2' class='if-wide'/>
                <row name='lower-2'  shift-layer='upper-roman''/>
            </ul>
            <ul>
                <row name='numpad-3' class='if-wide'/>
                <row name='lower-3'  shift-layer='upper-roman''/>
            </ul>
            <ul>
                <row name='numpad-4' class='if-wide'/>
                <li class='keycap' >;</li>
                <li class='keycap' >,</li>
                <li class='keycap w50' data-key=' ' data-alt-keys='space'>&nbsp;</li>
                <arrows/>
            </ul>
        </div>` , 'upper-roman' : `
        <div class='rows'>
            <ul>
                <row name='numpad-1' class='if-wide'/>
                <row name='upper-1'  shift-layer='lower-roman'/>
            </ul>
            <ul>
                <row name='numpad-2' class='if-wide'/>
                <row name='upper-2' shift-layer='lower-roman'/>
            </ul>
            <ul>
                <row name='numpad-3' class='if-wide'/>
                <row name='upper-3' shift-layer='lower-roman'/>
            </ul>
            <ul>
                <row name='numpad-4' class='if-wide'/>
                <li class='keycap' >;</li>
                <li class='keycap' data-alt-keys='.'>;</li>
                <li class='keycap w50' data-key=' '>&nbsp;</li>
                <arrows/>
            </ul>
        </div>` , 'symbols' : `
        <div class='rows'>
            <ul>
                <row name='numpad-1' class='if-wide'/>
                <li class='keycap tex' data-alt-keys='(' data-insert='\\lbrace '>{</li>
                <li class='keycap tex' data-alt-keys=')' data-insert='\\rbrace '>}</li>
                <li class='separator w5'></li>
                <li class='keycap tex' data-alt-keys='in' data-insert='\\in '>&#x2208;</li>
                <li class='keycap tex' data-alt-keys='!in' data-insert='\\notin '>&#x2209;</li>
                <li class='keycap tex' data-insert='\\Re '>&#x211c;<aside>Real</aside></li>
                <li class='keycap tex' data-insert='\\Im '>&#x2111;<aside>Imaginary</aside></li>
                <li class='keycap w15' data-insert='\\ulcorner#0\\urcorner '><span><sup>&#x250c;</sup><span><span style='color:#ddd'>o</span><sup>&#x2510;</sup></span><aside>ceil</aside></li>
                <li class='keycap tex' data-alt-keys='nabla' data-insert='\\nabla '>&#x2207;<aside>nabla</aside></li>
                <li class='keycap tex' data-alt-keys='infinity' data-insert='\\infty '>&#x221e;</li>

            </ul>
            <ul>
                <row name='numpad-2' class='if-wide'/>
                <li class='keycap tex' data-alt-keys='(' data-insert='\\lbrack '>[</li>
                <li class='keycap tex' data-alt-keys=')' data-insert='\\rbrack '>]</li>
                <li class='separator w5'></li>
                <li class='keycap tex' data-alt-keys='subset' data-insert='\\subset '>&#x2282;</li>
                <li class='keycap tex' data-alt-keys='superset' data-insert='\\supset '>&#x2283;</li>
                <li class='keycap tex' data-key='!' data-alt-keys='!'>!<aside>factorial</aside></li>
                <li class='keycap' data-latex='^{\\prime} '><span><sup><span><span style='color:#ddd'>o</span>&#x2032</sup></span><aside>prime</aside></li>
                <li class='keycap w15' data-insert='\\llcorner#0\\lrcorner '><span><sub>&#x2514;</sub><span style='color:#ddd'>o</span><sub>&#x2518;</sub></span><aside>floor</aside></li>
                <li class='keycap tex' data-insert='\\partial '>&#x2202;<aside>partial<br>derivative</aside></li>
                <li class='keycap tex' data-insert='\\emptyset '>&#x2205;<aside>empty set</aside></li>

            </ul>
            <ul>
                <row name='numpad-3' class='if-wide'/>
                <li class='keycap tex' data-alt-keys='(' data-insert='\\langle '>&#x27e8;</li>
                <li class='keycap tex' data-alt-keys=')' data-insert='\\rangle '>&#x27e9;</li>
                <li class='separator w5'></li>
                <li class='keycap tex' data-alt-keys='overline' data-latex='\\overline{#?}' data-aside='overline'></li>
                <li class='keycap tex' data-alt-keys='underline' data-latex='\\underline{#?}' data-aside='underline'></li>
                <li class='keycap tex' data-alt-keys='accents' data-insert='\\vec{#@}' data-latex='\\vec{#?}' data-aside='vector'></li>
                <li class='keycap tex small' data-alt-keys='xleftarrows' data-latex='\\xleftarrow{}' ></li>
                <li class='keycap tex small' data-alt-keys='xrightarrows' data-latex='\\xrightarrow{}' ></li>
                <li class='keycap tex' data-alt-keys='absnorm' data-insert='\\left| #0 \\right|' data-latex='\\left| #? \\right|' data-aside='abs'></li>

                <li class='action font-glyph bottom right w15'
                    data-shifted='<span class="warning"><svg class="svg-glyph"><use xlink:href="#svg-trash" /></svg></span>'
                    data-shifted-command='"deleteAll"'
                    data-alt-keys='delete' data-command='["performWithFeedback","deleteBackward"]'
                >&#x232b;</li>
            </ul>
            <ul>
                <row name='numpad-4' class='if-wide'/>
                <li class='keycap tex' data-insert=','>,</li>
                <li class='keycap tex' data-insert='\\cdot '>&#x22c5;<aside>centered dot</aside></li>
                <li class='keycap tex' data-insert='\\colon '>:<aside>colon</aside></li>
                <li class='keycap tex' data-insert='\\circ '>&#x2218;<aside>circle</aside></li>
                <li class='keycap tex' data-insert='\\approx '>&#x2248;<aside>approx.</aside></li>
                <li class='keycap tex' data-insert='\\ne '>&#x2260;</li>
                <li class='keycap tex' data-insert='\\pm '>&#x00b1;</li>
                <arrows/>
            </ul>
        </div>` , 'lower-greek' : `
        <div class='rows'>
            <ul><li class='keycap tex' data-insert='\\varphi '><i>&#x03c6;</i><aside>phi var.</aside></li>
                <li class='keycap tex' data-insert='\\varsigma '><i>&#x03c2;</i><aside>sigma var.</aside></li>
                <li class='keycap tex' data-insert='\\epsilon '><i>&#x03f5;</i></li>
                <li class='keycap tex' data-insert='\\rho '><i>&rho;</i></li>
                <li class='keycap tex' data-insert='\\tau '><i>&tau;</i></li>
                <li class='keycap tex' data-insert='\\upsilon '><i>&upsilon;</i></li>
                <li class='keycap tex' data-insert='\\theta '><i>&theta;</i></li>
                <li class='keycap tex' data-insert='\\iota '><i>&iota;</i></li>
                <li class='keycap tex' data-insert='\\omicron '>&omicron;</i></li>
                <li class='keycap tex' data-insert='\\pi '><i>&pi;</i></li>
            </ul>
            <ul><li class='keycap tex' data-insert='\\alpha ' data-shifted='&Alpha;' data-shifted-command='["insert","{\\\\char\\"391}"]'><i>&alpha;</i></li>
                <li class='keycap tex' data-insert='\\sigma '><i>&sigma;</i></li>
                <li class='keycap tex' data-insert='\\delta '><i>&delta;</i></li>
                <li class='keycap tex' data-insert='\\phi '><i>&#x03d5;</i></i></li>
                <li class='keycap tex' data-insert='\\gamma '><i>&gamma;</i></li>
                <li class='keycap tex' data-insert='\\eta '><i>&eta;</i></li>
                <li class='keycap tex' data-insert='\\xi '><i>&xi;</i></li>
                <li class='keycap tex' data-insert='\\kappa '><i>&kappa;</i></li>
                <li class='keycap tex' data-insert='\\lambda '><i>&lambda;</i></li>
            </ul>
            <ul><li class='shift modifier font-glyph bottom left w15 layer-switch' data-layer='upper-greek'>&#x21e7;</li>
                <li class='keycap tex' data-insert='\\zeta '><i>&zeta;</i></li>
                <li class='keycap tex' data-insert='\\chi '><i>&chi;</i></li>
                <li class='keycap tex' data-insert='\\psi '><i>&psi;</i></li>
                <li class='keycap tex' data-insert='\\omega '><i>&omega;</i></li>
                <li class='keycap tex' data-insert='\\beta '><i>&beta;</i></li>
                <li class='keycap tex' data-insert='\\nu '><i>&nu;</i></li>
                <li class='keycap tex' data-insert='\\mu '><i>&mu;</i></li>
                <li class='action font-glyph bottom right w15'
                    data-shifted='<span class="warning"><svg class="svg-glyph"><use xlink:href="#svg-trash" /></svg></span>'
                    data-shifted-command='"deleteAll"'
                    data-alt-keys='delete' data-command='["performWithFeedback","deleteBackward"]'
                >&#x232b;</li>
            </ul>
            <ul>
                <li class='keycap ' data-key=' '>&nbsp;</li>
                <li class='keycap'>,</li>
                <li class='keycap tex' data-insert='\\varepsilon '><i>&#x03b5;</i><aside>epsilon var.</aside></li>
                <li class='keycap tex' data-insert='\\vartheta '><i>&#x03d1;</i><aside>theta var.</aside></li>
                <li class='keycap tex' data-insert='\\varkappa '><i>&#x3f0;</i><aside>kappa var.</aside></li>
                <li class='keycap tex' data-insert='\\varpi '><i>&#x03d6;<aside>pi var.</aside></i></li>
                <li class='keycap tex' data-insert='\\varrho '><i>&#x03f1;</i><aside>rho var.</aside></li>
                <arrows/>
            </ul>
        </div>` , 'upper-greek' : `
        <div class='rows'>
            <ul><li class='keycap tex' data-insert='\\Phi '>&Phi;<aside>phi</aside></li>
                <li class='keycap tex' data-insert='\\Sigma '>&Sigma;<aside>sigma</aside></li>
                <li class='keycap tex' data-insert='{\\char"0190}'>&#x0190;<aside>epsilon</aside></li>
                <li class='keycap tex' data-insert='{\\char"3A1}'>&#x3A1;<aside>rho</aside></li>
                <li class='keycap tex' data-insert='{\\char"3A4}'>&#x3A4;<aside>tau</aside></li>
                <li class='keycap tex' data-insert='\\Upsilon '>&Upsilon;<aside>upsilon</aside></li>
                <li class='keycap tex' data-insert='\\Theta '>&Theta;<aside>theta</aside></li>
                <li class='keycap tex' data-insert='{\\char"399}'>&Iota;<aside>iota</aside></li>
                <li class='keycap tex' data-insert='{\\char"39F}'>&#x039F;<aside>omicron</aside></li>
                <li class='keycap tex' data-insert='\\Pi '>&Pi;<aside>pi</aside></li></ul>
            <ul><li class='keycap tex' data-insert='{\\char"391}'>&#x391;<aside>alpha</aside></li>
                <li class='keycap tex' data-insert='\\Sigma '>&Sigma;<aside>sigma</aside></li>
                <li class='keycap tex' data-insert='\\Delta '>&Delta;<aside>delta</aside></li>
                <li class='keycap tex' data-insert='\\Phi '>&#x03a6;<aside>phi</aside></li>
                <li class='keycap tex' data-insert='\\Gamma '>&Gamma;<aside>gamma</aside></li>
                <li class='keycap tex' data-insert='{\\char"397}'>&Eta;<aside>eta</aside></li>
                <li class='keycap tex' data-insert='\\Xi '>&Xi;<aside>xi</aside></li>
                <li class='keycap tex' data-insert='{\\char"39A}'>&Kappa;<aside>kappa</aside></li>
                <li class='keycap tex' data-insert='\\Lambda '>&Lambda;<aside>lambda</aside></li></ul>
            <ul><li class='shift modifier font-glyph bottom left selected w15 layer-switch' data-layer='lower-greek'>&#x21e7;</li>
                <li class='keycap tex' data-insert='{\\char"396}'>&Zeta;<aside>zeta</aside></li>
                <li class='keycap tex' data-insert='{\\char"3A7}'>&Chi;<aside>chi</aside></li>
                <li class='keycap tex' data-insert='\\Psi '>&Psi;<aside>psi</aside></li>
                <li class='keycap tex' data-insert='\\Omega '>&Omega;<aside>omega</aside></li>
                <li class='keycap tex' data-insert='{\\char"392}'>&Beta;<aside>beta</aside></li>
                <li class='keycap tex' data-insert='{\\char"39D}'>&Nu;<aside>nu</aside></li>
                <li class='keycap tex' data-insert='{\\char"39C}'>&Mu;<aside>mu</aside></li>
                <li class='action font-glyph bottom right w15' data-command='["performWithFeedback","deleteBackward"]'>&#x232b;</li></ul>
            <ul>
                <li class='separator w10'>&nbsp;</li>
                <li class='keycap'>.</li>
                <li class='keycap w50' data-key=' '>&nbsp;</li>
                <arrows/>
            </ul>
        </div>` , 'latex-lower' : `
        <div class='rows'>
            <ul><row name='lower-1' class='tt' shift-layer='latex-upper'/></ul>
            <ul><row name='lower-2' class='tt' shift-layer='latex-upper'/></ul>
            <ul><row name='lower-3' class='tt' shift-layer='latex-upper'/></ul>
            <ul>
                <li class='layer-switch font-glyph modifier bottom left' data-layer='latex-symbols'>01#</li>
                <li class='keycap tt' data-shifted='[' data-shifted-command='["insertAndUnshiftKeyboardLayer", "["]'>{</li>
                <li class='keycap tt' data-shifted=']' data-shifted-command='["insertAndUnshiftKeyboardLayer", "]"]'>}</li>
                <li class='keycap tt' data-shifted='(' data-shifted-command='["insertAndUnshiftKeyboardLayer", "("]'>^</li>
                <li class='keycap tt' data-shifted=')' data-shifted-command='["insertAndUnshiftKeyboardLayer", ")"]'>_</li>
                <li class='keycap w20' data-key=' '>&nbsp;</li>
                <arrows/>
            </ul>
        </div>` , 'latex-upper' : `
        <div class='rows'>
            <ul><row name='upper-1' class='tt' shift-layer='latex-lower'/></ul>
            <ul><row name='upper-2' class='tt' shift-layer='latex-lower'/></ul>
            <ul><row name='upper-3' class='tt' shift-layer='latex-lower'/></ul>
            <ul>
                <li class='layer-switch font-glyph modifier bottom left' data-layer='latex-symbols'01#</li>
                <li class='keycap tt'>[</li>
                <li class='keycap tt'>]</li>
                <li class='keycap tt'>(</li>
                <li class='keycap tt'>)</li>
                <li class='keycap w20' data-key=' '>&nbsp;</li>
                <arrows/>
            </ul>
        </div>` , 'latex-symbols' : `
        <div class='rows'>
            <ul><li class='keycap tt'>1</li><li class='keycap tt'>2</li><li class='keycap tt'>3</li><li class='keycap tt'>4</li><li class='keycap tt'>5</li><li class='keycap tt'>6</li><li class='keycap tt'>7</li><li class='keycap tt'>8</li><li class='keycap tt'>9</li><li class='keycap tt'>0</li></ul>
            <ul><li class='keycap tt'>!</li><li class='keycap tt'>@</li><li class='keycap tt'>#</li><li class='keycap tt'>$</li><li class='keycap tt'>%</li><li class='keycap tt'>^</li><li class='keycap tt'>&</li><li class='keycap tt'>*</li><li class='keycap tt'>+</li><li class='keycap tt'>=</li></ul>
            <ul>
                <li class='keycap tt'>\\</li>
                <li class='keycap tt'>|</li>
                <li class='keycap tt'>/</li>
                <li class='keycap tt'>\`</li>
                <li class='keycap tt'>;</li>
                <li class='keycap tt'>:</li>
                <li class='keycap tt'>?</li>
                <li class='keycap tt'>'</li>
                <li class='keycap tt'>"</li>
                <li class='action font-glyph bottom right'
                    data-shifted='<span class="warning"><svg class="svg-glyph"><use xlink:href="#svg-trash" /></svg></span>'
                    data-shifted-command='"deleteAll"'
                    data-alt-keys='delete' data-command='["performWithFeedback","deleteBackward"]'
                >&#x232b;</li>
            </ul>
            <ul>
                <li class='layer-switch font-glyph modifier bottom left' data-layer='latex-lower'>abc</li>
                <li class='keycap tt'>&lt;</li>
                <li class='keycap tt'>&gt;</li>
                <li class='keycap tt'>~</li>
                <li class='keycap tt'>,</li>
                <li class='keycap tt'>.</li>
                <li class='keycap' data-key=' '>&nbsp;</li>
                <arrows/>
            </ul>
        </div>` , 'functions' : `
        <div class='rows'>
            <ul><li class='separator'></li>
                <li class='fnbutton' data-insert='\\sin'></li>
                <li class='fnbutton' data-insert='\\sin^{-1}'></li>
                <li class='fnbutton' data-insert='\\ln'></li>
                <li class='fnbutton' data-insert='\\exponentialE^{#?}'></li>
                <li class='bigfnbutton' data-insert='\\operatorname{lcm}(#?)' data-latex='\\operatorname{lcm}()'></li>
                <li class='bigfnbutton' data-insert='\\operatorname{ceil}(#?)' data-latex='\\operatorname{ceil}()'></li>
                <li class='bigfnbutton' data-insert='\\lim_{n\\to\\infty}'></li>
                <li class='bigfnbutton' data-insert='\\int'></li>
                <li class='bigfnbutton' data-insert='\\operatorname{abs}(#?)' data-latex='\\operatorname{abs}()'></li>
            </ul>
            <ul><li class='separator'></li>
                <li class='fnbutton' data-latex='\\cos'></li>
                <li class='fnbutton' data-latex='\\cos^{-1}'></li>
                <li class='fnbutton' data-latex='\\ln_{10}'></li>
                <li class='fnbutton' data-latex='10^{#?}'></li>
                <li class='bigfnbutton' data-latex='\\operatorname{gcd}(#?)' data-latex='\\operatorname{gcd}()'></li>
                <li class='bigfnbutton' data-latex='\\operatorname{floor}(#?)' data-latex='\\operatorname{floor}()'></li>
                <li class='bigfnbutton' data-latex='\\sum_{n\\mathop=0}^{\\infty}'></li>
                <li class='bigfnbutton' data-latex='\\int_{0}^{\\infty}'></li>
                <li class='bigfnbutton' data-latex='\\operatorname{sign}(#?)' data-latex='\\operatorname{sign}()'></li>
            </ul>
            <ul><li class='separator'></li>
                <li class='fnbutton' data-latex='\\tan'></li>
                <li class='fnbutton' data-latex='\\tan^{-1}'></li>
                <li class='fnbutton' data-latex='\\log_{#?}'></li>
                <li class='fnbutton' data-latex='\\sqrt[#?]{#0}'></li>
                <li class='bigfnbutton' data-insert='#0 \\mod' data-latex='\\mod'></li>
                <li class='bigfnbutton' data-insert='\\operatorname{round}(#?) ' data-latex='\\operatorname{round}()'></li>
                <li class='bigfnbutton' data-insert='\\prod_{n\\mathop=0}^{\\infty}' data-latex='{\\scriptstyle \\prod_{n=0}^{\\infty}}'></li>
                <li class='bigfnbutton' data-insert='\\frac{\\differentialD #0}{\\differentialD x}'></li>
                <li class='action font-glyph bottom right' data-command='["performWithFeedback","deleteBackward"]'>&#x232b;</li></ul>
            <ul><li class='separator'></li>
                <li class='fnbutton'>(</li>
                <li class='fnbutton'>)</li>
                <li class='fnbutton' data-insert='^{#?}' data-latex='x^{#?}'></li>
                <li class='fnbutton' data-insert='_{#?}' data-latex='x_{#?}'></li>
                <li class='keycap w20 ' data-key=' '>&nbsp;</li>
                <arrows/>
            </ul>
        </div>` , 'style' : `
        <div class='rows'>
            <ul>
                <li class='keycap' data-alt-keys='foreground-color' data-command='["applyStyle",{"color":"red"}]'><span style='border-radius: 50%;width:22px;height:22px; border: 3px solid #cc2428'></span></li>
                <li class='keycap' data-alt-keys='background-color' data-command='["applyStyle",{"backgroundColor":"yellow"}]'><span style='border-radius: 50%;width:22px;height:22px; background:#fff590'></span></li>
                <li class='separator w5'></li>
                <li class='keycap' data-command='["applyStyle",{"size":"3"}]' data-latex='\\scriptsize\\text{small}'></li>
                <li class='keycap' data-command='["applyStyle",{"size":"5"}]' data-latex='\\scriptsize\\text{normal}'></li>
                <li class='keycap' data-command='["applyStyle",{"size":"9"}]' data-latex='\\huge\\text{big}'></li>
                <li class='separator w5'></li>
                <li class='keycap' data-latex='\\langle' data-command='["insert", "\\\\langle", {"smartFence":true}]'></li>
            </ul>
            <ul>
                <li class='keycap' data-command='["applyStyle",{"series":"l"}]' data-latex='\\fontseries{l}\\text{Ab}'></li>
                <li class='keycap' data-command='["applyStyle",{"series":"m"}]' data-latex='\\fontseries{m}\\text{Ab}'></li>
                <li class='keycap' data-command='["applyStyle",{"series":"b"}]' data-latex='\\fontseries{b}\\text{Ab}'></li>
                <li class='keycap' data-command='["applyStyle",{"series":"bx"}]' data-latex='\\fontseries{bx}\\text{Ab}'></li>
                <li class='keycap' data-command='["applyStyle",{"series":"sb"}]' data-latex='\\fontseries{sb}\\text{Ab}'></li>
                <li class='keycap' data-command='["applyStyle",{"series":"c"}]' data-latex='\\fontseries{c}\\text{Ab}'></li>
            </ul>
            <ul>
                <li class='keycap' data-command='["applyStyle",{"shape":"up"}]' data-latex='\\textup{Ab}'></li>
                <li class='keycap' data-command='["applyStyle",{"shape":"it"}]' data-latex='\\textit{Ab}'></li>
                <li class='keycap' data-command='["applyStyle",{"shape":"sl"}]' data-latex='\\textsl{Ab}'></li>
                <li class='keycap' data-command='["applyStyle",{"shape":"sc"}]' data-latex='\\textsc{Ab}'></li>
                <li class='separator w5'></li>
                <li class='keycap' data-insert='\\emph{#?} ' data-latex='\\text{\\emph{emph}}'></li>
            </ul>
            <ul>
                <li class='keycap' data-command='["applyStyle",{"fontFamily":"cmr"}]' data-latex='\\textrm{Az}'></li>
                <li class='keycap' data-command='["applyStyle",{"fontFamily":"cmtt"}]' data-latex='\\texttt{Az}'></li>
                <li class='keycap' data-command='["applyStyle",{"fontFamily":"cmss"}]' data-latex='\\textsf{Az}'></li>

                <li class='keycap' data-command='["applyStyle",{"fontFamily":"bb"}]'  data-latex='\\mathbb{AZ}'></li>
                <li class='keycap' data-command='["applyStyle",{"fontFamily":"scr"}]'  data-latex='\\mathscr{AZ}'></li>
                <li class='keycap' data-command='["applyStyle",{"fontFamily":"cal"}]' data-latex='\\mathcal{A1}'></li>
                <li class='keycap' data-command='["applyStyle",{"fontFamily":"frak"}]' data-latex='\\mathfrak{Az}'></li>
            </ul>
        </div>` , } ; function latexToMarkup ( latex , arg ) { latex = latex . replace ( /(^|[^\\])#@/g , '$1#?' ) ; const root = new Atom ( 'root' , { mode : 'math' } ) ; root . body = typeset ( parseLatex ( latex , { parseMode : 'math' , args : arg , macros : getMacros ( ) , registers : getDefaultRegisters ( ) , } ) ) ; const box = coalesce ( adjustInterAtomSpacing ( new Box ( root . render ( new Context ( { macros : getMacros ( ) , registers : getDefaultRegisters ( ) , smartFence : false , } , { fontSize : DEFAULT_FONT_SIZE , } , 'displaystyle' ) ) , { classes : 'ML__base' } ) ) ) ; return makeStruts ( box , { classes : 'ML__mathlive' } ) . toMarkup ( ) ; } /**
 * Return a markup string for the keyboard toolbar for the specified layer.
 */ function makeKeyboardToolbar ( options , keyboardIDs , currentKeyboard ) { let result = "<div class='left'>" ; const keyboardList = keyboardIDs . replace ( /\s+/g , ' ' ) . split ( ' ' ) ; if ( keyboardList . length > 1 ) { const keyboards = { ... KEYBOARDS , ... ( options . customVirtualKeyboards ?? { } ) , } ; for ( const keyboard of keyboardList ) { if ( ! keyboards [ keyboard ] ) { console . error ( 'Unknown virtual keyboard "' , keyboard , '"' ) ; break ; } result += "<div class='" ; if ( keyboard === currentKeyboard ) { result += 'selected ' ; } else if ( keyboards [ keyboard ] . command ) { result += 'action ' ; } else { result += 'layer-switch ' ; } result += ( keyboards [ keyboard ] . classes ?? '' ) + "'" ; if ( keyboards [ keyboard ] . tooltip ) { result += "data-ML__tooltip='" + ( l10n ( keyboards [ keyboard ] . tooltip ) ?? keyboards [ keyboard ] . tooltip ) + "' " ; result += "data-placement='top' data-delay='1s'" ; } if ( keyboard !== currentKeyboard ) { if ( typeof keyboards [ keyboard ] . command === 'string' ) { result += `data-command='"${ keyboards [ keyboard ] . command as string }"'` ; } else if ( Array . isArray ( keyboards [ keyboard ] . command ) ) { result += `data-command='"${ ( keyboards [ keyboard ] . command as string [ ] ) . join ( '' ) }"'` ; } if ( keyboards [ keyboard ] . layer ) { result += "data-layer='" + keyboards [ keyboard ] . layer + "'" ; } } result += '>' + keyboards [ keyboard ] . label + '</div>' ; } } result += '</div>' ; const toolbarOptions = options . virtualKeyboardToolbar ; const availableActions = toolbarOptions === 'default' ? [ 'copyToClipboard' , 'undo' , 'redo' ] : [ ] ; const actionsMarkup = { copyToClipboard : `
            <div class='action'
                data-command='"copyToClipboard"'
                data-ML__tooltip='${ l10n ( 'tooltip.copy to clipboard' ) }' data-placement='top' data-delay='1s'>
                <svg><use xlink:href='#svg-copy' /></svg>
            </div>
        ` , undo : `
            <div class='action disabled'
                data-command='"undo"'
                data-ML__tooltip='${ l10n ( 'tooltip.undo' ) }' data-placement='top' data-delay='1s'>
                <svg><use xlink:href='#svg-undo' /></svg>
            </div>
        ` , redo : `
            <div class='action disabled'
                data-command='"redo"'
                data-ML__tooltip='${ l10n ( 'tooltip.redo' ) }' data-placement='top' data-delay='1s'>
                <svg><use xlink:href='#svg-redo' /></svg>
            </div>
        ` , } ; if ( availableActions . length > 0 ) { result += `
            <div class='right'>
                ${ availableActions . map ( ( action ) => actionsMarkup [ action ] ) . join ( '' ) }
            </div>
        ` ; } return "<div class='keyboard-toolbar' role='toolbar'>" + result + '</div>' ; } export function makeKeycap ( keyboard , elementList , chainedCommand ? ) { for ( const element of elementList ) { let html = undefined ; if ( element . getAttribute ( 'data-latex' ) ) { html = latexToMarkup ( element . getAttribute ( 'data-latex' ) ! . replace ( /&quot;/g , '"' ) , ( ) => '\\placeholder{}' ) ; } else if ( element . getAttribute ( 'data-insert' ) && element . innerHTML === '' ) { html = latexToMarkup ( element . getAttribute ( 'data-insert' ) ! . replace ( /&quot;/g , '"' ) , ( ) => '\\placeholder{}' ) ; } else if ( element . getAttribute ( 'data-content' ) ) { html = element . getAttribute ( 'data-content' ) ! . replace ( /&quot;/g , '"' ) ; } if ( element . getAttribute ( 'data-aside' ) ) { html = ( html ?? '' ) + '<aside>' + element . getAttribute ( 'data-aside' ) ! . replace ( /&quot;/g , '"' ) + '</aside>' ; } if ( html !== undefined ) { element . innerHTML = keyboard . options . createHTML ( html ) ; } if ( element . getAttribute ( 'data-classes' ) ) { element . classList . add ( element . getAttribute ( 'data-classes' ) ! ) ; } const key = element . getAttribute ( 'data-insert' ) ?. replace ( /&quot;/g , '"' ) ; if ( key && SHIFTED_KEYS [ key ] ) { element . dataset . shifted = SHIFTED_KEYS [ key ] [ 0 ] ; element . dataset . shiftedCommand = JSON . stringify ( [ 'insertAndUnshiftKeyboardLayer' , SHIFTED_KEYS [ key ] [ 1 ] , ] ) ; } let selector ; if ( element . getAttribute ( 'data-command' ) ) { selector = JSON . parse ( element . getAttribute ( 'data-command' ) ! ) ; } else if ( element . getAttribute ( 'data-insert' ) ) { selector = [ 'insert' , element . getAttribute ( 'data-insert' ) ! , { focus : true , feedback : true , scrollIntoView : true , mode : 'math' , format : 'latex' , resetStyle : true , } , ] ; } else if ( element . getAttribute ( 'data-latex' ) ) { selector = [ 'insert' , element . getAttribute ( 'data-latex' ) ! , { focus : true , feedback : true , scrollIntoView : true , mode : 'math' , format : 'latex' , resetStyle : true , } , ] ; } else { selector = [ 'typedText' , element . getAttribute ( 'data-key' ) ?? element . textContent ! , { focus : true , feedback : true , simulateKeystroke : true } , ] ; } if ( chainedCommand ) { selector = [ chainedCommand , selector ] ; } let handlers = selector ; const altKeysetId = element . getAttribute ( 'data-alt-keys' ) ; if ( altKeysetId ) { const altKeys = ALT_KEYS [ altKeysetId ] ; if ( altKeys ) { handlers = { default : selector , pressAndHoldStart : [ 'showAlternateKeys' , altKeysetId ] , pressAndHoldEnd : 'hideAlternateKeys' , } ; } } attachButtonHandlers ( ( command ) => keyboard . executeCommand ( command ) , element , handlers ) ; } } /**
 * Expand the shortcut tags (e.g. <row>) inside a layer.
 */ function expandLayerMarkup ( options , layer ) { const ROWS = { qwerty : { 'lower-1' : 'qwertyuiop' , 'lower-2' : ' asdfghjkl ' , 'lower-3' : '^zxcvbnm~' , 'upper-1' : 'QWERTYUIOP' , 'upper-2' : ' ASDFGHJKL ' , 'upper-3' : '^ZXCVBNM~' , 'numpad-1' : '789/' , 'numpad-2' : '456*' , 'numpad-3' : '123-' , 'numpad-4' : '0.=+' , } , azerty : { 'lower-1' : 'azertyuiop' , 'lower-2' : 'qsdfghjklm' , 'lower-3' : '^ wxcvbn ~' , 'upper-1' : 'AZERTYUIOP' , 'upper-2' : 'QSDFGHJKLM' , 'upper-3' : '^ WXCVBN ~' , } , qwertz : { 'lower-1' : 'qwertzuiop' , 'lower-2' : ' asdfghjkl ' , 'lower-3' : '^yxcvbnm~' , 'upper-1' : 'QWERTZUIOP' , 'upper-2' : ' ASDFGHJKL' , 'upper-3' : '^YXCVBNM~' , } , dvorak : { 'lower-1' : '^  pyfgcrl ' , 'lower-2' : 'aoeuidhtns' , 'lower-3' : 'qjkxbmwvz~' , 'upper-1' : '^  PYFGCRL ' , 'upper-2' : 'AOEUIDHTNS' , 'upper-3' : 'QJKXBMWVZ~' , } , colemak : { 'lower-1' : ' qwfpgjluy ' , 'lower-2' : 'arstdhneio' , 'lower-3' : '^zxcvbkm~' , 'upper-1' : ' QWFPGNLUY ' , 'upper-2' : 'ARSTDHNEIO' , 'upper-3' : '^ZXCVBKM~' , } , } ; let layoutName = options . virtualKeyboardLayout ; if ( layoutName === 'auto' ) { const activeLayout = getActiveKeyboardLayout ( ) ; if ( activeLayout ) { layoutName = activeLayout . virtualLayout ; } if ( ! layoutName || layoutName === 'auto' ) { layoutName = ( { fr : 'azerty' , be : 'azerty' , al : 'qwertz' , ba : 'qwertz' , cz : 'qwertz' , de : 'qwertz' , hu : 'qwertz' , sk : 'qwertz' , ch : 'qwertz' , } as const ) [ l10nOptions . locale . slice ( 0 , 2 ) ] ?? 'qwerty' ; } } const layout = ROWS [ layoutName ] ?? ROWS . qwerty ; let result = layer ; let row ; result = result . replace ( /<arrows\/>/g , `
        <li class='action' data-command='["performWithFeedback","moveToPreviousChar"]'
            data-shifted='<svg class="svg-glyph"><use xlink:href="#svg-angle-double-left" /></svg>'
            data-shifted-command='["performWithFeedback","extendToPreviousChar"]'>
            <svg class="svg-glyph"><use xlink:href='#svg-arrow-left' /></svg>
        </li>
        <li class='action' data-command='["performWithFeedback","moveToNextChar"]'
            data-shifted='<svg class="svg-glyph"><use xlink:href="#svg-angle-double-right" /></svg>'
            data-shifted-command='["performWithFeedback","extendToNextChar"]'>
            <svg class="svg-glyph"><use xlink:href='#svg-arrow-right' /></svg>
        </li>
        <li class='action' data-command='["performWithFeedback","moveToNextPlaceholder"]'>
        <svg class="svg-glyph"><use xlink:href='#svg-tab' /></svg></li>` ) ; let m = result . match ( /(<row\s+)(.*)((?:<\/row|\/)>)/ ) ; while ( m ) { row = '' ; const attributesArray = m [ 2 ] . match ( /[a-zA-Z][a-zA-Z\d-]*=(['"])(.*?)\1/g ) ; const attributes = { } ; if ( attributesArray ) { for ( const attribute of attributesArray ) { const m2 = attribute . match ( /([a-zA-Z][a-zA-Z\d-]*)=(['"])(.*?)\2/ ) ; if ( m2 ) { attributes [ m2 [ 1 ] ] = m2 [ 3 ] ; } } } let keys = layout [ attributes . name ] as string ; if ( ! keys ) keys = ROWS . qwerty [ attributes . name ] ; if ( ! keys ) { console . warn ( 'Unknown roman keyboard row:' , attributes . name ) ; } else { for ( const c of keys ) { let cls = attributes . class ?? '' ; if ( cls ) cls = ` ${ cls }` ; if ( c === '~' ) { row += `<li class='action font-glyph bottom right ` ; row += keys . length - ( keys . match ( / /g ) || [ ] ) . length / 2 === 10 ? 'w10' : 'w15' ; row += `' data-shifted='<span class="warning"><svg class="svg-glyph"><use xlink:href="#svg-trash" /></svg></span>'
                        data-shifted-command='"deleteAll"'
                        data-alt-keys='delete' data-command='["performWithFeedback","deleteBackward"]'
                        >&#x232b;</li>` ; } else if ( c === ' ' ) { row += "<li class='separator w5'></li>" ; } else if ( c === '^' ) { row += `<li class='shift modifier font-glyph bottom left w15 layer-switch' data-layer='` + attributes [ 'shift-layer' ] + `'>&#x21e7;</li>` ; } else if ( c === '/' ) { row += "<li class='keycap" + cls + "' data-alt-keys='/' data-insert='\\frac{#@}{#?}'>&divide;</li>" ; } else if ( c === '*' ) { row += "<li class='keycap" + cls + "' data-alt-keys='*' data-insert='\\times '>&times;</li>" ; } else if ( c === '-' ) { row += "<li class='keycap" + cls + "' data-alt-keys='-' data-key='-' data-alt-keys='-'>&#x2212;</li>" ; } else if ( cls . includes ( 'tt' ) ) { row += `<li class='keycap${ cls }' data-alt-keys='${ c }' ` + `data-command='["typedText","${ c }",{"mode":"command", "focus":true, "feedback":true}]'` + `>${ c }</li>` ; } else { row += "<li class='keycap" + cls + "' data-alt-keys='" + c + "'>" + c + '</li>' ; } } } result = result . replace ( new RegExp ( m [ 1 ] + m [ 2 ] + m [ 3 ] ) , row ) ; m = result . match ( /(<row\s+)(.*)((?:<\/row|\/)>)/ ) ; } return result ; } /**
 * Construct a virtual keyboard element based on the config options in the
 * mathfield and an optional theme.
 */ export function makeKeyboardElement ( keyboard , theme ) { throwIfNotInBrowser ( ) ; const svgIcons = `<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">

            <symbol id="svg-command" viewBox="0 0 640 512">
                <path d="M34.495 36.465l211.051 211.05c4.686 4.686 4.686 12.284 0 16.971L34.495 475.535c-4.686 4.686-12.284 4.686-16.97 0l-7.071-7.07c-4.686-4.686-4.686-12.284 0-16.971L205.947 256 10.454 60.506c-4.686-4.686-4.686-12.284 0-16.971l7.071-7.07c4.686-4.687 12.284-4.687 16.97 0zM640 468v-10c0-6.627-5.373-12-12-12H300c-6.627 0-12 5.373-12 12v10c0 6.627 5.373 12 12 12h328c6.627 0 12-5.373 12-12z"/>
            </symbol>

            <symbol id="svg-undo" viewBox="0 0 512 512">
                <path d="M20 8h10c6.627 0 12 5.373 12 12v110.625C85.196 57.047 165.239 7.715 256.793 8.001 393.18 8.428 504.213 120.009 504 256.396 503.786 393.181 392.834 504 256 504c-63.926 0-122.202-24.187-166.178-63.908-5.113-4.618-5.354-12.561-.482-17.433l7.069-7.069c4.503-4.503 11.749-4.714 16.482-.454C150.782 449.238 200.935 470 256 470c117.744 0 214-95.331 214-214 0-117.744-95.331-214-214-214-82.862 0-154.737 47.077-190.289 116H180c6.627 0 12 5.373 12 12v10c0 6.627-5.373 12-12 12H20c-6.627 0-12-5.373-12-12V20c0-6.627 5.373-12 12-12z"/>
            </symbol>
            <symbol id="svg-redo" viewBox="0 0 512 512">
                <path d="M492 8h-10c-6.627 0-12 5.373-12 12v110.625C426.804 57.047 346.761 7.715 255.207 8.001 118.82 8.428 7.787 120.009 8 256.396 8.214 393.181 119.166 504 256 504c63.926 0 122.202-24.187 166.178-63.908 5.113-4.618 5.354-12.561.482-17.433l-7.069-7.069c-4.503-4.503-11.749-4.714-16.482-.454C361.218 449.238 311.065 470 256 470c-117.744 0-214-95.331-214-214 0-117.744 95.331-214 214-214 82.862 0 154.737 47.077 190.289 116H332c-6.627 0-12 5.373-12 12v10c0 6.627 5.373 12 12 12h160c6.627 0 12-5.373 12-12V20c0-6.627-5.373-12-12-12z"/>
            </symbol>
            <symbol id="svg-arrow-left" viewBox="0 0 192 512">
                <path d="M25.1 247.5l117.8-116c4.7-4.7 12.3-4.7 17 0l7.1 7.1c4.7 4.7 4.7 12.3 0 17L64.7 256l102.2 100.4c4.7 4.7 4.7 12.3 0 17l-7.1 7.1c-4.7 4.7-12.3 4.7-17 0L25 264.5c-4.6-4.7-4.6-12.3.1-17z"/>
            </symbol>
            <symbol id="svg-arrow-right" viewBox="0 0 192 512">
                    <path d="M166.9 264.5l-117.8 116c-4.7 4.7-12.3 4.7-17 0l-7.1-7.1c-4.7-4.7-4.7-12.3 0-17L127.3 256 25.1 155.6c-4.7-4.7-4.7-12.3 0-17l7.1-7.1c4.7-4.7 12.3-4.7 17 0l117.8 116c4.6 4.7 4.6 12.3-.1 17z"/>
            </symbol>
            <symbol id="svg-tab" viewBox="0 0 448 512">
                    <path d="M32 217.1c0-8.8 7.2-16 16-16h144v-93.9c0-7.1 8.6-10.7 13.6-5.7l143.5 143.1c6.3 6.3 6.3 16.4 0 22.7L205.6 410.4c-5 5-13.6 1.5-13.6-5.7v-93.9H48c-8.8 0-16-7.2-16-16v-77.7m-32 0v77.7c0 26.5 21.5 48 48 48h112v61.9c0 35.5 43 53.5 68.2 28.3l143.6-143c18.8-18.8 18.8-49.2 0-68L228.2 78.9c-25.1-25.1-68.2-7.3-68.2 28.3v61.9H48c-26.5 0-48 21.6-48 48zM436 64h-8c-6.6 0-12 5.4-12 12v360c0 6.6 5.4 12 12 12h8c6.6 0 12-5.4 12-12V76c0-6.6-5.4-12-12-12z"/>
            </symbol>
            <symbol id="svg-copy" viewBox="0 0 448 512">
                <path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM352 32.491a15.88 15.88 0 0 1 7.431 4.195l51.882 51.883A15.885 15.885 0 0 1 415.508 96H352V32.491zM288 464c0 8.822-7.178 16-16 16H48c-8.822 0-16-7.178-16-16V144c0-8.822 7.178-16 16-16h80v240c0 26.51 21.49 48 48 48h112v48zm128-96c0 8.822-7.178 16-16 16H176c-8.822 0-16-7.178-16-16V48c0-8.822 7.178-16 16-16h144v72c0 13.2 10.8 24 24 24h72v240z"/>
            </symbol>
            <symbol id="svg-angle-double-right" viewBox="0 0 320 512">
                <path d="M166.9 264.5l-117.8 116c-4.7 4.7-12.3 4.7-17 0l-7.1-7.1c-4.7-4.7-4.7-12.3 0-17L127.3 256 25.1 155.6c-4.7-4.7-4.7-12.3 0-17l7.1-7.1c4.7-4.7 12.3-4.7 17 0l117.8 116c4.6 4.7 4.6 12.3-.1 17zm128-17l-117.8-116c-4.7-4.7-12.3-4.7-17 0l-7.1 7.1c-4.7 4.7-4.7 12.3 0 17L255.3 256 153.1 356.4c-4.7 4.7-4.7 12.3 0 17l7.1 7.1c4.7 4.7 12.3 4.7 17 0l117.8-116c4.6-4.7 4.6-12.3-.1-17z"/>
            </symbol>
            <symbol id="svg-angle-double-left" viewBox="0 0 320 512">
                <path d="M153.1 247.5l117.8-116c4.7-4.7 12.3-4.7 17 0l7.1 7.1c4.7 4.7 4.7 12.3 0 17L192.7 256l102.2 100.4c4.7 4.7 4.7 12.3 0 17l-7.1 7.1c-4.7 4.7-12.3 4.7-17 0L153 264.5c-4.6-4.7-4.6-12.3.1-17zm-128 17l117.8 116c4.7 4.7 12.3 4.7 17 0l7.1-7.1c4.7-4.7 4.7-12.3 0-17L64.7 256l102.2-100.4c4.7-4.7 4.7-12.3 0-17l-7.1-7.1c-4.7-4.7-12.3-4.7-17 0L25 247.5c-4.6 4.7-4.6 12.3.1 17z"/>
            </symbol>
            <symbol id="svg-trash" viewBox="0 0 448 512">
                <path d="M336 64l-33.6-44.8C293.3 7.1 279.1 0 264 0h-80c-15.1 0-29.3 7.1-38.4 19.2L112 64H24C10.7 64 0 74.7 0 88v2c0 3.3 2.7 6 6 6h26v368c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V96h26c3.3 0 6-2.7 6-6v-2c0-13.3-10.7-24-24-24h-88zM184 32h80c5 0 9.8 2.4 12.8 6.4L296 64H152l19.2-25.6c3-4 7.8-6.4 12.8-6.4zm200 432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V96h320v368zm-176-44V156c0-6.6 5.4-12 12-12h8c6.6 0 12 5.4 12 12v264c0 6.6-5.4 12-12 12h-8c-6.6 0-12-5.4-12-12zm-80 0V156c0-6.6 5.4-12 12-12h8c6.6 0 12 5.4 12 12v264c0 6.6-5.4 12-12 12h-8c-6.6 0-12-5.4-12-12zm160 0V156c0-6.6 5.4-12 12-12h8c6.6 0 12 5.4 12 12v264c0 6.6-5.4 12-12 12h-8c-6.6 0-12-5.4-12-12z"/>
            </symbol>
        </svg>
        ` ; let markup = svgIcons ; keyboard . create ( ) ; ALT_KEYS_BASE [ 'foreground-color' ] = [ ] ; for ( const color of Object . keys ( FOREGROUND_COLORS ) ) { ALT_KEYS_BASE [ 'foreground-color' ] . push ( { class : 'small-button' , content : '<span style="border-radius:50%;width:32px;height:32px; box-sizing: border-box; border: 3px solid ' + FOREGROUND_COLORS [ color ] + '"></span>' , command : [ 'applyStyle' , { color } ] , } ) ; } ALT_KEYS_BASE [ 'background-color' ] = [ ] ; for ( const color of Object . keys ( BACKGROUND_COLORS ) ) { ALT_KEYS_BASE [ 'background-color' ] . push ( { class : 'small-button' , content : '<span style="border-radius:50%;width:32px;height:32px; background:' + BACKGROUND_COLORS [ color ] + '"></span>' , command : [ 'applyStyle' , { backgroundColor : color } ] , } ) ; } ALT_KEYS = { ... ALT_KEYS_BASE } ; for ( const key of Object . keys ( ALT_KEYS ) ) { ALT_KEYS [ key ] = ALT_KEYS [ key ] . slice ( ) ; } const UPPER_ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ; const LOWER_ALPHA = 'abcdefghijklmnopqrstuvwxyz' ; const DIGITS = '0123456789' ; for ( let i = 0 ; i < 26 ; i ++ ) { const key = UPPER_ALPHA [ i ] ; if ( ! ALT_KEYS [ key ] ) ALT_KEYS [ key ] = [ ] ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathbb{' + key + '}' , aside : 'blackboard' , insert : '\\mathbb{' + key + '}' , } ) ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathbf{' + key + '}' , aside : 'bold' , insert : '\\mathbf{' + key + '}' , } ) ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathsf{' + key + '}' , aside : 'sans' , insert : '\\mathsf{' + key + '}' , } ) ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathtt{' + key + '}' , aside : 'monospace' , insert : '\\mathtt{' + key + '}' , } ) ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathcal{' + key + '}' , aside : 'calligraphy' , insert : '\\mathcal{' + key + '}' , } ) ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathfrak{' + key + '}' , aside : 'fraktur' , insert : '\\mathfrak{' + key + '}' , } ) ; } for ( let i = 0 ; i <= 26 ; i ++ ) { const key = LOWER_ALPHA [ i ] ; if ( ! ALT_KEYS [ key ] ) ALT_KEYS [ key ] = [ ] ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathsf{' + key + '}' , aside : 'sans' , insert : '\\mathsf{' + key + '}' , } ) ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathbf{' + key + '}' , aside : 'bold' , insert : '\\mathbf{' + key + '}' , } ) ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathtt{' + key + '}' , aside : 'monospace' , insert : '\\mathtt{' + key + '}' , } ) ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathfrak{' + key + '}' , aside : 'fraktur' , insert : '\\mathfrak{' + key + '}' , } ) ; } for ( let i = 0 ; i < 10 ; i ++ ) { const key = DIGITS [ i ] ; if ( ! ALT_KEYS [ key ] ) ALT_KEYS [ key ] = [ ] ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathbf{' + key + '}' , aside : 'bold' , insert : '\\mathbf{' + key + '}' , } ) ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathsf{' + key + '}' , aside : 'sans' , insert : '\\mathsf{' + key + '}' , } ) ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathtt{' + key + '}' , aside : 'monospace' , insert : '\\mathtt{' + key + '}' , } ) ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathcal{' + key + '}' , aside : 'script' , insert : '\\mathcal{' + key + '}' , } ) ; ALT_KEYS [ key ] . unshift ( { latex : '\\mathfrak{' + key + '}' , aside : 'fraktur' , insert : '\\mathfrak{' + key + '}' , } ) ; } let keyboardIDs = keyboard . options . virtualKeyboards ; if ( ! keyboardIDs ) { keyboardIDs = 'all' ; } keyboardIDs = keyboardIDs . replace ( /\ball\b/i , 'numeric functions symbols roman  greek' ) ; const layers = { ... LAYERS , ... ( keyboard . options . customVirtualKeyboardLayers ?? { } ) , } ; const keyboards = { ... KEYBOARDS , ... ( keyboard . options . customVirtualKeyboards ?? { } ) , } ; const keyboardList = keyboardIDs . replace ( /\s+/g , ' ' ) . split ( ' ' ) ; for ( const keyboardName of keyboardList ) { if ( ! keyboards [ keyboardName ] ) { console . error ( 'Unknown virtual keyboard "' + keyboardName + '"' ) ; continue ; } let keyboardLayers = keyboards [ keyboardName ] . layers ?? [ ] ; if ( keyboards [ keyboardName ] . layer ) { keyboardLayers . push ( keyboards [ keyboardName ] . layer ! ) ; } keyboardLayers = [ ... new Set ( keyboardLayers ) ] ; for ( const layerName of keyboardLayers ) { if ( ! layers [ layerName ] ) { console . error ( 'Unknown virtual keyboard layer: "' , layerName , '"' ) ; break ; } if ( typeof layers [ layerName ] === 'object' ) { const layer = layers [ layerName ] as Partial < VirtualKeyboardLayer > ; let layerMarkup = '' ; if ( layer . styles ) { layerMarkup += `<style>${ layer . styles }</style>` ; } if ( layer . backdrop ) { layerMarkup += `<div class='${ layer . backdrop }'>` ; } if ( layer . container ) { layerMarkup += `<div class='${ layer . container }'>` ; } if ( layer . rows ) { layerMarkup += `<div class='rows'>` ; for ( const row of layer . rows ) { layerMarkup += `<ul>` ; for ( const keycap of row ) { layerMarkup += `<li` ; if ( keycap . class && /separator/ . test ( keycap . class ) ) { layerMarkup += ` class="${ keycap . class }"` ; } else if ( keycap . class ) { layerMarkup += ` class="keycap ${ keycap . class }"` ; } else { layerMarkup += ` class="keycap"` ; } if ( keycap . key ) { layerMarkup += ` data-key="${ keycap . key }"` ; } if ( keycap . command ) { if ( typeof keycap . command === 'string' ) { layerMarkup += ` data-command='"${ keycap . command }"'` ; } else { layerMarkup += ` data-command='` ; layerMarkup += JSON . stringify ( keycap . command ) ; layerMarkup += `'` ; } } if ( keycap . insert ) { layerMarkup += ` data-insert="${ keycap . insert }"` ; } if ( keycap . latex ) { layerMarkup += ` data-latex="${ keycap . latex }"` ; } if ( keycap . aside ) { layerMarkup += ` data-aside="${ keycap . aside }"` ; } if ( keycap . variants ) { const keysetId = Date . now ( ) . toString ( 36 ) . slice ( - 2 ) + Math . floor ( Math . random ( ) * 0x186a0 ) . toString ( 36 ) ; ALT_KEYS [ keysetId ] = keycap . variants ; layerMarkup += ` data-alt-keys="${ keysetId }"` ; } if ( keycap . shifted ) { layerMarkup += ` data-shifted="${ keycap . shifted }"` ; } if ( keycap . shiftedCommand ) { layerMarkup += ` data-shifted-command="${ keycap . shiftedCommand }"` ; } layerMarkup += `>${ keycap . label ? keycap . label : '' }</li>` ; } layerMarkup += `</ul>` ; } layerMarkup += `</div>` ; } if ( layer . container ) { layerMarkup += '</div>' ; } if ( layer . backdrop ) { layerMarkup += '</div>' ; } layers [ layerName ] = layerMarkup ; } markup += `<div tabindex="-1" class='keyboard-layer' data-layer='${ layerName }'>` ; markup += makeKeyboardToolbar ( keyboard . options , keyboardIDs , keyboardName ) ; const layerMarkup = layers [ layerName ] ; markup += expandLayerMarkup ( keyboard . options , layerMarkup ) ; markup += '</div>' ; } } const result = document . createElement ( 'div' ) ; result . className = 'ML__keyboard' ; if ( theme ) { result . classList . add ( theme ) ; } else if ( keyboard . options . virtualKeyboardTheme ) { result . classList . add ( keyboard . options . virtualKeyboardTheme ) ; } const plate = document . createElement ( 'div' ) ; plate . className = 'ML__keyboard--plate' ; plate . innerHTML = keyboard . options . createHTML ( markup ) ; result . appendChild ( plate ) ; const keycaps = result . querySelectorAll < HTMLElement > ( '.keycap, .action, .fnbutton, .bigfnbutton' ) ; for ( const keycap of keycaps ) { keycap . id = 'ML__k' + Date . now ( ) . toString ( 36 ) . slice ( - 2 ) + Math . floor ( Math . random ( ) * 0x186a0 ) . toString ( 36 ) ; } makeKeycap ( keyboard , [ ... keycaps ] ) ; const elementList = result . querySelectorAll < HTMLElement > ( '.layer-switch' ) ; for ( const element of elementList ) { if ( element . classList . contains ( 'shift' ) ) { attachButtonHandlers ( ( command ) => keyboard . executeCommand ( command ) , element , { pressed : 'shiftKeyboardLayer' , default : [ 'switchKeyboardLayer' , element . getAttribute ( 'data-layer' ) ] , pressAndHoldEnd : 'unshiftKeyboardLayer' , } ) ; } else { attachButtonHandlers ( ( command ) => keyboard . executeCommand ( command ) , element , { default : [ 'switchKeyboardLayer' , element . getAttribute ( 'data-layer' ) ] , } ) ; } } const layerElements = result . querySelectorAll ( '.keyboard-layer' ) ; for ( const x of layerElements ) { x . addEventListener ( 'mousedown' , ( evt ) => { evt . preventDefault ( ) ; evt . stopPropagation ( ) ; } ) ; x . addEventListener ( 'touchstart' , ( evt ) => { evt . preventDefault ( ) ; evt . stopPropagation ( ) ; } , { passive : false } ) ; } console . assert ( layerElements . length > 0 , 'No virtual keyboards available' ) ; layerElements [ 0 ] ?. classList . add ( 'is-visible' ) ; return result ; } export function unshiftKeyboardLayer ( keyboard ) { hideAlternateKeys ( ) ; const keycaps = keyboard . element ! . querySelectorAll < HTMLElement > ( 'div.keyboard-layer.is-visible .rows .keycap, div.keyboard-layer.is-visible .rows .action' ) ; if ( keycaps ) { for ( const keycap of keycaps ) { const content = keycap . getAttribute ( 'data-unshifted-content' ) ; if ( content ) { keycap . innerHTML = keyboard . options . createHTML ( content ) ; } const command = keycap . getAttribute ( 'data-unshifted-command' ) ; if ( command ) { keycap . dataset . command = command ; } } } return false ; } export function onUndoStateChanged ( keyboard , canUndoState , canRedoState ) { const toolbar = keyboard . element ?. querySelector ( '.keyboard-toolbar' ) ; if ( ! toolbar ) return false ; const undoButton = toolbar . querySelector ( '[data-command=\'"undo"\']' ) ; const redoButton = toolbar . querySelector ( '[data-command=\'"redo"\']' ) ; if ( redoButton ) { if ( canRedoState ) { redoButton . classList . remove ( 'disabled' ) ; } else { redoButton . classList . add ( 'disabled' ) ; } } if ( undoButton ) { if ( canUndoState ) { undoButton . classList . remove ( 'disabled' ) ; } else { undoButton . classList . add ( 'disabled' ) ; } } return false ; }