mathlive
Version:
Render and edit beautifully typeset math
1,007 lines (918 loc) • 72 kB
JavaScript
/**
* @module editor/virtualKeyboard
* @private
*/
import MathAtom from '../core/mathAtom.js';
import Span from '../core/span.js';
import Lexer from '../core/lexer.js';
import ParserModule from '../core/parser.js';
import Color from '../core/color.js';
import '../addons/outputLatex.js';
import { l10n } from './l10n.js';
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', 'symbols']
},
'greek': {
tooltip: 'keyboard.tooltip.greek',
layer: 'lower-greek',
label: 'αβγ',
classes: 'tex-math',
layers: ['lower-greek', 'upper-greek']
},
'functions': {
tooltip: 'keyboard.tooltip.functions',
layer: 'functions',
label: '<i>f</i> ()',
classes: 'tex',
layers: ['functions']
},
'command': {
tooltip: 'keyboard.tooltip.command',
// For the command keyboard, perform a command rather than
// doing a simple layer switch, as we want to enter command mode
// when the keyboard is activated
command: 'enterCommandMode',
label: `<svg><use xlink:href='#svg-command' /></svg>`,
layers: ['lower-command', 'upper-command', 'symbols-command']
},
'style': {
tooltip: 'keyboard.tooltip.style',
layer: 'style',
label: '<b>b</b><i>i</i>𝔹',
}
}
const SHIFTED_KEYS = {
'\\varphi ': {label: 'Φ', insert: '\\Phi '},
'\\varsigma ': {label: 'Σ', insert: '\\Sigma '},
'\\epsilon ': {label: 'Ɛ', insert: '{\\char"0190}'},
'\\rho ': {label: 'Ρ', insert: '{\\char"3A1}'},
'\\tau ': {label: 'Τ', insert: '{\\char"3A4}'},
'\\upsilon ': {label: 'Υ', insert: '\\Upsilon '},
'\\theta ': {label: 'Θ', insert: '\\Theta '},
'\\iota ': {label: 'Ι', insert: '{\\char"399}'},
'\\omicron ': {label: 'Ο', insert: '{\\char"39F}'},
'\\pi ': {label: 'Π', insert: '\\Pi '},
'\\alpha ': {label: 'Α', insert: '{\\char"391}'},
'\\sigma ': {label: 'Σ', insert: '\\Sigma '},
'\\delta ': {label: 'Δ', insert: '\\Delta '},
'\\phi ': {label: 'Φ', insert: '\\Phi '},
'\\gamma ': {label: 'Γ', insert: '\\Gamma '},
'\\eta ': {label: 'Η', insert: '{\\char"397}'},
'\\xi ': {label: 'Ξ', insert: '\\Xi '},
'\\kappa ': {label: 'Κ', insert: '{\\char"39A}'},
'\\lambda ': {label: 'Λ', insert: '\\Lambda '},
'\\zeta ': {label: 'Ζ', insert: '{\\char"396}'},
'\\chi ': {label: 'Χ', insert: '{\\char"3A7}'},
'\\psi ': {label: 'Ψ', insert: '\\Psi '},
'\\omega ': {label: 'Ω', insert: '\\Omega '},
'\\beta ': {label: 'Β', insert: '{\\char"392}'},
'\\nu ': {label: 'Ν', insert: '{\\char"39D}'},
'\\mu ': {label: 'Μ', insert: '{\\char"39C}'}
}
// const FUNCTIONS = [
// 'Basic',
// ['\\sin', '\\cos', '\\tan', '\\min', '\\max', '\\gcd', '\\lcm', '\\repeat', 'encapsulate', 'recognize'],
// 'Operators',
// ['\\sum', '\\prod', '\\bigcup_x']
// ]
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', classes:'box'},
{latex:'\\cdots', aside:'center ellipsis', classes:'box'},
{latex:'\\ldotp', aside:'low dot', classes:'box'} ,
{latex:'\\ldots', aside:'low ellipsis', classes:'box'} ,
{latex:'\\vdots', aside:'', classes:'box'} ,
{latex:'\\ddots', aside:'', classes:'box'} ,
'\\odot', '\\oslash', '\\circledcirc',
],
'*': [
'\\cdot', '\\ast', '\\star', '\\bigstar',
'\\ltimes', '\\rtimes', '\\rightthreetimes','\\leftthreetimes',
'\\intercal', '\\prod', {latex:'\\prod_{n\\mathop=0}^{\\infty}', classes:'small'},
],
'+': [
'\\pm', '\\mp',
'\\sum', {latex:'\\sum_{n\\mathop=0}^{\\infty}', classes:'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', '\\nless', '\\nleq',
'\\precsim', '\\lesssim', '\\lessgtr', '\\prec', '\\preccurlyeq', '\\lessdot',
'\\nprec',
],
'>': [
'\\geq', '\\geqq', '\\gneqq', '\\gg', '\\ngtr', '\\ngeq',
'\\succsim', '\\gtrsim', '\\gtrless', '\\succ', '\\succcurlyeq', '\\gtrdot',
'\\nsucc'
],
'set': [
'\\in', '\\owns',
'\\subset', '\\nsubset', '\\supset', '\\nsupset'
],
'!set': [
'\\notin', '\\backepsilon'
],
'subset': [],
'supset': [],
'infinity': ['\\aleph_0', '\\aleph_1', '\\omega', '\\mathfrak{m}'],
'numeric-pi': ['\\prod', '\\theta', '\\rho',
'\\sin', '\\cos', '\\tan'
],
'ee': [
'\\times 10^{#?}',
'\\ln', '\\ln_{10}', '\\log'
],
'^': ['_{#?}'],
// Integrals
'int': [
{latex:'\\int_{#?}^{#?}', classes:'small'},
{latex:'\\int', classes:'small'},
{latex:'\\smallint', classes:'small'},
{latex:'\\iint', classes:'small'},
{latex:'\\iiint', classes:'small'},
{latex:'\\oint', classes:'small'},
{latex: '\\dfrac{\\rd}{\\rd x}', classes:'small'},
{latex:'\\frac{\\partial}{\\partial x}', classes:'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{#@}'],
// 'absnorm': [{latex:'\\lVert #@ \\rVert', aside:'norm'},
// {latex:'\\lvert #@ \\rvert', aside:'determinant'},
// {latex:'\\begin{cardinality} #@ \\end{cardinality}', aside:'cardinality'},
// {latex:'\\lvert #@ \\rvert', aside:'length'},
// {latex:'\\lvert #@ \\rvert', aside:'order'},
// ],
'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(#?)', classes:'small'}, {latex:'g(#?)', classes:'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:'\\barwedge', aside:'bar wedge'},
// {latex:'\\curlyvee', aside:'curly vee'},
// {latex:'\\veebar', aside:'vee bar'},
{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'}
],
// @todo could also delete to end
'delete': [{label: '<span class="warning"><svg><use xlink:href="#svg-trash" /></svg></span>',
command: '"deleteAll"' }],
// @todo Tab: could turn on speech, visible keyboard...
'->|': []
};
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-key='ee' data-alt-keys='ee'>e</li>
<li class='keycap tex' data-key='ii' 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='<'><</li>
<li class='keycap tex' data-key='>' data-alt-keys='>'>></li>
<li class='separator w5'></li>
<row name='numpad-2'/>
<li class='separator w5'></li>
<li class='keycap tex' data-alt-keys='x2' data-insert='#@^{2}'><span><i>x</i> ²</span></li>
<li class='keycap tex' data-alt-keys='^' data-insert='#@^{#?}'><span><i>x</i><sup> <small>⬚</small></sup></span></li>
<li class='keycap tex' data-alt-keys='sqrt' 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 tex small' data-alt-keys='int' data-latex='\\int_0^\\infty'><span></span></li>
<li class='keycap tex' data-latex='\\forall' data-alt-keys='logic' ></li>
<li class='action font-glyph bottom right' data-alt-keys='delete' data-command='["performWithFeedback","deletePreviousChar"]'>⌫</li></ul>
</ul>
<ul>
<li class='keycap' data-alt-keys='foreground-color' data-command='["applyStyle",{"color":"#cc2428"}]'><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":"#fff590"}]'><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='layer-switch font-glyph modifier bottom left' data-layer='symbols'>∞≠</li>
<li class='keycap' data-alt-keys=','>,</li>
<li class='keycap w50' data-key=' ' data-alt-keys='space'> </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='layer-switch font-glyph modifier bottom left' data-layer='symbols'>∞≠</li>
<li class='keycap' data-alt-keys='.'>;</li>
<li class='keycap w50' data-key=' '> </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='set' data-insert='\\in '>∈</li>
<li class='keycap tex' data-alt-keys='!set' data-insert='\\notin '>∉</li>
<li class='keycap tex' data-insert='\\Re '>ℜ<aside>Real</aside></li>
<li class='keycap tex' data-insert='\\Im '>ℑ<aside>Imaginary</aside></li>
<li class='keycap w15' data-insert='\\ulcorner#0\\urcorner '><span><sup>┌</sup><span><span style='color:#ddd'>o</span><sup>┐</sup></span><aside>ceil</aside></li>
<li class='keycap tex' data-alt-keys='nabla' data-insert='\\nabla '>∇<aside>nabla</aside></li>
<li class='keycap tex' data-alt-keys='infinity' data-insert='\\infty '>∞</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 '>⊂</li>
<li class='keycap tex' data-alt-keys='supset' data-insert='\\supset '>⊃</li>
<li class='keycap tex' data-key='!' data-alt-keys='!'>!<aside>factorial</aside></li>
<li class='keycap' data-insert='^{\\prime} '><span><sup><span><span style='color:#ddd'>o</span>′</sup></span><aside>prime</aside></li>
<li class='keycap w15' data-insert='\\llcorner#0\\lrcorner '><span><sub>└</sub><span style='color:#ddd'>o</span><sub>┘</sub></span><aside>floor</aside></li>
<li class='keycap tex' data-insert='\\partial '>∂<aside>partial<br>derivative</aside></li>
<li class='keycap tex' data-insert='\\emptyset '>∅<aside>empty set</aside></li>
</ul>
<ul>
<row name='numpad-3' class='if-wide'/>
<li class='keycap tex' data-alt-keys='(' data-insert='\\langle '>⟨</li>
<li class='keycap tex' data-alt-keys=')' data-insert='\\rangle '>⟩</li>
<li class='separator w5'></li>
<li class='keycap tex' data-insert='\\subseteq '>⊆</li>
<li class='keycap tex' data-insert='\\supseteq '>⊇</li>
<li class='keycap tex' data-alt-keys='accents' data-insert='\\vec{#@}' data-latex='\\vec{#?}' data-aside='vector'></li>
<li class='keycap tex' data-alt-keys='accents' data-insert='\\bar{#@}' data-latex='\\bar{#?}' data-aside='bar'></li>
<li class='keycap tex' data-alt-keys='absnorm' data-insert='\\lvert #@ \\rvert ' data-latex='\\lvert #? \\rvert' data-aside='abs'></li>
<li class='keycap tex' data-insert='\\ast '>∗<aside>asterisk</aside></li>
<li class='action font-glyph bottom right w15'
data-shifted='<span class="warning"><svg><use xlink:href="#svg-trash" /></svg></span>'
data-shifted-command='"deleteAll"'
data-alt-keys='delete' data-command='["performWithFeedback","deletePreviousChar"]'
>⌫</li>
</ul>
<ul>
<row name='numpad-4' class='if-wide'/>
<li class='layer-switch font-glyph modifier bottom left' data-layer='lower-roman'>abc</li>
<li class='keycap tex' data-insert='\\cdot '>⋅<aside>centered dot</aside></li>
<li class='keycap tex' data-insert='\\colon '>:<aside>colon</aside></li>
<li class='keycap tex' data-insert='\\circ '>∘<aside>circle</aside></li>
<li class='keycap tex' data-insert='\\approx '>≈<aside>approx.</aside></li>
<li class='keycap tex' data-insert='\\ne '>≠</li>
<li class='keycap tex' data-insert='\\pm '>±</li>
<arrows/>
</ul>
</div>`,
'lower-greek': `
<div class='rows'>
<ul><li class='keycap tex' data-insert='\\varphi '><i>φ</i><aside>phi var.</aside></li>
<li class='keycap tex' data-insert='\\varsigma '><i>ς</i><aside>sigma var.</aside></li>
<li class='keycap tex' data-insert='\\epsilon '><i>ϵ</i></li>
<li class='keycap tex' data-insert='\\rho '><i>ρ</i></li>
<li class='keycap tex' data-insert='\\tau '><i>τ</i></li>
<li class='keycap tex' data-insert='\\upsilon '><i>υ</i></li>
<li class='keycap tex' data-insert='\\theta '><i>θ</i></li>
<li class='keycap tex' data-insert='\\iota '><i>ι</i></li>
<li class='keycap tex' data-insert='\\omicron '>ο</i></li>
<li class='keycap tex' data-insert='\\pi '><i>π</i></li>
</ul>
<ul><li class='keycap tex' data-insert='\\alpha ' data-shifted='Α' data-shifted-command='["insert","{\\\\char\\"391}"]'><i>α</i></li>
<li class='keycap tex' data-insert='\\sigma '><i>σ</i></li>
<li class='keycap tex' data-insert='\\delta '><i>δ</i></li>
<li class='keycap tex' data-insert='\\phi '><i>ϕ</i></i></li>
<li class='keycap tex' data-insert='\\gamma '><i>γ</i></li>
<li class='keycap tex' data-insert='\\eta '><i>η</i></li>
<li class='keycap tex' data-insert='\\xi '><i>ξ</i></li>
<li class='keycap tex' data-insert='\\kappa '><i>κ</i></li>
<li class='keycap tex' data-insert='\\lambda '><i>λ</i></li>
</ul>
<ul><li class='shift modifier font-glyph bottom left w15 layer-switch' data-layer='upper-greek'>⇧</li>
<li class='keycap tex' data-insert='\\zeta '><i>ζ</i></li>
<li class='keycap tex' data-insert='\\chi '><i>χ</i></li>
<li class='keycap tex' data-insert='\\psi '><i>ψ</i></li>
<li class='keycap tex' data-insert='\\omega '><i>ω</i></li>
<li class='keycap tex' data-insert='\\beta '><i>β</i></li>
<li class='keycap tex' data-insert='\\nu '><i>ν</i></li>
<li class='keycap tex' data-insert='\\mu '><i>μ</i></li>
<li class='action font-glyph bottom right w15'
data-shifted='<span class="warning"><svg><use xlink:href="#svg-trash" /></svg></span>'
data-shifted-command='"deleteAll"'
data-alt-keys='delete' data-command='["performWithFeedback","deletePreviousChar"]'
>⌫</li>
</ul>
<ul>
<li class='keycap ' data-key=' '> </li>
<li class='keycap'>,</li>
<li class='keycap tex' data-insert='\\varepsilon '><i>ε</i><aside>epsilon var.</aside></li>
<li class='keycap tex' data-insert='\\vartheta '><i>ϑ</i><aside>theta var.</aside></li>
<li class='keycap tex' data-insert='\\varkappa '><i>ϰ</i><aside>kappa var.</aside></li>
<li class='keycap tex' data-insert='\\varpi '><i>ϖ<aside>pi var.</aside></i></li>
<li class='keycap tex' data-insert='\\varrho '><i>ϱ</i><aside>rho var.</aside></li>
<arrows/>
</ul>
</div>`,
'upper-greek': `
<div class='rows'>
<ul><li class='keycap tex' data-insert='\\Phi '>Φ<aside>phi</aside></li>
<li class='keycap tex' data-insert='\\Sigma '>Σ<aside>sigma</aside></li>
<li class='keycap tex' data-insert='{\\char"0190}'>Ɛ<aside>epsilon</aside></li>
<li class='keycap tex' data-insert='{\\char"3A1}'>Ρ<aside>rho</aside></li>
<li class='keycap tex' data-insert='{\\char"3A4}'>Τ<aside>tau</aside></li>
<li class='keycap tex' data-insert='\\Upsilon '>Υ<aside>upsilon</aside></li>
<li class='keycap tex' data-insert='\\Theta '>Θ<aside>theta</aside></li>
<li class='keycap tex' data-insert='{\\char"399}'>Ι<aside>iota</aside></li>
<li class='keycap tex' data-insert='{\\char"39F}'>Ο<aside>omicron</aside></li>
<li class='keycap tex' data-insert='\\Pi '>Π<aside>pi</aside></li></ul>
<ul><li class='keycap tex' data-insert='{\\char"391}'>Α<aside>alpha</aside></li>
<li class='keycap tex' data-insert='\\Sigma '>Σ<aside>sigma</aside></li>
<li class='keycap tex' data-insert='\\Delta '>Δ<aside>delta</aside></li>
<li class='keycap tex' data-insert='\\Phi '>Φ<aside>phi</aside></li>
<li class='keycap tex' data-insert='\\Gamma '>Γ<aside>gamma</aside></li>
<li class='keycap tex' data-insert='{\\char"397}'>Η<aside>eta</aside></li>
<li class='keycap tex' data-insert='\\Xi '>Ξ<aside>xi</aside></li>
<li class='keycap tex' data-insert='{\\char"39A}'>Κ<aside>kappa</aside></li>
<li class='keycap tex' data-insert='\\Lambda '>Λ<aside>lambda</aside></li></ul>
<ul><li class='shift modifier font-glyph bottom left selected w15 layer-switch' data-layer='lower-greek'>⇧</li>
<li class='keycap tex' data-insert='{\\char"396}'>Ζ<aside>zeta</aside></li>
<li class='keycap tex' data-insert='{\\char"3A7}'>Χ<aside>chi</aside></li>
<li class='keycap tex' data-insert='\\Psi '>Ψ<aside>psi</aside></li>
<li class='keycap tex' data-insert='\\Omega '>Ω<aside>omega</aside></li>
<li class='keycap tex' data-insert='{\\char"392}'>Β<aside>beta</aside></li>
<li class='keycap tex' data-insert='{\\char"39D}'>Ν<aside>nu</aside></li>
<li class='keycap tex' data-insert='{\\char"39C}'>Μ<aside>mu</aside></li>
<li class='action font-glyph bottom right w15' data-command='["performWithFeedback","deletePreviousChar"]'>⌫</li></ul>
<ul>
<li class='separator w10'> </li>
<li class='keycap'>.</li>
<li class='keycap w50' data-key=' '> </li>
<arrows/>
</ul>
</div>`,
'lower-command': `
<div class='rows'>
<ul><row name='lower-1' class='tt' shift-layer='upper-command'/></ul>
<ul><row name='lower-2' class='tt' shift-layer='upper-command'/></ul>
<ul><row name='lower-3' class='tt' shift-layer='upper-command'/></ul>
<ul>
<li class='layer-switch font-glyph modifier bottom left' data-layer='symbols-command'>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=' '> </li>
<arrows/>
</ul>
</div>`,
'upper-command': `
<div class='rows'>
<ul><row name='upper-1' class='tt' shift-layer='lower-command'/></ul>
<ul><row name='upper-2' class='tt' shift-layer='lower-command'/></ul>
<ul><row name='upper-3' class='tt' shift-layer='lower-command'/></ul>
<ul>
<li class='layer-switch font-glyph modifier bottom left' data-layer='symbols-command'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=' '> </li>
<arrows/>
</ul>
</div>`,
'symbols-command': `
<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><use xlink:href="#svg-trash" /></svg></span>'
data-shifted-command='"deleteAll"'
data-alt-keys='delete' data-command='["performWithFeedback","deletePreviousChar"]'
>⌫</li>
</ul>
<ul>
<li class='layer-switch font-glyph modifier bottom left' data-layer='lower-command'>abc</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' data-key=' '> </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-insert='\\cos'></li>
<li class='fnbutton' data-insert='\\cos^{-1}'></li>
<li class='fnbutton' data-insert='\\ln_{10}'></li>
<li class='fnbutton' data-insert='10^{#?}'></li>
<li class='bigfnbutton' data-insert='\\operatorname{gcd}(#?)' data-latex='\\operatorname{gcd}()'></li>
<li class='bigfnbutton' data-insert='\\operatorname{floor}(#?)' data-latex='\\operatorname{floor}()'></li>
<li class='bigfnbutton' data-insert='\\sum_{n\\mathop=0}^{\\infty}'></li>
<li class='bigfnbutton' data-insert='\\int_{0}^{\\infty}'></li>
<li class='bigfnbutton' data-insert='\\operatorname{sign}(#?)' data-latex='\\operatorname{sign}()'></li>
</ul>
<ul><li class='separator'></li>
<li class='fnbutton' data-insert='\\tan'></li>
<li class='fnbutton' data-insert='\\tan^{-1}'></li>
<li class='fnbutton' data-insert='\\log_{#?}'></li>
<li class='fnbutton' data-insert='\\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='{\\tiny \\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","deletePreviousChar"]'>⌫</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=' '> </li>
<arrows/>
</ul>
</div>`,
'style': `
<div class='rows'>
<ul>
<li class='keycap' data-alt-keys='foreground-color' data-command='["applyStyle",{"color":"#cc2428"}]'><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":"#fff590"}]'><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":"size3"}]' data-latex='\\scriptsize\\text{small}'></li>
<li class='keycap' data-command='["applyStyle",{"size":"size5"}]' data-latex='\\scriptsize\\text{normal}'></li>
<li class='keycap' data-command='["applyStyle",{"size":"size9"}]' 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, mf) {
// Since we don't have preceding atoms, we'll interpret #@ as a placeholder
latex = latex.replace(/(^|[^\\])#@/g, '$1#?');
const parse = ParserModule.parseTokens(Lexer.tokenize(latex), 'math',
arg, mf.config.macros);
const spans = MathAtom.decompose({
mathstyle: 'displaystyle',
macros: mf.config.macros
}, parse);
const base = Span.makeSpan(spans, 'ML__base');
const topStrut = Span.makeSpan('', 'ML__strut');
topStrut.setStyle('height', base.height, 'em');
const bottomStrut = Span.makeSpan('', 'ML__strut--bottom');
bottomStrut.setStyle('height', base.height + base.depth, 'em');
bottomStrut.setStyle('vertical-align', -base.depth, 'em');
const wrapper = Span.makeSpan([topStrut, bottomStrut, base], 'ML__mathlive');
return wrapper.toMarkup();
}
/**
* Return a markup string for the keyboard toolbar for the specified layer.
* @private
*/
function makeKeyboardToolbar(mf, keyboardIDs, currentKeyboard) {
// The left hand side of the toolbar has a list of all the available keyboards
let result = "<div class='left'>";
const keyboardList = keyboardIDs.replace(/\s+/g, ' ').split(' ');
if (keyboardList.length > 1) {
const keyboards = Object.assign({}, KEYBOARDS, mf.config.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-tooltip='" + l10n(keyboards[keyboard].tooltip) + "' ";
result += "data-placement='top' data-delay='1s'";
}
if (keyboard !== currentKeyboard) {
if (keyboards[keyboard].command) {
result += "data-command='\"" + keyboards[keyboard].command + "\"'";
}
if (keyboards[keyboard].layer) {
result += "data-layer='" + keyboards[keyboard].layer + "'";
}
}
result += '>' + keyboards[keyboard].label + '</div>';
}
}
result += '</div>';
// The right hand side of the toolbar, with the copy/undo/redo commands
result += `
<div class='right'>
<div class='action'
data-command='"copyToClipboard"'
data-tooltip='${l10n('tooltip.copy to clipboard')}' data-placement='top' data-delay='1s'>
<svg><use xlink:href='#svg-copy' /></svg>
</div>
<div class='action disabled'
data-command='"undo"'
data-tooltip='${l10n('tooltip.undo')}' data-placement='top' data-delay='1s'>
<svg><use xlink:href='#svg-undo' /></svg>
</div>
<div class='action disabled'
data-command='"redo"'
data-tooltip='${l10n('tooltip.redo')}' data-placement='top' data-delay='1s'>
<svg><use xlink:href='#svg-redo' /></svg>
</div>
</div>
`;
return "<div class='keyboard-toolbar' role='toolbar'>" + result + "</div>";
}
function makeKeycap(mf, elList, chainedCommand) {
for (let i = 0; i < elList.length; ++i) {
const el = elList[i];
// Display
if (el.getAttribute('data-latex')) {
el.innerHTML = latexToMarkup(el.getAttribute('data-latex').replace(/"/g, '"'),
{'?':'{\\color{#555}{\\tiny \\char"2B1A}}'}, mf);
} else if (el.innerHTML === '' && el.getAttribute('data-insert')) {
el.innerHTML = latexToMarkup(el.getAttribute('data-insert').replace(/"/g, '"'),
{'?':'{\\color{#555}{\\tiny \\char"2B1A}}'}, mf);
} else if (el.getAttribute('data-content')) {
el.innerHTML = el.getAttribute('data-content').replace(/"/g, '"');
}
if (el.getAttribute('data-aside')) {
el.innerHTML += '<aside>' + el.getAttribute('data-aside').replace(/"/g, '"') + '</aside>';
}
if (el.getAttribute('data-classes')) {
el.classList.add(el.getAttribute('data-classes'));
}
let key = el.getAttribute('data-insert');
if (key) key = key.replace(/"/g, '"');
if (key && SHIFTED_KEYS[key]) {
el.setAttribute('data-shifted', SHIFTED_KEYS[key].label);
el.setAttribute('data-shifted-command',
JSON.stringify(['insertAndUnshiftKeyboardLayer', SHIFTED_KEYS[key].insert]));
}
// Commands
let handlers;
if (el.getAttribute('data-command')) {
handlers = JSON.parse(el.getAttribute('data-command'));
} else if (el.getAttribute('data-insert')) {
handlers = ['insert', el.getAttribute('data-insert'),
{focus:true, feedback:true, mode:'math', format:'auto', resetStyle:true}];
} else if (el.getAttribute('data-latex')) {
handlers = ['insert', el.getAttribute('data-latex'),
{focus:true, feedback:true, mode:'math', format:'auto', resetStyle:true}];
} else {
handlers = ['typedText', el.getAttribute('data-key') || el.textContent,
{focus:true, feedback:true, simulateKeystroke:true}];
}
if (chainedCommand) {
handlers = [chainedCommand, handlers];
}
if (el.getAttribute('data-alt-keys')) {
const altKeys = ALT_KEYS[el.getAttribute('data-alt-keys')];
if (altKeys) {
handlers = {
default: handlers,
pressAndHoldStart: ['showAlternateKeys', el.getAttribute('data-alt-keys'), altKeys],
pressAndHoldEnd: 'hideAlternateKeys'
}
} else {
console.warn('Unknown alt key set: "' + el.getAttribute('data-alt-keys'));
}
}
mf._attachButtonHandlers(el, handlers);
}
}
/**
* Expand the shortcut tags (e.g. <row>) inside a layer.
* @param {object} mf
* @param {string} layer
* @private
*/
function expandLayerMarkup(mf, layer) {
const ROWS = {
// First row should be 10 key wide
// Second row should be 10 key wide
// Third row should be 8.5 key wide
// One row should have ^ (shift key) which is 1.5 key wide
// One row should have ~ (delete key) which is .5 or 1.5 key wide
'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~'
},
}
const layout = ROWS[mf.config.virtualKeyboardLayout] ?
ROWS[mf.config.virtualKeyboardLayout] : ROWS['qwerty'];
let result = layer;
let row;
result = result.replace(/<arrows\/>/g, `
<li class='action' data-command='["performWithFeedback","moveToPreviousChar"]'
data-shifted='<svg><use xlink:href="#svg-angle-double-left" /></svg>'
data-shifted-command='["performWithFeedback","extendToPreviousChar"]'>
<svg><use xlink:href='#svg-arrow-left' /></svg>
</li>
<li class='action' data-command='["performWithFeedback","moveToNextChar"]'
data-shifted='<svg><use xlink:href="#svg-angle-double-right" /></svg>'
data-shifted-command='["performWithFeedback","extendToNextChar"]'>
<svg><use xlink:href='#svg-arrow-right' /></svg>
</li>
<li class='action' data-command='["performWithFeedback","moveToNextPlaceholder"]'>
<svg><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-Z0-9-]*=(['"])(.*?)\1/g);
const attributes = {};
for (const attribute of attributesArray) {
const m2 = attribute.match(/([a-zA-Z][a-zA-Z0-9-]*)=(['"])(.*?)\2/);
attributes[m2[1]] = m2[3];
}
let keys = layout[attributes['name']];
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><use xlink:href="#svg-trash" /></svg></span>'
data-shifted-command='"deleteAll"'
data-alt-keys='delete' data-command='["performWithFeedback","deletePreviousChar"]'
>⌫</li>`;
} else if (c === ' ') {
// Separator
row += "<li class='separator w5'></li>";
} else if (c === '^') {
// Shift key
row += `<li class='shift modifier font-glyph bottom left w15 layer-switch' data-layer='` +
attributes['shift-layer'] + `'>⇧</li>`;
} else if (c === '/') {
row += "<li class='keycap" + cls + "' data-alt-keys='/' data-insert='\\frac{#0}{#?}'>÷</li>";
} else if (c === '*') {
row += "<li class='keycap" + cls + "' data-alt-keys='*' data-insert='\\times '>×</li>";
} else if (c === '-') {
row += "<li class='keycap" + cls + "' data-alt-keys='*' data-key='-' data-alt-keys='-'>−</li>";
} else if (/tt/.test(cls)) {
row += "<li class='keycap" + cls + "' data-alt-keys='" + c + "'" +
` data-command='["typedText","` + c + `",{"commandMode":true, "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.
* @param {obj