diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..212566614 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..fe3c1c22b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +patreon: arnog +custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CCB2LY5M6SM5W&source=url diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index cb4ccf9df..4b4035e1f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,23 +7,35 @@ assignees: '' --- +**Funding** +> If you're using MathLive consider donating to project development via [Patreon](https://patreon.com/arnog) (recurring donation) or [PayPal](https://www.paypal.me/arnogourdol) (one time donation). + +> Issues submitted by funding partners are given higher priority. + +> We welcome both individual and corporate sponsors. In addition to Patreon and PayPal, we can also accept short-term development contracts for specific features or maintenance of the project. + + **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error +[Steps to reproduce the behavior, for example:] +1. [Go to '...'] +2. [Click on '....'] +3. [Scroll down to '....'] +4. [See error] **Expected behavior** -A clear and concise description of what you expected to happen. +[A clear and concise description of what you expected to happen.] [Is this a regression: did it use to work in a previous version?] **Screenshots** -If applicable, add screenshots to help explain your problem. +[If applicable, add screenshots to help explain your problem.] + +**Source Code** +[If applicable, provide a code sample demonstrating the issue. Use JSFiddle, CodePen or similar to provide a relevant snippet.] + **Environment** - Device: [pc, mac, iPhone, Android...] diff --git a/.npmrc b/.npmrc index 033cca1f5..7394de28d 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1 @@ -loglevel="silent" \ No newline at end of file +loglevel="warn" \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 892d6d1a2..b9875fd20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,13 @@ language: node_js node_js: - lts/* +before_install: +- '[[ $(node -v) =~ ^v9.*$ ]] || npm install -g npm@latest' # skipped when using node 9 +- npm install -g greenkeeper-lockfile install: -- npm ci +- npm install +before_script: greenkeeper-lockfile-update +after_script: greenkeeper-lockfile-upload cache: directories: - node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c4e854f6..3630323d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +## 0.30.1 (July 30, 2019) + +### Features / Improvements +- Added Typescript type definition + +## 0.30 (July 18, 2019) + +### Non-backward compatible changes + +- #157: Public APIs that don't start with `$` have been removed. If your code +used any of these APIs, add a `$` in front of their name. See #157 for the +complete list. + +### Features / Improvements + +- #231: `smartMode` now supports Greek (the language). Also, Greek localization. +- Don't display i-beam cursor over non-interactive content +- Use CSS class `.ML__smart-fence__close` to style closing smart fence +- Added speech support for text mode and units (contributed by @NSoiffer) + +### Bug Fixes +- Fixed an issue where clicking past the end of the equation would select the +numerator or denominator if the last element was a fraction, instead of place +the cursor after the fraction (regression) +- Removed dependency on open-cli +- #220 Fixed an issue where tabbing out of a mathfield would break command mode and some functions +- #209, #214, #211 et. al. Improvements to SSML support and karaoke mode contributed by @NSoiffer +- #217 Fixed an issue with parentheses in numerator of fractions +- #212: Fix round-tripping of `\mathbb` +- #194: When using the virtual keyboard, interpolate `#@` +- Fixed an issue where "(" was incorrectly gobbled as argument to a fraction +- Fixed an issue where smartFence off was ignored +- #202: use numeric character references instead of named entities in MathML output + + + ## 0.29.1 (May 19, 2019) ### Bug fixes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45c2ee8bc..171d5a241 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,17 @@ # Contributing to MathLive -There are many ways you can get involved with MathLive. Contributing to -an open source project is fun and rewarding. +There are many ways you can get involved with MathLive. Contributing to an open source project is fun and rewarding. + +## Funding + +If you're using MathLive consider donating to project development via [Patreon](https://patreon.com/arnog) (recurring donation) or [PayPal](https://www.paypal.me/arnogourdol) (one time donation). + +If you are using MathLive in your project, encourage the business partners in your organization to provide financial support of open source projects. + +Funds go to general development, support, and infrastructure costs. + +We welcome both individual and corporate sponsors. In addition to Patreon and PayPal, we can also accept short-term development contracts for specific features or maintenance of the project. + ## Contributing Issues diff --git a/README.md b/README.md index 61e797b74..ee9178aac 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,11 @@ Global changes : MathLive is a JavaScript library to render and edit math. -- [x] Tex-quality typesetting +- [x] TeX-quality typesetting - [x] Easy to use interface for math editing - [x] Fast and small - [x] Works great on desktop and on mobile devices thanks to an extensive set of virtual keyboards -- [x] Outputs **LaTeX**, **MathML** and **JSON** (Abstract Syntax Tree, MASTON)** +- [x] Outputs **LaTeX**, **MathML** and **JSON** (Abstract Syntax Tree, MASTON) - [x] And it is easy to customize to your needs! Try it at [mathlive.io](https://mathlive.io) @@ -90,23 +90,23 @@ Try it at [mathlive.io](https://mathlive.io) ## How To Use MathLive -### To display math -You can use MathLive to simply render math equations by +### Displaying Math +Render math equations by [adding a few lines to your web page](tutorials/USAGE_GUIDE.md). ```html ... - - - + +

Euler's Identity

$$e^{i\pi} + 1 = 0$$

- @@ -114,7 +114,7 @@ You can use MathLive to simply render math equations by ``` -### To edit math +### Editing Math You can also incorporate a “math field” to edit math just like you would edit text. The MathLive APIs allow you to interact with the math field, including extracting its content, inserting placeholders and more. @@ -137,7 +137,7 @@ including extracting its content, inserting placeholders and more. ``` -### More examples +### More Examples More examples are available at https://mathlive.io/deploy/examples/ @@ -160,6 +160,9 @@ This will make a local build of MathLive, run a local HTTP server and open a pag ## How You Can Help +* Using MathLive in your project? Want to support the project ongoing maintenance? +Consider becoming a patron on [Patreon](https://patreon.com/arnog) or making a +donation with [PayPal](https://www.paypal.me/arnogourdol) * Something wrong? Got ideas for new features? Write up an issue. Read about [Contributing](CONTRIBUTING.md) and follow our [Code of Conduct](CODE_OF_CONDUCT.md) * Want to use MathLive in your web page? The [Usage Guide](tutorials/USAGE_GUIDE.md) diff --git a/WELCOME.md b/WELCOME.md index 2985230ac..0bad72068 100644 --- a/WELCOME.md +++ b/WELCOME.md @@ -1,6 +1,6 @@

- +

diff --git a/assets/logo-1024.jpg b/assets/logo-1024.jpg deleted file mode 100644 index d3d4e3945..000000000 Binary files a/assets/logo-1024.jpg and /dev/null differ diff --git a/assets/logo-240.jpg b/assets/logo-240.jpg deleted file mode 100644 index 668b6ff0e..000000000 Binary files a/assets/logo-240.jpg and /dev/null differ diff --git a/assets/logo-240.png b/assets/logo-240.png deleted file mode 100644 index 99bb375af..000000000 Binary files a/assets/logo-240.png and /dev/null differ diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 000000000..af744841d Binary files /dev/null and b/assets/logo.png differ diff --git a/css/mathlive.core.less b/css/mathlive.core.less index eda86d511..772f461b5 100644 --- a/css/mathlive.core.less +++ b/css/mathlive.core.less @@ -33,6 +33,7 @@ .ML__base { display: inline-block; position: relative; + cursor: text; } .ML__strut, .ML__strut--bottom { @@ -171,7 +172,6 @@ body[theme="dark"] .ML__fieldcontainer { line-height: 0; padding: 2px; width: 100%; - cursor: text; } diff --git a/css/mathlive.less b/css/mathlive.less index bebb0c54d..f0d14268e 100755 --- a/css/mathlive.less +++ b/css/mathlive.less @@ -583,6 +583,12 @@ } +/* When using smartFence, the anticipated closing fence is displayed +with this style */ +.ML__smart-fence__close { + opacity: .5; +} + /* The element that displays the keys as the user type them */ .ML__keystroke-caption { visibility: hidden; diff --git a/dist/addons/debug.js b/dist/addons/debug.js index 900a0b3ec..196d06d9b 100644 --- a/dist/addons/debug.js +++ b/dist/addons/debug.js @@ -184,7 +184,7 @@ function spanToString(span, indent) { if (span.style) { for (var s in span.style) { - if (span.style.hasOwnProperty(s)) { + if (Object.prototype.hasOwnProperty.call(span.style, s)) { result += indent + s + ':"'; result += span.style[s] + '",\n'; } @@ -304,7 +304,7 @@ function spanToMarkup(span, indent) { if (span.style) { for (var s in span.style) { - if (span.style.hasOwnProperty(s)) { + if (Object.prototype.hasOwnProperty.call(span.style, s)) { result += ' ' + s + ':'; result += ' ' + span.style[s] + '; '; } diff --git a/dist/addons/maston.js b/dist/addons/maston.js index 575b0b380..fa5917a3b 100644 --- a/dist/addons/maston.js +++ b/dist/addons/maston.js @@ -431,6 +431,7 @@ function getArg(ast, index) { * Given a canonical name, return its precedence * @param {string} canonicalName, for example "and" * @return {number} + * @private */ @@ -449,6 +450,7 @@ function getAssociativity(canonicalName) { * * @param {string} name function canonical name * @return {string} + * @private */ @@ -465,6 +467,7 @@ function getLatexTemplateForFunction(name) { * * @param {string} name symbol name, e.g. "alpha" * @return {string} + * @private */ @@ -502,6 +505,7 @@ function isFunction(canonicalName) { * * @param {string} latex, for example '\\times' * @return {string} the canonical name for the input, for example '*' + * @private */ @@ -530,6 +534,7 @@ function getCanonicalName(latex) { * or -1 if not an operator * @param {object} atom * @return {number} + * @private */ @@ -684,6 +689,7 @@ function getString(atom) { * * @param {object} expr - Abstract Syntax Tree object * @return {string} A string, the symbol, or undefined + * @private */ @@ -695,6 +701,7 @@ function asSymbol(node) { * @param {object} node - Abstract Syntax Tree node * @return {number} A JavaScript number, the value of the AST or NaN * @private + * @private */ @@ -748,6 +755,7 @@ function hasSub(node) { * @param {object} expr * @param {string} type * @param {string} value + * @private */ @@ -769,6 +777,7 @@ function isAtom(expr, type, value) { * * @param {string} functionName * @param {object} params + * @private */ @@ -818,6 +827,7 @@ function wrapNum(num) { * Return the negative of the expression. Usually { fn:'negate', arg } * but for numbers, the negated number * @param {object} node + * @private */ @@ -851,6 +861,7 @@ function nextIsSupsub(expr) { * or to a following 'msubsup' atom. * After the call, the index points to the next atom to process. * @param {object} expr + * @private */ @@ -920,6 +931,7 @@ function parseSupsub(expr, options) { } /** * Parse postfix operators, such as "!" (factorial) + * @private */ @@ -971,6 +983,7 @@ function parsePostfix(expr, options) { * * This function handles all three cases * + * @private */ @@ -1138,6 +1151,7 @@ function nextIsDigraph(expr, digraph) { * =: * °C U+2103 * °F U+2109 + * @private * */ @@ -1654,6 +1668,7 @@ function parseExpression(expr, options) { * Return a string escaped as necessary to comply with the JSON format * @param {string} s * @return {string} + * @private */ @@ -1665,6 +1680,7 @@ function escapeText(s) { * * @return {object} * @method MathAtom#toAST + * @private */ @@ -1805,7 +1821,7 @@ _mathAtom.default.MathAtom.prototype.toAST = function (options) { case 'enclose': // result = '(.*)<\/mo>$/.test(mathML)) { - mathML = '' + mathML; // INVISIBLE TIMES + mathML = '' + mathML; } if (body.endsWith('>f') || body.endsWith('>g')) { - mathML += ''; // APPLY FUNCTION - + mathML += ''; stream.lastType = 'applyfunction'; } else { stream.lastType = /^(.*)<\/mo>$/.test(mathML) ? 'mo' : 'mi'; @@ -151,6 +150,7 @@ function scanIdentifier(stream, final, options) { * Superscripts can be encoded either as an attribute on the last atom * or as a standalone, empty, atom following the one to which it applies. * @param {object} stream + * @private */ @@ -317,7 +317,7 @@ function scanFence(stream, final, options) { mathML += ''; if (stream.lastType === 'mi' || stream.lastType === 'mn' || stream.lastType === 'mfrac' || stream.lastType === 'fence') { - mathML = '' + mathML; // INVISIBLE TIMES + mathML = '' + mathML; } stream.index = closeIndex + 1; @@ -406,7 +406,7 @@ function scanOperator(stream, final, options) { if ((stream.lastType === 'mi' || stream.lastType === 'mn') && !/^(.*)<\/mo>$/.test(mathML)) { - mathML = '' + mathML; // INVISIBLE TIMES + mathML = '' + mathML; } stream.index += 1; @@ -538,6 +538,7 @@ function toString(atoms) { * Return a MathML fragment representation of a single atom * * @return {string} + * @private */ @@ -776,26 +777,29 @@ _mathAtom.default.MathAtom.prototype.toMathML = function (options) { break; case 'mord': - result = SPECIAL_IDENTIFIERS[command] || command || (typeof this.body === 'string' ? this.body : ''); - m = command ? command.match(/[{]?\\char"([0-9abcdefABCDEF]*)[}]?/) : null; - - if (m) { - // It's a \char command - result = '&#x' + m[1] + ';'; - } else if (result.length > 0 && result.charAt(0) === '\\') { - // This is an identifier with no special handling. Use the - // Unicode value - if (typeof this.body === 'string' && this.body.charCodeAt(0) > 255) { - result = '&#x' + ('000000' + this.body.charCodeAt(0).toString(16)).substr(-4) + ';'; - } else if (typeof this.body === 'string') { - result = this.body.charAt(0); - } else { - result = this.body; + { + result = SPECIAL_IDENTIFIERS[command] || command || (typeof this.body === 'string' ? this.body : ''); + m = command ? command.match(/[{]?\\char"([0-9abcdefABCDEF]*)[}]?/) : null; + + if (m) { + // It's a \char command + result = '&#x' + m[1] + ';'; + } else if (result.length > 0 && result.charAt(0) === '\\') { + // This is an identifier with no special handling. Use the + // Unicode value + if (typeof this.body === 'string' && this.body.charCodeAt(0) > 255) { + result = '&#x' + ('000000' + this.body.charCodeAt(0).toString(16)).substr(-4) + ';'; + } else if (typeof this.body === 'string') { + result = this.body.charAt(0); + } else { + result = this.body; + } } - } - result = '' + xmlEscape(result) + ''; - break; + var tag = /\d/.test(result) ? 'mn' : 'mi'; + result = '<' + tag + variant + makeID(this.id, options) + '>' + xmlEscape(result) + ''; + break; + } case 'mbin': case 'mrel': @@ -861,7 +865,7 @@ _mathAtom.default.MathAtom.prototype.toMathML = function (options) { result = 'a', + 'A': 'capital A', '+': 'plus ', '-': 'minus ', ';': ' semi-colon ', @@ -620,21 +622,19 @@ _mathAtom.default.toSpeakableFragment = function (atom, options) { return result; }; /** - * @param {MathAtom[]} [atoms] The atoms to represent as speakable text. + * @param {MathAtom[]} atoms The atoms to represent as speakable text. * If omitted, `this` is used. - * @param {Object.} [options] + * @param {Object.} speechOptions + * @private */ -_mathAtom.default.toSpeakableText = function (atoms, options) { - if (!options) { - options = { - textToSpeechMarkup: '', - // no markup - textToSpeechRules: 'mathlive' - }; - } - +_mathAtom.default.toSpeakableText = function (atoms, speechOptions) { + var options = speechOptions ? JSON.parse(JSON.stringify(speechOptions)) : { + textToSpeechMarkup: '', + // no markup + textToSpeechRules: 'mathlive' + }; options.speechMode = 'math'; if (window.sre && options.textToSpeechRules === 'sre') { @@ -661,7 +661,7 @@ _mathAtom.default.toSpeakableText = function (atoms, options) { return window.sre.System.getInstance().toSpeech(mathML); } - return ''; // return window.sre.toSpeech(MathAtom.toMathML(atoms)); + return ''; } var result = _mathAtom.default.toSpeakableFragment(atoms, options); diff --git a/dist/core/color.js b/dist/core/color.js index 5662be60d..f26dc969d 100644 --- a/dist/core/color.js +++ b/dist/core/color.js @@ -24,6 +24,7 @@ exports.default = void 0; * @constant * @type {Object.} * @memberof module:color + * @private */ var MATHEMATICA_COLORS = { 'm0': '#3f3d99', @@ -109,6 +110,7 @@ var LINE_COLORS = ['#cc2428', // red * @constant NAMED_COLORS * @memberof module:color * @type {Object.} + * @private */ var NAMED_COLORS = { diff --git a/dist/core/definitions.js b/dist/core/definitions.js index faffb7712..9ca6e09f0 100644 --- a/dist/core/definitions.js +++ b/dist/core/definitions.js @@ -251,7 +251,7 @@ function matchCodepoint(parseMode, cp) { if (parseMode === 'math') { for (var p in MATH_SYMBOLS) { - if (MATH_SYMBOLS.hasOwnProperty(p)) { + if (Object.prototype.hasOwnProperty.call(MATH_SYMBOLS, p)) { if (MATH_SYMBOLS[p].value === s) { result = p; break; @@ -260,7 +260,7 @@ function matchCodepoint(parseMode, cp) { } } else { for (var _p in TEXT_SYMBOLS) { - if (TEXT_SYMBOLS.hasOwnProperty(_p)) { + if (Object.prototype.hasOwnProperty.call(TEXT_SYMBOLS, _p)) { if (TEXT_SYMBOLS[_p] === s) { result = _p; break; @@ -544,7 +544,7 @@ function unicodeToMathVariant(char) { for (var c in MATH_LETTER_EXCEPTIONS) { - if (MATH_LETTER_EXCEPTIONS.hasOwnProperty(c)) { + if (Object.prototype.hasOwnProperty.call(MATH_LETTER_EXCEPTIONS, c)) { if (MATH_LETTER_EXCEPTIONS[c] === codepoint) { codepoint = c; break; @@ -571,6 +571,8 @@ function unicodeToMathVariant(char) { * return the corresponding unicode character (a string) * @param {string} char * @param {string} variant + * @memberof module:definitions + * @private */ @@ -808,7 +810,7 @@ function suggest(s) { var result = []; // Iterate over items in the dictionary for (var p in FUNCTIONS) { - if (FUNCTIONS.hasOwnProperty(p)) { + if (Object.prototype.hasOwnProperty.call(FUNCTIONS, p)) { if (p.startsWith(s) && !FUNCTIONS[p].infix) { result.push({ match: p, @@ -819,7 +821,7 @@ function suggest(s) { } for (var _p2 in MATH_SYMBOLS) { - if (MATH_SYMBOLS.hasOwnProperty(_p2)) { + if (Object.prototype.hasOwnProperty.call(MATH_SYMBOLS, _p2)) { if (_p2.startsWith(s)) { result.push({ match: _p2, @@ -3300,6 +3302,8 @@ defineFunction(['\\hspace', '\\hspace*' // \hspace* inserts a non-breakable spac * If possible, i.e. if they are all simple atoms, return a string made up of * their body * @param {object[]} atoms + * @memberof module:definitions + * @private */ function getSimpleString(atoms) { diff --git a/dist/core/delimiters.js b/dist/core/delimiters.js index 11f4f69b0..77c67b823 100755 --- a/dist/core/delimiters.js +++ b/dist/core/delimiters.js @@ -351,7 +351,7 @@ function makeSizedDelim(type, delim, size, context, classes) { console.assert(false, 'Unknown delimiter \'' + delim + '\''); return null; } -/** +/* * There are three different sequences of delimiter sizes that the delimiters * follow depending on the kind of delimiter. This is used when creating custom * sized delimiters to decide whether to create a small, large, or stacked @@ -426,7 +426,7 @@ var stackLargeDelimiterSequence = [{ }, { type: 'stack' }]; -/** +/* * Get the font used in a delimiter based on what kind of delimiter it is. */ diff --git a/dist/core/fontMetrics.js b/dist/core/fontMetrics.js index c4c6333b7..b995c42ef 100755 --- a/dist/core/fontMetrics.js +++ b/dist/core/fontMetrics.js @@ -31,7 +31,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de // - Hangul syllables: [\uAC00-\uD7AF] // Notably missing are half width Katakana and Romaji glyphs. var cjkRegex = /[\u3040-\u309F]|[\u30A0-\u30FF]|[\u4E00-\u9FAF]|[\uAC00-\uD7AF]/; -/** +/* * * In TeX, there are actually three sets of dimensions, one for each of * textstyle, scriptstyle, and scriptscriptstyle. These are provided in the @@ -132,7 +132,7 @@ var ptPerEm = 10.0; // The space between adjacent `|` columns in an array defini // article.cls.txt:455 var doubleRuleSep = 2.0 / ptPerEm; -/** +/* * This is just a mapping from common names to real metrics */ @@ -311,6 +311,7 @@ var getCharacterMetrics = function getCharacterMetrics(character, fontCode) { * with a unit, which will override the `unit` paramter * @param {string} unit * @param {number} precision + * @private */ diff --git a/dist/core/lexer.js b/dist/core/lexer.js index 623b37e6e..ac965e1dd 100644 --- a/dist/core/lexer.js +++ b/dist/core/lexer.js @@ -78,6 +78,7 @@ function () { * Return the next char and advance * @return {string} * @method module:core/lexer#Lexer#get + * @private */ }, { diff --git a/dist/core/mathAtom.js b/dist/core/mathAtom.js index 8b33013fa..0389c0060 100644 --- a/dist/core/mathAtom.js +++ b/dist/core/mathAtom.js @@ -129,8 +129,7 @@ var SIZING_MULTIPLIER = { * right after this element, it automatically moves to the last position * inside this element. * - * @class module:core/mathatom#MathAtom - * @global + * @class * @private */ @@ -554,6 +553,7 @@ function () { * * @return {MathAtom[]} * @method MathAtom#filter + * @private */ }, { @@ -976,6 +976,7 @@ function () { * also be set to 'auto', which indicates it should use the current mathstyle * * @method MathAtom#decomposeGenfrac + * @private */ }, { @@ -1116,7 +1117,7 @@ function () { leftDelim.applyStyle(this.getStyle()); rightDelim.applyStyle(this.getStyle()); var result = makeOrd([leftDelim, frac, rightDelim], context.parentSize !== context.size ? 'sizing reset-' + context.parentSize + ' ' + context.size : ''); - return result; + return this.bind(context, result); } /** * \left....\right @@ -1126,7 +1127,8 @@ function () { * leftDelim (resp. rightDelim) will be undefined. We still need to handle * those cases. * - * @method MathAtom#decomposeLeftright + * @method MathAtom#decomposeLeftright + * @private */ }, { @@ -1263,7 +1265,7 @@ function () { var body = makeVlist(context, [inner, lineClearance, line, ruleWidth]); if (!this.index) { - return makeOrd([delim, body], 'sqrt'); + return this.bind(context, makeOrd([delim, body], 'sqrt')); } // Handle the optional root index // The index is always in scriptscript style @@ -1282,7 +1284,7 @@ function () { var rootVlist = makeVlist(context, [root], 'shift', -toShift); // Add a class surrounding it so we can add on the appropriate // kerning - return makeOrd([makeSpan(rootVlist, 'root'), delim, body], 'sqrt'); + return this.bind(context, makeOrd([makeSpan(rootVlist, 'root'), delim, body], 'sqrt')); } }, { key: "decomposeAccent", @@ -1335,6 +1337,7 @@ function () { * \overline and \underline * * @method MathAtom#decomposeLine + * @private */ }, { @@ -1381,6 +1384,7 @@ function () { * \rule * @memberof MathAtom * @instance + * @private */ }, { @@ -1672,6 +1676,7 @@ function () { * calculate the placement of the supsub * @return {Span[]} * @method MathAtom#decompose + * @private */ }, { @@ -1935,6 +1940,7 @@ function () { * @param {Context} context * @param {Span} span * @method MathAtom#bind + * @private */ }, { @@ -1959,6 +1965,7 @@ function () { * @param {(string|Span[])} body * @return {Span} * @method MathAtom#makeSpan + * @private */ }, { diff --git a/dist/core/mathstyle.js b/dist/core/mathstyle.js index e6b43bd68..cb27c6833 100755 --- a/dist/core/mathstyle.js +++ b/dist/core/mathstyle.js @@ -21,7 +21,7 @@ var metrics = [{}, {}, {}]; var i; for (var key in _fontMetrics.SIGMAS) { - if (_fontMetrics.SIGMAS.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(_fontMetrics.SIGMAS, key)) { for (i = 0; i < 3; i++) { metrics[i][key] = _fontMetrics.SIGMAS[key][i]; } diff --git a/dist/core/parser.js b/dist/core/parser.js index 3bab51f01..869946304 100644 --- a/dist/core/parser.js +++ b/dist/core/parser.js @@ -17,7 +17,9 @@ var _mathAtom = _interopRequireDefault(require("./mathAtom.js")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } @@ -128,7 +130,8 @@ function () { /** * Return the last atom of the math list * If there isn't one, insert a `msubsup` and return it. - * @method Parser#lastMathAtom + * @method module:core/parser#Parser#lastMathAtom + * @private */ }, { @@ -151,6 +154,7 @@ function () { * @param {string} type * @return {boolean} True if the next token is of the specified type * @method module:core/parser#Parser#hasToken + * @private */ }, { @@ -165,6 +169,7 @@ function () { * specified value. If `value` is empty, return true if the token is of type * `'literal'` * @method Parser#hasLiteral + * @private */ }, { @@ -178,6 +183,7 @@ function () { * @return {boolean} True if the next token is of type `'literal` and matches * the specified regular expression pattern. * @method module:core/parser#Parser#hasLiteralPattern + * @private */ }, { @@ -227,7 +233,7 @@ function () { return false; } - /** + /* * Return the appropriate value for a placeholder, either a default * one, or if a value was provided for #? via args, that value. */ @@ -272,6 +278,7 @@ function () { /** * @param {string} type * @method module:core/parser#Parser#parseToken + * @private */ }, { @@ -1412,17 +1419,17 @@ function () { if (explicitGroup) { // Create a temporary style var saveStyle = this.style; - this.style = _objectSpread({}, this.style, attributes); + this.style = _objectSpread({}, this.style, {}, attributes); result = this.scanArg(explicitGroup); this.style = saveStyle; } else { // Merge the new style info with the current style - this.style = _objectSpread({}, this.style, attributes); + this.style = _objectSpread({}, this.style, {}, attributes); } this.parseMode = savedMode; } else { - result = new MathAtom(this.parseMode, info.type, explicitGroup ? this.scanArg(explicitGroup) : null, _objectSpread({}, this.style, attributes)); + result = new MathAtom(this.parseMode, info.type, explicitGroup ? this.scanArg(explicitGroup) : null, _objectSpread({}, this.style, {}, attributes)); } } else { var style = _objectSpread({}, this.style); @@ -1515,7 +1522,7 @@ function () { return result; } - /** + /* * Attempt to scan the macro name and return an atom list if successful. * Otherwise, it wasn't a macro. */ diff --git a/dist/core/span.js b/dist/core/span.js index 3f7018e69..b0d0d47f4 100644 --- a/dist/core/span.js +++ b/dist/core/span.js @@ -182,6 +182,7 @@ function () { * - fontSize: 'size1', 'size2'... * - color: * - background: + * @private */ }, { @@ -356,6 +357,7 @@ function () { /** * * @param {number} left + * @private */ }, { @@ -369,6 +371,7 @@ function () { /** * * @param {number} right + * @private */ }, { @@ -501,7 +504,7 @@ function () { if (this.attributes) { for (var attribute in this.attributes) { - if (this.attributes.hasOwnProperty(attribute)) { + if (Object.prototype.hasOwnProperty.call(this.attributes, attribute)) { result += ' ' + attribute + '="' + this.attributes[attribute] + '"'; } } @@ -561,7 +564,7 @@ function () { var isSelected = /ML__selected/.test(this.classes); for (var style in this.style) { - if (this.style.hasOwnProperty(style)) { + if (Object.prototype.hasOwnProperty.call(this.style, style)) { // Render the style property, except the background // of selected spans if (style !== 'background-color' || !isSelected) { @@ -681,7 +684,7 @@ function () { if (this.style && span.style) { for (var style in this.style) { - if (this.style.hasOwnProperty(style) && span.style.hasOwnProperty(style)) { + if (Object.prototype.hasOwnProperty.call(this.style, style) && Object.prototype.hasOwnProperty.call(span.style, style)) { if (this.style[style] !== span.style[style]) return false; } } @@ -1035,6 +1038,7 @@ function makeStyleWrap(type, children, fromStyle, toStyle, classes) { * * @param {Span} body * @param {string} svgMarkup + * @private */ @@ -1312,7 +1316,7 @@ var FONT_CLASS = { * @param {(string|Span[])} symbol the character for which we're seeking the font * @param {string} fontFamily such as 'mathbf', 'mathfrak', etc... * @return {string} a font name - * @memberof module:mathAtom + * @memberof module:span * @private */ diff --git a/dist/editor/editor-editableMathlist.js b/dist/editor/editor-editableMathlist.js index 6fb2b677c..ee458cc19 100644 --- a/dist/editor/editor-editableMathlist.js +++ b/dist/editor/editor-editableMathlist.js @@ -30,7 +30,9 @@ function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } @@ -50,7 +52,7 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope * ``` * * @param {Object.} config - * @param {Element} target - A target object passed as the first argument of + * @param {HTMLElement} target - A target object passed as the first argument of * callback functions. Typically, a MathField. * @property {MathAtom[]} root - The root element of the math expression. * @property {Object[]} path - The path to the element that is the @@ -141,6 +143,8 @@ EditableMathlist.prototype.filter = function (cb, dir) { /** * Enumerator * @param {function} cb - A callback called for each atom in the mathlist. + * @method EditableMathlist#forEach + * @private */ @@ -151,6 +155,8 @@ EditableMathlist.prototype.forEach = function (cb) { * * @param {function} cb - A callback called for each selected atom in the * mathlist. + * @method EditableMathlist#forEachSelected + * @private */ @@ -192,6 +198,7 @@ EditableMathlist.prototype.toString = function () { /** * When changing the selection, if the former selection is an empty list, * insert a placeholder if necessary. For example, if in an empty numerator. + * @private */ @@ -253,6 +260,8 @@ EditableMathlist.prototype.contentDidChange = function () { * @param {string|Array} selection * @param {number} extent the length of the selection * @return {boolean} true if the path has actually changed + * @method EditableMathlist#setPath + * @private */ @@ -325,7 +334,7 @@ EditableMathlist.prototype.wordBoundary = function (path, dir) { iter.path[iter.path.length - 1].offset += i; return iter.path; }; -/** +/* * Calculates the offset of the "next word". * This is inspired by the behavior of text editors on macOS, namely: blue yellow @@ -503,6 +512,7 @@ EditableMathlist.prototype.setRange = function (from, to, options) { * @param {MathAtom[][]} array * @param {object} rowCol * @return {number} + * @private */ @@ -525,6 +535,7 @@ function arrayIndex(array, rowCol) { * @return {object} * - row: number * - col: number + * @private */ @@ -558,6 +569,7 @@ function arrayColRow(array, index) { * * @param {MathAtom[][]} array * @param {number|string|object} colrow + * @private */ @@ -579,6 +591,7 @@ function arrayCell(array, colrow) { /** * Total numbers of cells (include sparse cells) in the array. * @param {MathAtom[][]} array + * @private */ @@ -620,6 +633,7 @@ function arrayCellCount(array) { * @param {string} separator * @param {object} style * @return {MathAtom[]} + * @private */ @@ -674,6 +688,7 @@ function arrayJoinColumns(row, separator, style) { * @param {strings} separators * @param {object} style * @return {MathAtom[]} + * @private */ @@ -719,6 +734,7 @@ function arrayJoinRows(array, separators, style) { * @param {MathAtom} array * @param {number} col * @return {number} + * @private */ @@ -750,6 +766,7 @@ function arrayColumnCellCount(array, col) { * Remove the indicated column from the array * @param {MathAtom} array * @param {number} col + * @private */ @@ -768,6 +785,7 @@ function arrayRemoveColumn(array, col) { * Remove the indicated row from the array * @param {MathAtom} atom * @param {number} row + * @private */ @@ -778,6 +796,7 @@ function arrayRemoveRow(array, row) { * Return the first non-empty cell, row by row * @param {MathAtom[][]} array * @return {string} + * @private */ @@ -800,6 +819,7 @@ function arrayFirstCellByRow(array) { * @param {MathAtom[][]} array * @param {object} colRow * @param {number} dir + * @private */ @@ -992,6 +1012,7 @@ EditableMathlist.prototype.sibling = function (offset) { /** * @return {boolean} True if the selection is an insertion point. * @method EditableMathlist#isCollapsed + * @private */ @@ -1024,6 +1045,7 @@ EditableMathlist.prototype.collapseBackward = function () { * Return true if the atom could be a part of a number * i.e. "-12354.568" * @param {object} atom + * @private */ @@ -1037,6 +1059,7 @@ function isNumber(atom) { * the selection is a superscript or subscript, the group is the supsub. * When the selection is in a text zone, the "group" is a word. * @method EditableMathlist#selectGroup_ + * @private */ @@ -1091,6 +1114,7 @@ EditableMathlist.prototype.selectGroup_ = function () { /** * Select all the atoms in the math field. * @method EditableMathlist#selectAll_ + * @private */ @@ -1104,6 +1128,7 @@ EditableMathlist.prototype.selectAll_ = function () { /** * Delete everything in the field * @method EditableMathlist#deleteAll_ + * @private */ @@ -2153,6 +2178,7 @@ EditableMathlist.prototype.leap = function (dir, callHandler) { } } + this.suppressChangeNotifications = savedSuppressChangeNotifications; return false; } // Set the selection to the next placeholder @@ -2236,7 +2262,7 @@ function removeParen(list) { return list; } -/** +/* * If it's a fraction with a parenthesized numerator or denominator * remove the parentheses * */ @@ -2253,7 +2279,7 @@ EditableMathlist.prototype.simplifyParen = function (atoms) { var genFracIndex = 0; var nonGenFracCount = 0; - for (var j = 0; atoms[i].body; j++) { + for (var j = 0; atoms[i].body[j]; j++) { if (atoms[i].body[j].type === 'genfrac') { genFracCount++; genFracIndex = j; @@ -2352,13 +2378,13 @@ function applyStyleToUnstyledAtoms(atom, style) { /** * @param {string} s * @param {Object.} options - * @param {string} options.insertionMode - + * @param {"replaceSelection"|"replaceAll"|"insertBefore"|"insertAfter"} options.insertionMode - * * 'replaceSelection' (default) * * 'replaceAll' * * 'insertBefore' * * 'insertAfter' * - * @param {string} options.selectionMode - Describes where the selection + * @param {"placeholder"|"after"|"before"} options.selectionMode - Describes where the selection * will be after the insertion: * * `'placeholder'`: the selection will be the first available placeholder * in the item that has been inserted) (default) @@ -2370,12 +2396,12 @@ function applyStyleToUnstyledAtoms(atom, style) { * * @param {string} options.placeholder - The placeholder string, if necessary * - * @param {string} options.format - The format of the string `s`: + * @param {"auto"|"latex"} options.format - The format of the string `s`: * * `'auto'`: the string is interpreted as a latex fragment or command or * ASCIIMath (default) * * `'latex'`: the string is interpreted strictly as a latex fragment * - * @param {string} options.smartFence - If true, promote plain fences, e.g. `(`, + * @param {boolean} options.smartFence - If true, promote plain fences, e.g. `(`, * as `\left...\right` or `\mleft...\mright` * * @param {boolean} options.suppressChangeNotifications - If true, the @@ -2385,6 +2411,7 @@ function applyStyleToUnstyledAtoms(atom, style) { * @param {object} options.style * * @method EditableMathlist#insert + * @private */ @@ -2606,6 +2633,7 @@ EditableMathlist.prototype.insert = function (s, options) { * @param {string} fence * @param {object} style * @return {boolean} + * @private */ @@ -2668,12 +2696,9 @@ EditableMathlist.prototype._insertSmartFence = function (fence, style) { var lDelim; - - for (var delim in _definitions.default.RIGHT_DELIM) { - if (_definitions.default.RIGHT_DELIM.hasOwnProperty(delim)) { - if (fence === _definitions.default.RIGHT_DELIM[delim]) lDelim = delim; - } - } + Object.keys(_definitions.default.RIGHT_DELIM).forEach(function (delim) { + if (fence === _definitions.default.RIGHT_DELIM[delim]) lDelim = delim; + }); if (lDelim) { // We found the matching open fence, so it was a valid close fence. @@ -2820,6 +2845,7 @@ EditableMathlist.prototype._deleteAtoms = function (count) { /** * Delete multiple characters * @method EditableMathlist#delete + * @private */ @@ -2847,6 +2873,7 @@ EditableMathlist.prototype.delete = function (count) { * If dir = 0, delete only if the selection is not collapsed * @method EditableMathlist#delete_ * @instance + * @private */ @@ -3040,6 +3067,7 @@ EditableMathlist.prototype.delete_ = function (dir) { }; /** * @method EditableMathlist#moveToNextPlaceholder_ + * @private */ @@ -3048,6 +3076,7 @@ EditableMathlist.prototype.moveToNextPlaceholder_ = function () { }; /** * @method EditableMathlist#moveToPreviousPlaceholder_ + * @private */ @@ -3056,6 +3085,7 @@ EditableMathlist.prototype.moveToPreviousPlaceholder_ = function () { }; /** * @method EditableMathlist#moveToNextChar_ + * @private */ @@ -3064,6 +3094,7 @@ EditableMathlist.prototype.moveToNextChar_ = function () { }; /** * @method EditableMathlist#moveToPreviousChar_ + * @private */ @@ -3072,6 +3103,7 @@ EditableMathlist.prototype.moveToPreviousChar_ = function () { }; /** * @method EditableMathlist#moveUp_ + * @private */ @@ -3080,6 +3112,7 @@ EditableMathlist.prototype.moveUp_ = function () { }; /** * @method EditableMathlist#moveDown_ + * @private */ @@ -3088,6 +3121,7 @@ EditableMathlist.prototype.moveDown_ = function () { }; /** * @method EditableMathlist#moveToNextWord_ + * @private */ @@ -3096,6 +3130,7 @@ EditableMathlist.prototype.moveToNextWord_ = function () { }; /** * @method EditableMathlist#moveToPreviousWord_ + * @private */ @@ -3104,6 +3139,7 @@ EditableMathlist.prototype.moveToPreviousWord_ = function () { }; /** * @method EditableMathlist#moveToGroupStart_ + * @private */ @@ -3112,6 +3148,7 @@ EditableMathlist.prototype.moveToGroupStart_ = function () { }; /** * @method EditableMathlist#moveToGroupEnd_ + * @private */ @@ -3120,6 +3157,7 @@ EditableMathlist.prototype.moveToGroupEnd_ = function () { }; /** * @method EditableMathlist#moveToMathFieldStart_ + * @private */ @@ -3128,6 +3166,7 @@ EditableMathlist.prototype.moveToMathFieldStart_ = function () { }; /** * @method EditableMathlist#moveToMathFieldEnd_ + * @private */ @@ -3136,6 +3175,7 @@ EditableMathlist.prototype.moveToMathFieldEnd_ = function () { }; /** * @method EditableMathlist#deleteNextChar_ + * @private */ @@ -3144,6 +3184,7 @@ EditableMathlist.prototype.deleteNextChar_ = function () { }; /** * @method EditableMathlist#deletePreviousChar_ + * @private */ @@ -3152,6 +3193,7 @@ EditableMathlist.prototype.deletePreviousChar_ = function () { }; /** * @method EditableMathlist#deleteNextWord_ + * @private */ @@ -3161,6 +3203,7 @@ EditableMathlist.prototype.deleteNextWord_ = function () { }; /** * @method EditableMathlist#deletePreviousWord_ + * @private */ @@ -3170,6 +3213,7 @@ EditableMathlist.prototype.deletePreviousWord_ = function () { }; /** * @method EditableMathlist#deleteToGroupStart_ + * @private */ @@ -3179,6 +3223,7 @@ EditableMathlist.prototype.deleteToGroupStart_ = function () { }; /** * @method EditableMathlist#deleteToGroupEnd_ + * @private */ @@ -3188,6 +3233,8 @@ EditableMathlist.prototype.deleteToGroupEnd_ = function () { }; /** * @method EditableMathlist#deleteToMathFieldEnd_ + * @private + * @private */ @@ -3200,6 +3247,7 @@ EditableMathlist.prototype.deleteToMathFieldEnd_ = function () { * the insertion point past both of them. Does nothing to a selected range of * text. * @method EditableMathlist#transpose_ + * @private */ @@ -3207,6 +3255,7 @@ EditableMathlist.prototype.transpose_ = function () {} // @todo /** * @method EditableMathlist#extendToNextChar_ + * @private */ ; @@ -3215,6 +3264,7 @@ EditableMathlist.prototype.extendToNextChar_ = function () { }; /** * @method EditableMathlist#extendToPreviousChar_ + * @private */ @@ -3223,6 +3273,7 @@ EditableMathlist.prototype.extendToPreviousChar_ = function () { }; /** * @method EditableMathlist#extendToNextWord_ + * @private */ @@ -3233,6 +3284,7 @@ EditableMathlist.prototype.extendToNextWord_ = function () { }; /** * @method EditableMathlist#extendToPreviousWord_ + * @private */ @@ -3245,6 +3297,7 @@ EditableMathlist.prototype.extendToPreviousWord_ = function () { * If the selection is in a denominator, the selection will be extended to * include the numerator. * @method EditableMathlist#extendUp_ + * @private */ @@ -3257,6 +3310,7 @@ EditableMathlist.prototype.extendUp_ = function () { * If the selection is in a numerator, the selection will be extended to * include the denominator. * @method EditableMathlist#extendDown_ + * @private */ @@ -3272,6 +3326,7 @@ EditableMathlist.prototype.extendDown_ = function () { * "1" and "2", invoking `extendToNextBoundary_` would extend the selection * to "234". * @method EditableMathlist#extendToNextBoundary_ + * @private */ @@ -3287,6 +3342,7 @@ EditableMathlist.prototype.extendToNextBoundary_ = function () { * "5" and "6", invoking `extendToPreviousBoundary` would extend the selection * to "2345". * @method EditableMathlist#extendToPreviousBoundary_ + * @private */ @@ -3297,6 +3353,7 @@ EditableMathlist.prototype.extendToPreviousBoundary_ = function () { }; /** * @method EditableMathlist#extendToGroupStart_ + * @private */ @@ -3305,6 +3362,7 @@ EditableMathlist.prototype.extendToGroupStart_ = function () { }; /** * @method EditableMathlist#extendToGroupEnd_ + * @private */ @@ -3313,6 +3371,7 @@ EditableMathlist.prototype.extendToGroupEnd_ = function () { }; /** * @method EditableMathlist#extendToMathFieldStart_ + * @private */ @@ -3324,6 +3383,7 @@ EditableMathlist.prototype.extendToMathFieldStart_ = function () { /** * Extend the selection to the end of the math field. * @method EditableMathlist#extendToMathFieldEnd_ + * @private */ @@ -3336,6 +3396,7 @@ EditableMathlist.prototype.extendToMathFieldEnd_ = function () { * Switch the cursor to the superscript and select it. If there is no subscript * yet, create one. * @method EditableMathlist#moveToSuperscript_ + * @private */ @@ -3375,6 +3436,7 @@ EditableMathlist.prototype.moveToSuperscript_ = function () { * Switch the cursor to the subscript and select it. If there is no subscript * yet, create one. * @method EditableMathlist#moveToSubscript_ + * @private */ @@ -3418,6 +3480,7 @@ EditableMathlist.prototype.moveToSubscript_ = function () { * - denominator: move to numerator * - otherwise: move to superscript * @method EditableMathlist#moveToOpposite_ + * @private */ @@ -3444,6 +3507,7 @@ EditableMathlist.prototype.moveToOpposite_ = function () { }; /** * @method EditableMathlist#moveBeforeParent_ + * @private */ @@ -3457,6 +3521,7 @@ EditableMathlist.prototype.moveBeforeParent_ = function () { }; /** * @method EditableMathlist#moveAfterParent_ + * @private */ @@ -3544,6 +3609,7 @@ EditableMathlist.prototype.convertParentToArray = function () { }; /** * @method EditableMathlist#addRowAfter_ + * @private */ @@ -3557,6 +3623,7 @@ EditableMathlist.prototype.addRowAfter_ = function () { }; /** * @method EditableMathlist#addRowBefore_ + * @private */ @@ -3570,6 +3637,7 @@ EditableMathlist.prototype.addRowBefore_ = function () { }; /** * @method EditableMathlist#addColumnAfter_ + * @private */ @@ -3583,6 +3651,7 @@ EditableMathlist.prototype.addColumnAfter_ = function () { }; /** * @method EditableMathlist#addColumnBefore_ + * @private */ @@ -3602,6 +3671,7 @@ EditableMathlist.prototype.addColumnBefore_ = function () { * those sections, and apply it to the entire selection. * * @method EditableMathlist#applyStyle + * @private */ @@ -3696,6 +3766,7 @@ EditableMathlist.prototype._applyStyle = function (style) { * - "JavaScript Latex": a variant that is LaTeX, but with escaped backslashes * \\frac{1}{2} \\sin x * @param {string} s + * @private */ @@ -3886,6 +3957,7 @@ function parseMathExpression(s, config) { * @return {object} * - match: the parsed (and converted) portion of the string that is an argument * - rest: the raw, unconverted, rest of the string + * @private */ diff --git a/dist/editor/editor-keyboard.js b/dist/editor/editor-keyboard.js index f941ce59c..ededf3d69 100644 --- a/dist/editor/editor-keyboard.js +++ b/dist/editor/editor-keyboard.js @@ -104,6 +104,7 @@ var VIRTUAL_KEY_NAMES = { * - Returns "Alt-Alt" when only the Alt key is pressed * @memberof module:editor/keyboard * @param {Event} evt + * @private */ function keyboardEventToString(evt) { @@ -118,10 +119,6 @@ function keyboardEventToString(evt) { } } - if (!keyname && evt.code) { - keyname = KEY_NAMES[evt.code] || evt.code; - } - if (!keyname) { if (INTL_KEY[evt.key]) { keyname = INTL_KEY[evt.key]; @@ -139,6 +136,10 @@ function keyboardEventToString(evt) { } } + if (!keyname && evt.code) { + keyname = KEY_NAMES[evt.code] || evt.code; + } + var modifiers = []; if (evt.ctrlKey) modifiers.push('Ctrl'); if (evt.metaKey) modifiers.push('Meta'); @@ -157,11 +158,11 @@ function keyboardEventToString(evt) { * in the `keystroke()` handler while text input should be handled in * `typedtext()`. * - * @param {Element} textarea A `TextArea` element that will capture the keyboard + * @param {HTMLElement} textarea A `TextArea` element that will capture the keyboard * events. While this element will usually be a `TextArea`, it could be any * element that is focusable and can receive keyboard events. * @param {Object.} handlers - * @param {Element} [handlers.container] + * @param {HTMLElement} [handlers.container] * @param {function} handlers.keystroke invoked on a key down event, including * for special keys such as ESC, arrow keys, tab, etc... and their variants * with modifiers. diff --git a/dist/editor/editor-mathfield.js b/dist/editor/editor-mathfield.js index 77b50da96..41ebba934 100644 --- a/dist/editor/editor-mathfield.js +++ b/dist/editor/editor-mathfield.js @@ -55,10 +55,18 @@ function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object. function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } } -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + /* Note: The OutputLatex, OutputMathML, MASTON and OutputSpokenText modules are required, @@ -67,6 +75,66 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope They modify the MathAtom class, adding toLatex(), toAST(), toMathML() and toSpeakableText() respectively. */ + +/** + * @typedef {function} MathFieldCallback + * @param {MathField} mf + * @return {void} + * @global + */ + +/** + @typedef MathFieldConfig + @type {Object} + @property {string} locale? + @property {object} strings? + @property {number} horizontalSpacingScale? + @property {string} namespace? + @property {function} substituteTextArea? + @property {"math" | "text"} defaultMode? + @property {MathFieldCallback} onFocus? + @property {MathFieldCallback} onBlur? + @property {function} onKeystroke? + @property {function} onAnnounce? + @property {boolean} overrideDefaultInlineShortcuts? + @property {object} inlineShortcuts? + @property {number} inlineShortcutTimeout? + @property {boolean} smartFence? + @property {boolean} smartSuperscript? + @property {number} scriptDepth? + @property {boolean} removeExtraneousParentheses? + @property {boolean} ignoreSpacebarInMathMode? + @property {string} virtualKeyboardToggleGlyph? + @property {"manual" | "onfocus" | "off" } virtualKeyboardMode? + @property {"all" | "numeric" | "roman" | "greek" | "functions" | "command" | string} virtualKeyboards? + @property {"qwerty" | "azerty" | "qwertz" | "dvorak" | "colemak"} virtualKeyboardRomanLayout? + @property {object} customVirtualKeyboardLayers? + @property {object} customVirtualKeyboards? + @property {"material" | "apple" | ""} virtualKeyboardTheme? + @property {boolean} keypressVibration? + @property {string} keypressSound? + @property {string} plonkSound? + @property {"mathlive" | "sre"} textToSpeechRules? + @property {"ssml" | "mac"} textToSpeechMarkup? + @property {object} textToSpeechRulesOptions? + @property {"local" | "amazon"} speechEngine? + @property {string} speechEngineVoice? + @property {string} speechEngineRate? + @property {function} onMoveOutOf? + @property {function} onTabOutOf? + @property {MathFieldCallback} onContentWillChange? + @property {MathFieldCallback} onContentDidChange? + @property {MathFieldCallback} onSelectionWillChange? + @property {MathFieldCallback} onSelectionDidChange? + @property {function} onUndoStateWillChange? + @property {function} onUndoStateDidChange? + @property {function} onModeChange? + @property {function} onVirtualKeyboardToggle? + @property {function} onReadAloudStatus? + @property {function} handleSpeak? + @property {function} handleReadAloud? + @global + */ var HAPTIC_FEEDBACK_DURATION = 3; // in ms var AUDIO_FEEDBACK_VOLUME = 0.5; // from 0.0 to 1.0 @@ -183,3797 +251,4023 @@ function releaseSharedElement(el) { return el; } /** - * To create a mathfield, you would typically use {@linkcode module:MathLive#makeMathField MathLive.makeMathField()} - * instead of invoking directly this constructor. - * - * **Note** - * - Method names that begin with `$` are public. - * - Method names that _begin with_ an underbar `_` are private and meant - * to be used only by the implementation of the class. - * - Method names that _end with_ an underbar `_` are selectors. They can - * be invoked by calling [`MathField.$perform()`]{@link MathField#$perform}. Note - * that the selector name does not include the underbar. - * - * For example: - * ``` - * mf.$perform('selectAll'); - * ``` + * Validate a style specification object + * @param {object} style + * @private + */ + + +function validateStyle(style) { + var result = {}; + + if (typeof style.mode === 'string') { + result.mode = style.mode.toLowerCase(); + console.assert(result.mode === 'math' || result.mode === 'text' || result.mode === 'command'); + } + + if (typeof style.color === 'string') { + result.color = style.color; + } + + if (typeof style.backgroundColor === 'string') { + result.backgroundColor = style.backgroundColor; + } + + if (typeof style.fontFamily === 'string') { + result.fontFamily = style.fontFamily; + } + + if (typeof style.series === 'string') { + result.fontSeries = style.series; + } + + if (typeof style.fontSeries === 'string') { + result.fontSeries = style.fontSeries.toLowerCase(); + } + + if (result.fontSeries) { + result.fontSeries = { + "bold": 'b', + "medium": 'm', + "normal": 'mn' + }[result.fontSeries] || result.fontSeries; + } + + if (typeof style.shape === 'string') { + result.fontShape = style.shape; + } + + if (typeof style.fontShape === 'string') { + result.fontShape = style.fontShape.toLowerCase(); + } + + if (result.fontShape) { + result.fontShape = { + "italic": 'it', + "up": 'n', + "upright": 'n', + "normal": 'n' + }[result.fontShape] || result.fontShape; + } + + if (typeof style.size === 'string') { + result.fontSize = style.size; + } else if (typeof style.size === 'number') { + result.fontSize = 'size' + Math.min(0, Math.max(10, style.size)); + } + + if (typeof style.fontSize === 'string') { + result.fontSize = style.fontSize.toLowerCase(); + } + + if (result.fontSize) { + result.fontSize = { + 'tiny': 'size1', + 'scriptsize': 'size2', + 'footnotesize': 'size3', + 'small': 'size4', + 'normal': 'size5', + 'normalsize': 'size5', + 'large': 'size6', + 'Large': 'size7', + 'LARGE': 'size8', + 'huge': 'size9', + 'Huge': 'size10' + }[result.fontSize] || result.fontSize; + } + + return result; +} +/* + * **Note** + * - Method names that begin with `$` are public. + * - Method names that _begin with_ an underbar `_` are private and meant + * to be used only by the implementation of the class. + * - Method names that _end with_ an underbar `_` are selectors. They can + * be invoked by calling [`MathField.$perform()`]{@link MathField#$perform}. Note + * that the selector name does not include the underbar. + * + * For example: + * ``` + * mf.$perform('selectAll'); + * ``` +*/ + +/** * - * @param {Element} element - The DOM element that this mathfield is attached to. - * Note that `element.mathfield` is this object. - * @param {object} config - See {@tutorial CONFIG} for details - * @property {Element} element - The DOM element this mathfield is attached to. + * @property {HTMLElement} element - The DOM element this mathfield is attached to. * @property {Object.} config - A set of key/value pairs that can * be used to customize the behavior of the mathfield * @property {string} id - A unique ID identifying this mathfield * @property {boolean} keystrokeCaptionVisible - True if the keystroke caption * panel is visible - * @property {boolean} virtualKeyboardVisible - True if the virtual keyboard is + * @property {boolean} virtualKeyboardVisible - True if the virtual keyboard is * visible * @property {string} keystrokeBuffer The last few keystrokes, to look out * for inline shortcuts - * @property {object[]} keystrokeBufferStates The saved state for each of the + * @property {object[]} keystrokeBufferStates The saved state for each of the * past keystrokes - * @class + * @class MathField * @global */ -function MathField(element, config) { - var _this = this; - - // Setup default config options - this.$setConfig(config || {}); - this.element = element; - element.mathfield = this; // Save existing content - - this.originalContent = element.innerHTML; - var elementText = this.element.textContent; - if (elementText) elementText = elementText.trim(); // Additional elements used for UI. - // They are retrieved in order a bit later, so they need to be kept in sync - // 1.0/ The field, where the math equation will be displayed - // 1.1/ The virtual keyboard toggle - // 2/ The popover panel which displays info in command mode - // 3/ The keystroke caption panel (option+shift+K) - // 4/ The virtual keyboard - // 5.0/ The area to stick MathML for screen reading larger exprs (not used right now) - // The for the area is that focus would bounce their and then back triggering the - // screen reader to read it - // 5.1/ The aria-live region for announcements - - var markup = ''; - - if (!this.config.substituteTextArea) { - if (/android|ipad|ipod|iphone/i.test(navigator.userAgent)) { - // On Android or iOS, don't use a textarea, which has the side effect of - // bringing up the OS virtual keyboard - markup += "\n \n \n "; - } else { - markup += '' + '' + ''; - } - } else { - if (typeof this.config.substituteTextArea === 'string') { - markup += this.config.substituteTextArea; +var MathField = +/*#__PURE__*/ +function () { + /** + * To create a mathfield, you would typically use {@linkcode module:MathLive#makeMathField MathLive.makeMathField()} + * instead of invoking directly this constructor. + * + * + * @param {HTMLElement} element - The DOM element that this mathfield is attached to. + * Note that `element.mathfield` is this object. + * @param {MathFieldConfig} config - See {@tutorial CONFIG} for details + * @method MathField#constructor + * @private + */ + function MathField(element, config) { + var _this = this; + + _classCallCheck(this, MathField); + + // Setup default config options + this.$setConfig(config || {}); + this.element = element; + element.mathfield = this; // Save existing content + + this.originalContent = element.innerHTML; + var elementText = this.element.textContent; + + if (elementText) { + elementText = elementText.trim(); + } // Additional elements used for UI. + // They are retrieved in order a bit later, so they need to be kept in sync + // 1.0/ The field, where the math equation will be displayed + // 1.1/ The virtual keyboard toggle + // 2/ The popover panel which displays info in command mode + // 3/ The keystroke caption panel (option+shift+K) + // 4/ The virtual keyboard + // 5.0/ The area to stick MathML for screen reading larger exprs (not used right now) + // The for the area is that focus would bounce their and then back triggering the + // screen reader to read it + // 5.1/ The aria-live region for announcements + + + var markup = ''; + + if (!this.config.substituteTextArea) { + if (/android|ipad|ipod|iphone/i.test(navigator.userAgent)) { + // On Android or iOS, don't use a textarea, which has the side effect of + // bringing up the OS virtual keyboard + markup += "\n \n \n "; + } else { + markup += '' + '' + ''; + } } else { - // We don't really need this one, but we keep it here so that the - // indexes below remain the same whether a substituteTextArea is - // provided or not. - markup += ''; + if (typeof this.config.substituteTextArea === 'string') { + markup += this.config.substituteTextArea; + } else { + // We don't really need this one, but we keep it here so that the + // indexes below remain the same whether a substituteTextArea is + // provided or not. + markup += ''; + } } - } - markup += '' + ''; // If no value is specified for the virtualKeyboardMode, use - // `onfocus` on touch-capable devices and `off` otherwise. + markup += '' + ''; // If no value is specified for the virtualKeyboardMode, use + // `onfocus` on touch-capable devices and `off` otherwise. - if (!this.config.virtualKeyboardMode) { - this.config.virtualKeyboardMode = window.matchMedia && window.matchMedia("(any-pointer: coarse)").matches ? 'onfocus' : 'off'; - } // Only display the virtual keyboard toggle if the virtual keyboard mode is - // 'manual' + if (!this.config.virtualKeyboardMode) { + this.config.virtualKeyboardMode = window.matchMedia && window.matchMedia("(any-pointer: coarse)").matches ? 'onfocus' : 'off'; + } // Only display the virtual keyboard toggle if the virtual keyboard mode is + // 'manual' - if (this.config.virtualKeyboardMode === 'manual') { - markup += "'; } else { - markup += ""; + markup += ''; } - markup += ''; - } else { - markup += ''; - } + markup += ''; + markup += "\n
\n \n \n
\n "; + this.element.innerHTML = markup; + var iChild = 0; // index of child -- used to make changes below easier - markup += '
'; - markup += "\n
\n \n \n
\n "; - this.element.innerHTML = markup; - var iChild = 0; // index of child -- used to make changes below easier + if (typeof this.config.substituteTextArea === 'function') { + this.textarea = this.config.substituteTextArea(); + } else { + this.textarea = this.element.children[iChild++].firstElementChild; + } - if (typeof this.config.substituteTextArea === 'function') { - this.textarea = this.config.substituteTextArea(); - } else { - this.textarea = this.element.children[iChild++].firstElementChild; - } + this.field = this.element.children[iChild].children[0]; // Listen to 'wheel' events to scroll (horizontally) the field when it overflows - this.field = this.element.children[iChild].children[0]; // Listen to 'wheel' events to scroll (horizontally) the field when it overflows + this.field.addEventListener('wheel', function (ev) { + ev.preventDefault(); + ev.stopPropagation(); + var wheelDelta = typeof ev.deltaX === 'undefined' ? ev.detail : -ev.deltaX; - this.field.addEventListener('wheel', function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - var wheelDelta = typeof ev.deltaX === 'undefined' ? ev.detail : -ev.deltaX; - if (!isFinite(wheelDelta)) wheelDelta = ev.wheelDelta / 10; + if (!isFinite(wheelDelta)) { + wheelDelta = ev.wheelDelta / 10; + } - _this.field.scroll({ - top: 0, - left: _this.field.scrollLeft - wheelDelta * 5 + _this.field.scroll({ + top: 0, + left: _this.field.scrollLeft - wheelDelta * 5 + }); + }, { + passive: false }); - }, { - passive: false - }); - this.virtualKeyboardToggleDOMNode = this.element.children[iChild++].children[1]; - - this._attachButtonHandlers(this.virtualKeyboardToggleDOMNode, { - default: 'toggleVirtualKeyboard', - alt: 'toggleVirtualKeyboardAlt', - shift: 'toggleVirtualKeyboardShift' - }); - - this.ariaLiveText = this.element.children[iChild].children[0]; - this.accessibleNode = this.element.children[iChild++].children[1]; // Some panels are shared amongst instances of mathfield - // (there's a single instance in the document) - - this.popover = getSharedElement('mathlive-popover-panel', 'ML__popover'); - this.keystrokeCaption = getSharedElement('mathlive-keystroke-caption-panel', 'ML__keystroke-caption'); // The keystroke caption panel and the command bar are - // initially hidden - - this.keystrokeCaptionVisible = false; - this.virtualKeyboardVisible = false; - this.keystrokeBuffer = ''; - this.keystrokeBufferStates = []; - this.keystrokeBufferResetTimer = null; // This index indicates which of the suggestions available to - // display in the popover panel - - this.suggestionIndex = 0; // The input mode (text, math, command) - // While mathlist.anchorMode() represent the mode of the current selection, - // this.mode is the mode chosen by the user. It indicates the mode the - // next character typed will be interpreted in. - // It is often identical to mathlist.anchorMode() since changing the selection - // changes the mode, but sometimes it is not, for example when a user - // enters a mode changing command. - - this.mode = config.defaultMode || 'math'; - this.smartModeSuppressed = false; // Current style (color, weight, italic, etc...) - // Reflects the style to be applied on next insertion, if any - - this.style = {}; // Focus/blur state - - this.blurred = true; - on(this.element, 'focus', this); - on(this.element, 'blur', this); // Capture clipboard events - - on(this.textarea, 'cut', this); - on(this.textarea, 'copy', this); - on(this.textarea, 'paste', this); // Delegate keyboard events - - _editorKeyboard.default.delegateKeyboardEvents(this.textarea, { - container: this.element, - allowDeadKey: function allowDeadKey() { - return _this.mode === 'text'; - }, - typedText: this._onTypedText.bind(this), - paste: this._onPaste.bind(this), - keystroke: this._onKeystroke.bind(this), - focus: this._onFocus.bind(this), - blur: this._onBlur.bind(this) - }); // Delegate mouse and touch events - - - if (window.PointerEvent) { - // Use modern pointer events if available - on(this.field, 'pointerdown', this); - } else { - on(this.field, 'touchstart:active mousedown', this); - } // Request notification for when the window is resized ( - // or the device switched from portrait to landscape) to adjust - // the UI (popover, etc...) + this.virtualKeyboardToggleDOMNode = this.element.children[iChild++].children[1]; + this._attachButtonHandlers(this.virtualKeyboardToggleDOMNode, { + default: 'toggleVirtualKeyboard', + alt: 'toggleVirtualKeyboardAlt', + shift: 'toggleVirtualKeyboardShift' + }); - on(window, 'resize', this); // Override some handlers in the config + this.ariaLiveText = this.element.children[iChild].children[0]; + this.accessibleNode = this.element.children[iChild++].children[1]; // Some panels are shared amongst instances of mathfield + // (there's a single instance in the document) - var localConfig = _objectSpread({}, config); + this.popover = getSharedElement('mathlive-popover-panel', 'ML__popover'); + this.keystrokeCaption = getSharedElement('mathlive-keystroke-caption-panel', 'ML__keystroke-caption'); // The keystroke caption panel and the command bar are + // initially hidden - localConfig.onSelectionDidChange = MathField.prototype._onSelectionDidChange.bind(this); - localConfig.onContentDidChange = MathField.prototype._onContentDidChange.bind(this); - localConfig.onAnnounce = this.config.onAnnounce; - localConfig.macros = this.config.macros; - localConfig.removeExtraneousParentheses = this.config.removeExtraneousParentheses; - this.mathlist = new _editorEditableMathlist.default.EditableMathlist(localConfig, this); // Prepare to manage undo/redo + this.keystrokeCaptionVisible = false; + this.virtualKeyboardVisible = false; + this.keystrokeBuffer = ''; + this.keystrokeBufferStates = []; + this.keystrokeBufferResetTimer = null; // This index indicates which of the suggestions available to + // display in the popover panel - this.undoManager = new _editorUndo.default.UndoManager(this.mathlist); // If there was some content in the element, use it for the initial - // value of the mathfield + this.suggestionIndex = 0; // The input mode (text, math, command) + // While mathlist.anchorMode() represent the mode of the current selection, + // this.mode is the mode chosen by the user. It indicates the mode the + // next character typed will be interpreted in. + // It is often identical to mathlist.anchorMode() since changing the selection + // changes the mode, but sometimes it is not, for example when a user + // enters a mode changing command. - if (elementText.length > 0) { - this.$latex(elementText); - } // Now start recording potentially undoable actions + this.mode = config.defaultMode || 'math'; + this.smartModeSuppressed = false; // Current style (color, weight, italic, etc...) + // Reflects the style to be applied on next insertion, if any + this.style = {}; // Focus/blur state - this.undoManager.startRecording(); - this.undoManager.snapshot(this.config); -} -/** - * handleEvent is a function invoked when an event is registered with an - * object instead ( see `addEventListener()` in `on()`) - * The name is defined by addEventListener() and cannot be changed. - * This pattern is used to be able to release bound event handlers, - * (event handlers that need access to `this`) as the bind() function - * would create a new function that would have to be kept track off - * to be able to properly remove the event handler later. - */ + this.blurred = true; + on(this.element, 'focus', this); + on(this.element, 'blur', this); // Capture clipboard events + + on(this.textarea, 'cut', this); + on(this.textarea, 'copy', this); + on(this.textarea, 'paste', this); // Delegate keyboard events + + _editorKeyboard.default.delegateKeyboardEvents(this.textarea, { + container: this.element, + allowDeadKey: function allowDeadKey() { + return _this.mode === 'text'; + }, + typedText: this._onTypedText.bind(this), + paste: this._onPaste.bind(this), + keystroke: this._onKeystroke.bind(this), + focus: this._onFocus.bind(this), + blur: this._onBlur.bind(this) + }); // Delegate mouse and touch events -MathField.prototype.handleEvent = function (evt) { - var _this2 = this; + if (window.PointerEvent) { + // Use modern pointer events if available + on(this.field, 'pointerdown', this); + } else { + on(this.field, 'touchstart:active mousedown', this); + } // Request notification for when the window is resized ( + // or the device switched from portrait to landscape) to adjust + // the UI (popover, etc...) - switch (evt.type) { - case 'focus': - this._onFocus(evt); - break; + on(window, 'resize', this); // Override some handlers in the config - case 'blur': - this._onBlur(evt); + var localConfig = _objectSpread({}, config); - break; + localConfig.onSelectionDidChange = MathField.prototype._onSelectionDidChange.bind(this); + localConfig.onContentDidChange = MathField.prototype._onContentDidChange.bind(this); + localConfig.onAnnounce = this.config.onAnnounce; + localConfig.macros = this.config.macros; + localConfig.removeExtraneousParentheses = this.config.removeExtraneousParentheses; + this.mathlist = new _editorEditableMathlist.default.EditableMathlist(localConfig, this); // Prepare to manage undo/redo - case 'touchstart': - this._onPointerDown(evt); + this.undoManager = new _editorUndo.default.UndoManager(this.mathlist); // If there was some content in the element, use it for the initial + // value of the mathfield - break; + if (elementText.length > 0) { + this.$latex(elementText); + } // Now start recording potentially undoable actions - case 'mousedown': - this._onPointerDown(evt); - break; + this.undoManager.startRecording(); + this.undoManager.snapshot(this.config); + } + /* + * handleEvent is a function invoked when an event is registered with an + * object instead ( see `addEventListener()` in `on()`) + * The name is defined by addEventListener() and cannot be changed. + * This pattern is used to be able to release bound event handlers, + * (event handlers that need access to `this`) as the bind() function + * would create a new function that would have to be kept track off + * to be able to properly remove the event handler later. + */ + + + _createClass(MathField, [{ + key: "handleEvent", + value: function handleEvent(evt) { + var _this2 = this; + + switch (evt.type) { + case 'focus': + this._onFocus(evt); - case 'pointerdown': - this._onPointerDown(evt); + break; - break; + case 'blur': + this._onBlur(evt); - case 'resize': - { - if (this._resizeTimer) window.cancelAnimationFrame(this._resizeTimer); - this._resizeTimer = window.requestAnimationFrame(function () { - return _this2._onResize(); - }); - break; - } + break; - case 'cut': - this._onCut(evt); + case 'touchstart': + this._onPointerDown(evt); - break; + break; - case 'copy': - this._onCopy(evt); + case 'mousedown': + this._onPointerDown(evt); - break; + break; - case 'paste': - this._onPaste(evt); + case 'pointerdown': + this._onPointerDown(evt); - break; + break; - default: - console.warn('Unexpected event type', evt.type); - } -}; -/** - * Revert this math field to its original content. After this method has been - * called, no other methods can be called on the MathField object. To turn the - * element back into a MathField, call `MathLive.makeMathField()` on the - * element again to get a new math field object. - * - * @method MathField#$revertToOriginalContent - */ + case 'resize': + { + if (this._resizeTimer) { + window.cancelAnimationFrame(this._resizeTimer); + } + this._resizeTimer = window.requestAnimationFrame(function () { + return _this2._onResize(); + }); + break; + } -MathField.prototype.revertToOriginalContent = MathField.prototype.$revertToOriginalContent = function () { - this.element.innerHTML = this.originalContent; - this.element.mathfield = null; - delete this.accessibleNode; - delete this.ariaLiveText; - delete this.field; - off(this.textarea, 'cut', this); - off(this.textarea, 'copy', this); - off(this.textarea, 'paste', this); - this.textarea.remove(); - delete this.textarea; - this.virtualKeyboardToggleDOMNode.remove(); - delete this.virtualKeyboardToggleDOMNode; - delete releaseSharedElement(this.popover); - delete releaseSharedElement(this.keystrokeCaption); - delete releaseSharedElement(this.virtualKeyboard); - delete releaseSharedElement(document.getElementById('mathlive-alternate-keys-panel')); - off(this.element, 'pointerdown', this); - off(this.element, 'touchstart:active mousedown', this); - off(this.element, 'focus', this); - off(this.element, 'blur', this); - off(window, 'resize', this); -}; + case 'cut': + this._onCut(evt); -MathField.prototype._resetKeystrokeBuffer = function () { - this.keystrokeBuffer = ''; - this.keystrokeBufferStates = []; - clearTimeout(this.keystrokeBufferResetTimer); -}; -/** - * Utility function that returns the element which has the caret - * - * @param {DomElement} el - * @private - */ + break; + case 'copy': + this._onCopy(evt); -function _findElementWithCaret(el) { - if (el.classList.contains('ML__caret') || el.classList.contains('ML__text-caret') || el.classList.contains('ML__command-caret')) { - return el; - } + break; - var result; - Array.from(el.children).forEach(function (child) { - result = result || _findElementWithCaret(child); - }); - return result; -} -/** - * Return the (x,y) client coordinates of the caret - * - * @method MathField#_getCaretPosition - * @private - */ + case 'paste': + this._onPaste(evt); + break; -MathField.prototype._getCaretPosition = function () { - var caret = _findElementWithCaret(this.field); + default: + console.warn('Unexpected event type', evt.type); + } + } + /** + * Revert this math field to its original content. After this method has been + * called, no other methods can be called on the MathField object. To turn the + * element back into a MathField, call `MathLive.makeMathField()` on the + * element again to get a new math field object. + * + * @method MathField#$revertToOriginalContent + */ - if (caret) { - var bounds = caret.getBoundingClientRect(); - return { - x: bounds.right + window.scrollX, - y: bounds.bottom + window.scrollY - }; - } + }, { + key: "$revertToOriginalContent", + value: function $revertToOriginalContent() { + this.element.innerHTML = this.originalContent; + this.element.mathfield = null; + delete this.accessibleNode; + delete this.ariaLiveText; + delete this.field; + off(this.textarea, 'cut', this); + off(this.textarea, 'copy', this); + off(this.textarea, 'paste', this); + this.textarea.remove(); + delete this.textarea; + this.virtualKeyboardToggleDOMNode.remove(); + delete this.virtualKeyboardToggleDOMNode; + delete releaseSharedElement(this.popover); + delete releaseSharedElement(this.keystrokeCaption); + delete releaseSharedElement(this.virtualKeyboard); + delete releaseSharedElement(document.getElementById('mathlive-alternate-keys-panel')); + off(this.element, 'pointerdown', this); + off(this.element, 'touchstart:active mousedown', this); + off(this.element, 'focus', this); + off(this.element, 'blur', this); + off(window, 'resize', this); + } + }, { + key: "_resetKeystrokeBuffer", + value: function _resetKeystrokeBuffer() { + this.keystrokeBuffer = ''; + this.keystrokeBufferStates = []; + clearTimeout(this.keystrokeBufferResetTimer); + } + /** + * Return the (x,y) client coordinates of the caret + * + * @method MathField#_getCaretPosition + * @private + */ - return null; -}; + }, { + key: "_getCaretPosition", + value: function _getCaretPosition() { + var caret = _findElementWithCaret(this.field); + + if (caret) { + var bounds = caret.getBoundingClientRect(); + return { + x: bounds.right + window.scrollX, + y: bounds.bottom + window.scrollY + }; + } -MathField.prototype._getSelectionBounds = function () { - var selectedNodes = this.field.querySelectorAll('.ML__selected'); - - if (selectedNodes && selectedNodes.length > 0) { - var selectionRect = { - top: Infinity, - bottom: -Infinity, - left: Infinity, - right: -Infinity - }; // Calculate the union of the bounds of all the selected spans - - selectedNodes.forEach(function (node) { - var bounds = node.getBoundingClientRect(); - if (bounds.left < selectionRect.left) selectionRect.left = bounds.left; - if (bounds.right > selectionRect.right) selectionRect.right = bounds.right; - if (bounds.bottom > selectionRect.bottom) selectionRect.bottom = bounds.bottom; - if (bounds.top < selectionRect.top) selectionRect.top = bounds.top; - }); - var fieldRect = this.field.getBoundingClientRect(); - var w = selectionRect.right - selectionRect.left; - var h = selectionRect.bottom - selectionRect.top; - selectionRect.left = Math.ceil(selectionRect.left - fieldRect.left + this.field.scrollLeft); - selectionRect.right = selectionRect.left + w; - selectionRect.top = Math.ceil(selectionRect.top - fieldRect.top); - selectionRect.bottom = selectionRect.top + h; - return selectionRect; - } + return null; + } + }, { + key: "_getSelectionBounds", + value: function _getSelectionBounds() { + var selectedNodes = this.field.querySelectorAll('.ML__selected'); + + if (selectedNodes && selectedNodes.length > 0) { + var selectionRect = { + top: Infinity, + bottom: -Infinity, + left: Infinity, + right: -Infinity + }; // Calculate the union of the bounds of all the selected spans + + selectedNodes.forEach(function (node) { + var bounds = node.getBoundingClientRect(); + + if (bounds.left < selectionRect.left) { + selectionRect.left = bounds.left; + } - return null; -}; -/** - * Return a tuple of an element and a distance from point (x, y) - * @param {Element} el - * @param {number} x - * @param {number} y - * @function module:editor/mathfield#nearestElementFromPoint - * @private - */ + if (bounds.right > selectionRect.right) { + selectionRect.right = bounds.right; + } + if (bounds.bottom > selectionRect.bottom) { + selectionRect.bottom = bounds.bottom; + } -function nearestElementFromPoint(el, x, y) { - var result = { - element: null - }; - var considerChildren = true; + if (bounds.top < selectionRect.top) { + selectionRect.top = bounds.top; + } + }); + var fieldRect = this.field.getBoundingClientRect(); + var w = selectionRect.right - selectionRect.left; + var h = selectionRect.bottom - selectionRect.top; + selectionRect.left = Math.ceil(selectionRect.left - fieldRect.left + this.field.scrollLeft); + selectionRect.right = selectionRect.left + w; + selectionRect.top = Math.ceil(selectionRect.top - fieldRect.top); + selectionRect.bottom = selectionRect.top + h; + return selectionRect; + } - if (!el.getAttribute('data-atom-id')) { - // This element may not have a matching atom, but its children might - result.distance = Number.POSITIVE_INFINITY; - } else { - result.element = el; // Calculate the (square of the) distance to the rectangle + return null; + } + /** + * @param {number} x + * @param {number} y + * @param {object} options + * @param {boolean} options.bias if 0, the midpoint of the bounding box + * is considered to return the sibling. If <0, the left sibling is + * favored, if >0, the right sibling + * @private + */ - var r = el.getBoundingClientRect(); - var dx = Math.max(r.left - x, x - r.right); - var dy = Math.max(r.top - y, y - r.bottom); - result.distance = dx * dx + dy * dy; // Only consider children if the target is inside the (horizontal) - // bounds of the element. - // This avoid searching the numerator/denominator when a fraction - // is the last element in the formula. + }, { + key: "_pathFromPoint", + value: function _pathFromPoint(x, y, options) { + options = options || {}; + options.bias = options.bias || 0; + var result; // Try to find the deepest element that is near the point that was + // clicked on (the point could be outside of the element) + + var nearest = nearestElementFromPoint(this.field, x, y); + var el = nearest.element; + var id = el ? el.getAttribute('data-atom-id') : null; + + if (id) { + // Let's find the atom that has a matching ID with the element that + // was clicked on (or near) + var paths = this.mathlist.filter(function (_path, atom) { + // If the atom allows children to be selected, match only if + // the ID of the atom matches the one we're looking for. + if (!atom.captureSelection) { + return atom.id === id; + } // If the atom does not allow children to be selected + // (captureSelection === true), the element matches if any of + // its children has an ID that matches. + + + return atom.filter(function (childAtom) { + return childAtom.id === id; + }).length > 0; + }); - considerChildren = x >= r.left && x <= r.right; - } + if (paths && paths.length > 0) { + // (There should be exactly one atom that matches this ID...) + // Set the result to the path to this atom + result = _editorMathpath.default.pathFromString(paths[0]).path; - if (considerChildren && el.children) { - Array.from(el.children).forEach(function (child) { - var nearest = nearestElementFromPoint(child, x, y); + if (options.bias === 0) { + // If the point clicked is to the left of the vertical midline, + // adjust the path to *before* the atom (i.e. after the + // preceding atom) + var bounds = el.getBoundingClientRect(); - if (nearest.element && nearest.distance <= result.distance) { - result = nearest; + if (x < bounds.left + bounds.width / 2 && !el.classList.contains('ML__placeholder')) { + result[result.length - 1].offset = Math.max(0, result[result.length - 1].offset - 1); + } + } else if (options.bias < 0) { + result[result.length - 1].offset = Math.min(this.mathlist.siblings().length - 1, Math.max(0, result[result.length - 1].offset + options.bias)); + } + } } - }); - } - return result; -} -/** - * @param {number} x - * @param {number} y - * @param {object} options - * @param {boolean} options.bias if 0, the midpoint of the bounding box - * is considered to return the sibling. If <0, the left sibling is - * favored, if >0, the right sibling - */ + return result; + } + }, { + key: "_onPointerDown", + value: function _onPointerDown(evt) { + var that = this; + var anchor; + var trackingPointer = false; + var trackingWords = false; + var dirty = false; // If a mouse button other than the main one was pressed, return + + if (evt.buttons !== 1) { + return; + } + function endPointerTracking(evt) { + if (window.PointerEvent) { + off(that.field, 'pointermove', onPointerMove); + off(that.field, 'pointerend pointerleave pointercancel', endPointerTracking); // off(window, 'pointermove', onPointerMove); + // off(window, 'pointerup blur', endPointerTracking); -MathField.prototype._pathFromPoint = function (x, y, options) { - options = options || {}; - options.bias = options.bias || 0; - var result; // Try to find the deepest element that is near the point that was - // clicked on (the point could be outside of the element) - - var nearest = nearestElementFromPoint(this.field, x, y); - var el = nearest.element; - var id = el ? el.getAttribute('data-atom-id') : null; - - if (id) { - // Let's find the atom that has a matching ID with the element that - // was clicked on (or near) - var paths = this.mathlist.filter(function (_path, atom) { - // If the atom allows children to be selected, match only if - // the ID of the atom matches the one we're looking for. - if (!atom.captureSelection) { - return atom.id === id; - } // If the atom does not allow children to be selected - // (captureSelection === true), the element matches if any of - // its children has an ID that matches. - - - return atom.filter(function (childAtom) { - return childAtom.id === id; - }).length > 0; - }); + that.field.releasePointerCapture(evt.pointerId); + } else { + off(that.field, 'touchmove', onPointerMove); + off(that.field, 'touchend touchleave', endPointerTracking); + off(window, 'mousemove', onPointerMove); + off(window, 'mouseup blur', endPointerTracking); + } - if (paths && paths.length > 0) { - // (There should be exactly one atom that matches this ID...) - // Set the result to the path to this atom - result = _editorMathpath.default.pathFromString(paths[0]).path; + trackingPointer = false; + clearInterval(scrollInterval); + that.element.querySelectorAll('.ML__scroller').forEach(function (x) { + return x.parentNode.removeChild(x); + }); + evt.preventDefault(); + evt.stopPropagation(); + } - if (options.bias === 0) { - // If the point clicked is to the left of the vertical midline, - // adjust the path to *before* the atom (i.e. after the - // preceding atom) - var bounds = el.getBoundingClientRect(); - - if (x < bounds.left + bounds.width / 2 && !el.classList.contains('ML__placeholder')) { - result[result.length - 1].offset = Math.max(0, result[result.length - 1].offset - 1); + var scrollLeft = false; + var scrollRight = false; + var scrollInterval = setInterval(function () { + if (scrollLeft) { + that.field.scroll({ + top: 0, + left: that.field.scrollLeft - 16 + }); + } else if (scrollRight) { + that.field.scroll({ + top: 0, + left: that.field.scrollLeft + 16 + }); } - } else if (options.bias < 0) { - result[result.length - 1].offset = Math.min(this.mathlist.siblings().length - 1, Math.max(0, result[result.length - 1].offset + options.bias)); - } - } - } + }, 32); - return result; -}; + function onPointerMove(evt) { + var x = evt.touches ? evt.touches[0].clientX : evt.clientX; + var y = evt.touches ? evt.touches[0].clientY : evt.clientY; // Ignore events that are within small spatial and temporal bounds + // of the pointer down -var lastTap; -var tapCount = 0; + var hysteresis = evt.pointerType === 'touch' ? 20 : 5; -MathField.prototype._onPointerDown = function (evt) { - var that = this; - var anchor; - var trackingPointer = false; - var trackingWords = false; - var dirty = false; // If a mouse button other than the main one was pressed, return + if (Date.now() < anchorTime + 500 && Math.abs(anchorX - x) < hysteresis && Math.abs(anchorY - y) < hysteresis) { + evt.preventDefault(); + evt.stopPropagation(); + return; + } - if (evt.buttons !== 1) return; + var fieldBounds = that.field.getBoundingClientRect(); + scrollRight = x > fieldBounds.right; + scrollLeft = x < fieldBounds.left; + var actualAnchor = anchor; - function endPointerTracking(evt) { - if (window.PointerEvent) { - off(that.field, 'pointermove', onPointerMove); - off(that.field, 'pointerend pointerleave pointercancel', endPointerTracking); // off(window, 'pointermove', onPointerMove); - // off(window, 'pointerup blur', endPointerTracking); + if (window.PointerEvent) { + if (!evt.isPrimary) { + actualAnchor = that._pathFromPoint(evt.clientX, evt.clientY, { + bias: 0 + }); + } + } else { + if (evt.touches && evt.touches.length === 2) { + actualAnchor = that._pathFromPoint(evt.touches[1].clientX, evt.touches[1].clientY, { + bias: 0 + }); + } + } - that.field.releasePointerCapture(evt.pointerId); - } else { - off(that.field, 'touchmove', onPointerMove); - off(that.field, 'touchend touchleave', endPointerTracking); - off(window, 'mousemove', onPointerMove); - off(window, 'mouseup blur', endPointerTracking); - } + var focus = that._pathFromPoint(x, y, { + bias: x <= anchorX ? x === anchorX ? 0 : -1 : +1 + }); - trackingPointer = false; - clearInterval(scrollInterval); - that.element.querySelectorAll('.ML__scroller').forEach(function (x) { - return x.parentNode.removeChild(x); - }); - evt.preventDefault(); - evt.stopPropagation(); - } + if (focus && that.mathlist.setRange(actualAnchor, focus, { + extendToWordBoundary: trackingWords + })) { + // Re-render if the range has actually changed + that._requestUpdate(); + } // Prevent synthetic mouseMove event when this is a touch event - var scrollLeft = false; - var scrollRight = false; - var scrollInterval = setInterval(function () { - if (scrollLeft) { - that.field.scroll({ - top: 0, - left: that.field.scrollLeft - 16 - }); - } else if (scrollRight) { - that.field.scroll({ - top: 0, - left: that.field.scrollLeft + 16 - }); - } - }, 32); - function onPointerMove(evt) { - var x = evt.touches ? evt.touches[0].clientX : evt.clientX; - var y = evt.touches ? evt.touches[0].clientY : evt.clientY; // Ignore events that are within small spatial and temporal bounds - // of the pointer down + evt.preventDefault(); + evt.stopPropagation(); + } - var hysteresis = evt.pointerType === 'touch' ? 20 : 5; + var anchorX = evt.touches ? evt.touches[0].clientX : evt.clientX; + var anchorY = evt.touches ? evt.touches[0].clientY : evt.clientY; + var anchorTime = Date.now(); // Calculate the tap count - if (Date.now() < anchorTime + 500 && Math.abs(anchorX - x) < hysteresis && Math.abs(anchorY - y) < hysteresis) { - evt.preventDefault(); - evt.stopPropagation(); - return; - } + if (lastTap && Math.abs(lastTap.x - anchorX) < 5 && Math.abs(lastTap.y - anchorY) < 5 && Date.now() < lastTap.time + 500) { + tapCount += 1; + lastTap.time = anchorTime; + } else { + lastTap = { + x: anchorX, + y: anchorY, + time: anchorTime + }; + tapCount = 1; + } - var fieldBounds = that.field.getBoundingClientRect(); - scrollRight = x > fieldBounds.right; - scrollLeft = x < fieldBounds.left; - var actualAnchor = anchor; + var bounds = this.field.getBoundingClientRect(); + + if (anchorX >= bounds.left && anchorX <= bounds.right && anchorY >= bounds.top && anchorY <= bounds.bottom) { + // Create divs to block out pointer tracking to the left and right of + // the math field (to avoid triggering the hover of the virtual + // keyboard toggle, for example) + var div = document.createElement('div'); + div.className = 'ML__scroller'; + this.element.appendChild(div); + div.style.left = bounds.left - 200 + 'px'; + div = document.createElement('div'); + div.className = 'ML__scroller'; + this.element.appendChild(div); + div.style.left = bounds.right + 'px'; // Focus the math field + + if (!this.$hasFocus()) { + dirty = true; + + if (this.textarea.focus) { + this.textarea.focus(); + } + } // Clicking or tapping the field resets the keystroke buffer and + // smart mode - if (window.PointerEvent) { - if (!evt.isPrimary) { - actualAnchor = that._pathFromPoint(evt.clientX, evt.clientY, { - bias: 0 - }); - } - } else { - if (evt.touches && evt.touches.length === 2) { - actualAnchor = that._pathFromPoint(evt.touches[1].clientX, evt.touches[1].clientY, { + + this._resetKeystrokeBuffer(); + + this.smartModeSuppressed = false; + anchor = this._pathFromPoint(anchorX, anchorY, { bias: 0 }); - } - } - var focus = that._pathFromPoint(x, y, { - bias: x <= anchorX ? x === anchorX ? 0 : -1 : +1 - }); + if (anchor) { + if (evt.shiftKey) { + // Extend the selection if the shift-key is down + this.mathlist.setRange(this.mathlist.path, anchor); + anchor = _editorMathpath.default.clone(this.mathlist.path); + anchor[anchor.length - 1].offset -= 1; + } else { + this.mathlist.setPath(anchor, 0); + } // The selection has changed, so we'll need to re-render - if (focus && that.mathlist.setRange(actualAnchor, focus, { - extendToWordBoundary: trackingWords - })) { - // Re-render if the range has actually changed - that._requestUpdate(); - } // Prevent synthetic mouseMove event when this is a touch event + dirty = true; // Reset any user-specified style - evt.preventDefault(); - evt.stopPropagation(); - } + this.style = {}; // evt.detail contains the number of consecutive clicks + // for double-click, triple-click, etc... + // (note that evt.detail is not set when using pointerEvent) - var anchorX = evt.touches ? evt.touches[0].clientX : evt.clientX; - var anchorY = evt.touches ? evt.touches[0].clientY : evt.clientY; - var anchorTime = Date.now(); // Calculate the tap count + if (evt.detail === 3 || tapCount > 2) { + endPointerTracking(evt); - if (lastTap && Math.abs(lastTap.x - anchorX) < 5 && Math.abs(lastTap.y - anchorY) < 5 && Date.now() < lastTap.time + 500) { - tapCount += 1; - lastTap.time = anchorTime; - } else { - lastTap = { - x: anchorX, - y: anchorY, - time: anchorTime - }; - tapCount = 1; - } + if (evt.detail === 3 || tapCount === 3) { + // This is a triple-click + this.mathlist.selectAll_(); + } + } else if (!trackingPointer) { + trackingPointer = true; - var bounds = this.field.getBoundingClientRect(); - - if (anchorX >= bounds.left && anchorX <= bounds.right && anchorY >= bounds.top && anchorY <= bounds.bottom) { - // Create divs to block out pointer tracking to the left and right of - // the math field (to avoid triggering the hover of the virtual - // keyboard toggle, for example) - var div = document.createElement('div'); - div.className = 'ML__scroller'; - this.element.appendChild(div); - div.style.left = bounds.left - 200 + 'px'; - div = document.createElement('div'); - div.className = 'ML__scroller'; - this.element.appendChild(div); - div.style.left = bounds.right + 'px'; // Focus the math field - - if (!this.hasFocus()) { - dirty = true; - if (this.textarea.focus) this.textarea.focus(); - } // Clicking or tapping the field resets the keystroke buffer and - // smart mode - - - this._resetKeystrokeBuffer(); - - this.smartModeSuppressed = false; - anchor = this._pathFromPoint(anchorX, anchorY, { - bias: 0 - }); + if (window.PointerEvent) { + on(that.field, 'pointermove', onPointerMove); + on(that.field, 'pointerend pointercancel pointerup', endPointerTracking); + that.field.setPointerCapture(evt.pointerId); + } else { + on(window, 'blur', endPointerTracking); + + if (evt.touches) { + // To receive the subsequent touchmove/touch, need to + // listen to this evt.target. + // This was a touch event + on(evt.target, 'touchmove', onPointerMove); + on(evt.target, 'touchend', endPointerTracking); + } else { + on(window, 'mousemove', onPointerMove); + on(window, 'mouseup', endPointerTracking); + } + } - if (anchor) { - if (evt.shiftKey) { - // Extend the selection if the shift-key is down - this.mathlist.setRange(this.mathlist.path, anchor); - anchor = _editorMathpath.default.clone(this.mathlist.path); - anchor[anchor.length - 1].offset -= 1; + if (evt.detail === 2 || tapCount === 2) { + // This is a double-click + trackingWords = true; + this.mathlist.selectGroup_(); + } + } + } } else { - this.mathlist.setPath(anchor, 0); - } // The selection has changed, so we'll need to re-render + lastTap = null; + } + if (dirty) { + this._requestUpdate(); + } // Prevent the browser from handling, in particular when this is a + // touch event prevent the synthetic mouseDown event from being generated - dirty = true; // Reset any user-specified style - this.style = {}; // evt.detail contains the number of consecutive clicks - // for double-click, triple-click, etc... - // (note that evt.detail is not set when using pointerEvent) + evt.preventDefault(); + } + }, { + key: "_onSelectionDidChange", + value: function _onSelectionDidChange() { + // Every atom before the new caret position is now committed + this.mathlist.commitCommandStringBeforeInsertionPoint(); // If the selection is not collapsed, put it in the textarea + // This will allow cut/copy to work. + + var result = ''; + this.mathlist.forEachSelected(function (atom) { + result += atom.toLatex(); + }); - if (evt.detail === 3 || tapCount > 2) { - endPointerTracking(evt); + if (result) { + this.textarea.value = result; // The textarea may be a span (on mobile, for example), so check that + // it has a select() before calling it. - if (evt.detail === 3 || tapCount === 3) { - // This is a triple-click - this.mathlist.selectAll_(); + if (this.$hasFocus() && this.textarea.select) { + this.textarea.select(); } - } else if (!trackingPointer) { - trackingPointer = true; + } else { + this.textarea.value = ''; + this.textarea.setAttribute('aria-label', ''); + } // Update the mode - if (window.PointerEvent) { - on(that.field, 'pointermove', onPointerMove); - on(that.field, 'pointerend pointercancel pointerup', endPointerTracking); - that.field.setPointerCapture(evt.pointerId); - } else { - on(window, 'blur', endPointerTracking); - - if (evt.touches) { - // To receive the subsequent touchmove/touch, need to - // listen to this evt.target. - // This was a touch event - on(evt.target, 'touchmove', onPointerMove); - on(evt.target, 'touchend', endPointerTracking); - } else { - on(window, 'mousemove', onPointerMove); - on(window, 'mouseup', endPointerTracking); - } + + { + var previousMode = this.mode; + this.mode = this.mathlist.anchorMode() || this.config.defaultMode; + + if (this.mode !== previousMode && typeof this.config.onModeChange === 'function') { + this.config.onModeChange(this, this.mode); } - if (evt.detail === 2 || tapCount === 2) { - // This is a double-click - trackingWords = true; - this.mathlist.selectGroup_(); + if (previousMode === 'command' && this.mode !== 'command') { + _editorPopover.default.hidePopover(this); + + this.mathlist.removeCommandString(); } + } // Defer the updating of the popover position: we'll need the tree to be + // re-rendered first to get an updated caret position + + _editorPopover.default.updatePopoverPosition(this, { + deferred: true + }); // Invoke client handlers, if provided. + + + if (typeof this.config.onSelectionDidChange === 'function') { + this.config.onSelectionDidChange(this); } } - } else { - lastTap = null; - } + }, { + key: "_onContentDidChange", + value: function _onContentDidChange() { + if (this.undoManager.canRedo()) { + this.element.classList.add('can-redo'); + } else { + this.element.classList.remove('can-redo'); + } - if (dirty) this._requestUpdate(); // Prevent the browser from handling, in particular when this is a - // touch event prevent the synthetic mouseDown event from being generated + if (this.undoManager.canUndo()) { + this.element.classList.add('can-undo'); + } else { + this.element.classList.remove('can-undo'); + } - evt.preventDefault(); -}; + if (typeof this.config.onContentDidChange === 'function') { + this.config.onContentDidChange(this); + } + } + /* Returns the speech text of the next atom after the selection or + * an 'end of' phrasing based on what structure we are at the end of + */ -MathField.prototype._onSelectionDidChange = function () { - // Every atom before the new caret position is now committed - this.mathlist.commitCommandStringBeforeInsertionPoint(); // If the selection is not collapsed, put it in the textarea - // This will allow cut/copy to work. + }, { + key: "_nextAtomSpeechText", + value: function _nextAtomSpeechText(oldMathlist) { + function relation(parent, leaf) { + var EXPR_NAME = { + // 'array': 'should not happen', + 'numer': 'numerator', + 'denom': 'denominator', + 'index': 'index', + 'body': 'parent', + 'subscript': 'subscript', + 'superscript': 'superscript' + }; + var PARENT_NAME = { + 'enclose': 'cross out', + 'leftright': 'fence', + 'surd': 'square root', + 'root': 'math field' + }; + return leaf.relation === 'body' ? PARENT_NAME[parent.type] : EXPR_NAME[leaf.relation]; + } - var result = ''; - this.mathlist.forEachSelected(function (atom) { - result += atom.toLatex(); - }); + var oldPath = oldMathlist ? oldMathlist.path : []; + var path = this.mathlist.path; + var leaf = path[path.length - 1]; + var result = ''; - if (result) { - this.textarea.value = result; // The textarea may be a span (on mobile, for example), so check that - // it has a select() before calling it. + while (oldPath.length > path.length) { + result += 'out of ' + relation(oldMathlist.parent(), oldPath[oldPath.length - 1]) + '; '; + oldPath.pop(); + } - if (this.hasFocus() && this.textarea.select) { - this.textarea.select(); - } - } else { - this.textarea.value = ''; - this.textarea.setAttribute('aria-label', ''); - } // Update the mode + if (!this.mathlist.isCollapsed()) { + return speakableText(this, '', this.mathlist.getSelectedAtoms()); + } // announce start of denominator, etc - { - var previousMode = this.mode; - this.mode = this.mathlist.anchorMode() || this.config.defaultMode; + var relationName = relation(this.mathlist.parent(), leaf); - if (this.mode !== previousMode && typeof this.config.onModeChange === 'function') { - this.config.onModeChange(this, this.mode); - } + if (leaf.offset === 0) { + result += (relationName ? 'start of ' + relationName : 'unknown') + ': '; + } - if (previousMode === 'command' && this.mode !== 'command') { - _editorPopover.default.hidePopover(this); + var atom = this.mathlist.sibling(Math.max(1, this.mathlist.extent)); + + if (atom) { + result += speakableText(this, '', atom); + } else if (leaf.offset !== 0) { + // don't say both start and end + result += relationName ? 'end of ' + relationName : 'unknown'; + } - this.mathlist.removeCommandString(); + return result; + } + }, { + key: "_announce", + value: function _announce(command, mathlist, atoms) { + if (typeof this.config.onAnnounce === 'function') { + this.config.onAnnounce(this, command, mathlist, atoms); + } } - } // Defer the updating of the popover position: we'll need the tree to be - // re-rendered first to get an updated caret position + }, { + key: "_onFocus", + value: function _onFocus() { + if (this.blurred) { + this.blurred = false; // The textarea may be a span (on mobile, for example), so check that + // it has a focus() before calling it. + + if (this.textarea.focus) { + this.textarea.focus(); + } - _editorPopover.default.updatePopoverPosition(this, { - deferred: true - }); // Invoke client handlers, if provided. + if (this.config.virtualKeyboardMode === 'onfocus') { + this.showVirtualKeyboard_(); + } + _editorPopover.default.updatePopoverPosition(this); - if (typeof this.config.onSelectionDidChange === 'function') { - this.config.onSelectionDidChange(this); - } -}; + if (this.config.onFocus) { + this.config.onFocus(this); + } -MathField.prototype._onContentDidChange = function () { - if (this.undoManager.canRedo()) { - this.element.classList.add('can-redo'); - } else { - this.element.classList.remove('can-redo'); - } + this._requestUpdate(); + } + } + }, { + key: "_onBlur", + value: function _onBlur() { + if (!this.blurred) { + this.blurred = true; + this.ariaLiveText.textContent = ''; + + if (this.config.virtualKeyboardMode === 'onfocus') { + this.hideVirtualKeyboard_(); + } - if (this.undoManager.canUndo()) { - this.element.classList.add('can-undo'); - } else { - this.element.classList.remove('can-undo'); - } + this.complete_({ + discard: true + }); - if (typeof this.config.onContentDidChange === 'function') { - this.config.onContentDidChange(this); - } -}; -/* Returns the speech text of the next atom after the selection or - * an 'end of' phrasing based on what structure we are at the end of - */ + this._requestUpdate(); + + if (this.config.onBlur) { + this.config.onBlur(this); + } + } + } + }, { + key: "_onResize", + value: function _onResize() { + this.element.classList.remove('ML__isNarrowWidth', 'ML__isWideWidth', 'ML__isExtendedWidth'); + + if (window.innerWidth >= 1024) { + this.element.classList.add('ML__isExtendedWidth'); + } else if (window.innerWidth >= 768) { + this.element.classList.add('ML__isWideWidth'); + } else { + this.element.classList.add('ML__isNarrowWidth'); + } + _editorPopover.default.updatePopoverPosition(this); + } + }, { + key: "toggleKeystrokeCaption_", + value: function toggleKeystrokeCaption_() { + this.keystrokeCaptionVisible = !this.keystrokeCaptionVisible; + this.keystrokeCaption.innerHTML = ''; -MathField.prototype._nextAtomSpeechText = function (oldMathlist) { - function relation(parent, leaf) { - var EXPR_NAME = { - // 'array': 'should not happen', - 'numer': 'numerator', - 'denom': 'denominator', - 'index': 'index', - 'body': 'parent', - 'subscript': 'subscript', - 'superscript': 'superscript' - }; - var PARENT_NAME = { - 'enclose': 'cross out', - // FIX -- should base on type of enclose - 'leftright': 'fence', - 'surd': 'square root', - 'root': 'math field' - }; - return leaf.relation === 'body' ? PARENT_NAME[parent.type] : EXPR_NAME[leaf.relation]; - } + if (!this.keystrokeCaptionVisible) { + this.keystrokeCaption.style.visibility = 'hidden'; + } + } + }, { + key: "_showKeystroke", + value: function _showKeystroke(keystroke) { + var vb = this.keystrokeCaption; + + if (vb && this.keystrokeCaptionVisible) { + var bounds = this.element.getBoundingClientRect(); + vb.style.left = bounds.left + 'px'; + vb.style.top = bounds.top - 64 + 'px'; + vb.innerHTML = '' + (_editorShortcuts.default.stringify(keystroke) || keystroke) + '' + vb.innerHTML; + vb.style.visibility = 'visible'; + setTimeout(function () { + if (vb.childNodes.length > 0) { + vb.removeChild(vb.childNodes[vb.childNodes.length - 1]); + } - var oldPath = oldMathlist ? oldMathlist.path : []; - var path = this.mathlist.path; - var leaf = path[path.length - 1]; - var result = ''; + if (vb.childNodes.length === 0) { + vb.style.visibility = 'hidden'; + } + }, 3000); + } + } + /** + * @param {string|string[]} command - A selector, or an array whose first element + * is a selector, and whose subsequent elements are arguments to the selector. + * Note that selectors do not include a final "_". They can be passed either + * in camelCase or kebab-case. So: + * ```javascript + * mf.$perform('selectAll'); + * mf.$perform('select-all'); + * ``` + * both calls are valid and invoke the same selector. + * + * @method MathField#$perform + */ - while (oldPath.length > path.length) { - result += 'out of ' + relation(oldMathlist.parent(), oldPath[oldPath.length - 1]) + '; '; - oldPath.pop(); - } + }, { + key: "$perform", + value: function $perform(command) { + if (!command) { + return false; + } - if (!this.mathlist.isCollapsed()) { - return speakableText(this, '', this.mathlist.getSelectedAtoms()); - } // announce start of denominator, etc + var handled = false; + var selector; + var args = []; + var dirty = false; + if (Array.isArray(command)) { + selector = command[0]; + args = command.slice(1); + } else { + selector = command; + } // Convert kebab case (like-this) to camel case (likeThis). - var relationName = relation(this.mathlist.parent(), leaf); - if (leaf.offset === 0) { - result += (relationName ? 'start of ' + relationName : 'unknown') + ': '; - } + selector = selector.replace(/-\w/g, function (m) { + return m[1].toUpperCase(); + }); + selector += '_'; - var atom = this.mathlist.sibling(Math.max(1, this.mathlist.extent)); + if (typeof this.mathlist[selector] === 'function') { + var _this$mathlist; - if (atom) { - result += speakableText(this, '', atom); - } else if (leaf.offset !== 0) { - // don't say both start and end - result += relationName ? 'end of ' + relationName : 'unknown'; - } + if (/^(delete|transpose|add)/.test(selector)) { + this._resetKeystrokeBuffer(); + } - return result; -}; + if (/^(delete|transpose|add)/.test(selector) && this.mode !== 'command') { + // Update the undo state to account for the current selection + this.undoManager.pop(); + this.undoManager.snapshot(this.config); + } -function speakableText(mathfield, prefix, atoms) { - var config = Object.assign({}, mathfield.config); - config.textToSpeechMarkup = ''; - return prefix + _mathAtom.default.toSpeakableText(atoms, config); -} -/** - * Announce a change in selection or content via the aria-live region. - * This is the default implementation for this function. It can be overridden - * via `config.onAnnounce` - * @param {object} target typically, a MathField - * @param {string} command the command that invoked the change - * @param {Atom[]} [oldMathlist=[]] the previous value of mathlist before the change - * @param {Atom[]} [atomsToSpeak=[] ] - * @method MathField#_onAnnounce - * @private - */ + (_this$mathlist = this.mathlist)[selector].apply(_this$mathlist, _toConsumableArray(args)); + if (/^(delete|transpose|add)/.test(selector) && this.mode !== 'command') { + this.undoManager.snapshot(this.config); + } -function _onAnnounce(target, command, oldMathlist, atomsToSpeak) { - //** Fix: the focus is the end of the selection, so it is before where we want it - var liveText = ''; // const command = moveAmount > 0 ? "right" : "left"; + if (/^(delete)/.test(selector) && this.mode === 'command') { + var _command = this.mathlist.extractCommandStringAroundInsertionPoint(); - if (command === 'plonk') { - // Use this sound to indicate (minor) errors, for - // example when a command has no effect. - if (target.plonkSound) { - target.plonkSound.load(); - target.plonkSound.play().catch(function (err) { - return console.warn(err); - }); - } // As a side effect, reset the keystroke buffer + var suggestions = _definitions.default.suggest(_command); + if (suggestions.length === 0) { + _editorPopover.default.hidePopover(this); + } else { + _editorPopover.default.showPopoverWithLatex(this, suggestions[0].match, suggestions.length > 1); + } + } - target._resetKeystrokeBuffer(); - } else if (command === 'delete') { - liveText = speakableText(target, 'deleted: ', atomsToSpeak); //*** FIX: could also be moveUp or moveDown -- do something different like provide context??? - } else if (command === 'focus' || /move/.test(command)) { - //*** FIX -- should be xxx selected/unselected */ - liveText = (target.mathlist.isCollapsed() ? '' : 'selected: ') + target._nextAtomSpeechText(oldMathlist); - } else if (command === 'replacement') { - // announce the contents - liveText = speakableText(target, '', target.mathlist.sibling(0)); - } else if (command === 'line') { - // announce the current line -- currently that's everything - liveText = speakableText(target, '', target.mathlist.root); - target.accessibleNode.innerHTML = '' + _mathAtom.default.toMathML(target.mathlist.root, target.config) + ''; - target.textarea.setAttribute('aria-label', 'after: ' + liveText); - /*** FIX -- testing hack for setting braille ***/ - // target.accessibleNode.focus(); - // console.log("before sleep"); - // sleep(1000).then(() => { - // target.textarea.focus(); - // console.log("after sleep"); - // }); - } else { - liveText = atomsToSpeak ? speakableText(target, command + " ", atomsToSpeak) : command; - } // aria-live regions are only spoken when it changes; force a change by - // alternately using nonbreaking space or narrow nonbreaking space + dirty = true; + handled = true; + } else if (typeof this[selector] === 'function') { + dirty = this[selector].apply(this, _toConsumableArray(args)); + handled = true; + } // If the command changed the selection so that it is no longer + // collapsed, or if it was an editing command, reset the inline + // shortcut buffer and the user style - var ariaLiveChangeHack = /\u00a0/.test(target.ariaLiveText.textContent) ? " \u202F " : " \xA0 "; - target.ariaLiveText.textContent = liveText + ariaLiveChangeHack; // this.textarea.setAttribute('aria-label', liveText + ariaLiveChangeHack); -} + if (!this.mathlist.isCollapsed() || /^(transpose|paste|complete|((moveToNextChar|moveToPreviousChar|extend).*))_$/.test(selector)) { + this._resetKeystrokeBuffer(); -MathField.prototype._announce = function (command, mathlist, atoms) { - if (typeof this.config.onAnnounce === 'function') { - this.config.onAnnounce(this, command, mathlist, atoms); - } -}; + this.style = {}; + } // Render the mathlist -MathField.prototype._onFocus = function () { - if (this.blurred) { - this.blurred = false; // The textarea may be a span (on mobile, for example), so check that - // it has a focus() before calling it. - if (this.textarea.focus) this.textarea.focus(); + if (dirty) { + this._requestUpdate(); + } - if (this.config.virtualKeyboardMode === 'onfocus') { - this.showVirtualKeyboard_(); + return handled; } + /** + * Perform a command, but: + * * focus the mathfield + * * provide haptic and audio feedback + * This is used by the virtual keyboard when command keys (delete, arrows, etc..) + * are pressed. + * @param {string} command + * @private + */ - _editorPopover.default.updatePopoverPosition(this); - - if (this.config.onFocus) this.config.onFocus(this); + }, { + key: "performWithFeedback_", + value: function performWithFeedback_(command) { + this.$focus(); - this._requestUpdate(); - } -}; + if (this.config.keypressVibration && navigator.vibrate) { + navigator.vibrate(HAPTIC_FEEDBACK_DURATION); + } // Convert kebab case to camel case. -MathField.prototype._onBlur = function () { - if (!this.blurred) { - this.blurred = true; - this.ariaLiveText.textContent = ''; - if (this.config.virtualKeyboardMode === 'onfocus') { - this.hideVirtualKeyboard_(); - } + command = command.replace(/-\w/g, function (m) { + return m[1].toUpperCase(); + }); - this.complete_({ - discard: true - }); + if (command === 'moveToNextPlaceholder' || command === 'moveToPreviousPlaceholder' || command === 'complete') { + if (this.returnKeypressSound) { + this.returnKeypressSound.load(); + this.returnKeypressSound.play().catch(function (err) { + return console.warn(err); + }); + } else if (this.keypressSound) { + this.keypressSound.load(); + this.keypressSound.play().catch(function (err) { + return console.warn(err); + }); + } + } else if (command === 'deletePreviousChar' || command === 'deleteNextChar' || command === 'deletePreviousWord' || command === 'deleteNextWord' || command === 'deleteToGroupStart' || command === 'deleteToGroupEnd' || command === 'deleteToMathFieldStart' || command === 'deleteToMathFieldEnd') { + if (this.deleteKeypressSound) { + this.deleteKeypressSound.load(); + this.deleteKeypressSound.play().catch(function (err) { + return console.warn(err); + }); + } else if (this.keypressSound) { + this.keypressSound.load(); + this.keypressSound.play().catch(function (err) { + return console.warn(err); + }); + } + } else if (this.keypressSound) { + this.keypressSound.load(); + this.keypressSound.play().catch(function (err) { + return console.warn(err); + }); + } - this._requestUpdate(); + return this.$perform(command); + } + /** + * Convert the atoms before the anchor to 'text' mode + * @param {number} count - how many atoms back to look at + * @param {function} until - callback to indicate when to stop + * @private + */ - if (this.config.onBlur) this.config.onBlur(this); - } -}; + }, { + key: "convertLastAtomsToText_", + value: function convertLastAtomsToText_(count, until) { + if (typeof count === 'function') { + until = count; + count = Infinity; + } -MathField.prototype._onResize = function () { - this.element.classList.remove('ML__isNarrowWidth', 'ML__isWideWidth', 'ML__isExtendedWidth'); + if (count === undefined) { + count = Infinity; + } - if (window.innerWidth >= 1024) { - this.element.classList.add('ML__isExtendedWidth'); - } else if (window.innerWidth >= 768) { - this.element.classList.add('ML__isWideWidth'); - } else { - this.element.classList.add('ML__isNarrowWidth'); - } + var i = 0; + var done = false; + this.mathlist.contentWillChange(); - _editorPopover.default.updatePopoverPosition(this); -}; + while (!done) { + var atom = this.mathlist.sibling(i); + done = count === 0 || !atom || atom.mode !== 'math' || !(/mord|textord|mpunct/.test(atom.type) || atom.type === 'mop' && /[a-zA-Z]+/.test(atom.body)) || atom.superscript || atom.subscript || until && !until(atom); -MathField.prototype.toggleKeystrokeCaption_ = function () { - this.keystrokeCaptionVisible = !this.keystrokeCaptionVisible; - this.keystrokeCaption.innerHTML = ''; + if (!done) { + atom.applyStyle({ + mode: 'text' + }); + atom.latex = atom.body; + } - if (!this.keystrokeCaptionVisible) { - this.keystrokeCaption.style.visibility = 'hidden'; - } -}; + i -= 1; + count -= 1; + } -MathField.prototype._showKeystroke = function (keystroke) { - var vb = this.keystrokeCaption; + this.mathlist.contentDidChange(); + } + /** + * Convert the atoms before the anchor to 'math' mode 'mord' + * @param {number} count - how many atoms back to look at + * @param {function} until - callback to indicate when to stop + * @private + */ - if (vb && this.keystrokeCaptionVisible) { - var bounds = this.element.getBoundingClientRect(); - vb.style.left = bounds.left + 'px'; - vb.style.top = bounds.top - 64 + 'px'; - vb.innerHTML = '' + (_editorShortcuts.default.stringify(keystroke) || keystroke) + '' + vb.innerHTML; - vb.style.visibility = 'visible'; - setTimeout(function () { - if (vb.childNodes.length > 0) { - vb.removeChild(vb.childNodes[vb.childNodes.length - 1]); + }, { + key: "convertLastAtomsToMath_", + value: function convertLastAtomsToMath_(count, until) { + if (typeof count === 'function') { + until = count; + count = Infinity; } - if (vb.childNodes.length === 0) { - vb.style.visibility = 'hidden'; + if (count === undefined) { + count = Infinity; } - }, 3000); - } -}; -/** - * @param {string|string[]} command - A selector, or an array whose first element - * is a selector, and whose subsequent elements are arguments to the selector. - * Note that selectors do not include a final "_". They can be passed either - * in camelCase or kebab-case. So: - * ```javascript - * mf.$perform('selectAll'); - * mf.$perform('select-all'); - * ``` - * both calls are valid and invoke the same selector. - * - * @method MathField#$perform - */ + this.mathlist.contentWillChange(); + var i = 0; + var done = false; -MathField.prototype.perform = MathField.prototype.$perform = function (command) { - if (!command) return false; - var handled = false; - var selector; - var args = []; - var dirty = false; + while (!done) { + var atom = this.mathlist.sibling(i); + done = count === 0 || !atom || atom.mode !== 'text' || atom.body === ' ' || until && !until(atom); - if (Array.isArray(command)) { - selector = command[0]; - args = command.slice(1); - } else { - selector = command; - } // Convert kebab case (like-this) to camel case (likeThis). - - - selector = selector.replace(/-\w/g, function (m) { - return m[1].toUpperCase(); - }); - selector += '_'; + if (!done) { + atom.applyStyle({ + mode: 'math', + type: 'mord' + }); + } - if (typeof this.mathlist[selector] === 'function') { - var _this$mathlist; + i -= 1; + count -= 1; + } - if (/^(delete|transpose|add)/.test(selector)) { - this._resetKeystrokeBuffer(); + this.removeIsolatedSpace_(); + this.mathlist.contentDidChange(); } + /** + * Going backwards from the anchor, if a text zone consisting of a single + * space character is found (i.e. it is surrounded by math zone), + * remove it. + * @private + */ - if (/^(delete|transpose|add)/.test(selector) && this.mode !== 'command') { - // Update the undo state to account for the current selection - this.undoManager.pop(); - this.undoManager.snapshot(this.config); + }, { + key: "removeIsolatedSpace_", + value: function removeIsolatedSpace_() { + var i = 0; + + while (this.mathlist.sibling(i) && this.mathlist.sibling(i).mode === 'math') { + i -= 1; + } // If the atom before the last one converted is a + // text mode space, preceded by a math mode atom, + // remove the space + + + if (this.mathlist.sibling(i) && this.mathlist.sibling(i).mode === 'text' && this.mathlist.sibling(i).body === ' ' && (!this.mathlist.sibling(i - 1) || this.mathlist.sibling(i - 1).mode === 'math')) { + this.mathlist.contentWillChange(); + this.mathlist.siblings().splice(i - 1, 1); + this.mathlist.contentDidChange(); // We need to adjust the selection after doing some surgery on the atoms list + // But we don't want to receive selection notification changes + // which could have a side effect of changing the mode :( + + var save = this.mathlist.suppressChangeNotifications; + this.mathlist.suppressChangeNotifications = true; + this.mathlist.setSelection(this.mathlist.anchorOffset() - 1); + this.mathlist.suppressChangeNotifications = save; + } } + /** + * Return the characters before anchor that could potentially be turned + * into text mode. + * This excludes things like 'mop' (e.g. \sin) + * @return {string} + * @method MathField#getTextBeforeAnchor_ + * @private + */ - (_this$mathlist = this.mathlist)[selector].apply(_this$mathlist, _toConsumableArray(args)); + }, { + key: "getTextBeforeAnchor_", + value: function getTextBeforeAnchor_() { + // Going backwards, accumulate + var result = ''; + var i = 0; + var done = false; + + while (!done) { + var atom = this.mathlist.sibling(i); + done = !(atom && (atom.mode === 'text' && !atom.type || atom.mode === 'math' && /mord|textord|mpunct/.test(atom.type))); + + if (!done) { + result = atom.body + result; + } + + i -= 1; + } - if (/^(delete|transpose|add)/.test(selector) && this.mode !== 'command') { - this.undoManager.snapshot(this.config); + return result; } + /** + * Consider whether to switch mode give the content before the anchor + * and the character being input + * + * @param {string} keystroke + * @param {Event} evt - a Event corresponding to the keystroke + * @method MathField#smartMode_ + * @return {boolean} true if the mode should change + * @private + */ - if (/^(delete)/.test(selector) && this.mode === 'command') { - var _command = this.mathlist.extractCommandStringAroundInsertionPoint(); + }, { + key: "smartMode_", + value: function smartMode_(keystroke, evt) { + if (this.smartModeSuppressed) { + return false; + } - var suggestions = _definitions.default.suggest(_command); + if (this.mathlist.endOffset() < this.mathlist.siblings().length - 1) { + return false; + } - if (suggestions.length === 0) { - _editorPopover.default.hidePopover(this); - } else { - _editorPopover.default.showPopoverWithLatex(this, suggestions[0].match, suggestions.length > 1); + if (!evt || evt.ctrlKey || evt.metaKey) { + return false; } - } - dirty = true; - handled = true; - } else if (typeof this[selector] === 'function') { - dirty = this[selector].apply(this, _toConsumableArray(args)); - handled = true; - } // If the command changed the selection so that it is no longer - // collapsed, or if it was an editing command, reset the inline - // shortcut buffer and the user style + var c = _editorKeyboard.default.eventToChar(evt); + if (c.length > 1) { + return false; + } // Backspace, Left, etc... - if (!this.mathlist.isCollapsed() || /^(transpose|paste|complete|((moveToNextChar|moveToPreviousChar|extend).*))_$/.test(selector)) { - this._resetKeystrokeBuffer(); - this.style = {}; - } // Render the mathlist + if (!this.mathlist.isCollapsed()) { + // There is a selection + if (this.mode === 'text') { + if (/[/_^]/.test(c)) { + return true; + } + } + return false; + } - if (dirty) this._requestUpdate(); - return handled; -}; -/** - * Perform a command, but: - * * focus the mathfield - * * provide haptic and audio feedback - * This is used by the virtual keyboard when command keys (delete, arrows, etc..) - * are pressed. - * @param {string} command - */ + var context = this.getTextBeforeAnchor_() + c; + if (this.mode === 'text') { + // We're in text mode. Should we switch to math? + if (keystroke === 'Esc' || /[/\\]/.test(c)) { + // If this is a command for a fraction, + // or the '\' command mode key + // switch to 'math' + return true; + } -MathField.prototype.performWithFeedback_ = function (command) { - this.focus(); + if (/[\^_]/.test(c)) { + // If this is a superscript or subscript + // switch to 'math' + if (/(^|\s)[a-zA-Z][^_]$/.test(context)) { + // If left hand context is a single letter, + // convert it to math + this.convertLastAtomsToMath_(1); + } - if (this.config.keypressVibration && navigator.vibrate) { - navigator.vibrate(HAPTIC_FEEDBACK_DURATION); - } // Convert kebab case to camel case. + return true; + } // If this is a closing matching fence + // switch to 'math' mode - command = command.replace(/-\w/g, function (m) { - return m[1].toUpperCase(); - }); + var lFence = { + ')': '(', + '}': '{', + ']': '[' + }[c]; - if (command === 'moveToNextPlaceholder' || command === 'moveToPreviousPlaceholder' || command === 'complete') { - if (this.returnKeypressSound) { - this.returnKeypressSound.load(); - this.returnKeypressSound.play().catch(function (err) { - return console.warn(err); - }); - } else if (this.keypressSound) { - this.keypressSound.load(); - this.keypressSound.play().catch(function (err) { - return console.warn(err); - }); - } - } else if (command === 'deletePreviousChar' || command === 'deleteNextChar' || command === 'deletePreviousWord' || command === 'deleteNextWord' || command === 'deleteToGroupStart' || command === 'deleteToGroupEnd' || command === 'deleteToMathFieldStart' || command === 'deleteToMathFieldEnd') { - if (this.deleteKeypressSound) { - this.deleteKeypressSound.load(); - this.deleteKeypressSound.play().catch(function (err) { - return console.warn(err); - }); - } else if (this.keypressSound) { - this.keypressSound.load(); - this.keypressSound.play().catch(function (err) { - return console.warn(err); - }); - } - } else if (this.keypressSound) { - this.keypressSound.load(); - this.keypressSound.play().catch(function (err) { - return console.warn(err); - }); - } + if (lFence && this.mathlist.parent() && this.mathlist.parent().type === 'leftright' && this.mathlist.parent().leftDelim === lFence) { + return true; + } - return this.perform(command); -}; -/** - * Convert the atoms before the anchor to 'text' mode - * @param {number} count - how many atoms back to look at - * @param {function} until - callback to indicate when to stop - */ + if (/(^|[^a-zA-Z])(a|I)[ ]$/.test(context)) { + // Single letters that are valid words in the current language + // Do nothing. @todo: localization + return false; + } + if (/[\$\xA2-\xA5\u0E3F\u20A1\u20A4\u20A7-\u20A9\u20AC\u20B1\u20B9\u20BA]/.test(c)) { + // A currency symbol. + // Switch to math mode + return true; + } -MathField.prototype.convertLastAtomsToText_ = function (count, until) { - if (typeof count === 'function') { - until = count; - count = Infinity; - } + if (/(^|[^a-zA-Z'’])[a-zA-Z][ ]$/.test(context)) { + // An isolated letter, followed by a space: + // Convert the letter to math, stay in text mode. + this.convertLastAtomsToMath_(1); + return false; + } - if (count === undefined) count = Infinity; - var i = 0; - var done = false; - this.mathlist.contentWillChange(); + if (/[^0-9]\.[^0-9\s]$/.test(context)) { + // A period followed by something other than space or a digit + // and not preceded by a digit. + // We thought this was a text period, but turns out it's not + // Turn it into a \cdot + this.convertLastAtomsToMath_(1); + var atom = this.mathlist.sibling(0); + atom.body = '⋅'; // centered dot + + atom.autoFontFamily = 'cmr'; + atom.latex = '\\cdot'; + return true; + } - while (!done) { - var atom = this.mathlist.sibling(i); - done = count === 0 || !atom || atom.mode !== 'math' || !(/mord|textord|mpunct/.test(atom.type) || atom.type === 'mop' && /[a-zA-Z]+/.test(atom.body)) || atom.superscript || atom.subscript || until && !until(atom); + if (/(^|\s)[a-zA-Z][^a-zA-Z]$/.test(context)) { + // Single letter (x), followed by a non-letter (>, =...) + this.convertLastAtomsToMath_(1); + return true; + } - if (!done) { - atom.applyStyle({ - mode: 'text' - }); - atom.latex = atom.body; - } + if (/\.[0-9]$/.test(context)) { + // If the new character is a digit, + // and it was preceded by a dot (which may have been converted + // to text) + // turn the dot back into 'math' + this.convertLastAtomsToMath_(1); + return true; + } - i -= 1; - count -= 1; - } + if (/[(][0-9+\-.]$/.test(context)) { + // An open paren followed by a number + // Turn the paren back to math and switch. + this.convertLastAtomsToMath_(1); + return true; + } - this.mathlist.contentDidChange(); -}; -/** - * Convert the atoms before the anchor to 'math' mode 'mord' - * @param {number} count - how many atoms back to look at - * @param {function} until - callback to indicate when to stop - */ + if (/[(][a-z][,;]$/.test(context)) { + // An open paren followed by a single letter, then a "," or ";" + // Turn the paren back and letter to math and switch. + this.convertLastAtomsToMath_(2); + return true; + } // The tests above can look behind and change what had previously + // been entered. Now, let's just look at the typed character. + + + if (/[0-9+\-=><*|]$/.test(c)) { + // If this new character looks like a number, + // or a relational operator (=, <, >) + // or a "*" or "|" + // (note that <=, >=, etc... are handled separately as shortcuts) + // switch to 'math' + this.removeIsolatedSpace_(); + return true; + } + } else { + // We're in math mode. Should we switch to text? + if (keystroke === 'Spacebar') { + this.convertLastAtomsToText_(function (a) { + return /[a-z][:,;.]$/.test(a.body); + }); + return true; + } + if (/[a-zA-Z]{3,}$/.test(context) && !/(dxd|abc|xyz|uvw)$/.test(context)) { + // A sequence of three characters + // (except for some exceptions) + // Convert them to text. + this.convertLastAtomsToText_(function (a) { + return /[a-zA-Z:,;.]/.test(a.body); + }); + return true; + } -MathField.prototype.convertLastAtomsToMath_ = function (count, until) { - if (typeof count === 'function') { - until = count; - count = Infinity; - } + if (/(^|\W)(if|If)$/i.test(context)) { + // @todo localization + this.convertLastAtomsToText_(1); + return true; + } - if (count === undefined) count = Infinity; - this.mathlist.contentWillChange(); - var i = 0; - var done = false; + if (/(\u0393|\u0394|\u0398|\u039B|\u039E|\u03A0|\u03A3|\u03A5|\u03A6|\u03A8|\u03A9|[\u03B1-\u03C9]|\u03D1|\u03D5|\u03D6|\u03F1|\u03F5){3,}$/.test(context) && !/(αβγ)$/.test(context)) { + // A sequence of three *greek* characters + // (except for one exception) + // Convert them to text. + this.convertLastAtomsToText_(function (a) { + return /(:|,|;|(?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])|\u0393|\u0394|\u0398|\u039B|\u039E|\u03A0|\u03A3|\u03A5|\u03A6|\u03A8|\u03A9|[\u03B1-\u03C9]|\u03D1|\u03D5|\u03D6|\u03F1|\u03F5)/.test(a.body); + }); + return true; + } - while (!done) { - var atom = this.mathlist.sibling(i); - done = count === 0 || !atom || atom.mode !== 'text' || atom.body === ' ' || until && !until(atom); + if (/\?|\./.test(c)) { + // If the last character is a period or question mark, + // turn it to 'text' + return true; + } + } - if (!done) { - atom.applyStyle({ - mode: 'math', - type: 'mord' - }); + return false; } + /** + * @param {string} keystroke + * @param {Event} evt - optional, an Event corresponding to the keystroke + * @method MathField#_onKeystroke + * @private + */ - i -= 1; - count -= 1; - } - - this.removeIsolatedSpace_(); - this.mathlist.contentDidChange(); -}; -/** - * Going backwards from the anchor, if a text zone consisting of a single - * space character is found (i.e. it is surrounded by math zone), - * remove it. - */ - + }, { + key: "_onKeystroke", + value: function _onKeystroke(keystroke, evt) { + var _this3 = this; -MathField.prototype.removeIsolatedSpace_ = function () { - var i = 0; + // 1. Display the keystroke in the keystroke panel (if visible) + this._showKeystroke(keystroke); // 2. Reset the timer for the keystroke buffer reset - while (this.mathlist.sibling(i) && this.mathlist.sibling(i).mode === 'math') { - i -= 1; - } // If the atom before the last one converted is a - // text mode space, preceded by a math mode atom, - // remove the space + clearTimeout(this.keystrokeBufferResetTimer); // 3. Give a chance to the custom keystroke handler to intercept the event - if (this.mathlist.sibling(i) && this.mathlist.sibling(i).mode === 'text' && this.mathlist.sibling(i).body === ' ' && (!this.mathlist.sibling(i - 1) || this.mathlist.sibling(i - 1).mode === 'math')) { - this.mathlist.contentWillChange(); - this.mathlist.siblings().splice(i - 1, 1); - this.mathlist.contentDidChange(); // We need to adjust the selection after doing some surgery on the atoms list - // But we don't want to receive selection notification changes - // which could have a side effect of changing the mode :( + if (this.config.onKeystroke && !this.config.onKeystroke(this, keystroke, evt)) { + if (evt && evt.preventDefault) { + evt.preventDefault(); + evt.stopPropagation(); + } - var save = this.mathlist.suppressChangeNotifications; - this.mathlist.suppressChangeNotifications = true; - this.mathlist.setSelection(this.mathlist.anchorOffset() - 1); - this.mathlist.suppressChangeNotifications = save; - } -}; -/** - * Return the characters before anchor that could potentially be turned - * into text mode. - * This excludes things like 'mop' (e.g. \sin) - * @return {string} - * @method MathField#getTextBeforeAnchor_ - * @private - */ + return false; + } // 4. Let's try to find a matching shortcut or command -MathField.prototype.getTextBeforeAnchor_ = function () { - // Going backwards, accumulate - var result = ''; - var i = 0; - var done = false; + var shortcut; + var stateIndex; + var selector; + var resetKeystrokeBuffer = false; // 4.1 Check if the keystroke, prefixed with the previously typed keystrokes, + // would match a long shortcut (i.e. '~~') + // Ignore the key if command or control is pressed (it may be a shortcut, + // see 4.3) - while (!done) { - var atom = this.mathlist.sibling(i); - done = !(atom && (atom.mode === 'text' && !atom.type || atom.mode === 'math' && /mord|textord|mpunct/.test(atom.type))); + if (this.mode !== 'command' && (!evt || !evt.ctrlKey && !evt.metaKey)) { + var c = _editorKeyboard.default.eventToChar(evt); // The Backspace key will be handled as a delete command later (5.4) + // const c = Keyboard.eventToChar(evt); - if (!done) { - result = atom.body + result; - } - i -= 1; - } + if (c !== 'Backspace') { + if (!c || c.length > 1) { + // It was a non-alpha character (PageUp, End, etc...) + this._resetKeystrokeBuffer(); + } else { + // Find the longest substring that matches a shortcut + var candidate = this.keystrokeBuffer + c; + var i = 0; + + while (!shortcut && i < candidate.length) { + var siblings = void 0; + + if (this.keystrokeBufferStates[i]) { + var mathlist = new _editorEditableMathlist.default.EditableMathlist(); + mathlist.root = _mathAtom.default.makeRoot('math', _parser.default.parseTokens(_lexer.default.tokenize(this.keystrokeBufferStates[i].latex), this.config.default, null, this.config.macros)); + mathlist.setPath(this.keystrokeBufferStates[i].selection); + siblings = mathlist.siblings(); + } else { + siblings = this.mathlist.siblings(); + } - return result; -}; -/** - * Consider whether to switch mode give the content before the anchor - * and the character being input - * - * @param {string} keystroke - * @param {Event} evt - a Event corresponding to the keystroke - * @method MathField#smartMode_ - * @return {boolean} true if the mode should change - * @private - */ + shortcut = _editorShortcuts.default.forString(this.mode, siblings, candidate.slice(i), this.config); + i += 1; + } + stateIndex = i - 1; + this.keystrokeBuffer += c; + this.keystrokeBufferStates.push(this.undoManager.save()); -MathField.prototype.smartMode_ = function (keystroke, evt) { - if (this.smartModeSuppressed) return false; - if (this.mathlist.endOffset() < this.mathlist.siblings().length - 1) return false; - if (!evt || evt.ctrlKey || evt.metaKey) return false; + if (_editorShortcuts.default.startsWithString(candidate, this.config).length <= 1) { + resetKeystrokeBuffer = true; + } else { + if (this.config.inlineShortcutTimeout) { + // Set a timer to reset the shortcut buffer + this.keystrokeBufferResetTimer = setTimeout(function () { + _this3._resetKeystrokeBuffer(); + }, this.config.inlineShortcutTimeout); + } + } + } + } + } // 4.2. Should we switch mode? + // Need to check this before determing if there's a valid shortcut + // since if we switch to math mode, we may want to apply the shortcut + // e.g. "slope = rise/run" + + + if (this.config.smartMode) { + var previousMode = this.mode; + + if (shortcut) { + // If we found a shortcut (e.g. "alpha"), + // switch to math mode and insert it + this.mode = 'math'; + } else if (this.smartMode_(keystroke, evt)) { + this.mode = { + 'math': 'text', + 'text': 'math' + }[this.mode]; + selector = ''; + } // Notify of mode change + + + if (this.mode !== previousMode && typeof this.config.onModeChange === 'function') { + this.config.onModeChange(this, this.mode); + } + } // 4.3 Check if this matches a keystroke shortcut + // Need to check this **after** checking for inline shortcuts because + // shift+backquote is a keystroke that inserts "\~"", but "~~" is a + // shortcut for "\approx" and needs to have priority over shift+backquote - var c = _editorKeyboard.default.eventToChar(evt); - if (c.length > 1) return false; // Backspace, Left, etc... + if (!shortcut && !selector) { + selector = _editorShortcuts.default.selectorForKeystroke(this.mode, keystroke); + } // No shortcut :( We're done. - if (!this.mathlist.isCollapsed()) { - // There is a selection - if (this.mode === 'text') { - if (/[/_^]/.test(c)) return true; - } - return false; - } + if (!shortcut && !selector) { + return true; + } // 5. Perform the action matching this shortcut + // 5.1 Remove any error indicator (wavy underline) on the current command + // sequence (if there are any) - var context = this.getTextBeforeAnchor_() + c; - if (this.mode === 'text') { - // We're in text mode. Should we switch to math? - if (keystroke === 'Esc' || /[/\\]/.test(c)) { - // If this is a command for a fraction, - // or the '\' command mode key - // switch to 'math' - return true; - } + this.mathlist.decorateCommandStringAroundInsertionPoint(false); // 5.2 If we have a `moveAfterParent` selector (usually triggered with + // `spacebar), and we're at the end of a smart fence, close the fence with + // an empty (.) right delimiter - if (/[\^_]/.test(c)) { - // If this is a superscript or subscript - // switch to 'math' - if (/(^|\s)[a-zA-Z][^_]$/.test(context)) { - // If left hand context is a single letter, - // convert it to math - this.convertLastAtomsToMath_(1); - } + var parent = this.mathlist.parent(); - return true; - } // If this is a closing matching fence - // switch to 'math' mode + if (selector === 'moveAfterParent' && parent && parent.type === 'leftright' && this.mathlist.endOffset() === this.mathlist.siblings().length - 1 && this.config.smartFence && this.mathlist._insertSmartFence('.')) { + // Pressing the space bar (moveAfterParent selector) when at the end + // of a potential smartfence will close it as a semi-open fence + selector = ''; + this._requestUpdate(); // Re-render the closed smartfence - var lFence = { - ')': '(', - '}': '{', - ']': '[' - }[c]; + } // 5.3 If this is the Spacebar and we're just before or right after + // a text zone, insert the space inside the text zone - if (lFence && this.mathlist.parent() && this.mathlist.parent().type === 'leftright' && this.mathlist.parent().leftDelim === lFence) { - return true; - } - if (/(^|[^a-zA-Z])(a|I)[ ]$/.test(context)) { - // Single letters that are valid words in the current language - // Do nothing. @todo: localization - return false; - } + if (this.mode === 'math' && keystroke === 'Spacebar' && !shortcut) { + var nextSibling = this.mathlist.sibling(1); + var previousSibling = this.mathlist.sibling(-1); - if (/[\$\xA2-\xA5\u0E3F\u20A1\u20A4\u20A7-\u20A9\u20AC\u20B1\u20B9\u20BA]/.test(c)) { - // A currency symbol. - // Switch to math mode - return true; - } + if (nextSibling && nextSibling.mode === 'text' || previousSibling && previousSibling.mode === 'text') { + this.mathlist.insert(' ', { + mode: 'text' + }); + } + } // 5.4 If there's a selector, perform it. + + + if (selector && !this.$perform(selector) || shortcut) { + // // 6.5 insert the shortcut + if (shortcut) { + // If the shortcut is a mandatory escape sequence (\}, etc...) + // don't make it undoable, this would result in syntactically incorrect + // formulas + if (!/^(\\{|\\}|\\[|\\]|\\@|\\#|\\$|\\%|\\^|\\_|\\backslash)$/.test(shortcut)) { + // To enable the substitution to be undoable, + // insert the character before applying the substitution + var _style = _objectSpread({}, this.mathlist.anchorStyle(), {}, this.style); + + this.mathlist.insert(_editorKeyboard.default.eventToChar(evt), { + suppressChangeNotifications: true, + mode: this.mode, + style: _style + }); + var saveMode = this.mode; // Create a snapshot with the inserted character - if (/(^|[^a-zA-Z'’])[a-zA-Z][ ]$/.test(context)) { - // An isolated letter, followed by a space: - // Convert the letter to math, stay in text mode. - this.convertLastAtomsToMath_(1); - return false; - } + this.undoManager.snapshotAndCoalesce(this.config); // Revert to the state before the beginning of the shortcut + // (restore doesn't change the undo stack) - if (/[^0-9]\.[^0-9\s]$/.test(context)) { - // A period followed by something other than space or a digit - // and not preceded by a digit. - // We thought this was a text period, but turns out it's not - // Turn it into a \cdot - this.convertLastAtomsToMath_(1); - var atom = this.mathlist.sibling(0); - atom.body = '⋅'; // centered dot + this.undoManager.restore(this.keystrokeBufferStates[stateIndex], _objectSpread({}, this.config, { + suppressChangeNotifications: true + })); + this.mode = saveMode; + } - atom.autoFontFamily = 'cmr'; - atom.latex = '\\cdot'; - return true; - } + this.mathlist.contentWillChange(); + var save = this.mathlist.suppressChangeNotifications; + this.mathlist.suppressChangeNotifications = true; // Insert the substitute, possibly as a smart fence - if (/(^|\s)[a-zA-Z][^a-zA-Z]$/.test(context)) { - // Single letter (x), followed by a non-letter (>, =...) - this.convertLastAtomsToMath_(1); - return true; - } + var style = _objectSpread({}, this.mathlist.anchorStyle(), {}, this.style); - if (/\.[0-9]$/.test(context)) { - // If the new character is a digit, - // and it was preceded by a dot (which may have been converted - // to text) - // turn the dot back into 'math' - this.convertLastAtomsToMath_(1); - return true; - } + this.mathlist.insert(shortcut, { + format: 'latex', + mode: this.mode, + style: style, + smartFence: true + }); // Check if as a result of the substitution there is now an isolated + // (text mode) space (surrounded by math). In which case, remove it. - if (/[(][0-9+\-.]$/.test(context)) { - // An open paren followed by a number - // Turn the paren back to math and switch. - this.convertLastAtomsToMath_(1); - return true; - } + this.removeIsolatedSpace_(); // Switch (back) to text mode if the shortcut ended with a space - if (/[(][a-z][,;]$/.test(context)) { - // An open paren followed by a single letter, then a "," or ";" - // Turn the paren back and letter to math and switch. - this.convertLastAtomsToMath_(2); - return true; - } // The tests above can look behind and change what had previously - // been entered. Now, let's just look at the typed character. + if (shortcut.endsWith(' ')) { + this.mode = 'text'; + this.mathlist.insert(' ', { + mode: 'text', + style: style + }); + } + this.mathlist.suppressChangeNotifications = save; + this.mathlist.contentDidChange(); + this.undoManager.snapshot(this.config); - if (/[0-9+\-=><*|]$/.test(c)) { - // If this new character looks like a number, - // or a relational operator (=, <, >) - // or a "*" or "|" - // (note that <=, >=, etc... are handled separately as shortcuts) - // switch to 'math' - this.removeIsolatedSpace_(); - return true; - } - } else { - // We're in math mode. Should we switch to text? - if (keystroke === 'Spacebar') { - this.convertLastAtomsToText_(function (a) { - return /[a-z][:,;.]$/.test(a.body); - }); - return true; - } + this._requestUpdate(); - if (/[a-zA-Z]{3,}$/.test(context) && !/(dxd|abc|xyz|uvw)$/.test(context)) { - // A sequence of three characters - // (except for some exceptions) - // Convert them to text. - this.convertLastAtomsToText_(function (a) { - return /[a-zA-Z:,;.]/.test(a.body); - }); - return true; - } + this._announce('replacement'); // If we're done with the shortcuts (found a unique one), reset it. - if (/(^|\W)(if|If)$/i.test(context)) { - // @todo localization - this.convertLastAtomsToText_(1); - return true; - } - if (/\?|\./.test(c)) { - // If the last character is a period or question mark, - // turn it to 'text' - return true; - } - } + if (resetKeystrokeBuffer) { + this._resetKeystrokeBuffer(); + } + } + } // 6. Make sure the insertion point is scrolled into view - return false; -}; -/** - * @param {string} keystroke - * @param {Event} evt - optional, an Event corresponding to the keystroke - * @method MathField#_onKeystroke - * @private - */ + this.scrollIntoView(); // 7. Keystroke has been handled, if it wasn't caught in the default + // case, so prevent propagation -MathField.prototype._onKeystroke = function (keystroke, evt) { - var _this3 = this; + if (evt && evt.preventDefault) { + evt.preventDefault(); + evt.stopPropagation(); + } - // 1. Display the keystroke in the keystroke panel (if visible) - this._showKeystroke(keystroke); // 2. Reset the timer for the keystroke buffer reset + return false; + } + /** + * This handler is invoked when text has been typed, pasted in or input with + * an input method. As a result, `text` can be a sequence of characters to + * be inserted. + * @param {string} text + * @param {object} options + * @param {boolean} options.focus - If true, the mathfield will be focused + * @param {boolean} options.feedback - If true, provide audio and haptic feedback + * @param {boolean} options.simulateKeystroke - If true, generate some synthetic + * keystrokes (useful to trigger inline shortcuts, for example) + * @param {boolean} options.commandMode - If true, switch to command mode if + * necessary, then insert text + * @private + */ + }, { + key: "_onTypedText", + value: function _onTypedText(text, options) { + options = options || {}; // Focus, then provide audio and haptic feedback - clearTimeout(this.keystrokeBufferResetTimer); // 3. Give a chance to the custom keystroke handler to intercept the event + if (options.focus) { + this.$focus(); + } - if (this.config.onKeystroke && !this.config.onKeystroke(this, keystroke, evt)) { - if (evt && evt.preventDefault) { - evt.preventDefault(); - evt.stopPropagation(); - } + if (options.feedback) { + if (this.config.keypressVibration && navigator.vibrate) { + navigator.vibrate(HAPTIC_FEEDBACK_DURATION); + } - return false; - } // 4. Let's try to find a matching shortcut or command + if (this.keypressSound) { + this.keypressSound.load(); + this.keypressSound.play().catch(function (err) { + return console.warn(err); + }); + } + } + if (options.commandMode && this.mode !== 'command') { + this.switchMode_('command'); + } // Remove any error indicator on the current command sequence + // (if there is one) - var shortcut; - var stateIndex; - var selector; - var resetKeystrokeBuffer = false; // 4.1 Check if the keystroke, prefixed with the previously typed keystrokes, - // would match a long shortcut (i.e. '~~') - // Ignore the key if command or control is pressed (it may be a shortcut, - // see 4.3) - if (this.mode !== 'command' && (!evt || !evt.ctrlKey && !evt.metaKey)) { - var c = _editorKeyboard.default.eventToChar(evt); // The Backspace key will be handled as a delete command later (5.4) - // const c = Keyboard.eventToChar(evt); + this.mathlist.decorateCommandStringAroundInsertionPoint(false); + if (options.simulateKeystroke) { + // for (const c of text) { + var c = text.charAt(0); - if (c !== 'Backspace') { - if (!c || c.length > 1) { - // It was a non-alpha character (PageUp, End, etc...) - this._resetKeystrokeBuffer(); - } else { - // Find the longest substring that matches a shortcut - var candidate = this.keystrokeBuffer + c; - var i = 0; - - while (!shortcut && i < candidate.length) { - var siblings = void 0; - - if (this.keystrokeBufferStates[i]) { - var mathlist = new _editorEditableMathlist.default.EditableMathlist(); - mathlist.root = _mathAtom.default.makeRoot('math', _parser.default.parseTokens(_lexer.default.tokenize(this.keystrokeBufferStates[i].latex), this.config.default, null, this.config.macros)); - mathlist.setPath(this.keystrokeBufferStates[i].selection); - siblings = mathlist.siblings(); - } else { - siblings = this.mathlist.siblings(); - } + var ev = _editorKeyboard.default.charToEvent(c); - shortcut = _editorShortcuts.default.forString(this.mode, siblings, candidate.slice(i), this.config); - i += 1; - } + if (!this.$keystroke(_editorKeyboard.default.keyboardEventToString(ev), ev)) { + return; + } // } - stateIndex = i - 1; - this.keystrokeBuffer += c; - this.keystrokeBufferStates.push(this.undoManager.save()); + } // Insert the specified text at the current insertion point. + // If the selection is not collapsed, the content will be deleted first. - if (_editorShortcuts.default.startsWithString(candidate, this.config).length <= 1) { - resetKeystrokeBuffer = true; - } else { - if (this.config.inlineShortcutTimeout) { - // Set a timer to reset the shortcut buffer - this.keystrokeBufferResetTimer = setTimeout(function () { - _this3._resetKeystrokeBuffer(); - }, this.config.inlineShortcutTimeout); - } - } - } - } - } // 4.2. Should we switch mode? - // Need to check this before determing if there's a valid shortcut - // since if we switch to math mode, we may want to apply the shortcut - // e.g. "slope = rise/run" + var popoverText = ''; + var displayArrows = false; - if (this.config.smartMode) { - var previousMode = this.mode; + if (this.pasteInProgress) { + this.pasteInProgress = false; // This call was made in response to a paste event. + // Interpret `text` as a 'smart' expression (could be LaTeX, could be + // UnicodeMath) - if (shortcut) { - // If we found a shortcut (e.g. "alpha"), - // switch to math mode and insert it - this.mode = 'math'; - } else if (this.smartMode_(keystroke, evt)) { - this.mode = { - 'math': 'text', - 'text': 'math' - }[this.mode]; - selector = ''; - } // Notify of mode change + this.mathlist.insert(text, { + smartFence: this.config.smartFence, + mode: 'math' + }); + } else { + var style = _objectSpread({}, this.mathlist.anchorStyle(), {}, this.style); // Decompose the string into an array of graphemes. + // This is necessary to correctly process what is displayed as a single + // glyph (a grapheme) but which is composed of multiple Unicode + // codepoints. This is the case in particular for some emojis, such as + // those with a skin tone modifier, the country flags emojis or + // compound emojis such as the professional emojis, including the + // David Bowie emoji: 👨🏻‍🎤 - if (this.mode !== previousMode && typeof this.config.onModeChange === 'function') { - this.config.onModeChange(this, this.mode); - } - } // 4.3 Check if this matches a keystroke shortcut - // Need to check this **after** checking for inline shortcuts because - // shift+backquote is a keystroke that inserts "\~"", but "~~" is a - // shortcut for "\approx" and needs to have priority over shift+backquote + var graphemes = _graphemeSplitter.default.splitGraphemes(text); + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; - if (!shortcut && !selector) { - selector = _editorShortcuts.default.selectorForKeystroke(this.mode, keystroke); - } // No shortcut :( We're done. + try { + for (var _iterator3 = graphemes[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var _c = _step3.value; + if (this.mode === 'command') { + this.mathlist.removeSuggestion(); + this.suggestionIndex = 0; + var command = this.mathlist.extractCommandStringAroundInsertionPoint(); - if (!shortcut && !selector) return true; // 5. Perform the action matching this shortcut - // 5.1 Remove any error indicator (wavy underline) on the current command - // sequence (if there are any) + var suggestions = _definitions.default.suggest(command + _c); - this.mathlist.decorateCommandStringAroundInsertionPoint(false); // 5.2 If we have a `moveAfterParent` selector (usually triggered with - // `spacebar), and we're at the end of a smart fence, close the fence with - // an empty (.) right delimiter + displayArrows = suggestions.length > 1; - var parent = this.mathlist.parent(); + if (suggestions.length === 0) { + this.mathlist.insert(_c, { + mode: 'command' + }); - if (selector === 'moveAfterParent' && parent && parent.type === 'leftright' && this.mathlist.endOffset() === this.mathlist.siblings().length - 1 && this.config.smartFence && this.mathlist._insertSmartFence('.')) { - // Pressing the space bar (moveAfterParent selector) when at the end - // of a potential smartfence will close it as a semi-open fence - selector = ''; + if (/^\\[a-zA-Z\\*]+$/.test(command + _c)) { + // This looks like a command name, but not a known one + this.mathlist.decorateCommandStringAroundInsertionPoint(true); + } - this._requestUpdate(); // Re-render the closed smartfence + _editorPopover.default.hidePopover(this); + } else { + this.mathlist.insert(_c, { + mode: 'command' + }); - } // 5.3 If this is the Spacebar and we're just before or right after - // a text zone, insert the space inside the text zone + if (suggestions[0].match !== command + _c) { + this.mathlist.insertSuggestion(suggestions[0].match, -suggestions[0].match.length + command.length + 1); + } + popoverText = suggestions[0].match; + } + } else if (this.mode === 'math') { + // Some characters are mapped to commands. Handle them here. + // This is important to handle synthetic text input and + // non-US keyboards, on which, fop example, the '^' key is + // not mapped to 'Shift-Digit6'. + var selector = { + '^': 'moveToSuperscript', + '_': 'moveToSubscript', + ' ': 'moveAfterParent' + }[_c]; + + if (selector) { + if (selector === 'moveToSuperscript') { + if (this._superscriptDepth() >= this.config.scriptDepth[1]) { + this._announce('plonk'); + + return; + } + } else if (selector === 'moveToSubscript') { + if (this._subscriptDepth() >= this.config.scriptDepth[0]) { + this._announce('plonk'); + + return; + } + } + + this.$perform(selector); + } else { + if (this.config.smartSuperscript && this.mathlist.relation() === 'superscript' && /[0-9]/.test(_c) && this.mathlist.siblings().filter(function (x) { + return x.type !== 'first'; + }).length === 0) { + // We are inserting a digit into an empty superscript + // If smartSuperscript is on, insert the digit, and + // exit the superscript. + this.mathlist.insert(_c, { + mode: 'math', + style: style + }); + this.mathlist.moveAfterParent_(); + } else { + this.mathlist.insert(_c, { + mode: 'math', + style: style, + smartFence: this.config.smartFence + }); + } + } + } else if (this.mode === 'text') { + this.mathlist.insert(_c, { + mode: 'text', + style: style + }); + } + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return != null) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + } - if (this.mode === 'math' && keystroke === 'Spacebar' && !shortcut) { - var nextSibling = this.mathlist.sibling(1); - var previousSibling = this.mathlist.sibling(-1); + if (this.mode !== 'command') { + this.undoManager.snapshotAndCoalesce(this.config); + } // Render the mathlist - if (nextSibling && nextSibling.mode === 'text' || previousSibling && previousSibling.mode === 'text') { - this.mathlist.insert(' ', { - mode: 'text' - }); - } - } // 5.4 If there's a selector, perform it. + this._requestUpdate(); // Make sure the insertion point is visible - if (selector && !this.perform(selector) || shortcut) { - // // 6.5 insert the shortcut - if (shortcut) { - // If the shortcut is a mandatory escape sequence (\}, etc...) - // don't make it undoable, this would result in syntactically incorrect - // formulas - if (!/^(\\{|\\}|\\[|\\]|\\@|\\#|\\$|\\%|\\^|\\_|\\backslash)$/.test(shortcut)) { - // To enable the substitution to be undoable, - // insert the character before applying the substitution - var _style = _objectSpread({}, this.mathlist.anchorStyle(), this.style); - this.mathlist.insert(_editorKeyboard.default.eventToChar(evt), { - suppressChangeNotifications: true, - mode: this.mode, - style: _style - }); - var saveMode = this.mode; // Create a snapshot with the inserted character + this.scrollIntoView(); // Since the location of the popover depends on the position of the caret + // only show the popover after the formula has been rendered and the + // position of the caret calculated - this.undoManager.snapshotAndCoalesce(this.config); // Revert to the state before the beginning of the shortcut - // (restore doesn't change the undo stack) + _editorPopover.default.showPopoverWithLatex(this, popoverText, displayArrows); + } + /* + * Return a hash (32-bit integer) representing the content of the mathfield + * (but not the selection state) + */ - this.undoManager.restore(this.keystrokeBufferStates[stateIndex], _objectSpread({}, this.config, { - suppressChangeNotifications: true - })); - this.mode = saveMode; + }, { + key: "_hash", + value: function _hash() { + var result = 0; + var str = this.mathlist.root.toLatex(false); + + for (var i = 0; i < str.length; i++) { + result = result * 31 + str.charCodeAt(i); + result = result | 0; // Force it to a 32-bit number } - this.mathlist.contentWillChange(); - var save = this.mathlist.suppressChangeNotifications; - this.mathlist.suppressChangeNotifications = true; // Insert the substitute, possibly as a smart fence - - var style = _objectSpread({}, this.mathlist.anchorStyle(), this.style); - - this.mathlist.insert(shortcut, { - format: 'latex', - mode: this.mode, - style: style, - smartFence: true - }); // Check if as a result of the substitution there is now an isolated - // (text mode) space (surrounded by math). In which case, remove it. - - this.removeIsolatedSpace_(); // Switch (back) to text mode if the shortcut ended with a space - - if (shortcut.endsWith(' ')) { - this.mode = 'text'; - this.mathlist.insert(' ', { - mode: 'text', - style: style + return Math.abs(result); + } + }, { + key: "_requestUpdate", + value: function _requestUpdate() { + var _this4 = this; + + if (!this.dirty) { + this.dirty = true; + requestAnimationFrame(function (_) { + return _this4._render(); }); } + } + /** + * Lay-out the math field and generate the DOM. + * + * This is usually done automatically, but if the font-size, or other geometric + * attributes are modified, outside of MathLive, this function may need to be + * called explicitly. + * + * @method MathField#render + * @private + */ - this.mathlist.suppressChangeNotifications = save; - this.mathlist.contentDidChange(); - this.undoManager.snapshot(this.config); + }, { + key: "_render", + value: function _render(renderOptions) { + renderOptions = renderOptions || {}; + this.dirty = false; // + // 1. Stop and reset read aloud state + // + + if (!window.mathlive) { + window.mathlive = {}; + } // + // 2. Validate selection + // + + + if (!this.mathlist.anchor()) { + console.warn('Invalid selection, resetting it. ' + _editorMathpath.default.pathToString(this.mathlist.path)); + this.mathlist.path = [{ + relation: 'body', + offset: 0 + }]; + } // + // 3. Update selection state and blinking cursor (caret) + // + + + this.mathlist.forEach(function (a) { + a.caret = ''; + a.isSelected = false; + }); + var hasFocus = this.$hasFocus(); - this._requestUpdate(); + if (this.mathlist.isCollapsed()) { + this.mathlist.anchor().caret = hasFocus ? this.mode : ''; + } else { + this.mathlist.forEachSelected(function (a) { + a.isSelected = true; + }); + } // + // 4. Create spans corresponding to the updated mathlist + // + + + var spans = _mathAtom.default.decompose({ + mathstyle: 'displaystyle', + generateID: { + // Using the hash as a seed for the ID + // keeps the IDs the same until the content of the field changes. + seed: this._hash(), + // The `groupNumbers` flag indicates that extra spans should be generated + // to represent group of atoms, for example, a span to group + // consecutive digits to represent a number. + groupNumbers: renderOptions.forHighlighting + }, + macros: this.config.macros + }, this.mathlist.root); // + // 5. Construct struts around the spans + // + + + var base = _span.default.makeSpan(spans, 'ML__base'); + + base.attributes = { + // Sometimes Google Translate kicks in an attempts to 'translate' math + // This doesn't work very well, so turn off translate + 'translate': 'no', + // Hint to screen readers to not attempt to read this span + // They should use instead the 'aria-label' below. + 'aria-hidden': 'true' + }; - this._announce('replacement'); // If we're done with the shortcuts (found a unique one), reset it. + var topStrut = _span.default.makeSpan('', 'ML__strut'); + topStrut.setStyle('height', base.height, 'em'); + var struts = [topStrut]; - if (resetKeystrokeBuffer) { - this._resetKeystrokeBuffer(); - } - } - } // 6. Make sure the insertion point is scrolled into view + if (base.depth !== 0) { + var bottomStrut = _span.default.makeSpan('', 'ML__strut--bottom'); + bottomStrut.setStyle('height', base.height + base.depth, 'em'); + bottomStrut.setStyle('vertical-align', -base.depth, 'em'); + struts.push(bottomStrut); + } - this.scrollIntoView(); // 7. Keystroke has been handled, if it wasn't caught in the default - // case, so prevent propagation + struts.push(base); - if (evt && evt.preventDefault) { - evt.preventDefault(); - evt.stopPropagation(); - } + var wrapper = _span.default.makeSpan(struts, 'ML__mathlive'); // + // 6. Generate markup and accessible node + // - return false; -}; -/** - * This handler is invoked when text has been typed, pasted in or input with - * an input method. As a result, `text` can be a sequence of characters to - * be inserted. - * @param {string} text - * @param {object} options - * @param {boolean} options.focus - If true, the mathfield will be focused - * @param {boolean} options.feedback - If true, provide audio and haptic feedback - * @param {boolean} options.simulateKeystroke - If true, generate some synthetic - * keystrokes (useful to trigger inline shortcuts, for example) - * @param {boolean} options.commandMode - If true, switch to command mode if - * necessary, then insert text - */ + this.field.innerHTML = wrapper.toMarkup(0, this.config.horizontalSpacingScale); + this.field.classList.toggle('ML__focused', hasFocus); // Probably want to generate content on the fly depending on what to speak -MathField.prototype._onTypedText = function (text, options) { - options = options || {}; // Focus, then provide audio and haptic feedback + this.accessibleNode.innerHTML = "" + _mathAtom.default.toMathML(this.mathlist.root, this.config) + ""; //this.ariaLiveText.textContent = ""; + // + // 7. Calculate selection rectangle + // - if (options.focus) this.focus(); + var selectionRect = this._getSelectionBounds(); - if (options.feedback) { - if (this.config.keypressVibration && navigator.vibrate) { - navigator.vibrate(HAPTIC_FEEDBACK_DURATION); + if (selectionRect) { + var selectionElement = document.createElement('div'); + selectionElement.classList.add('ML__selection'); + selectionElement.style.position = 'absolute'; + selectionElement.style.left = selectionRect.left + 'px'; + selectionElement.style.top = selectionRect.top + 'px'; + selectionElement.style.width = Math.ceil(selectionRect.right - selectionRect.left) + 'px'; + selectionElement.style.height = Math.ceil(selectionRect.bottom - selectionRect.top - 1) + 'px'; + this.field.insertBefore(selectionElement, this.field.childNodes[0]); + } } - - if (this.keypressSound) { - this.keypressSound.load(); - this.keypressSound.play().catch(function (err) { - return console.warn(err); - }); + }, { + key: "_onPaste", + value: function _onPaste() { + // Make note we're in the process of pasting. The subsequent call to + // onTypedText() will take care of interpreting the clipboard content + this.pasteInProgress = true; + return true; } - } + }, { + key: "_onCut", + value: function _onCut() { + // Clearing the selection will have the side effect of clearing the + // content of the textarea. However, the textarea value is what will + // be copied to the clipboard, so defer the clearing of the selection + // to later, after the cut operation has been handled. + setTimeout(function () { + this.$clearSelection(); + + this._requestUpdate(); + }.bind(this), 0); + return true; + } + }, { + key: "_onCopy", + value: function _onCopy(e) { + if (this.mathlist.isCollapsed()) { + e.clipboardData.setData('text/plain', this.$text('latex-expanded')); + e.clipboardData.setData('application/json', this.$text('json')); + e.clipboardData.setData('application/xml', this.$text('mathML')); + } else { + e.clipboardData.setData('text/plain', this.$selectedText('latex-expanded')); + e.clipboardData.setData('application/json', this.$selectedText('json')); + e.clipboardData.setData('application/xml', this.$selectedText('mathML')); + } // Prevent the current document selection from being written to the clipboard. - if (options.commandMode && this.mode !== 'command') { - this.switchMode_('command'); - } // Remove any error indicator on the current command sequence - // (if there is one) + e.preventDefault(); + } + }, { + key: "formatMathlist", + value: function formatMathlist(root, format) { + format = format || 'latex'; + var result = ''; + + if (format === 'latex' || format === 'latex-expanded') { + result = root.toLatex(format === 'latex-expanded'); + } else if (format === 'mathML') { + result = root.toMathML(this.config); + } else if (format === 'spoken') { + result = _mathAtom.default.toSpeakableText(root, this.config); + } else if (format === 'spoken-text') { + var saveTextToSpeechMarkup = this.config.textToSpeechMarkup; + this.config.textToSpeechMarkup = ''; + result = _mathAtom.default.toSpeakableText(root, this.config); + this.config.textToSpeechMarkup = saveTextToSpeechMarkup; + } else if (format === 'spoken-ssml' || format === 'spoken-ssml-withHighlighting') { + var _saveTextToSpeechMarkup = this.config.textToSpeechMarkup; + var saveGenerateID = this.config.generateID; + this.config.textToSpeechMarkup = 'ssml'; + + if (format === 'spoken-ssml-withHighlighting') { + this.config.generateID = true; + } - this.mathlist.decorateCommandStringAroundInsertionPoint(false); + result = _mathAtom.default.toSpeakableText(root, this.config); + this.config.textToSpeechMarkup = _saveTextToSpeechMarkup; + this.config.generateID = saveGenerateID; + } else if (format === 'json') { + var json = _mathAtom.default.toAST(root, this.config); - if (options.simulateKeystroke) { - // for (const c of text) { - var c = text.charAt(0); + result = JSON.stringify(json); + } else if (format === 'ASCIIMath') { + result = (0, _outputASCIIMath.toASCIIMath)(root, this.config); + } else { + console.warn('Unknown format :', format); + } - var ev = _editorKeyboard.default.charToEvent(c); + return result; + } // + // PUBLIC API + // + + /** + * Return a textual representation of the mathfield. + * @param {string} [format='latex']. One of + * * `'latex'` + * * `'latex-expanded'` : all macros are recursively expanded to their definition + * * `'spoken'` + * * `'spoken-text'` + * * `'spoken-ssml'` + * * `spoken-ssml-withHighlighting` + * * `'mathML'` + * * `'json'` + * @return {string} + * @method MathField#$text + */ - if (!this.$keystroke(_editorKeyboard.default.keyboardEventToString(ev), ev)) return; // } - } // Insert the specified text at the current insertion point. - // If the selection is not collapsed, the content will be deleted first. + }, { + key: "$text", + value: function $text(format) { + return this.formatMathlist(this.mathlist.root, format); + } + /** + * Return a textual representation of the selection in the mathfield. + * @param {string} [format='latex']. One of + * * `'latex'` + * * `'latex-expanded'` : all macros are recursively expanded to their definition + * * `'spoken'` + * * `'spoken-text'` + * * `'spoken-ssml'` + * * `spoken-ssml-withHighlighting` + * * `'mathML'` + * * `'json'` + * @return {string} + * @method MathField#$selectedText + */ + }, { + key: "$selectedText", + value: function $selectedText(format) { + var atoms = this.mathlist.getSelectedAtoms(); - var popoverText = ''; - var displayArrows = false; + if (!atoms) { + return ''; + } - if (this.pasteInProgress) { - this.pasteInProgress = false; // This call was made in response to a paste event. - // Interpret `text` as a 'smart' expression (could be LaTeX, could be - // UnicodeMath) + var root = _mathAtom.default.makeRoot('math', atoms); - this.mathlist.insert(text, { - smartFence: this.config.smartFence, - mode: 'math' - }); - } else { - var style = _objectSpread({}, this.mathlist.anchorStyle(), this.style); // Decompose the string into an array of graphemes. - // This is necessary to correctly process what is displayed as a single - // glyph (a grapheme) but which is composed of multiple Unicode - // codepoints. This is the case in particular for some emojis, such as - // those with a skin tone modifier, the country flags emojis or - // compound emojis such as the professional emojis, including the - // David Bowie emoji: 👨🏻‍🎤 + return this.formatMathlist(root, format); + } + /** + * Return true if the length of the selection is 0, that is, if it is a single + * insertion point. + * @return {boolean} + * @method MathField#$selectionIsCollapsed + */ + }, { + key: "$selectionIsCollapsed", + value: function $selectionIsCollapsed() { + return this.mathlist.isCollapsed(); + } + /** + * Return the depth of the selection group. If the selection is at the root level, + * returns 0. If the selection is a portion of the numerator of a fraction + * which is at the root level, return 1. Note that in that case, the numerator + * would be the "selection group". + * @return {number} + * @method MathField#$selectionDepth + */ - var graphemes = _graphemeSplitter.default.splitGraphemes(text); + }, { + key: "$selectionDepth", + value: function $selectionDepth() { + return this.mathlist.path.length; + } + }, { + key: "_superscriptDepth", + value: function _superscriptDepth() { + var result = 0; + var i = 0; + var atom = this.mathlist.ancestor(i); + var wasSuperscript = false; + + while (atom) { + if (atom.superscript || atom.subscript) { + result += 1; + } - var _iteratorNormalCompletion3 = true; - var _didIteratorError3 = false; - var _iteratorError3 = undefined; + if (atom.superscript) { + wasSuperscript = true; + } else if (atom.subscript) { + wasSuperscript = false; + } - try { - for (var _iterator3 = graphemes[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { - var _c = _step3.value; + i += 1; + atom = this.mathlist.ancestor(i); + } - if (this.mode === 'command') { - this.mathlist.removeSuggestion(); - this.suggestionIndex = 0; - var command = this.mathlist.extractCommandStringAroundInsertionPoint(); + return wasSuperscript ? result : 0; + } + }, { + key: "_subscriptDepth", + value: function _subscriptDepth() { + var result = 0; + var i = 0; + var atom = this.mathlist.ancestor(i); + var wasSubscript = false; + + while (atom) { + if (atom.superscript || atom.subscript) { + result += 1; + } - var suggestions = _definitions.default.suggest(command + _c); + if (atom.superscript) { + wasSubscript = false; + } else if (atom.subscript) { + wasSubscript = true; + } - displayArrows = suggestions.length > 1; + i += 1; + atom = this.mathlist.ancestor(i); + } - if (suggestions.length === 0) { - this.mathlist.insert(_c, { - mode: 'command' - }); - - if (/^\\[a-zA-Z\\*]+$/.test(command + _c)) { - // This looks like a command name, but not a known one - this.mathlist.decorateCommandStringAroundInsertionPoint(true); - } + return wasSubscript ? result : 0; + } + /** + * Return true if the selection starts at the beginning of the selection group. + * @return {boolean} + * @method MathField#$selectionAtStart + */ - _editorPopover.default.hidePopover(this); - } else { - this.mathlist.insert(_c, { - mode: 'command' - }); + }, { + key: "$selectionAtStart", + value: function $selectionAtStart() { + return this.mathlist.startOffset() === 0; + } + /** + * Return true if the selection extends to the end of the selection group. + * @return {boolean} + * @method MathField#$selectionAtEnd + */ - if (suggestions[0].match !== command + _c) { - this.mathlist.insertSuggestion(suggestions[0].match, -suggestions[0].match.length + command.length + 1); - } + }, { + key: "$selectionAtEnd", + value: function $selectionAtEnd() { + return this.mathlist.endOffset() >= this.mathlist.siblings().length - 1; + } + /* + * True if the entire group is selected + * */ - popoverText = suggestions[0].match; - } - } else if (this.mode === 'math') { - // Some characters are mapped to commands. Handle them here. - // This is important to handle synthetic text input and - // non-US keyboards, on which, fop example, the '^' key is - // not mapped to 'Shift-Digit6'. - var selector = { - '^': 'moveToSuperscript', - '_': 'moveToSubscript', - ' ': 'moveAfterParent' - }[_c]; - - if (selector) { - if (selector === 'moveToSuperscript') { - if (this._superscriptDepth() >= this.config.scriptDepth[1]) { - this._announce('plonk'); - - return; - } - } else if (selector === 'moveToSubscript') { - if (this._subscriptDepth() >= this.config.scriptDepth[0]) { - this._announce('plonk'); + }, { + key: "groupIsSelected", + value: function groupIsSelected() { + return this.mathlist.startOffset() === 0 && this.mathlist.endOffset() >= this.mathlist.siblings().length - 1; + } + /** + * If `text` is not empty, sets the content of the mathfield to the + * text interpreted as a LaTeX expression. + * If `text` is empty (or omitted), return the content of the mahtfield as a + * LaTeX expression. + * @param {string} text + * + * @param {Object.} options + * @param {boolean} options.suppressChangeNotifications - If true, the + * handlers for the contentWillChange and contentDidChange notifications will + * not be invoked. Default `false`. + * + * @return {string} + * @method MathField#$latex + */ - return; - } - } + }, { + key: "$latex", + value: function $latex(text, options) { + if (text) { + var oldValue = this.mathlist.root.toLatex(); + + if (text !== oldValue) { + options = options || {}; + this.mathlist.insert(text, Object.assign({}, this.config, { + insertionMode: 'replaceAll', + selectionMode: 'after', + format: 'latex', + mode: 'math', + suppressChangeNotifications: options.suppressChangeNotifications + })); + this.undoManager.snapshot(this.config); - this.perform(selector); - } else { - if (this.config.smartSuperscript && this.mathlist.relation() === 'superscript' && /[0-9]/.test(_c) && this.mathlist.siblings().filter(function (x) { - return x.type !== 'first'; - }).length === 0) { - // We are inserting a digit into an empty superscript - // If smartSuperscript is on, insert the digit, and - // exit the superscript. - this.mathlist.insert(_c, { - mode: 'math', - style: style - }); - this.mathlist.moveAfterParent_(); - } else { - this.mathlist.insert(_c, { - mode: 'math', - style: style, - smartFence: this.config.smartFence - }); - } - } - } else if (this.mode === 'text') { - this.mathlist.insert(_c, { - mode: 'text', - style: style - }); - } - } - } catch (err) { - _didIteratorError3 = true; - _iteratorError3 = err; - } finally { - try { - if (!_iteratorNormalCompletion3 && _iterator3.return != null) { - _iterator3.return(); - } - } finally { - if (_didIteratorError3) { - throw _iteratorError3; + this._requestUpdate(); } - } + + return text; + } // Return the content as LaTeX + + + return this.mathlist.root.toLatex(); } - } + /** + * Return the DOM element associated with this mathfield. + * + * Note that `this.$el().mathfield = this` + * @return {HTMLElement} + * @method MathField#$el + */ - if (this.mode !== 'command') { - this.undoManager.snapshotAndCoalesce(this.config); - } // Render the mathlist + }, { + key: "$el", + value: function $el() { + return this.element; + } + }, { + key: "undo", + value: function undo() { + this.complete_(); // Undo to the previous state + this.undoManager.undo(this.config); + return true; + } + }, { + key: "redo", + value: function redo() { + this.complete_(); + this.undoManager.redo(this.config); + return true; + } + }, { + key: "scrollIntoView", + value: function scrollIntoView() { + // If a render is pending, do it now to make sure we have correct layout + // and caret position + if (this.dirty) { + this._render(); + } - this._requestUpdate(); // Make sure the insertion point is visible + var pos = this._getCaretPosition(); + var fieldBounds = this.field.getBoundingClientRect(); - this.scrollIntoView(); // Since the location of the popover depends on the position of the caret - // only show the popover after the formula has been rendered and the - // position of the caret calculated + if (!pos) { + var selectionBounds = this._getSelectionBounds(); - _editorPopover.default.showPopoverWithLatex(this, popoverText, displayArrows); -}; -/** - * Return a hash (32-bit integer) representing the content of the mathfield - * (but not the selection state) - */ + if (selectionBounds) { + pos = { + x: selectionBounds.right + fieldBounds.left - this.field.scrollLeft, + y: selectionBounds.top + fieldBounds.top - this.field.scrollTop + }; + } + } + if (pos) { + var x = pos.x - window.scrollX; -MathField.prototype._hash = function () { - var result = 0; - var str = this.mathlist.root.toLatex(false); + if (x < fieldBounds.left) { + this.field.scroll({ + top: 0, + left: x - fieldBounds.left + this.field.scrollLeft - 20, + behavior: 'smooth' + }); + } else if (x > fieldBounds.right) { + this.field.scroll({ + top: 0, + left: x - fieldBounds.right + this.field.scrollLeft + 20, + behavior: 'smooth' + }); + } + } + } + }, { + key: "scrollToStart", + value: function scrollToStart() { + this.field.scroll(0, 0); + } + }, { + key: "scrollToEnd", + value: function scrollToEnd() { + var fieldBounds = this.field.getBoundingClientRect(); + this.field.scroll(fieldBounds.left - window.scrollX, 0); + } + /** + * + * @method MathField#enterCommandMode_ + * @private + */ - for (var i = 0; i < str.length; i++) { - result = result * 31 + str.charCodeAt(i); - result = result | 0; // Force it to a 32-bit number - } + }, { + key: "enterCommandMode_", + value: function enterCommandMode_() { + this.switchMode_('command'); + } + }, { + key: "copyToClipboard_", + value: function copyToClipboard_() { + this.$focus(); // If the selection is empty, select the entire field before + // copying it. - return Math.abs(result); -}; + if (this.mathlist.isCollapsed()) { + this.$select(); + } -MathField.prototype._requestUpdate = function () { - var _this4 = this; + document.execCommand('copy'); + return false; + } + }, { + key: "cutToClipboard_", + value: function cutToClipboard_() { + this.$focus(); + document.execCommand('cut'); + return true; + } + }, { + key: "pasteFromClipboard_", + value: function pasteFromClipboard_() { + this.$focus(); + document.execCommand('paste'); + return true; + } + /** + * This method can be invoked as a selector with {@linkcode MathField#$perform $perform("insert")} + * or called explicitly. + * + * It will insert the specified block of text at the current insertion point, + * according to the insertion mode specified. + * + * After the insertion, the selection will be set according to the `selectionMode`. + * @param {string} s - The text to be inserted + * + * @param {Object.} [options={}] + * + * @param {'placeholder' | 'after' | 'before' | 'item'} options.selectionMode - Describes where the selection + * will be after the insertion: + * * `'placeholder'`: the selection will be the first available placeholder + * in the item that has been inserted (default) + * * `'after'`: the selection will be an insertion point after the item that + * has been inserted, + * * `'before'`: the selection will be an insertion point before + * the item that has been inserted + * * `'item'`: the item that was inserted will be selected + * + * @param {'auto' | 'latex'} options.format - The format of the string `s`: + * * `'auto'`: the string is interpreted as a latex fragment or command) + * (default) + * * `'latex'`: the string is interpreted strictly as a latex fragment + * + * @param {boolean} options.focus - If true, the mathfield will be focused after + * the insertion + * + * @param {boolean} options.feedback - If true, provide audio and haptic feedback + * + * @param {'text' | 'math' | ''} options.mode - 'text' or 'math'. If empty, the current mode + * is used (default) + * + * @param {boolean} options.resetStyle - If true, the style after the insertion + * is the same as the style before (if false, the style after the + * insertion is the style of the last inserted atom). + * + * @method MathField#$insert + */ - if (!this.dirty) { - this.dirty = true; - requestAnimationFrame(function (_) { - return _this4._render(); - }); - } -}; -/** - * Lay-out the math field and generate the DOM. - * - * This is usually done automatically, but if the font-size, or other geometric - * attributes are modified, outside of MathLive, this function may need to be - * called explicitly. - * - * @method MathField#render - * @private - */ + }, { + key: "$insert", + value: function $insert(s, options) { + if (typeof s === 'string' && s.length > 0) { + options = options || {}; + if (options.focus) { + this.$focus(); + } -MathField.prototype._render = function (renderOptions) { - renderOptions = renderOptions || {}; - this.dirty = false; // - // 1. Stop and reset read aloud state - // + if (options.feedback) { + if (this.config.keypressVibration && navigator.vibrate) { + navigator.vibrate(HAPTIC_FEEDBACK_DURATION); + } - if (!window.mathlive) window.mathlive = {}; // - // 2. Validate selection - // + if (this.keypressSound) { + this.keypressSound.load(); + this.keypressSound.play(); + } + } - if (!this.mathlist.anchor()) { - console.warn('Invalid selection, resetting it. ' + _editorMathpath.default.pathToString(this.mathlist.path)); - this.mathlist.path = [{ - relation: 'body', - offset: 0 - }]; - } // - // 3. Update selection state and blinking cursor (caret) - // + if (s === '\\\\') { + // This string is interpreted as an "insert row after" command + this.mathlist.addRowAfter_(); + } else if (s === '&') { + this.mathlist.addColumnAfter_(); + } else { + var savedStyle = this.style; + this.mathlist.insert(s, _objectSpread({ + mode: this.mode, + style: this.mathlist.anchorStyle() + }, options)); + + if (options.resetStyle) { + this.style = savedStyle; + } + } + this.undoManager.snapshot(this.config); - this.mathlist.forEach(function (a) { - a.caret = ''; - a.isSelected = false; - }); - var hasFocus = this.hasFocus(); + this._requestUpdate(); - if (this.mathlist.isCollapsed()) { - this.mathlist.anchor().caret = hasFocus ? this.mode : ''; - } else { - this.mathlist.forEachSelected(function (a) { - a.isSelected = true; - }); - } // - // 4. Create spans corresponding to the updated mathlist - // - - - var spans = _mathAtom.default.decompose({ - mathstyle: 'displaystyle', - generateID: { - // Using the hash as a seed for the ID - // keeps the IDs the same until the content of the field changes. - seed: this._hash(), - // The `groupNumbers` flag indicates that extra spans should be generated - // to represent group of atoms, for example, a span to group - // consecutive digits to represent a number. - groupNumbers: renderOptions.forHighlighting - }, - macros: this.config.macros - }, this.mathlist.root); // - // 5. Construct struts around the spans - // - - - var base = _span.default.makeSpan(spans, 'ML__base'); - - base.attributes = { - // Sometimes Google Translate kicks in an attempts to 'translate' math - // This doesn't work very well, so turn off translate - 'translate': 'no', - // Hint to screen readers to not attempt to read this span - // They should use instead the 'aria-label' below. - 'aria-hidden': 'true' - }; + return true; + } - var topStrut = _span.default.makeSpan('', 'ML__strut'); + return false; + } + }, { + key: "switchMode_", + value: function switchMode_(mode, prefix, suffix) { + this._resetKeystrokeBuffer(); // Suppress (temporarily) smart mode if switching to/from text or math + // This prevents switching to/from command mode from supressing smart mode. - topStrut.setStyle('height', base.height, 'em'); - var struts = [topStrut]; - if (base.depth !== 0) { - var bottomStrut = _span.default.makeSpan('', 'ML__strut--bottom'); + this.smartModeSuppressed = /text|math/.test(this.mode) && /text|math/.test(mode); - bottomStrut.setStyle('height', base.height + base.depth, 'em'); - bottomStrut.setStyle('vertical-align', -base.depth, 'em'); - struts.push(bottomStrut); - } + if (prefix) { + this.$insert(prefix, { + format: 'latex', + mode: { + 'math': 'text', + 'text': 'math' + }[mode] + }); + } // Remove any error indicator on the current command sequence (if there is one) - struts.push(base); - var wrapper = _span.default.makeSpan(struts, 'ML__mathlive'); // - // 6. Generate markup and accessible node - // + this.mathlist.decorateCommandStringAroundInsertionPoint(false); + if (mode === 'command') { + this.mathlist.removeSuggestion(); - this.field.innerHTML = wrapper.toMarkup(0, this.config.horizontalSpacingScale); - this.field.classList.toggle('ML__focused', hasFocus); // Probably want to generate content on the fly depending on what to speak + _editorPopover.default.hidePopover(this); - this.accessibleNode.innerHTML = "" + _mathAtom.default.toMathML(this.mathlist.root, this.config) + ""; //this.ariaLiveText.textContent = ""; - // - // 7. Calculate selection rectangle - // + this.suggestionIndex = 0; // Switch to the command mode keyboard layer - var selectionRect = this._getSelectionBounds(); + if (this.virtualKeyboardVisible) { + this.switchKeyboardLayer_('lower-command'); + } - if (selectionRect) { - var selectionElement = document.createElement('div'); - selectionElement.classList.add('ML__selection'); - selectionElement.style.position = 'absolute'; - selectionElement.style.left = selectionRect.left + 'px'; - selectionElement.style.top = selectionRect.top + 'px'; - selectionElement.style.width = Math.ceil(selectionRect.right - selectionRect.left) + 'px'; - selectionElement.style.height = Math.ceil(selectionRect.bottom - selectionRect.top - 1) + 'px'; - this.field.insertBefore(selectionElement, this.field.childNodes[0]); - } -}; + this.mathlist.insert("\x1B", { + mode: 'math' + }); + } else { + this.mode = mode; + } -MathField.prototype._onPaste = function () { - // Make note we're in the process of pasting. The subsequent call to - // onTypedText() will take care of interpreting the clipboard content - this.pasteInProgress = true; - return true; -}; + if (suffix) { + this.$insert(suffix, { + format: 'latex', + mode: mode + }); + } // Notify of mode change -MathField.prototype._onCut = function () { - // Clearing the selection will have the side effect of clearing the - // content of the textarea. However, the textarea value is what will - // be copied to the clipboard, so defer the clearing of the selection - // to later, after the cut operation has been handled. - setTimeout(function () { - this.clearSelection(); - - this._requestUpdate(); - }.bind(this), 0); - return true; -}; -MathField.prototype._onCopy = function (e) { - if (this.mathlist.isCollapsed()) { - e.clipboardData.setData('text/plain', this.$text('latex-expanded')); - e.clipboardData.setData('application/json', this.$text('json')); - e.clipboardData.setData('application/xml', this.$text('mathML')); - } else { - e.clipboardData.setData('text/plain', this.$selectedText('latex-expanded')); - e.clipboardData.setData('application/json', this.$selectedText('json')); - e.clipboardData.setData('application/xml', this.$selectedText('mathML')); - } // Prevent the current document selection from being written to the clipboard. + if (typeof this.config.onModeChange === 'function') { + this.config.onModeChange(this, this.mode); + } + this._requestUpdate(); + } + /** + * When in command mode, insert the select command and return to math mode + * If escape is true, the command is discared. + * @param {object} options + * @param {boolean} options.discard if true, the command is discarded and the + * mode switched back to math + * @param {boolean} options.acceptSuggestion if true, accept the suggestion to + * complete the command. Otherwise, only use what has been entered so far. + * @method MathField#complete_ + * @private + */ - e.preventDefault(); -}; + }, { + key: "complete_", + value: function complete_(options) { + options = options || {}; -MathField.prototype.formatMathlist = function (root, format) { - format = format || 'latex'; - var result = ''; - - if (format === 'latex' || format === 'latex-expanded') { - result = root.toLatex(format === 'latex-expanded'); - } else if (format === 'mathML') { - result = root.toMathML(this.config); - } else if (format === 'spoken') { - result = _mathAtom.default.toSpeakableText(root, this.config); - } else if (format === 'spoken-text') { - var save = this.config.textToSpeechMarkup; - this.config.textToSpeechMarkup = ''; - result = _mathAtom.default.toSpeakableText(root, this.config); - this.config.textToSpeechMarkup = save; - } else if (format === 'spoken-ssml') { - var _save = this.config.textToSpeechMarkup; - this.config.textToSpeechMarkup = 'ssml'; - result = _mathAtom.default.toSpeakableText(root, this.config); - this.config.textToSpeechMarkup = _save; - } else if (format === 'json') { - var json = _mathAtom.default.toAST(root, this.config); - - result = JSON.stringify(json); - } else if (format === 'ASCIIMath') { - result = (0, _outputASCIIMath.toASCIIMath)(root, this.config); - } else { - console.warn('Unknown format :', format); - } + _editorPopover.default.hidePopover(this); - return result; -}; // -// PUBLIC API -// + if (options.discard) { + this.mathlist.spliceCommandStringAroundInsertionPoint(null); + this.switchMode_('math'); + return true; + } -/** - * Return a textual representation of the mathfield. - * @param {string} [format='latex']. One of - * * `'latex'` - * * `'latex-expanded'` : all macros are recursively expanded to their definition - * * `'spoken'` - * * `'spoken-text'` - * * `'spoken-ssml'` - * * `'mathML'` - * * `'json'` - * @return {string} - * @method MathField#$text - */ + var command = this.mathlist.extractCommandStringAroundInsertionPoint(!options.acceptSuggestion); + if (command) { + if (command === '\\(' || command === '\\)') { + this.mathlist.spliceCommandStringAroundInsertionPoint([]); + this.mathlist.insert(command.slice(1), { + mode: this.mode + }); + } else { + // We'll assume we want to insert in math mode + // (commands are only available in math mode) + var mode = 'math'; -MathField.prototype.text = MathField.prototype.$text = function (format) { - return this.formatMathlist(this.mathlist.root, format); -}; -/** - * Return a textual representation of the selection in the mathfield. - * @param {string} [format='latex']. One of - * * `'latex'` - * * `'latex-expanded'` : all macros are recursively expanded to their definition - * * `'spoken'` - * * `'spoken-text'` - * * `'spoken-ssml'` - * * `'mathML'` - * * `'json'` - * @return {string} - * @method MathField#$selectedText - */ + if (_definitions.default.commandAllowed(mode, command)) { + var mathlist = _parser.default.parseTokens(_lexer.default.tokenize(command), mode, null, this.config.macros); + + this.mathlist.spliceCommandStringAroundInsertionPoint(mathlist); + } else { + // This wasn't a simple function or symbol. + // Interpret the input as LaTeX code + var _mathlist = _parser.default.parseTokens(_lexer.default.tokenize(command), mode, null, this.config.macros); + + if (_mathlist) { + this.mathlist.spliceCommandStringAroundInsertionPoint(_mathlist); + } else { + this.mathlist.decorateCommandStringAroundInsertionPoint(true); + } + } + } + this.undoManager.snapshot(this.config); -MathField.prototype.selectedText = MathField.prototype.$selectedText = function (format) { - var atoms = this.mathlist.getSelectedAtoms(); - if (!atoms) return ''; + this._announce('replacement'); - var root = _mathAtom.default.makeRoot('math', atoms); + return true; + } - return this.formatMathlist(root, format); -}; -/** - * Return true if the length of the selection is 0, that is, if it is a single - * insertion point. - * @return {boolean} - * @method MathField#$selectionIsCollapsed - */ + return false; + } + }, { + key: "_updateSuggestion", + value: function _updateSuggestion() { + this.mathlist.positionInsertionPointAfterCommitedCommand(); + this.mathlist.removeSuggestion(); + var command = this.mathlist.extractCommandStringAroundInsertionPoint(); + var suggestions = _definitions.default.suggest(command); -MathField.prototype.selectionIsCollapsed = MathField.prototype.$selectionIsCollapsed = function () { - return this.mathlist.isCollapsed(); -}; -/** - * Return the depth of the selection group. If the selection is at the root level, - * returns 0. If the selection is a portion of the numerator of a fraction - * which is at the root level, return 1. Note that in that case, the numerator - * would be the "selection group". - * @return {number} - * @method MathField#$selectionDepth - */ + if (suggestions.length === 0) { + _editorPopover.default.hidePopover(this); + this.mathlist.decorateCommandStringAroundInsertionPoint(true); + } else { + var index = this.suggestionIndex % suggestions.length; + var l = command.length - suggestions[index].match.length; -MathField.prototype.selectionDepth = MathField.prototype.$selectionDepth = function () { - return this.mathlist.path.length; -}; + if (l !== 0) { + this.mathlist.insertSuggestion(suggestions[index].match, l); + } -MathField.prototype._superscriptDepth = function () { - var result = 0; - var i = 0; - var atom = this.mathlist.ancestor(i); - var wasSuperscript = false; + _editorPopover.default.showPopoverWithLatex(this, suggestions[index].match, suggestions.length > 1); + } - while (atom) { - if (atom.superscript || atom.subscript) { - result += 1; + this._requestUpdate(); } + }, { + key: "nextSuggestion_", + value: function nextSuggestion_() { + this.suggestionIndex += 1; // The modulo of the suggestionIndex is used to determine which suggestion + // to display, so no need to worry about rolling over. - if (atom.superscript) { - wasSuperscript = true; - } else if (atom.subscript) { - wasSuperscript = false; + this._updateSuggestion(); + + return false; } + }, { + key: "previousSuggestion_", + value: function previousSuggestion_() { + this.suggestionIndex -= 1; - i += 1; - atom = this.mathlist.ancestor(i); - } + if (this.suggestionIndex < 0) { + // We're rolling over + // Get the list of suggestions, so we can know how many there are + // Not very efficient, but simple. + this.mathlist.removeSuggestion(); + var command = this.mathlist.extractCommandStringAroundInsertionPoint(); - return wasSuperscript ? result : 0; -}; + var suggestions = _definitions.default.suggest(command); -MathField.prototype._subscriptDepth = function () { - var result = 0; - var i = 0; - var atom = this.mathlist.ancestor(i); - var wasSubscript = false; + this.suggestionIndex = suggestions.length - 1; + } - while (atom) { - if (atom.superscript || atom.subscript) { - result += 1; - } + this._updateSuggestion(); - if (atom.superscript) { - wasSubscript = false; - } else if (atom.subscript) { - wasSubscript = true; + return false; } + /** + * Attach event handlers to an element so that it will react by executing + * a command when pressed. + * `'command'` can be: + * - a string, a single selector + * - an array, whose first element is a selector followed by one or more arguments. + * - an object, with the following keys: + * * 'default': command performed on up, with a down + up sequence with no + * delay between down and up + * * 'alt', 'shift', 'altshift' keys: command performed on up with + * one of these modifiers pressed + * * 'pressed': command performed on 'down' + * * 'pressAndHoldStart': command performed after a tap/down followed by a + * delay (optional) + * * 'pressAndHoldEnd': command performed on up, if there was a delay + * between down and up, if absent, 'default' is performed + * The value of the keys specify which selector (string + * or array) to perform depending on the keyboard state when the button is + * pressed. + * + * The 'pressed' and 'active' classes will get added to + * the element, as the :hover and :active pseudo-classes are not reliable + * (at least on Chrome Android). + * + * @param {HTMLElement} el + * @param {object|string} command + * @private + */ - i += 1; - atom = this.mathlist.ancestor(i); - } + }, { + key: "_attachButtonHandlers", + value: function _attachButtonHandlers(el, command) { + var that = this; + + if (_typeof(command) === 'object' && (command.default || command.pressed)) { + // Attach the default (no modifiers pressed) command to the element + if (command.default) { + el.setAttribute('data-' + this.config.namespace + 'command', JSON.stringify(command.default)); + } - return wasSubscript ? result : 0; -}; -/** - * Return true if the selection starts at the beginning of the selection group. - * @return {boolean} - * @method MathField#$selectionAtStart - */ + if (command.alt) { + el.setAttribute('data-' + this.config.namespace + 'command-alt', JSON.stringify(command.alt)); + } + if (command.altshift) { + el.setAttribute('data-' + this.config.namespace + 'command-altshift', JSON.stringify(command.altshift)); + } -MathField.prototype.selectionAtStart = MathField.prototype.$selectionAtStart = function () { - return this.mathlist.startOffset() === 0; -}; -/** - * Return true if the selection extends to the end of the selection group. - * @return {boolean} - * @method MathField#$selectionAtEnd - */ + if (command.shift) { + el.setAttribute('data-' + this.config.namespace + 'command-shift', JSON.stringify(command.shift)); + } // .pressed: command to perform when the button is pressed (i.e. + // on mouse down/touch). Otherwise the command is performed when + // the button is released -MathField.prototype.selectionAtEnd = MathField.prototype.$selectionAtEnd = function () { - return this.mathlist.endOffset() >= this.mathlist.siblings().length - 1; -}; -/** - * True if the entire group is selected - * */ + if (command.pressed) { + el.setAttribute('data-' + this.config.namespace + 'command-pressed', JSON.stringify(command.pressed)); + } + if (command.pressAndHoldStart) { + el.setAttribute('data-' + this.config.namespace + 'command-pressAndHoldStart', JSON.stringify(command.pressAndHoldStart)); + } -MathField.prototype.groupIsSelected = function () { - return this.mathlist.startOffset() === 0 && this.mathlist.endOffset() >= this.mathlist.siblings().length - 1; -}; -/** - * If `text` is not empty, sets the content of the mathfield to the - * text interpreted as a LaTeX expression. - * If `text` is empty (or omitted), return the content of the mahtfield as a - * LaTeX expression. - * @param {string} text - * - * @param {Object.} options - * @param {boolean} options.suppressChangeNotifications - If true, the - * handlers for the contentWillChange and contentDidChange notifications will - * not be invoked. Default `false`. - * - * @return {string} - * @method MathField#$latex - */ + if (command.pressAndHoldEnd) { + el.setAttribute('data-' + this.config.namespace + 'command-pressAndHoldEnd', JSON.stringify(command.pressAndHoldEnd)); + } + } else { + // We need to turn the command into a string to attach it to the dataset + // associated with the button (the command could be an array made of a + // selector and one or more parameters) + el.setAttribute('data-' + this.config.namespace + 'command', JSON.stringify(command)); + } + var pressHoldStart; + var pressHoldElement; + var touchID; + var syntheticTarget; // Target while touch move -MathField.prototype.latex = MathField.prototype.$latex = function (text, options) { - if (text) { - var oldValue = this.mathlist.root.toLatex(); + var pressAndHoldTimer; + on(el, 'mousedown touchstart:passive', function (ev) { + if (ev.type !== 'mousedown' || ev.buttons === 1) { + // The primary button was pressed or the screen was tapped. + ev.stopPropagation(); + ev.preventDefault(); + el.classList.add('pressed'); + pressHoldStart = Date.now(); // Record the ID of the primary touch point for tracking on touchmove - if (text !== oldValue) { - options = options || {}; - this.mathlist.insert(text, Object.assign({}, this.config, { - insertionMode: 'replaceAll', - selectionMode: 'after', - format: 'latex', - mode: 'math', - suppressChangeNotifications: options.suppressChangeNotifications - })); - this.undoManager.snapshot(this.config); + if (ev.type === 'touchstart') { + touchID = ev.changedTouches[0].identifier; + } // Parse the JSON to get the command (and its optional arguments) + // and perform it immediately - this._requestUpdate(); - } - return text; - } // Return the content as LaTeX + var _command2 = el.getAttribute('data-' + that.config.namespace + 'command-pressed'); + if (_command2) { + that.$perform(JSON.parse(_command2)); + } // If there is a `press and hold start` command, perform it + // after a delay, if we're still pressed by then. - return this.mathlist.root.toLatex(); -}; -/** - * Return the DOM element associated with this mathfield. - * - * Note that `this.$el().mathfield = this` - * @return {Element} - * @method MathField#$el - */ + var pressAndHoldStartCommand = el.getAttribute('data-' + that.config.namespace + 'command-pressAndHoldStart'); -MathField.prototype.el = MathField.prototype.$el = function () { - return this.element; -}; + if (pressAndHoldStartCommand) { + pressHoldElement = el; -MathField.prototype.undo_ = MathField.prototype.undo = function () { - this.complete_(); // Undo to the previous state + if (pressAndHoldTimer) { + clearTimeout(pressAndHoldTimer); + } - this.undoManager.undo(this.config); - return true; -}; + pressAndHoldTimer = window.setTimeout(function () { + if (el.classList.contains('pressed')) { + that.$perform(JSON.parse(pressAndHoldStartCommand)); + } + }, 300); + } + } + }); + on(el, 'mouseleave touchcancel', function () { + el.classList.remove('pressed'); // let command = el.getAttribute('data-' + that.config.namespace + + // 'command-pressAndHoldEnd'); + // const now = Date.now(); + // if (command && now > pressHoldStart + 300) { + // that.$perform(JSON.parse(command)); + // } + }); + on(el, 'touchmove:passive', function (ev) { + // Unlike with mouse tracking, touch tracking only sends events + // to the target that was originally tapped on. For consistency, + // we want to mimic the behavior of the mouse interaction by + // tracking the touch events and dispatching them to potential targets + ev.preventDefault(); + + for (var i = 0; i < ev.changedTouches.length; i++) { + if (ev.changedTouches[i].identifier === touchID) { + // Found a touch matching our primary/tracked touch + var target = document.elementFromPoint(ev.changedTouches[i].clientX, ev.changedTouches[i].clientY); + + if (target !== syntheticTarget && syntheticTarget) { + syntheticTarget.dispatchEvent(new MouseEvent('mouseleave'), { + bubbles: true + }); + syntheticTarget = null; + } -MathField.prototype.redo_ = MathField.prototype.redo = function () { - this.complete_(); - this.undoManager.redo(this.config); - return true; -}; + if (target) { + syntheticTarget = target; + target.dispatchEvent(new MouseEvent('mouseenter', { + bubbles: true, + buttons: 1 + })); + } + } + } + }); + on(el, 'mouseenter', function (ev) { + if (ev.buttons === 1) { + el.classList.add('pressed'); + } + }); + on(el, 'mouseup touchend click', function (ev) { + if (syntheticTarget) { + ev.stopPropagation(); + ev.preventDefault(); + var target = syntheticTarget; + syntheticTarget = null; + target.dispatchEvent(new MouseEvent('mouseup', { + bubbles: true + })); + return; + } -MathField.prototype.scrollIntoView_ = MathField.prototype.scrollIntoView = function () { - // If a render is pending, do it now to make sure we have correct layout - // and caret position - if (this.dirty) { - this._render(); - } + el.classList.remove('pressed'); + el.classList.add('active'); + + if (ev.type === 'click' && ev.detail !== 0) { + // This is a click event triggered by a mouse interaction + // (and not a keyboard interaction) + // Ignore it, we'll handle the mouseup (or touchend) instead. + ev.stopPropagation(); + ev.preventDefault(); + return; + } // Since we want the active state to be visible for a while, + // use a timer to remove it after a short delay + + + window.setTimeout(function () { + el.classList.remove('active'); + }, 150); + var command = el.getAttribute('data-' + that.config.namespace + 'command-pressAndHoldEnd'); + var now = Date.now(); // If the button has not been pressed for very long or if we were + // not the button that started the press and hold, don't consider + // it a press-and-hold. + + if (el !== pressHoldElement || now < pressHoldStart + 300) { + command = undefined; + } - var pos = this._getCaretPosition(); + if (!command && ev.altKey && ev.shiftKey) { + command = el.getAttribute('data-' + that.config.namespace + 'command-altshift'); + } - var fieldBounds = this.field.getBoundingClientRect(); + if (!command && ev.altKey) { + command = el.getAttribute('data-' + that.config.namespace + 'command-alt'); + } - if (!pos) { - var selectionBounds = this._getSelectionBounds(); + if (!command && ev.shiftKey) { + command = el.getAttribute('data-' + that.config.namespace + 'command-shift'); + } - if (selectionBounds) { - pos = { - x: selectionBounds.right + fieldBounds.left - this.field.scrollLeft, - y: selectionBounds.top + fieldBounds.top - this.field.scrollTop - }; - } - } + if (!command) { + command = el.getAttribute('data-' + that.config.namespace + 'command'); + } - if (pos) { - var x = pos.x - window.scrollX; + if (command) { + // Parse the JSON to get the command (and its optional arguments) + // and perform it + that.$perform(JSON.parse(command)); + } - if (x < fieldBounds.left) { - this.field.scroll({ - top: 0, - left: x - fieldBounds.left + this.field.scrollLeft - 20, - behavior: 'smooth' - }); - } else if (x > fieldBounds.right) { - this.field.scroll({ - top: 0, - left: x - fieldBounds.right + this.field.scrollLeft + 20, - behavior: 'smooth' + ev.stopPropagation(); + ev.preventDefault(); }); } - } -}; + }, { + key: "_makeButton", + value: function _makeButton(label, cls, ariaLabel, command) { + var button = document.createElement('span'); + button.innerHTML = label; -MathField.prototype.scrollToStart_ = MathField.prototype.scrollToStart = function () { - this.field.scroll(0, 0); -}; + if (cls) { + button.classList.add([].slice.call(cls.split(' '))); + } -MathField.prototype.scrollToEnd_ = MathField.prototype.scrollToEnd = function () { - var fieldBounds = this.field.getBoundingClientRect(); - this.field.scroll(fieldBounds.left - window.scrollX, 0); -}; -/** - * - * @method MathField#enterCommandMode_ - * @private - */ + if (ariaLabel) { + button.setAttribute('aria-label', ariaLabel); + } + this._attachButtonHandlers(button, command); -MathField.prototype.enterCommandMode_ = function () { - this.switchMode_('command'); -}; + return button; + } + /* + * Alternate options are displayed when a key on the virtual keyboard is pressed + * and held. + * + */ -MathField.prototype.copyToClipboard_ = function () { - this.focus(); // If the selection is empty, select the entire field before - // copying it. + }, { + key: "showAlternateKeys_", + value: function showAlternateKeys_(keycap, altKeys) { + var altContainer = getSharedElement('mathlive-alternate-keys-panel', 'ML__keyboard alternate-keys'); - if (this.mathlist.isCollapsed()) { - this.select(); - } + if (this.virtualKeyboard.classList.contains('material')) { + altContainer.classList.add('material'); + } - document.execCommand('copy'); - return false; -}; + if (altKeys.length >= 7) { + // Width 4 + altContainer.style.width = '286px'; + } else if (altKeys.length === 4 || altKeys.length === 2) { + // Width 2 + altContainer.style.width = '146px'; + } else if (altKeys.length === 1) { + // Width 1 + altContainer.style.width = '86px'; + } else { + // Width 3 + altContainer.style.width = '146px'; + } // Reset container height -MathField.prototype.cutToClipboard_ = function () { - this.focus(); - document.execCommand('cut'); - return true; -}; -MathField.prototype.pasteFromClipboard_ = function () { - this.focus(); - document.execCommand('paste'); - return true; -}; -/** - * This method can be invoked as a selector with {@linkcode MathField#$perform $perform("insert")} - * or called explicitly. - * - * It will insert the specified block of text at the current insertion point, - * according to the insertion mode specified. - * - * After the insertion, the selection will be set according to the `selectionMode`. - * @param {string} s - The text to be inserted - * - * @param {Object.} [options={}] - * - * @param {string} options.selectionMode - Describes where the selection - * will be after the insertion: - * * `'placeholder'`: the selection will be the first available placeholder - * in the item that has been inserted (default) - * * `'after'`: the selection will be an insertion point after the item that - * has been inserted, - * * `'before'`: the selection will be an insertion point before - * the item that has been inserted - * * `'item'`: the item that was inserted will be selected - * - * @param {string} options.mode - The mode (`'text'` or `'math'`) to use. Default - * is the current mode. - * - * @param {string} options.format - The format of the string `s`: - * * `'auto'`: the string is interpreted as a latex fragment or command) - * (default) - * * `'latex'`: the string is interpreted strictly as a latex fragment - * - * @param {boolean} options.focus - If true, the mathfield will be focused after - * the insertion - * - * @param {boolean} options.feedback - If true, provide audio and haptic feedback - * - * @param {string} options.mode - 'text' or 'math'. If empty, the current mode - * is used (default) - * - * @param {boolean} options.resetStyle - If true, the style after the insertion - * is the same as the style before (if false, the style after the - * insertion is the style of the last inserted atom). - * - * @method MathField#$insert - */ + altContainer.style.height = 'auto'; + var markup = ''; + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + try { + for (var _iterator4 = altKeys[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var altKey = _step4.value; + markup += ' 0) { - options = options || {}; - if (options.focus) this.focus(); + if (typeof altKey === 'string') { + markup += ' data-latex="' + altKey.replace(/"/g, '"') + '"'; + } else { + if (altKey.latex) { + markup += ' data-latex="' + altKey.latex.replace(/"/g, '"') + '"'; + } - if (options.feedback) { - if (this.config.keypressVibration && navigator.vibrate) { - navigator.vibrate(HAPTIC_FEEDBACK_DURATION); - } + if (altKey.content) { + markup += ' data-content="' + altKey.content.replace(/"/g, '"') + '"'; + } - if (this.keypressSound) { - this.keypressSound.load(); - this.keypressSound.play(); - } - } + if (altKey.insert) { + markup += ' data-insert="' + altKey.insert.replace(/"/g, '"') + '"'; + } - if (s === '\\\\') { - // This string is interpreted as an "insert row after" command - this.mathlist.addRowAfter_(); - } else if (s === '&') { - this.mathlist.addColumnAfter_(); - } else { - var savedStyle = this.style; - this.mathlist.insert(s, _objectSpread({ - mode: this.mode, - style: this.mathlist.anchorStyle() - }, options)); + if (altKey.command) { + markup += " data-command='" + altKey.command.replace(/"/g, '"') + "'"; + } - if (options.resetStyle) { - this.style = savedStyle; - } - } + if (altKey.aside) { + markup += ' data-aside="' + altKey.aside.replace(/"/g, '"') + '"'; + } - this.undoManager.snapshot(this.config); + if (altKey.classes) { + markup += ' data-classes="' + altKey.classes + '"'; + } + } - this._requestUpdate(); + markup += '>'; + markup += altKey.label || ''; + markup += ''; + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return != null) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } - return true; - } + markup = '
    ' + markup + '
'; + altContainer.innerHTML = markup; - return false; -}; + _editorVirtualKeyboard.default.makeKeycap(this, altContainer.getElementsByTagName('li'), 'performAlternateKeys'); -MathField.prototype.switchMode_ = function (mode, prefix, suffix) { - this._resetKeystrokeBuffer(); // Suppress (temporarily) smart mode if switching to/from text or math - // This prevents switching to/from command mode from supressing smart mode. + var keycapEl = this.virtualKeyboard.querySelector('div.keyboard-layer.is-visible div.rows ul li[data-alt-keys="' + keycap + '"]'); + var position = keycapEl.getBoundingClientRect(); + if (position) { + if (position.top - altContainer.clientHeight < 0) { + // altContainer.style.maxWidth = '320px'; // Up to six columns + altContainer.style.width = 'auto'; - this.smartModeSuppressed = /text|math/.test(this.mode) && /text|math/.test(mode); + if (altKeys.length <= 6) { + altContainer.style.height = '56px'; // 1 row + } else if (altKeys.length <= 12) { + altContainer.style.height = '108px'; // 2 rows + } else { + altContainer.style.height = '205px'; // 3 rows + } + } - if (prefix) { - this.insert(prefix, { - format: 'latex', - mode: { - 'math': 'text', - 'text': 'math' - }[mode] - }); - } // Remove any error indicator on the current command sequence (if there is one) + var top = (position.top - altContainer.clientHeight + 5).toString() + 'px'; + var 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; + } + }, { + key: "hideAlternateKeys_", + value: function hideAlternateKeys_() { + var altContainer = document.getElementById('mathlive-alternate-keys-panel'); + + if (altContainer) { + altContainer.classList.remove('is-visible'); + altContainer.innerHTML = ''; + delete releaseSharedElement(altContainer); + } - this.mathlist.decorateCommandStringAroundInsertionPoint(false); + return false; + } + /* + * The command invoked when an alternate key is pressed. + * We need to hide the Alternate Keys panel, then perform the + * command. + */ - if (mode === 'command') { - this.mathlist.removeSuggestion(); + }, { + key: "performAlternateKeys_", + value: function performAlternateKeys_(command) { + this.hideAlternateKeys_(); + return this.$perform(command); + } + }, { + key: "switchKeyboardLayer_", + value: function switchKeyboardLayer_(layer) { + if (this.config.virtualKeyboardMode !== 'off') { + if (layer !== 'lower-command' && layer !== 'upper-command' && layer !== 'symbols-command') { + // If we switch to a non-command keyboard layer, first exit command mode. + this.complete_(); + } - _editorPopover.default.hidePopover(this); + this.showVirtualKeyboard_(); // If the alternate keys panel was visible, hide it - this.suggestionIndex = 0; // Switch to the command mode keyboard layer + this.hideAlternateKeys_(); // If we were in a temporarily shifted state (shift-key held down) + // restore our state before switching to a new layer. - if (this.virtualKeyboardVisible) { - this.switchKeyboardLayer_('lower-command'); - } + this.unshiftKeyboardLayer_(); + var layers = this.virtualKeyboard.getElementsByClassName('keyboard-layer'); // Search for the requested layer - this.mathlist.insert("\x1B", { - mode: 'math' - }); - } else { - this.mode = mode; - } + var found = false; - if (suffix) { - this.insert(suffix, { - format: 'latex', - mode: mode - }); - } // Notify of mode change + for (var i = 0; i < layers.length; i++) { + if (layers[i].id === layer) { + found = true; + break; + } + } // We did find the layer, switch to it. + // If we didn't find it, do nothing and keep the current layer - if (typeof this.config.onModeChange === 'function') { - this.config.onModeChange(this, this.mode); - } + if (found) { + for (var _i = 0; _i < layers.length; _i++) { + if (layers[_i].id === layer) { + layers[_i].classList.add('is-visible'); + } else { + layers[_i].classList.remove('is-visible'); + } + } + } - this._requestUpdate(); -}; -/** - * When in command mode, insert the select command and return to math mode - * If escape is true, the command is discared. - * @param {object} options - * @param {boolean} options.discard if true, the command is discarded and the - * mode switched back to math - * @param {boolean} options.acceptSuggestion if true, accept the suggestion to - * complete the command. Otherwise, only use what has been entered so far. - * @method MathField#complete_ - * @private - */ + this.$focus(); + } + return true; + } + /* + * Temporarily change the labels and the command of the keys + * (for example when a modifier key is held down.) + */ -MathField.prototype.complete_ = function (options) { - options = options || {}; + }, { + key: "shiftKeyboardLayer_", + value: function shiftKeyboardLayer_() { + var keycaps = this.virtualKeyboard.querySelectorAll('div.keyboard-layer.is-visible .rows .keycap, div.keyboard-layer.is-visible .rows .action'); - _editorPopover.default.hidePopover(this); + if (keycaps) { + for (var i = 0; i < keycaps.length; i++) { + var keycap = keycaps[i]; + var shiftedContent = keycap.getAttribute('data-shifted'); - if (options.discard) { - this.mathlist.spliceCommandStringAroundInsertionPoint(null); - this.switchMode_('math'); - return true; - } + if (shiftedContent || /^[a-z]$/.test(keycap.innerHTML)) { + keycap.setAttribute('data-unshifted-content', keycap.innerHTML); - var command = this.mathlist.extractCommandStringAroundInsertionPoint(!options.acceptSuggestion); + if (!shiftedContent) { + shiftedContent = keycap.innerHTML.toUpperCase(); + } - if (command) { - if (command === '\\(' || command === '\\)') { - this.mathlist.spliceCommandStringAroundInsertionPoint([]); - this.mathlist.insert(command.slice(1), { - mode: this.mode - }); - } else { - // We'll assume we want to insert in math mode - // (commands are only available in math mode) - var mode = 'math'; + keycap.innerHTML = shiftedContent; + var command = keycap.getAttribute('data-' + this.config.namespace + 'command'); - if (_definitions.default.commandAllowed(mode, command)) { - var mathlist = _parser.default.parseTokens(_lexer.default.tokenize(command), mode, null, this.config.macros); + if (command) { + keycap.setAttribute('data-unshifted-command', command); + var shiftedCommand = keycap.getAttribute('data-shifted-command'); - this.mathlist.spliceCommandStringAroundInsertionPoint(mathlist); - } else { - // This wasn't a simple function or symbol. - // Interpret the input as LaTeX code - var _mathlist = _parser.default.parseTokens(_lexer.default.tokenize(command), mode, null, this.config.macros); + if (shiftedCommand) { + keycap.setAttribute('data-' + this.config.namespace + 'command', shiftedCommand); + } else { + var commandObj = JSON.parse(command); - if (_mathlist) { - this.mathlist.spliceCommandStringAroundInsertionPoint(_mathlist); - } else { - this.mathlist.decorateCommandStringAroundInsertionPoint(true); + if (Array.isArray(commandObj)) { + commandObj[1] = commandObj[1].toUpperCase(); + } + + keycap.setAttribute('data-' + this.config.namespace + 'command', JSON.stringify(commandObj)); + } + } + } } } - } - - this.undoManager.snapshot(this.config); - this._announce('replacement'); - - return true; - } + return false; + } + /* + * Restore the key labels and commands to the state before a modifier key + * was pressed. + * + */ - return false; -}; + }, { + key: "unshiftKeyboardLayer_", + value: function unshiftKeyboardLayer_() { + var keycaps = this.virtualKeyboard.querySelectorAll('div.keyboard-layer.is-visible .rows .keycap, div.keyboard-layer.is-visible .rows .action'); -MathField.prototype._updateSuggestion = function () { - this.mathlist.positionInsertionPointAfterCommitedCommand(); - this.mathlist.removeSuggestion(); - var command = this.mathlist.extractCommandStringAroundInsertionPoint(); + if (keycaps) { + for (var i = 0; i < keycaps.length; i++) { + var keycap = keycaps[i]; + var content = keycap.getAttribute('data-unshifted-content'); - var suggestions = _definitions.default.suggest(command); + if (content) { + keycap.innerHTML = content; + } - if (suggestions.length === 0) { - _editorPopover.default.hidePopover(this); + var command = keycap.getAttribute('data-unshifted-command'); - this.mathlist.decorateCommandStringAroundInsertionPoint(true); - } else { - var index = this.suggestionIndex % suggestions.length; - var l = command.length - suggestions[index].match.length; + if (command) { + keycap.setAttribute('data-' + this.config.namespace + 'command', command); + } + } + } - if (l !== 0) { - this.mathlist.insertSuggestion(suggestions[index].match, l); + return false; } + }, { + key: "insertAndUnshiftKeyboardLayer_", + value: function insertAndUnshiftKeyboardLayer_(c) { + this.$insert(c); + this.unshiftKeyboardLayer_(); + return true; + } + /* Toggle the virtual keyboard, but switch to the alternate theme if available */ - _editorPopover.default.showPopoverWithLatex(this, suggestions[index].match, suggestions.length > 1); - } + }, { + key: "toggleVirtualKeyboardAlt_", + value: function toggleVirtualKeyboardAlt_() { + var hadAltTheme = false; + + if (this.virtualKeyboard) { + hadAltTheme = this.virtualKeyboard.classList.contains('material'); + this.virtualKeyboard.remove(); + delete this.virtualKeyboard; + this.virtualKeyboard = null; + } - this._requestUpdate(); -}; + this.showVirtualKeyboard_(hadAltTheme ? '' : 'material'); + return false; + } + /* Toggle the virtual keyboard, but switch another keyboard layout */ -MathField.prototype.nextSuggestion_ = function () { - this.suggestionIndex += 1; // The modulo of the suggestionIndex is used to determine which suggestion - // to display, so no need to worry about rolling over. + }, { + key: "toggleVirtualKeyboardShift_", + value: function toggleVirtualKeyboardShift_() { + this.config.virtualKeyboardLayout = { + 'qwerty': 'azerty', + 'azerty': 'qwertz', + 'qwertz': 'dvorak', + 'dvorak': 'colemak', + 'colemak': 'qwerty' + }[this.config.virtualKeyboardLayout]; + var layer = this.virtualKeyboard ? this.virtualKeyboard.querySelector('div.keyboard-layer.is-visible') : null; + layer = layer ? layer.id : ''; + + if (this.virtualKeyboard) { + this.virtualKeyboard.remove(); + delete this.virtualKeyboard; + this.virtualKeyboard = null; + } - this._updateSuggestion(); + this.showVirtualKeyboard_(); - return false; -}; + if (layer) { + this.switchKeyboardLayer_(layer); + } -MathField.prototype.previousSuggestion_ = function () { - this.suggestionIndex -= 1; + return false; + } + }, { + key: "showVirtualKeyboard_", + value: function showVirtualKeyboard_(theme) { + this.virtualKeyboardVisible = false; + this.toggleVirtualKeyboard_(theme); + return false; + } + }, { + key: "hideVirtualKeyboard_", + value: function hideVirtualKeyboard_() { + this.virtualKeyboardVisible = true; + this.toggleVirtualKeyboard_(); + return false; + } + }, { + key: "toggleVirtualKeyboard_", + value: function toggleVirtualKeyboard_(theme) { + this.virtualKeyboardVisible = !this.virtualKeyboardVisible; - if (this.suggestionIndex < 0) { - // We're rolling over - // Get the list of suggestions, so we can know how many there are - // Not very efficient, but simple. - this.mathlist.removeSuggestion(); - var command = this.mathlist.extractCommandStringAroundInsertionPoint(); + if (this.virtualKeyboardVisible) { + if (this.virtualKeyboard) { + this.virtualKeyboard.classList.add('is-visible'); + } else { + // Construct the virtual keyboard + this.virtualKeyboard = _editorVirtualKeyboard.default.make(this, theme); // Let's make sure that tapping on the keyboard focuses the field - var suggestions = _definitions.default.suggest(command); + on(this.virtualKeyboard, 'touchstart:passive mousedown', function () { + that.$focus(); + }); + document.body.appendChild(this.virtualKeyboard); + } // For the transition effect to work, the property has to be changed + // after the insertion in the DOM. Use setTimeout - this.suggestionIndex = suggestions.length - 1; - } - this._updateSuggestion(); + var that = this; + window.setTimeout(function () { + that.virtualKeyboard.classList.add('is-visible'); + }, 1); + } else if (this.virtualKeyboard) { + this.virtualKeyboard.classList.remove('is-visible'); + } - return false; -}; -/** - * Attach event handlers to an element so that it will react by executing - * a command when pressed. - * `'command'` can be: - * - a string, a single selector - * - an array, whose first element is a selector followed by one or more arguments. - * - an object, with the following keys: - * * 'default': command performed on up, with a down + up sequence with no - * delay between down and up - * * 'alt', 'shift', 'altshift' keys: command performed on up with - * one of these modifiers pressed - * * 'pressed': command performed on 'down' - * * 'pressAndHoldStart': command performed after a tap/down followed by a - * delay (optional) - * * 'pressAndHoldEnd': command performed on up, if there was a delay - * between down and up, if absent, 'default' is performed - * The value of the keys specify which selector (string - * or array) to perform depending on the keyboard state when the button is - * pressed. - * - * The 'pressed' and 'active' classes will get added to - * the element, as the :hover and :active pseudo-classes are not reliable - * (at least on Chrome Android). - * - * @param {Element} el - * @param {object|string} command - */ + if (typeof this.config.onVirtualKeyboardToggle === 'function') { + this.config.onVirtualKeyboardToggle(this, this.virtualKeyboardVisible, this.virtualKeyboard); + } + return false; + } + /** + * Apply a style (color, bold, italic, etc...). + * + * If there is a selection, the style is applied to the selection + * + * If the selection already has this style, remove it. If the selection + * has the style partially applied (i.e. only some sections), remove it from + * those sections, and apply it to the entire selection. + * + * If there is no selection, the style will apply to the next character typed. + * + * @param {object} style an object with the following properties. All the + * properties are optional, but they can be combined. + * + * @param {string} [style.mode=''] - Either `'math'`, `'text'` or '`command`' + * @param {string} [style.color=''] - The text/fill color, as a CSS RGB value or + * a string for some 'well-known' colors, e.g. 'red', '#f00', etc... + * + * @param {string} [style.backgroundColor=''] - The background color. + * + * @param {string} [style.fontFamily=''] - The font family used to render text. + * This value can the name of a locally available font, or a CSS font stack, e.g. + * "Avenir", "Georgia, serif", etc... + * This can also be one of the following TeX-specific values: + * - 'cmr': Computer Modern Roman, serif + * - 'cmss': Computer Modern Sans-serif, latin characters only + * - 'cmtt': Typewriter, slab, latin characters only + * - 'cal': Calligraphic style, uppercase latin letters and digits only + * - 'frak': Fraktur, gothic, uppercase, lowercase and digits + * - 'bb': Blackboard bold, uppercase only + * - 'scr': Script style, uppercase only + * + * @param {string} [style.series=''] - The font 'series', i.e. weight and + * stretch. The following values can be combined, for example: "ebc": extra-bold, + * condensed. Aside from 'b', these attributes may not have visible effect if the + * font family does not support this attribute: + * - 'ul' ultra-light weight + * - 'el': extra-light + * - 'l': light + * - 'sl': semi-light + * - 'm': medium (default) + * - 'sb': semi-bold + * - 'b': bold + * - 'eb': extra-bold + * - 'ub': ultra-bold + * - 'uc': ultra-condensed + * - 'ec': extra-condensed + * - 'c': condensed + * - 'sc': semi-condensed + * - 'n': normal (default) + * - 'sx': semi-expanded + * - 'x': expanded + * - 'ex': extra-expanded + * - 'ux': ultra-expanded + * + * @param {string} [style.shape=''] - The font 'shape', i.e. italic. + * - 'auto': italic or upright, depending on mode and letter (single letters are + * italic in math mode) + * - 'up': upright + * - 'it': italic + * - 'sl': slanted or oblique (often the same as italic) + * - 'sc': small caps + * - 'ol': outline + * + * @param {string} [style.size=''] - The font size: 'size1'...'size10' + * 'size5' is the default size + * @method MathField#$applyStyle + * */ -MathField.prototype._attachButtonHandlers = function (el, command) { - var that = this; + }, { + key: "$applyStyle", + value: function $applyStyle(style) { + this._resetKeystrokeBuffer(); - if (_typeof(command) === 'object' && (command.default || command.pressed)) { - // Attach the default (no modifiers pressed) command to the element - if (command.default) { - el.setAttribute('data-' + this.config.namespace + 'command', JSON.stringify(command.default)); - } + style = validateStyle(style); - if (command.alt) { - el.setAttribute('data-' + this.config.namespace + 'command-alt', JSON.stringify(command.alt)); - } + if (style.mode) { + // There's a mode ('text', 'math', 'command') change + if (this.mathlist.isCollapsed()) { + // Nothing selected + this.switchMode_(style.mode); + } else { + // Convert the selection from one mode to another + var previousMode = this.mode; + var targetMode = (this.mathlist.anchorMode() || this.config.default) === 'math' ? 'text' : 'math'; + var convertedSelection = this.$selectedText('ASCIIMath'); - if (command.altshift) { - el.setAttribute('data-' + this.config.namespace + 'command-altshift', JSON.stringify(command.altshift)); - } + if (targetMode === 'math' && /^"[^"]+"$/.test(convertedSelection)) { + convertedSelection = convertedSelection.slice(1, -1); + } - if (command.shift) { - el.setAttribute('data-' + this.config.namespace + 'command-shift', JSON.stringify(command.shift)); - } // .pressed: command to perform when the button is pressed (i.e. - // on mouse down/touch). Otherwise the command is performed when - // the button is released + this.$insert(convertedSelection, { + mode: targetMode, + selectionMode: 'item', + format: targetMode === 'text' ? 'text' : 'ASCIIMath' + }); + this.mode = targetMode; + if (this.groupIsSelected()) { + // The entire group was selected. Adjust parent mode if + // appropriate + var parent = this.mathlist.parent(); - if (command.pressed) { - el.setAttribute('data-' + this.config.namespace + 'command-pressed', JSON.stringify(command.pressed)); - } + if (parent && (parent.type === 'group' || parent.type === 'root')) { + parent.mode = targetMode; + } + } // Notify of mode change - if (command.pressAndHoldStart) { - el.setAttribute('data-' + this.config.namespace + 'command-pressAndHoldStart', JSON.stringify(command.pressAndHoldStart)); - } - if (command.pressAndHoldEnd) { - el.setAttribute('data-' + this.config.namespace + 'command-pressAndHoldEnd', JSON.stringify(command.pressAndHoldEnd)); - } - } else { - // We need to turn the command into a string to attach it to the dataset - // associated with the button (the command could be an array made of a - // selector and one or more parameters) - el.setAttribute('data-' + this.config.namespace + 'command', JSON.stringify(command)); - } + if (this.mode !== previousMode && typeof this.config.onModeChange === 'function') { + this.config.onModeChange(this, this.mode); + } + } - var pressHoldStart; - var pressHoldElement; - var touchID; - var syntheticTarget; // Target while touch move + delete style.mode; + } - var pressAndHoldTimer; - on(el, 'mousedown touchstart:passive', function (ev) { - if (ev.type !== 'mousedown' || ev.buttons === 1) { - // The primary button was pressed or the screen was tapped. - ev.stopPropagation(); - ev.preventDefault(); - el.classList.add('pressed'); - pressHoldStart = Date.now(); // Record the ID of the primary touch point for tracking on touchmove + if (this.mathlist.isCollapsed()) { + // No selection, let's update the 'current' style + if (this.style.fontSeries && style.fontSeries === this.style.fontSeries) { + style.fontSeries = 'auto'; + } - if (ev.type === 'touchstart') touchID = ev.changedTouches[0].identifier; // Parse the JSON to get the command (and its optional arguments) - // and perform it immediately + if (style.fontShape && style.fontShape === this.style.fontShape) { + style.fontShape = 'auto'; + } - var _command2 = el.getAttribute('data-' + that.config.namespace + 'command-pressed'); + if (style.color && style.color === this.style.color) { + style.color = 'none'; + } - if (_command2) { - that.perform(JSON.parse(_command2)); - } // If there is a `press and hold start` command, perform it - // after a delay, if we're still pressed by then. + if (style.backgroundColor && style.backgroundColor === this.style.backgroundColor) { + style.backgroundColor = 'none'; + } + if (style.fontSize && style.fontSize === this.style.fontSize) { + style.fontSize = 'auto'; + } - var pressAndHoldStartCommand = el.getAttribute('data-' + that.config.namespace + 'command-pressAndHoldStart'); + this.style = _objectSpread({}, this.style, {}, style); // This style will be used the next time an atom is inserted + } else { + // Change the style of the selection + this.mathlist._applyStyle(style); - if (pressAndHoldStartCommand) { - pressHoldElement = el; - if (pressAndHoldTimer) clearTimeout(pressAndHoldTimer); - pressAndHoldTimer = window.setTimeout(function () { - if (el.classList.contains('pressed')) { - that.perform(JSON.parse(pressAndHoldStartCommand)); - } - }, 300); + this.undoManager.snapshot(this.config); } + + return true; } - }); - on(el, 'mouseleave touchcancel', function () { - el.classList.remove('pressed'); // let command = el.getAttribute('data-' + that.config.namespace + - // 'command-pressAndHoldEnd'); - // const now = Date.now(); - // if (command && now > pressHoldStart + 300) { - // that.perform(JSON.parse(command)); - // } - }); - on(el, 'touchmove:passive', function (ev) { - // Unlike with mouse tracking, touch tracking only sends events - // to the target that was originally tapped on. For consistency, - // we want to mimic the behavior of the mouse interaction by - // tracking the touch events and dispatching them to potential targets - ev.preventDefault(); - - for (var i = 0; i < ev.changedTouches.length; i++) { - if (ev.changedTouches[i].identifier === touchID) { - // Found a touch matching our primary/tracked touch - var target = document.elementFromPoint(ev.changedTouches[i].clientX, ev.changedTouches[i].clientY); - - if (target !== syntheticTarget && syntheticTarget) { - syntheticTarget.dispatchEvent(new MouseEvent('mouseleave'), { - bubbles: true - }); - syntheticTarget = null; + }, { + key: "$hasFocus", + value: function $hasFocus() { + return document.hasFocus() && document.activeElement === this.textarea; + } + }, { + key: "$focus", + value: function $focus() { + if (!this.$hasFocus()) { + // The textarea may be a span (on mobile, for example), so check that + // it has a focus() before calling it. + if (this.textarea.focus) { + this.textarea.focus(); } - if (target) { - syntheticTarget = target; - target.dispatchEvent(new MouseEvent('mouseenter', { - bubbles: true, - buttons: 1 - })); + this._announce('line'); + } + } + }, { + key: "$blur", + value: function $blur() { + if (this.$hasFocus()) { + if (this.textarea.blur) { + this.textarea.blur(); } } } - }); - on(el, 'mouseenter', function (ev) { - if (ev.buttons === 1) { - el.classList.add('pressed'); + }, { + key: "$select", + value: function $select() { + this.mathlist.selectAll_(); } - }); - on(el, 'mouseup touchend click', function (ev) { - if (syntheticTarget) { - ev.stopPropagation(); - ev.preventDefault(); - var target = syntheticTarget; - syntheticTarget = null; - target.dispatchEvent(new MouseEvent('mouseup', { - bubbles: true - })); - return; + }, { + key: "$clearSelection", + value: function $clearSelection() { + this.mathlist.delete_(); } + /** + * @param {string} keys - A string representation of a key combination. + * + * For example `'Alt-KeyU'`. + * + * See [W3C UIEvents](https://www.w3.org/TR/uievents/#code-virtual-keyboards) + * @param {Event} evt + * @return {boolean} + * @method MathField#$keystroke + */ - el.classList.remove('pressed'); - el.classList.add('active'); - - if (ev.type === 'click' && ev.detail !== 0) { - // This is a click event triggered by a mouse interaction - // (and not a keyboard interaction) - // Ignore it, we'll handle the mouseup (or touchend) instead. - ev.stopPropagation(); - ev.preventDefault(); - return; - } // Since we want the active state to be visible for a while, - // use a timer to remove it after a short delay - - - window.setTimeout(function () { - el.classList.remove('active'); - }, 150); - var command = el.getAttribute('data-' + that.config.namespace + 'command-pressAndHoldEnd'); - var now = Date.now(); // If the button has not been pressed for very long or if we were - // not the button that started the press and hold, don't consider - // it a press-and-hold. - - if (el !== pressHoldElement || now < pressHoldStart + 300) { - command = undefined; + }, { + key: "$keystroke", + value: function $keystroke(keys, evt) { + // This is the public API, while onKeystroke is the + // internal handler + return this._onKeystroke(keys, evt); } + /** + * Simulate a user typing the keys indicated by text. + * @param {string} text - A sequence of one or more characters. + * @method MathField#$typedText + */ - if (!command && ev.altKey && ev.shiftKey) { - command = el.getAttribute('data-' + that.config.namespace + 'command-altshift'); + }, { + key: "$typedText", + value: function $typedText(text) { + // This is the public API, while onTypedText is the + // internal handler + this._onTypedText(text); } + /** + * + * @param {string} text + * @param {Object.} [options={}] + * @param {boolean} options.focus - If true, the mathfield will be focused + * @param {boolean} options.feedback - If true, provide audio and haptic feedback + * @param {boolean} options.simulateKeystroke - If true, generate some synthetic + * keystrokes (useful to trigger inline shortcuts, for example) + * @private + */ - if (!command && ev.altKey) { - command = el.getAttribute('data-' + that.config.namespace + 'command-alt'); + }, { + key: "typedText_", + value: function typedText_(text, options) { + return this._onTypedText(text, options); } + /** + * + * Update the configuration options for this mathfield. + * + * @param {MathFieldConfig} [config={}] See {@tutorial CONFIG} for details. + * + * @method MathField#$setConfig + */ - if (!command && ev.shiftKey) { - command = el.getAttribute('data-' + that.config.namespace + 'command-shift'); - } + }, { + key: "$setConfig", + value: function $setConfig(conf) { + if (!this.config) { + this.config = { + smartFence: true, + smartSuperscript: true, + scriptDepth: [Infinity, Infinity], + removeExtraneousParentheses: true, + overrideDefaultInlineShortcuts: false, + virtualKeyboard: '', + virtualKeyboardLayout: 'qwerty', + namespace: '' + }; + } - if (!command) { - command = el.getAttribute('data-' + that.config.namespace + 'command'); - } + this.config = _objectSpread({}, this.config, {}, conf); - if (command) { - // Parse the JSON to get the command (and its optional arguments) - // and perform it - that.perform(JSON.parse(command)); - } + if (this.config.scriptDepth !== undefined && !Array.isArray(this.config.scriptDepth)) { + var depth = parseInt(this.config.scriptDepth); + this.config.scriptDepth = [depth, depth]; + } - ev.stopPropagation(); - ev.preventDefault(); - }); -}; + if (typeof this.config.removeExtraneousParentheses === 'undefined') { + this.config.removeExtraneousParentheses = true; + } -MathField.prototype._makeButton = function (label, cls, ariaLabel, command) { - var button = document.createElement('span'); - button.innerHTML = label; - if (cls) button.classList.add([].slice.call(cls.split(' '))); - if (ariaLabel) button.setAttribute('aria-label', ariaLabel); + this.config.onAnnounce = conf.onAnnounce || _onAnnounce; + this.config.macros = Object.assign({}, _definitions.default.MACROS, this.config.macros); // Validate the namespace (used for `data-` attributes) - this._attachButtonHandlers(button, command); + if (!/^[a-z]*[-]?$/.test(this.config.namespace)) { + throw Error('options.namespace must be a string of lowercase characters only'); + } - return button; -}; -/** - * Alternate options are displayed when a key on the virtual keyboard is pressed - * and held. - * - */ + if (!/-$/.test(this.config.namespace)) { + this.config.namespace += '-'; + } // Localization strings override (or localizations for new locales) + + + _l10n.l10n.locale = this.config.locale || _l10n.l10n.locale; + + _l10n.l10n.merge(this.config.strings); + + this.config.virtualKeyboardLayout = conf.virtualKeyboardLayout || { + 'fr': 'azerty', + 'be': 'azerty', + 'al': 'qwertz', + 'ba': 'qwertz', + 'cz': 'qwertz', + 'de': 'qwertz', + 'hu': 'qwertz', + 'sk': 'qwertz', + 'ch': 'qwertz' + }[_l10n.l10n.locale.substring(0, 2)] || 'qwerty'; // Possible keypress sound feedback + + this.keypressSound = undefined; + this.spacebarKeypressSound = undefined; + this.returnKeypressSound = undefined; + this.deleteKeypressSound = undefined; + + if (this.config.keypressSound) { + if (typeof this.config.keypressSound === 'string') { + this.keypressSound = new Audio(); + this.keypressSound.preload = 'none'; + this.keypressSound.src = this.config.keypressSound; + this.keypressSound.volume = AUDIO_FEEDBACK_VOLUME; + this.spacebarKeypressSound = this.keypressSound; + this.returnKeypressSound = this.keypressSound; + this.deleteKeypressSound = this.keypressSound; + } else { + console.assert(this.config.keypressSound.default); + this.keypressSound = new Audio(); + this.keypressSound.preload = 'none'; + this.keypressSound.src = this.config.keypressSound.default; + this.keypressSound.volume = AUDIO_FEEDBACK_VOLUME; + this.spacebarKeypressSound = this.keypressSound; + this.returnKeypressSound = this.keypressSound; + this.deleteKeypressSound = this.keypressSound; + + if (this.config.keypressSound.spacebar) { + this.spacebarKeypressSound = new Audio(); + this.spacebarKeypressSound.preload = 'none'; + this.spacebarKeypressSound.src = this.config.keypressSound.spacebar; + this.spacebarKeypressSound.volume = AUDIO_FEEDBACK_VOLUME; + } + if (this.config.keypressSound.return) { + this.returnKeypressSound = new Audio(); + this.returnKeypressSound.preload = 'none'; + this.returnKeypressSound.src = this.config.keypressSound.return; + this.returnKeypressSound.volume = AUDIO_FEEDBACK_VOLUME; + } -MathField.prototype.showAlternateKeys_ = function (keycap, altKeys) { - var altContainer = getSharedElement('mathlive-alternate-keys-panel', 'ML__keyboard alternate-keys'); + if (this.config.keypressSound.delete) { + this.deleteKeypressSound = new Audio(); + this.deleteKeypressSound.preload = 'none'; + this.deleteKeypressSound.src = this.config.keypressSound.delete; + this.deleteKeypressSound.volume = AUDIO_FEEDBACK_VOLUME; + } + } + } - if (this.virtualKeyboard.classList.contains('material')) { - altContainer.classList.add('material'); - } + if (this.config.plonkSound) { + this.plonkSound = new Audio(); + this.plonkSound.preload = 'none'; + this.plonkSound.src = this.config.plonkSound; + this.plonkSound.volume = AUDIO_FEEDBACK_VOLUME; + } + } + /** + * + * Speak some part of the expression, either with or without synchronized highlighting. + * + * @param {string} amount (all, selection, left, right, group, parent) + * @param {object} speakOptions + * @param {boolean} speakOptions.withHighlighting - If true, synchronized highlighting of speech will happen (if possible). Default is false. + * + * @method MathField#speak_ + */ - if (altKeys.length >= 7) { - // Width 4 - altContainer.style.width = '286px'; - } else if (altKeys.length === 4 || altKeys.length === 2) { - // Width 2 - altContainer.style.width = '146px'; - } else if (altKeys.length === 1) { - // Width 1 - altContainer.style.width = '86px'; - } else { - // Width 3 - altContainer.style.width = '146px'; - } // Reset container height + }, { + key: "speak_", + value: function speak_(amount, speakOptions) { + speakOptions = speakOptions || { + withHighlighting: false + }; + function getAtoms(mathField, amount) { + var result = null; - altContainer.style.height = 'auto'; - var markup = ''; - var _iteratorNormalCompletion4 = true; - var _didIteratorError4 = false; - var _iteratorError4 = undefined; + switch (amount) { + case 'all': + result = mathField.mathlist.root; + break; - try { - for (var _iterator4 = altKeys[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { - var altKey = _step4.value; - markup += '= 1) { + result = []; - if (altKey.command) { - markup += " data-command='" + altKey.command.replace(/"/g, '"') + "'"; - } + for (var i = 1; i <= last; i++) { + result.push(siblings[i]); + } + } - if (altKey.aside) { - markup += ' data-aside="' + altKey.aside.replace(/"/g, '"') + '"'; - } + break; + } - if (altKey.classes) { - markup += ' data-classes="' + altKey.classes + '"'; - } - } + case 'right': + { + var _siblings = mathField.mathlist.siblings(); - markup += '>'; - markup += altKey.label || ''; - markup += ''; - } - } catch (err) { - _didIteratorError4 = true; - _iteratorError4 = err; - } finally { - try { - if (!_iteratorNormalCompletion4 && _iterator4.return != null) { - _iterator4.return(); - } - } finally { - if (_didIteratorError4) { - throw _iteratorError4; - } - } - } + var first = mathField.mathlist.endOffset() + 1; - markup = '
    ' + markup + '
'; - altContainer.innerHTML = markup; + if (first <= _siblings.length - 1) { + result = []; - _editorVirtualKeyboard.default.makeKeycap(this, altContainer.getElementsByTagName('li'), 'performAlternateKeys'); + for (var _i2 = first; _i2 <= _siblings.length - 1; _i2++) { + result.push(_siblings[_i2]); + } + } - var keycapEl = this.virtualKeyboard.querySelector('div.keyboard-layer.is-visible div.rows ul li[data-alt-keys="' + keycap + '"]'); - var position = keycapEl.getBoundingClientRect(); + break; + } - if (position) { - if (position.top - altContainer.clientHeight < 0) { - // altContainer.style.maxWidth = '320px'; // Up to six columns - altContainer.style.width = 'auto'; + case 'start': + case 'end': + // not yet implemented + break; - if (altKeys.length <= 6) { - altContainer.style.height = '56px'; // 1 row - } else if (altKeys.length <= 12) { - altContainer.style.height = '108px'; // 2 rows - } else { - altContainer.style.height = '205px'; // 3 rows - } - } + case 'group': + result = mathField.mathlist.siblings(); + break; - var top = (position.top - altContainer.clientHeight + 5).toString() + 'px'; - var 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'); - } + case 'parent': + { + var parent = mathField.mathlist.parent(); - return false; -}; + if (parent && parent.type !== 'root') { + result = mathField.mathlist.parent(); + } -MathField.prototype.hideAlternateKeys_ = function () { - var altContainer = document.getElementById('mathlive-alternate-keys-panel'); + break; + } - if (altContainer) { - altContainer.classList.remove('is-visible'); - altContainer.innerHTML = ''; - delete releaseSharedElement(altContainer); - } + default: + console.log('unknown atom type "' + mathField.type + '"'); + break; + } - return false; -}; -/** - * The command invoked when an alternate key is pressed. - * We need to hide the Alternate Keys panel, then perform the - * command. - */ + return result; + } + function getFailedSpeech(amount) { + var result = ''; -MathField.prototype.performAlternateKeys_ = function (command) { - this.hideAlternateKeys_(); - return this.perform(command); -}; + switch (amount) { + case 'all': + console.log("Internal failure: speak all failed"); + break; -MathField.prototype.switchKeyboardLayer_ = function (layer) { - if (this.config.virtualKeyboardMode !== 'off') { - if (layer !== 'lower-command' && layer !== 'upper-command' && layer !== 'symbols-command') { - // If we switch to a non-command keyboard layer, first exit command mode. - this.complete_(); - } + case 'selection': + result = 'no selection'; + break; - this.showVirtualKeyboard_(); // If the alternate keys panel was visible, hide it + case 'left': + result = 'at start'; + break; - this.hideAlternateKeys_(); // If we were in a temporarily shifted state (shift-key held down) - // restore our state before switching to a new layer. + case 'right': + result = 'at end'; + break; - this.unshiftKeyboardLayer_(); - var layers = this.virtualKeyboard.getElementsByClassName('keyboard-layer'); // Search for the requested layer + case 'group': + console.log("Internal failure: speak group failed"); + break; - var found = false; + case 'parent': + result = 'no parent'; + break; - for (var i = 0; i < layers.length; i++) { - if (layers[i].id === layer) { - found = true; - break; - } - } // We did find the layer, switch to it. - // If we didn't find it, do nothing and keep the current layer - - - if (found) { - for (var _i = 0; _i < layers.length; _i++) { - if (layers[_i].id === layer) { - layers[_i].classList.add('is-visible'); - } else { - layers[_i].classList.remove('is-visible'); + default: + console.log('unknown speak_ param value: "' + amount + '"'); + break; } - } - } - - this.focus(); - } - - return true; -}; -/** - * Temporarily change the labels and the command of the keys - * (for example when a modifier key is held down.) - */ - - -MathField.prototype.shiftKeyboardLayer_ = function () { - var keycaps = this.virtualKeyboard.querySelectorAll('div.keyboard-layer.is-visible .rows .keycap, div.keyboard-layer.is-visible .rows .action'); - if (keycaps) { - for (var i = 0; i < keycaps.length; i++) { - var keycap = keycaps[i]; - var shiftedContent = keycap.getAttribute('data-shifted'); - - if (shiftedContent || /^[a-z]$/.test(keycap.innerHTML)) { - keycap.setAttribute('data-unshifted-content', keycap.innerHTML); - - if (!shiftedContent) { - shiftedContent = keycap.innerHTML.toUpperCase(); - } + return result; + } - keycap.innerHTML = shiftedContent; - var command = keycap.getAttribute('data-' + this.config.namespace + 'command'); + var atoms = getAtoms(this, amount); - if (command) { - keycap.setAttribute('data-unshifted-command', command); - var shiftedCommand = keycap.getAttribute('data-shifted-command'); + if (atoms === null) { + this.config.handleSpeak(getFailedSpeech(amount)); + return false; + } - if (shiftedCommand) { - keycap.setAttribute('data-' + this.config.namespace + 'command', shiftedCommand); - } else { - var commandObj = JSON.parse(command); + var options = _objectSpread({}, this.config); - if (Array.isArray(commandObj)) { - commandObj[1] = commandObj[1].toUpperCase(); - } + if (speakOptions.withHighlighting || options.speechEngine === 'amazon') { + options.textToSpeechMarkup = window.sre && options.textToSpeechRules === 'sre' ? 'ssml_step' : 'ssml'; - keycap.setAttribute('data-' + this.config.namespace + 'command', JSON.stringify(commandObj)); - } + if (speakOptions.withHighlighting) { + options.generateID = true; } } - } - } - - return false; -}; -/** - * Restore the key labels and commands to the state before a modifier key - * was pressed. - * - */ + var text = _mathAtom.default.toSpeakableText(atoms, options); -MathField.prototype.unshiftKeyboardLayer_ = function () { - var keycaps = this.virtualKeyboard.querySelectorAll('div.keyboard-layer.is-visible .rows .keycap, div.keyboard-layer.is-visible .rows .action'); + if (speakOptions.withHighlighting) { + window.mathlive.readAloudMathField = this; - if (keycaps) { - for (var i = 0; i < keycaps.length; i++) { - var keycap = keycaps[i]; - var content = keycap.getAttribute('data-unshifted-content'); + this._render({ + forHighlighting: true + }); - if (content) { - keycap.innerHTML = content; + if (this.config.handleReadAloud) { + this.config.handleReadAloud(this.field, text, this.config); + } + } else { + if (this.config.handleSpeak) { + this.config.handleSpeak(text, options); + } } - var command = keycap.getAttribute('data-unshifted-command'); - - if (command) { - keycap.setAttribute('data-' + this.config.namespace + 'command', command); - } + return false; } - } + }]); - return false; -}; - -MathField.prototype.insertAndUnshiftKeyboardLayer_ = function (c) { - this.insert_(c); - this.unshiftKeyboardLayer_(); - return true; -}; -/* Toggle the virtual keyboard, but switch to the alternate theme if available */ - - -MathField.prototype.toggleVirtualKeyboardAlt_ = function () { - var hadAltTheme = false; - - if (this.virtualKeyboard) { - hadAltTheme = this.virtualKeyboard.classList.contains('material'); - this.virtualKeyboard.remove(); - delete this.virtualKeyboard; - this.virtualKeyboard = null; - } - - this.showVirtualKeyboard_(hadAltTheme ? '' : 'material'); - return false; -}; -/* Toggle the virtual keyboard, but switch another keyboard layout */ - - -MathField.prototype.toggleVirtualKeyboardShift_ = function () { - this.config.virtualKeyboardLayout = { - 'qwerty': 'azerty', - 'azerty': 'qwertz', - 'qwertz': 'dvorak', - 'dvorak': 'colemak', - 'colemak': 'qwerty' - }[this.config.virtualKeyboardLayout]; - var layer = this.virtualKeyboard ? this.virtualKeyboard.querySelector('div.keyboard-layer.is-visible') : null; - layer = layer ? layer.id : ''; - - if (this.virtualKeyboard) { - this.virtualKeyboard.remove(); - delete this.virtualKeyboard; - this.virtualKeyboard = null; - } - - this.showVirtualKeyboard_(); - if (layer) this.switchKeyboardLayer_(layer); - return false; -}; - -MathField.prototype.showVirtualKeyboard_ = function (theme) { - this.virtualKeyboardVisible = false; - this.toggleVirtualKeyboard_(theme); - return false; -}; - -MathField.prototype.hideVirtualKeyboard_ = function () { - this.virtualKeyboardVisible = true; - this.toggleVirtualKeyboard_(); - return false; -}; - -MathField.prototype.toggleVirtualKeyboard_ = function (theme) { - this.virtualKeyboardVisible = !this.virtualKeyboardVisible; - - if (this.virtualKeyboardVisible) { - if (this.virtualKeyboard) { - this.virtualKeyboard.classList.add('is-visible'); - } else { - // Construct the virtual keyboard - this.virtualKeyboard = _editorVirtualKeyboard.default.make(this, theme); // Let's make sure that tapping on the keyboard focuses the field - - on(this.virtualKeyboard, 'touchstart:passive mousedown', function () { - that.focus(); - }); - document.body.appendChild(this.virtualKeyboard); - } // For the transition effect to work, the property has to be changed - // after the insertion in the DOM. Use setTimeout - - - var that = this; - window.setTimeout(function () { - that.virtualKeyboard.classList.add('is-visible'); - }, 1); - } else if (this.virtualKeyboard) { - this.virtualKeyboard.classList.remove('is-visible'); - } - - if (typeof this.config.onVirtualKeyboardToggle === 'function') { - this.config.onVirtualKeyboardToggle(this, this.virtualKeyboardVisible, this.virtualKeyboard); - } - - return false; -}; + return MathField; +}(); /** - * Validate a style specification object - * @param {object} style + * Utility function that returns the element which has the caret + * + * @param {DomElement} el + * @private */ -function validateStyle(style) { - var result = {}; - - if (typeof style.mode === 'string') { - result.mode = style.mode.toLowerCase(); - console.assert(result.mode === 'math' || result.mode === 'text' || result.mode === 'command'); - } - - if (typeof style.color === 'string') { - result.color = style.color; - } - - if (typeof style.backgroundColor === 'string') { - result.backgroundColor = style.backgroundColor; - } - - if (typeof style.fontFamily === 'string') { - result.fontFamily = style.fontFamily; - } - - if (typeof style.series === 'string') { - result.fontSeries = style.series; - } - - if (typeof style.fontSeries === 'string') { - result.fontSeries = style.fontSeries.toLowerCase(); - } - - if (result.fontSeries) { - result.fontSeries = { - "bold": 'b', - "medium": 'm', - "normal": 'mn' - }[result.fontSeries] || result.fontSeries; - } - - if (typeof style.shape === 'string') { - result.fontShape = style.shape; - } - - if (typeof style.fontShape === 'string') { - result.fontShape = style.fontShape.toLowerCase(); - } - - if (result.fontShape) { - result.fontShape = { - "italic": 'it', - "up": 'n', - "upright": 'n', - "normal": 'n' - }[result.fontShape] || result.fontShape; - } - - if (typeof style.size === 'string') { - result.fontSize = style.size; - } else if (typeof style.size === 'number') { - result.fontSize = 'size' + Math.min(0, Math.max(10, style.size)); - } - - if (typeof style.fontSize === 'string') { - result.fontSize = style.fontSize.toLowerCase(); - } - - if (result.fontSize) { - result.fontSize = { - 'tiny': 'size1', - 'scriptsize': 'size2', - 'footnotesize': 'size3', - 'small': 'size4', - 'normal': 'size5', - 'normalsize': 'size5', - 'large': 'size6', - 'Large': 'size7', - 'LARGE': 'size8', - 'huge': 'size9', - 'Huge': 'size10' - }[result.fontSize] || result.fontSize; +function _findElementWithCaret(el) { + if (el.classList.contains('ML__caret') || el.classList.contains('ML__text-caret') || el.classList.contains('ML__command-caret')) { + return el; } + var result; + Array.from(el.children).forEach(function (child) { + result = result || _findElementWithCaret(child); + }); return result; } /** - * Apply a style (color, bold, italic, etc...). - * - * If there is a selection, the style is applied to the selection - * - * If the selection already has this style, remove it. If the selection - * has the style partially applied (i.e. only some sections), remove it from - * those sections, and apply it to the entire selection. - * - * If there is no selection, the style will apply to the next character typed. - * - * @param {object} style an object with the following properties. All the - * properties are optional, but they can be combined. - * - * @param {string} [style.mode=''] - Either `'math'`, `'text'` or '`command`' - * @param {string} [style.color=''] - The text/fill color, as a CSS RGB value or - * a string for some 'well-known' colors, e.g. 'red', '#f00', etc... - * - * @param {string} [style.backgroundColor=''] - The background color. - * - * @param {string} [style.fontFamily=''] - The font family used to render text. - * This value can the name of a locally available font, or a CSS font stack, e.g. - * "Avenir", "Georgia, serif", etc... - * This can also be one of the following TeX-specific values: - * - 'cmr': Computer Modern Roman, serif - * - 'cmss': Computer Modern Sans-serif, latin characters only - * - 'cmtt': Typewriter, slab, latin characters only - * - 'cal': Calligraphic style, uppercase latin letters and digits only - * - 'frak': Fraktur, gothic, uppercase, lowercase and digits - * - 'bb': Blackboard bold, uppercase only - * - 'scr': Script style, uppercase only - * - * @param {string} [style.series=''] - The font 'series', i.e. weight and - * stretch. The following values can be combined, for example: "ebc": extra-bold, - * condensed. Aside from 'b', these attributes may not have visible effect if the - * font family does not support this attribute: - * - 'ul' ultra-light weight - * - 'el': extra-light - * - 'l': light - * - 'sl': semi-light - * - 'm': medium (default) - * - 'sb': semi-bold - * - 'b': bold - * - 'eb': extra-bold - * - 'ub': ultra-bold - * - 'uc': ultra-condensed - * - 'ec': extra-condensed - * - 'c': condensed - * - 'sc': semi-condensed - * - 'n': normal (default) - * - 'sx': semi-expanded - * - 'x': expanded - * - 'ex': extra-expanded - * - 'ux': ultra-expanded - * - * @param {string} [style.shape=''] - The font 'shape', i.e. italic. - * - 'auto': italic or upright, depending on mode and letter (single letters are - * italic in math mode) - * - 'up': upright - * - 'it': italic - * - 'sl': slanted or oblique (often the same as italic) - * - 'sc': small caps - * - 'ol': outline - * - * @param {string} [style.size=''] - The font size: 'size1'...'size10' - * 'size5' is the default size - * */ - - -MathField.prototype.$applyStyle = MathField.prototype.applyStyle_ = function (style) { - this._resetKeystrokeBuffer(); - - style = validateStyle(style); - - if (style.mode) { - // There's a mode ('text', 'math', 'command') change - if (this.mathlist.isCollapsed()) { - // Nothing selected - this.switchMode_(style.mode); - } else { - // Convert the selection from one mode to another - var previousMode = this.mode; - var targetMode = (this.mathlist.anchorMode() || this.config.default) === 'math' ? 'text' : 'math'; - var convertedSelection = this.$selectedText('ASCIIMath'); - - if (targetMode === 'math' && /^"[^"]+"$/.test(convertedSelection)) { - convertedSelection = convertedSelection.slice(1, -1); - } - - this.insert(convertedSelection, { - mode: targetMode, - selectionMode: 'item', - format: targetMode === 'text' ? 'text' : 'ASCIIMath' - }); - this.mode = targetMode; - - if (this.groupIsSelected()) { - // The entire group was selected. Adjust parent mode if - // appropriate - var parent = this.mathlist.parent(); - - if (parent && (parent.type === 'group' || parent.type === 'root')) { - parent.mode = targetMode; - } - } // Notify of mode change - - - if (this.mode !== previousMode && typeof this.config.onModeChange === 'function') { - this.config.onModeChange(this, this.mode); - } - } - - delete style.mode; - } - - if (this.mathlist.isCollapsed()) { - // No selection, let's update the 'current' style - if (this.style.fontSeries && style.fontSeries === this.style.fontSeries) { - style.fontSeries = 'auto'; - } - - if (style.fontShape && style.fontShape === this.style.fontShape) { - style.fontShape = 'auto'; - } - - if (style.color && style.color === this.style.color) { - style.color = 'none'; - } - - if (style.backgroundColor && style.backgroundColor === this.style.backgroundColor) { - style.backgroundColor = 'none'; - } - - if (style.fontSize && style.fontSize === this.style.fontSize) { - style.fontSize = 'auto'; - } - - this.style = _objectSpread({}, this.style, style); // This style will be used the next time an atom is inserted - } else { - // Change the style of the selection - this.mathlist._applyStyle(style); - - this.undoManager.snapshot(this.config); - } - - return true; -}; - -MathField.prototype.hasFocus = MathField.prototype.$hasFocus = function () { - return document.hasFocus() && document.activeElement === this.textarea; -}; - -MathField.prototype.focus = MathField.prototype.$focus = function () { - if (!this.hasFocus()) { - // The textarea may be a span (on mobile, for example), so check that - // it has a focus() before calling it. - if (this.textarea.focus) this.textarea.focus(); - - this._announce('line'); - } -}; - -MathField.prototype.blur = MathField.prototype.$blur = function () { - if (this.hasFocus()) { - if (this.textarea.blur) { - this.textarea.blur(); - } - } -}; - -MathField.prototype.select = MathField.prototype.$select = function () { - this.mathlist.selectAll_(); -}; - -MathField.prototype.clearSelection = MathField.prototype.$clearSelection = function () { - this.mathlist.delete_(); -}; -/** - * @param {string} keys - A string representation of a key combination. - * - * For example `'Alt-KeyU'`. - * - * See [W3C UIEvents](https://www.w3.org/TR/uievents/#code-virtual-keyboards) - * @param {Event} evt - * @return {boolean} - * @method MathField#$keystroke - */ - - -MathField.prototype.keystroke = MathField.prototype.$keystroke = function (keys, evt) { - // This is the public API, while onKeystroke is the - // internal handler - return this._onKeystroke(keys, evt); -}; -/** - * Simulate a user typing the keys indicated by text. - * @param {string} text - A sequence of one or more characters. - * @method MathField#$typedText - */ - - -MathField.prototype.typedText = MathField.prototype.$typedText = function (text) { - // This is the public API, while onTypedText is the - // internal handler - this._onTypedText(text); -}; -/** - * - * @param {string} text - * @param {Object.} [options={}] - * @param {boolean} options.focus - If true, the mathfield will be focused - * @param {boolean} options.feedback - If true, provide audio and haptic feedback - * @param {boolean} options.simulateKeystroke - If true, generate some synthetic - * keystrokes (useful to trigger inline shortcuts, for example) - */ - - -MathField.prototype.typedText_ = function (text, options) { - return this._onTypedText(text, options); -}; -/** - * - * Update the configuration options for this mathfield. - * - * @param {Object} [config={}] See {@tutorial CONFIG} for details. - * - * @method MathField#$setConfig + * Return a tuple of an element and a distance from point (x, y) + * @param {HTMLElement} el + * @param {number} x + * @param {number} y + * @function module:editor/mathfield#nearestElementFromPoint + * @private */ -MathField.prototype.setConfig = MathField.prototype.$setConfig = function (conf) { - if (!this.config) { - this.config = { - smartFence: true, - smartSuperscript: true, - scriptDepth: [Infinity, Infinity], - removeExtraneousParentheses: true, - overrideDefaultInlineShortcuts: false, - virtualKeyboard: '', - virtualKeyboardLayout: 'qwerty', - namespace: '' - }; - } - - this.config = _objectSpread({}, this.config, conf); - - if (!Array.isArray(this.config.scriptDepth)) { - var depth = parseInt(this.config.scriptDepth); - this.config.scriptDepth = [depth, depth]; - } +function nearestElementFromPoint(el, x, y) { + var result = { + element: null + }; + var considerChildren = true; - if (typeof this.config.removeExtraneousParentheses === 'undefined') { - this.config.removeExtraneousParentheses = true; - } + if (!el.getAttribute('data-atom-id')) { + // This element may not have a matching atom, but its children might + result.distance = Number.POSITIVE_INFINITY; + } else { + result.element = el; // Calculate the (square of the) distance to the rectangle - this.config.onAnnounce = conf.onAnnounce || _onAnnounce; - this.config.macros = Object.assign({}, _definitions.default.MACROS, this.config.macros); // Validate the namespace (used for `data-` attributes) + var r = el.getBoundingClientRect(); + var dx = Math.max(r.left - x, x - r.right); + var dy = Math.max(r.top - y, y - r.bottom); + result.distance = dx * dx + dy * dy; // Only consider children if the target is inside the (horizontal) + // bounds of the element. + // This avoid searching the numerator/denominator when a fraction + // is the last element in the formula. - if (!/^[a-z]*[-]?$/.test(this.config.namespace)) { - throw Error('options.namespace must be a string of lowercase characters only'); + considerChildren = x >= r.left && x <= r.right; } - if (!/-$/.test(this.config.namespace)) { - this.config.namespace += '-'; - } // Localization strings override (or localizations for new locales) - - - _l10n.l10n.locale = this.config.locale || _l10n.l10n.locale; - - _l10n.l10n.merge(this.config.strings); - - this.config.virtualKeyboardLayout = conf.virtualKeyboardLayout || { - 'fr': 'azerty', - // France - 'be': 'azerty', - // Belgium - 'al': 'qwertz', - // Albania - 'ba': 'qwertz', - // Bosnia - 'cz': 'qwertz', - // Czech - 'de': 'qwertz', - // Germany - 'hu': 'qwertz', - // Hungary - 'sk': 'qwertz', - // Slovakia - 'ch': 'qwertz' // Switzerland - - }[_l10n.l10n.locale.substring(0, 2)] || 'qwerty'; // Possible keypress sound feedback - - this.keypressSound = undefined; - this.spacebarKeypressSound = undefined; - this.returnKeypressSound = undefined; - this.deleteKeypressSound = undefined; - - if (this.config.keypressSound) { - if (typeof this.config.keypressSound === 'string') { - this.keypressSound = new Audio(); - this.keypressSound.preload = 'none'; - this.keypressSound.src = this.config.keypressSound; - this.keypressSound.volume = AUDIO_FEEDBACK_VOLUME; - this.spacebarKeypressSound = this.keypressSound; - this.returnKeypressSound = this.keypressSound; - this.deleteKeypressSound = this.keypressSound; - } else { - console.assert(this.config.keypressSound.default); - this.keypressSound = new Audio(); - this.keypressSound.preload = 'none'; - this.keypressSound.src = this.config.keypressSound.default; - this.keypressSound.volume = AUDIO_FEEDBACK_VOLUME; - this.spacebarKeypressSound = this.keypressSound; - this.returnKeypressSound = this.keypressSound; - this.deleteKeypressSound = this.keypressSound; + if (considerChildren && el.children) { + Array.from(el.children).forEach(function (child) { + var nearest = nearestElementFromPoint(child, x, y); - if (this.config.keypressSound.spacebar) { - this.spacebarKeypressSound = new Audio(); - this.spacebarKeypressSound.preload = 'none'; - this.spacebarKeypressSound.src = this.config.keypressSound.spacebar; - this.spacebarKeypressSound.volume = AUDIO_FEEDBACK_VOLUME; + if (nearest.element && nearest.distance <= result.distance) { + result = nearest; } + }); + } - if (this.config.keypressSound.return) { - this.returnKeypressSound = new Audio(); - this.returnKeypressSound.preload = 'none'; - this.returnKeypressSound.src = this.config.keypressSound.return; - this.returnKeypressSound.volume = AUDIO_FEEDBACK_VOLUME; - } + return result; +} - if (this.config.keypressSound.delete) { - this.deleteKeypressSound = new Audio(); - this.deleteKeypressSound.preload = 'none'; - this.deleteKeypressSound.src = this.config.keypressSound.delete; - this.deleteKeypressSound.volume = AUDIO_FEEDBACK_VOLUME; - } - } - } +var lastTap; +var tapCount = 0; - if (this.config.plonkSound) { - this.plonkSound = new Audio(); - this.plonkSound.preload = 'none'; - this.plonkSound.src = this.config.plonkSound; - this.plonkSound.volume = AUDIO_FEEDBACK_VOLUME; - } -}; +function speakableText(mathfield, prefix, atoms) { + var config = Object.assign({}, mathfield.config); + config.textToSpeechMarkup = ''; + config.textToSpeechRulesOptions = config.textToSpeechRulesOptions || {}; + config.textToSpeechRulesOptions.markup = 'none'; + return prefix + _mathAtom.default.toSpeakableText(atoms, config); +} /** - * - * Speak some part of the expression, either with or without synchronized highlighting. - * - * @param {string} amount (all, selection, left, right, group, parent) - * @param {object} speakOptions - * @param {boolean} speakOptions.withHighlighting - If true, synchronized highlighting of speech will happen (if possible) - * - * @method MathField#speak_ + * Announce a change in selection or content via the aria-live region. + * This is the default implementation for this function. It can be overridden + * via `config.onAnnounce` + * @param {object} target typically, a MathField + * @param {string} command the command that invoked the change + * @param {Atom[]} [oldMathlist=[]] the previous value of mathlist before the change + * @param {Atom[]} [atomsToSpeak=[] ] + * @method MathField#_onAnnounce + * @private */ -MathField.prototype.speak_ = function (amount, speakOptions) { - speakOptions = speakOptions || { - withHighlighting: false - }; - - function getAtoms(mathField, amount) { - var result = null; - - switch (amount) { - case 'all': - result = mathField.mathlist.root; - break; - - case 'selection': - if (!mathField.mathlist.isCollapsed()) { - result = mathField.mathlist.getSelectedAtoms(); - } - - break; - - case 'left': - { - var siblings = mathField.mathlist.siblings(); - var last = mathField.mathlist.startOffset(); - - if (last >= 1) { - result = []; - - for (var i = 1; i <= last; i++) { - result.push(siblings[i]); - } - } - - break; - } - - case 'right': - { - var _siblings = mathField.mathlist.siblings(); - - var first = mathField.mathlist.endOffset() + 1; - - if (first <= _siblings.length - 1) { - result = []; - - for (var _i2 = first; _i2 <= _siblings.length - 1; _i2++) { - result.push(_siblings[_i2]); - } - } - - break; - } - - case 'start': - case 'end': - // not yet implemented - break; - - case 'group': - result = mathField.mathlist.siblings(); - break; - - case 'parent': - { - var parent = mathField.mathlist.parent(); - - if (parent && parent.type !== 'root') { - result = mathField.mathlist.parent(); - } - - break; - } - - default: - console.log('unknown atom type "' + mathField.type + '"'); - break; - } - - return result; - } - - function getFailedSpeech(amount) { - var result = ''; - - switch (amount) { - case 'all': - console.log("Internal failure: speak all failed"); - break; - - case 'selection': - result = 'no selection'; - break; - - case 'left': - result = 'at start'; - break; - - case 'right': - result = 'at end'; - break; - - case 'group': - console.log("Internal failure: speak group failed"); - break; - - case 'parent': - result = 'no parent'; - break; - - default: - console.log('unknown speak_ param value: "' + amount + '"'); - break; - } - - return result; - } - - var atoms = getAtoms(this, amount); - - if (atoms === null) { - this.config.handleSpeak(getFailedSpeech(amount)); - return false; - } - - var options = this.config; - - if (speakOptions.withHighlighting | options.speechEngine === 'amazon') { - options.textToSpeechMarkup = window.sre && options.textToSpeechRules === 'sre' ? 'ssml_step' : 'ssml'; - } - - var text = _mathAtom.default.toSpeakableText(atoms, options); +function _onAnnounce(target, command, oldMathlist, atomsToSpeak) { + //** Fix: the focus is the end of the selection, so it is before where we want it + var liveText = ''; // const command = moveAmount > 0 ? "right" : "left"; - if (speakOptions.withHighlighting) { - window.mathlive.readAloudMathField = this; + if (command === 'plonk') { + // Use this sound to indicate (minor) errors, for + // example when a command has no effect. + if (target.plonkSound) { + target.plonkSound.load(); + target.plonkSound.play().catch(function (err) { + return console.warn(err); + }); + } // As a side effect, reset the keystroke buffer - this._render({ - forHighlighting: true - }); - if (this.config.handleReadAloud) { - this.config.handleReadAloud(this.field, text, this.config); - } + target._resetKeystrokeBuffer(); + } else if (command === 'delete') { + liveText = speakableText(target, 'deleted: ', atomsToSpeak); //*** FIX: could also be moveUp or moveDown -- do something different like provide context??? + } else if (command === 'focus' || /move/.test(command)) { + //*** FIX -- should be xxx selected/unselected */ + liveText = (target.mathlist.isCollapsed() ? '' : 'selected: ') + target._nextAtomSpeechText(oldMathlist); + } else if (command === 'replacement') { + // announce the contents + liveText = speakableText(target, '', target.mathlist.sibling(0)); + } else if (command === 'line') { + // announce the current line -- currently that's everything + liveText = speakableText(target, '', target.mathlist.root); + target.accessibleNode.innerHTML = '' + _mathAtom.default.toMathML(target.mathlist.root, target.config) + ''; + target.textarea.setAttribute('aria-label', 'after: ' + liveText); + /*** FIX -- testing hack for setting braille ***/ + // target.accessibleNode.focus(); + // console.log("before sleep"); + // sleep(1000).then(() => { + // target.textarea.focus(); + // console.log("after sleep"); + // }); } else { - if (this.config.handleSpeak) { - this.config.handleSpeak(text, options); - } - } + liveText = atomsToSpeak ? speakableText(target, command + " ", atomsToSpeak) : command; + } // aria-live regions are only spoken when it changes; force a change by + // alternately using nonbreaking space or narrow nonbreaking space - return false; -}; + var ariaLiveChangeHack = /\u00a0/.test(target.ariaLiveText.textContent) ? " \u202F " : " \xA0 "; + target.ariaLiveText.textContent = liveText + ariaLiveChangeHack; // this.textarea.setAttribute('aria-label', liveText + ariaLiveChangeHack); +} + +MathField.prototype.undo_ = MathField.prototype.undo; +MathField.prototype.redo_ = MathField.prototype.redo; +MathField.prototype.scrollIntoView_ = MathField.prototype.scrollIntoView; +MathField.prototype.scrollToStart_ = MathField.prototype.scrollToStart; +MathField.prototype.scrollToEnd_ = MathField.prototype.scrollToEnd; +MathField.prototype.insert_ = MathField.prototype.$insert; var _default = { MathField: MathField }; diff --git a/dist/editor/editor-shortcuts.js b/dist/editor/editor-shortcuts.js index 3fdfffa2c..583a36096 100644 --- a/dist/editor/editor-shortcuts.js +++ b/dist/editor/editor-shortcuts.js @@ -10,7 +10,6 @@ var _definitions = _interopRequireDefault(require("../core/definitions.js")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** - * @module editor/shortcuts * @private */ @@ -53,8 +52,8 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * - 'extend' keeps the anchor of the selection, but moves the focus (extends, * or shrinks, the range of selected items) * - * @memberof module:editor/shortcuts * @type {Object} + * @private */ var KEYBOARD_SHORTCUTS = { 'Left': 'moveToPreviousChar', @@ -240,10 +239,10 @@ var KEYBOARD_SHORTCUTS = { '!mac:Ctrl-Alt-Up': ['speak', 'parent', { withHighlighting: false }], - 'mac:Ctrl-Meta-Down': ['speak', 'group', { + 'mac:Ctrl-Meta-Down': ['speak', 'all', { withHighlighting: false }], - '!mac:Ctrl-Alt-Down': ['speak', 'group', { + '!mac:Ctrl-Alt-Down': ['speak', 'all', { withHighlighting: false }], 'mac:Ctrl-Meta-Left': ['speak', 'left', { @@ -270,10 +269,10 @@ var KEYBOARD_SHORTCUTS = { '!mac:Ctrl-Alt-Shift-Up': ['speak', 'parent', { withHighlighting: true }], - 'mac:Ctrl-Meta-Shift-Down': ['speak', 'group', { + 'mac:Ctrl-Meta-Shift-Down': ['speak', 'all', { withHighlighting: true }], - '!mac:Ctrl-Alt-Shift-Down': ['speak', 'group', { + '!mac:Ctrl-Alt-Shift-Down': ['speak', 'all', { withHighlighting: true }], 'mac:Ctrl-Meta-Shift-Left': ['speak', 'left', { @@ -305,8 +304,8 @@ var KEYBOARD_SHORTCUTS = { * For example, '\sqrt' -> 'math:Alt-KeyV'. This table provides the reverse * mapping for those more complex commands. It is used when displaying * keyboard shortcuts for specific commands in the popover. - * @memberof module:editor/shortcuts * @type {Object} + * @private */ }; @@ -337,7 +336,7 @@ var REVERSE_KEYBOARD_SHORTCUTS = { * without requiring an escape sequence or command. * * @type {Object.} - * @memberof module:editor/shortcuts + * @private */ }; @@ -359,8 +358,10 @@ var INLINE_SHORTCUTS = { mode: 'text', value: '\\pi ' }, - 'π': '\\pi', - 'Pi': '\\Pi', + 'Pi': { + mode: 'math', + value: '\\Pi' + }, 'theta': '\\theta', 'Theta': '\\Theta', // Letter-like @@ -1043,16 +1044,12 @@ function forCommand(command) { command = commandToString(command); var regex = new RegExp('^' + command.replace('\\', '\\\\').replace('|', '\\|').replace('*', '\\*').replace('$', '\\$').replace('^', '\\^') + '([^*a-zA-Z]|$)'); - - for (var shortcut in KEYBOARD_SHORTCUTS) { - if (KEYBOARD_SHORTCUTS.hasOwnProperty(shortcut)) { - if (regex.test(commandToString(KEYBOARD_SHORTCUTS[shortcut]))) { - var m = shortcut.match(/:([^:]*)$/); - if (m) result.push(m[1]); - } + Object.keys(KEYBOARD_SHORTCUTS).forEach(function (shortcut) { + if (regex.test(commandToString(KEYBOARD_SHORTCUTS[shortcut]))) { + var m = shortcut.match(/:([^:]*)$/); + if (m) result.push(m[1]); } - } - + }); return stringify(result); } /** diff --git a/dist/editor/editor-undo.js b/dist/editor/editor-undo.js index f4bedfc77..60e1f4c58 100644 --- a/dist/editor/editor-undo.js +++ b/dist/editor/editor-undo.js @@ -5,7 +5,9 @@ Object.defineProperty(exports, "__esModule", { }); exports.default = void 0; -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } @@ -180,6 +182,9 @@ function () { /** * * @param {Object.} options + * @instance + * @memberof UndoManager + * @private */ }, { @@ -196,6 +201,9 @@ function () { * Return an object capturing the state of the content and selection of the * math field. Pass this object to restore() to reset the value of the math * field to this saved value. This does not affect the undo stack. + * @instance + * @memberof UndoManager + * @private */ }, { @@ -210,6 +218,9 @@ function () { * Set the content and selection of the math field to a value previously * captured with save() or stored in the undo stack. * This does not affect the undo stack. + * @instance + * @memberof UndoManager + * @private */ }, { diff --git a/dist/editor/editor-virtualKeyboard.js b/dist/editor/editor-virtualKeyboard.js index b4da4782f..ed7c48665 100644 --- a/dist/editor/editor-virtualKeyboard.js +++ b/dist/editor/editor-virtualKeyboard.js @@ -23,6 +23,12 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + var KEYBOARDS = { 'numeric': { tooltip: 'keyboard.tooltip.numeric', @@ -841,6 +847,7 @@ function expandLayerMarkup(mf, layer) { * @param {object} mf * @param {string} theme * @result {} A DOM element + * @private */ @@ -918,84 +925,68 @@ function make(mf, theme) { } } - ALT_KEYS = {}; - ALT_KEYS = Object.assign({}, ALT_KEYS_BASE); - - for (var key in ALT_KEYS) { - if (ALT_KEYS.hasOwnProperty(key)) { - ALT_KEYS[key] = ALT_KEYS[key].slice(); - } - } - + ALT_KEYS = _objectSpread({}, ALT_KEYS_BASE); + Object.keys(ALT_KEYS).forEach(function (key) { + ALT_KEYS[key] = ALT_KEYS[key].slice(); + }); var upperAlpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; var lowerAlpha = 'abcdefghijklmnopqrstuvwxyz'; var digits = '0123456789'; for (var i = 0; i < 26; i++) { - var _key = upperAlpha[i]; - if (!ALT_KEYS[_key]) ALT_KEYS[_key] = []; - - ALT_KEYS[_key].unshift({ - latex: '\\mathbb{' + _key + '}', + var key = upperAlpha[i]; + if (!ALT_KEYS[key]) ALT_KEYS[key] = []; + ALT_KEYS[key].unshift({ + latex: '\\mathbb{' + key + '}', aside: 'blackboard', - insert: '\\mathbb{' + _key + '}' + insert: '\\mathbb{' + key + '}' }); - - ALT_KEYS[_key].unshift({ - latex: '\\mathbf{' + _key + '}', + ALT_KEYS[key].unshift({ + latex: '\\mathbf{' + key + '}', aside: 'bold', - insert: '\\mathbf{' + _key + '}' + insert: '\\mathbf{' + key + '}' }); - - ALT_KEYS[_key].unshift({ - latex: '\\mathsf{' + _key + '}', + ALT_KEYS[key].unshift({ + latex: '\\mathsf{' + key + '}', aside: 'sans', - insert: '\\mathsf{' + _key + '}' + insert: '\\mathsf{' + key + '}' }); - - ALT_KEYS[_key].unshift({ - latex: '\\mathtt{' + _key + '}', + ALT_KEYS[key].unshift({ + latex: '\\mathtt{' + key + '}', aside: 'monospace', - insert: '\\mathtt{' + _key + '}' + insert: '\\mathtt{' + key + '}' }); - - ALT_KEYS[_key].unshift({ - latex: '\\mathcal{' + _key + '}', + ALT_KEYS[key].unshift({ + latex: '\\mathcal{' + key + '}', aside: 'script', - insert: '\\mathcal{' + _key + '}' + insert: '\\mathcal{' + key + '}' }); - - ALT_KEYS[_key].unshift({ - latex: '\\mathfrak{' + _key + '}', + ALT_KEYS[key].unshift({ + latex: '\\mathfrak{' + key + '}', aside: 'fraktur', - insert: '\\mathfrak{' + _key + '}' + insert: '\\mathfrak{' + key + '}' }); - - ALT_KEYS[_key].unshift({ + ALT_KEYS[key].unshift({ latex: '\\mathbb{' + lowerAlpha[i] + '}', aside: 'blackboard', insert: '\\mathbb{' + lowerAlpha[i] + '}' }); - - ALT_KEYS[_key].unshift({ + ALT_KEYS[key].unshift({ latex: '\\mathbf{' + lowerAlpha[i] + '}', aside: 'bold', insert: '\\mathbf{' + lowerAlpha[i] + '}' }); - - ALT_KEYS[_key].unshift({ + ALT_KEYS[key].unshift({ latex: '\\mathsf{' + lowerAlpha[i] + '}', aside: 'sans', insert: '\\mathsf{' + lowerAlpha[i] + '}' }); - - ALT_KEYS[_key].unshift({ + ALT_KEYS[key].unshift({ latex: '\\mathcal{' + lowerAlpha[i] + '}', aside: 'script', insert: '\\mathcal{' + lowerAlpha[i] + '}' }); - - ALT_KEYS[_key].unshift({ + ALT_KEYS[key].unshift({ latex: '\\mathfrak{' + lowerAlpha[i] + '}', aside: 'fraktur', insert: '\\mathfrak{' + lowerAlpha[i] + '}' @@ -1003,70 +994,70 @@ function make(mf, theme) { } for (var _i = 0; _i <= 26; _i++) { - var _key2 = lowerAlpha[_i]; - if (!ALT_KEYS[_key2]) ALT_KEYS[_key2] = []; + var _key = lowerAlpha[_i]; + if (!ALT_KEYS[_key]) ALT_KEYS[_key] = []; - ALT_KEYS[_key2].unshift({ - latex: '\\mathsf{' + _key2 + '}', + ALT_KEYS[_key].unshift({ + latex: '\\mathsf{' + _key + '}', aside: 'sans', - insert: '\\mathbb{' + _key2 + '}' + insert: '\\mathbb{' + _key + '}' }); - ALT_KEYS[_key2].unshift({ - latex: '\\mathbf{' + _key2 + '}', + ALT_KEYS[_key].unshift({ + latex: '\\mathbf{' + _key + '}', aside: 'bold', - insert: '\\mathbf{' + _key2 + '}' + insert: '\\mathbf{' + _key + '}' }); - ALT_KEYS[_key2].unshift({ - latex: '\\mathtt{' + _key2 + '}', + ALT_KEYS[_key].unshift({ + latex: '\\mathtt{' + _key + '}', aside: 'monospace', - insert: '\\mathtt{' + _key2 + '}' + insert: '\\mathtt{' + _key + '}' }); - ALT_KEYS[_key2].unshift({ - latex: '\\mathfrak{' + _key2 + '}', + ALT_KEYS[_key].unshift({ + latex: '\\mathfrak{' + _key + '}', aside: 'fraktur', - insert: '\\mathfrak{' + _key2 + '}' + insert: '\\mathfrak{' + _key + '}' }); } for (var _i2 = 0; _i2 < 10; _i2++) { - var _key3 = digits[_i2]; - if (!ALT_KEYS[_key3]) ALT_KEYS[_key3] = []; // The mathbb font does not appear to include digits, + var _key2 = digits[_i2]; + if (!ALT_KEYS[_key2]) ALT_KEYS[_key2] = []; // The mathbb font does not appear to include digits, // although it's supposed to. // ALT_KEYS[key].push({ // latex: '\\underset{\\textsf{\\footnotesize blackboard}}{\\mathbb{' + key + '}}', // insert: '\\mathbb{' + key + '}}'}); - ALT_KEYS[_key3].unshift({ - latex: '\\mathbf{' + _key3 + '}', + ALT_KEYS[_key2].unshift({ + latex: '\\mathbf{' + _key2 + '}', aside: 'bold', - insert: '\\mathbf{' + _key3 + '}' + insert: '\\mathbf{' + _key2 + '}' }); - ALT_KEYS[_key3].unshift({ - latex: '\\mathsf{' + _key3 + '}', + ALT_KEYS[_key2].unshift({ + latex: '\\mathsf{' + _key2 + '}', aside: 'sans', - insert: '\\mathsf{' + _key3 + '}' + insert: '\\mathsf{' + _key2 + '}' }); - ALT_KEYS[_key3].unshift({ - latex: '\\mathtt{' + _key3 + '}', + ALT_KEYS[_key2].unshift({ + latex: '\\mathtt{' + _key2 + '}', aside: 'monospace', - insert: '\\mathtt{' + _key3 + '}' + insert: '\\mathtt{' + _key2 + '}' }); - ALT_KEYS[_key3].unshift({ - latex: '\\mathcal{' + _key3 + '}', + ALT_KEYS[_key2].unshift({ + latex: '\\mathcal{' + _key2 + '}', aside: 'script', - insert: '\\mathcal{' + _key3 + '}' + insert: '\\mathcal{' + _key2 + '}' }); - ALT_KEYS[_key3].unshift({ - latex: '\\mathfrak{' + _key3 + '}', + ALT_KEYS[_key2].unshift({ + latex: '\\mathfrak{' + _key2 + '}', aside: 'fraktur', - insert: '\\mathfrak{' + _key3 + '}' + insert: '\\mathfrak{' + _key2 + '}' }); } diff --git a/dist/editor/l10n.js b/dist/editor/l10n.js index a3cb97867..71f49de00 100644 --- a/dist/editor/l10n.js +++ b/dist/editor/l10n.js @@ -6,7 +6,9 @@ Object.defineProperty(exports, "__esModule", { exports.l10n = l10n; exports.default = void 0; -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } @@ -47,7 +49,7 @@ l10n.plural = function (value, s, options) { return result.split(';')[rule] || result.split(';')[0]; }; -/** +/* * Two forms for this function: * - merge(locale, strings) * Merge a dictionary of keys -> values for the specified locale @@ -62,16 +64,13 @@ l10n.merge = function (locale, strings) { var savedLocale = l10n._locale; l10n.locale = locale; // Load the necessary json file - l10n.strings[locale] = _objectSpread({}, l10n.strings[locale], strings); + l10n.strings[locale] = _objectSpread({}, l10n.strings[locale], {}, strings); l10n.locale = savedLocale; } else if (locale && !strings) { strings = locale; - - for (var l in strings) { - if (strings.hasOwnProperty(l)) { - l10n.merge(l, strings[l]); - } - } + Object.keys(strings).forEach(function (l) { + return l10n.merge(l, strings[l]); + }); } }; // Add getter and setter for the _locale property of l10n @@ -159,6 +158,17 @@ l10n.strings = { "tooltip.toggle virtual keyboard": "Virtuelle Tastatur umschalten", "tooltip.undo": "Widerrufen" }, + "el": { + "keyboard.tooltip.functions": "συναρτήσεις", + "keyboard.tooltip.greek": "ελληνικά γράμματα", + "keyboard.tooltip.command": "Λειτουργία εντολών LaTeX", + "keyboard.tooltip.numeric": "Αριθμητικός", + "keyboard.tooltip.roman": "Σύμβολα και ρωμαϊκά γράμματα", + "tooltip.copy to clipboard": "Αντιγραφή στο πρόχειρο", + "tooltip.redo": "Ξανακάνω", + "tooltip.toggle virtual keyboard": "Εναλλαγή εικονικού πληκτρολογίου", + "tooltip.undo": "Ξεκάνω" + }, "es": { "keyboard.tooltip.functions": "Funciones", "keyboard.tooltip.greek": "Letras griegas", diff --git a/dist/mathlive.core.css b/dist/mathlive.core.css index 9576e2df3..0f5c17eba 100644 --- a/dist/mathlive.core.css +++ b/dist/mathlive.core.css @@ -1 +1 @@ -.ML__mathlive{line-height:0;direction:ltr;text-align:left;text-indent:0;text-rendering:auto;font-style:normal;font-size-adjust:none;letter-spacing:normal;word-wrap:normal;word-spacing:normal;white-space:nowrap;display:inline-block;text-shadow:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:-webkit-min-content;width:-moz-min-content;width:min-content;-webkit-transform:translateZ(0);transform:translateZ(0)}.ML__base{display:inline-block;position:relative}.ML__strut,.ML__strut--bottom{display:inline-block;min-height:1em}.ML__caret:after{content:"";border:none;border-radius:2px;border-right:2px solid var(--caret);margin-right:-2px;position:relative;left:-1px;-webkit-animation:ML__caret-blink 1.05s step-end infinite forwards;animation:ML__caret-blink 1.05s step-end infinite forwards}.ML__text-caret:after{content:"";border:none;border-radius:1px;border-right:1px solid var(--caret);margin-right:-1px;position:relative;left:0;-webkit-animation:ML__caret-blink 1.05s step-end infinite forwards;animation:ML__caret-blink 1.05s step-end infinite forwards}.ML__command-caret:after{content:"_";border:none;margin-right:-1ex;position:relative;color:var(--caret);-webkit-animation:ML__caret-command-blink 1.05s step-end infinite forwards;animation:ML__caret-command-blink 1.05s step-end infinite forwards}.ML__text{white-space:pre}.ML__focused .ML__text{background:hsla(var(--hue),40%,50%,.1)}@-webkit-keyframes ML__caret-command-blink{0%,to{opacity:1}50%{opacity:0}}@keyframes ML__caret-command-blink{0%,to{opacity:1}50%{opacity:0}}@-webkit-keyframes ML__caret-blink{0%,to{border-color:var(--caret)}50%{border-color:transparent}}@keyframes ML__caret-blink{0%,to{border-color:var(--caret)}50%{border-color:transparent}}.ML__textarea__textarea{-webkit-transform:scale(0);transform:scale(0);resize:none;position:absolute;clip:rect(0 0 0 0);width:1px;height:1px;font-size:16px}.ML__fieldcontainer{display:flex;flex-flow:row;justify-content:space-between;align-items:flex-end;min-height:39px;touch-action:none;width:100%;--hue:212;--highlight:hsl(var(--hue),97%,85%);--caret:hsl(var(--hue),40%,49%);--highlight-inactive:#ccc;--primary:hsl(var(--hue),40%,50%);--secondary:hsl(var(--hue),19%,26%);--on-secondary:hsl(var(--hue),19%,26%)}@media (prefers-color-scheme:dark){body:not([theme=light]) .ML__fieldcontainer{--highlight:hsl(var(--hue),40%,49%);--highlight-inactive:hsl(var(--hue),10%,35%);--caret:hsl(var(--hue),97%,85%);--secondary:hsl(var(--hue),25%,35%);--on-secondary:#fafafa}}body[theme=dark] .ML__fieldcontainer{--highlight:hsl(var(--hue),40%,49%);--highlight-inactive:hsl(var(--hue),10%,35%);--caret:hsl(var(--hue),97%,85%);--secondary:hsl(var(--hue),25%,35%);--on-secondary:#fafafa}.ML__fieldcontainer:focus{outline:2px solid var(--primary);outline-offset:3px}.ML__fieldcontainer__field{align-self:center;position:relative;overflow:hidden;line-height:0;padding:2px;width:100%;cursor:text}.ML__virtual-keyboard-toggle{display:flex;align-self:center;align-items:center;flex-shrink:0;flex-direction:column;justify-content:center;width:34px;height:34px;padding:0;margin-right:4px;cursor:pointer;box-sizing:border-box;border-radius:50%;border:1px solid transparent;transition:background .2s cubic-bezier(.64,.09,.08,1);color:var(--primary);fill:currentColor;background:transparent}.ML__virtual-keyboard-toggle:hover{background:hsl(var(--hue),25%,35%);color:#fafafa;fill:currentColor;border-radius:50%;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2)}.ML__popover{visibility:hidden;min-width:160px;background-color:rgba(97,97,97,.95);color:#fff;text-align:center;border-radius:6px;position:fixed;z-index:1;display:flex;flex-direction:column;justify-content:center;box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22);transition:all .2s cubic-bezier(.64,.09,.08,1)}.ML__popover:after{content:"";position:absolute;top:-5px;left:calc(50% - 3px);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;font-size:1rem;border-bottom:5px solid rgba(97,97,97,.9)} \ No newline at end of file +.ML__mathlive{line-height:0;direction:ltr;text-align:left;text-indent:0;text-rendering:auto;font-style:normal;font-size-adjust:none;letter-spacing:normal;word-wrap:normal;word-spacing:normal;white-space:nowrap;display:inline-block;text-shadow:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:-webkit-min-content;width:-moz-min-content;width:min-content;transform:translateZ(0)}.ML__base{display:inline-block;position:relative;cursor:text}.ML__strut,.ML__strut--bottom{display:inline-block;min-height:1em}.ML__caret:after{content:"";border:none;border-radius:2px;border-right:2px solid var(--caret);margin-right:-2px;position:relative;left:-1px;-webkit-animation:ML__caret-blink 1.05s step-end infinite forwards;animation:ML__caret-blink 1.05s step-end infinite forwards}.ML__text-caret:after{content:"";border:none;border-radius:1px;border-right:1px solid var(--caret);margin-right:-1px;position:relative;left:0;-webkit-animation:ML__caret-blink 1.05s step-end infinite forwards;animation:ML__caret-blink 1.05s step-end infinite forwards}.ML__command-caret:after{content:"_";border:none;margin-right:-1ex;position:relative;color:var(--caret);-webkit-animation:ML__caret-command-blink 1.05s step-end infinite forwards;animation:ML__caret-command-blink 1.05s step-end infinite forwards}.ML__text{white-space:pre}.ML__focused .ML__text{background:hsla(var(--hue),40%,50%,.1)}@-webkit-keyframes ML__caret-command-blink{0%,to{opacity:1}50%{opacity:0}}@keyframes ML__caret-command-blink{0%,to{opacity:1}50%{opacity:0}}@-webkit-keyframes ML__caret-blink{0%,to{border-color:var(--caret)}50%{border-color:transparent}}@keyframes ML__caret-blink{0%,to{border-color:var(--caret)}50%{border-color:transparent}}.ML__textarea__textarea{transform:scale(0);resize:none;position:absolute;clip:rect(0 0 0 0);width:1px;height:1px;font-size:16px}.ML__fieldcontainer{display:flex;flex-flow:row;justify-content:space-between;align-items:flex-end;min-height:39px;touch-action:none;width:100%;--hue:212;--highlight:hsl(var(--hue),97%,85%);--caret:hsl(var(--hue),40%,49%);--highlight-inactive:#ccc;--primary:hsl(var(--hue),40%,50%);--secondary:hsl(var(--hue),19%,26%);--on-secondary:hsl(var(--hue),19%,26%)}@media (prefers-color-scheme:dark){body:not([theme=light]) .ML__fieldcontainer{--highlight:hsl(var(--hue),40%,49%);--highlight-inactive:hsl(var(--hue),10%,35%);--caret:hsl(var(--hue),97%,85%);--secondary:hsl(var(--hue),25%,35%);--on-secondary:#fafafa}}body[theme=dark] .ML__fieldcontainer{--highlight:hsl(var(--hue),40%,49%);--highlight-inactive:hsl(var(--hue),10%,35%);--caret:hsl(var(--hue),97%,85%);--secondary:hsl(var(--hue),25%,35%);--on-secondary:#fafafa}.ML__fieldcontainer:focus{outline:2px solid var(--primary);outline-offset:3px}.ML__fieldcontainer__field{align-self:center;position:relative;overflow:hidden;line-height:0;padding:2px;width:100%}.ML__virtual-keyboard-toggle{display:flex;align-self:center;align-items:center;flex-shrink:0;flex-direction:column;justify-content:center;width:34px;height:34px;padding:0;margin-right:4px;cursor:pointer;box-sizing:border-box;border-radius:50%;border:1px solid transparent;transition:background .2s cubic-bezier(.64,.09,.08,1);color:var(--primary);fill:currentColor;background:transparent}.ML__virtual-keyboard-toggle:hover{background:hsl(var(--hue),25%,35%);color:#fafafa;fill:currentColor;border-radius:50%;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2)}.ML__popover{visibility:hidden;min-width:160px;background-color:rgba(97,97,97,.95);color:#fff;text-align:center;border-radius:6px;position:fixed;z-index:1;display:flex;flex-direction:column;justify-content:center;box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22);transition:all .2s cubic-bezier(.64,.09,.08,1)}.ML__popover:after{content:"";position:absolute;top:-5px;left:calc(50% - 3px);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;font-size:1rem;border-bottom:5px solid rgba(97,97,97,.9)} \ No newline at end of file diff --git a/dist/mathlive.css b/dist/mathlive.css index e6876c2c0..41a8f1a11 100644 --- a/dist/mathlive.css +++ b/dist/mathlive.css @@ -1 +1 @@ -@font-face{font-family:KaTeX_AMS;src:url(fonts/KaTeX_AMS-Regular.woff2) format("woff2"),url(fonts/KaTeX_AMS-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Caligraphic;src:url(fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Bold.woff) format("woff");font-weight:700;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Caligraphic;src:url(fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Fraktur;src:url(fonts/KaTeX_Fraktur-Bold.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Bold.woff) format("woff");font-weight:700;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Fraktur;src:url(fonts/KaTeX_Fraktur-Regular.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Main-BoldItalic.woff) format("woff");font-weight:700;font-style:italic;font-display:"swap"}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-Bold.woff2) format("woff2"),url(fonts/KaTeX_Main-Bold.woff) format("woff");font-weight:700;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-Italic.woff2) format("woff2"),url(fonts/KaTeX_Main-Italic.woff) format("woff");font-weight:400;font-style:italic;font-display:"swap"}@font-face{font-family:KaTeX_Math;src:url(fonts/KaTeX_Math-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Math-BoldItalic.woff) format("woff");font-weight:700;font-style:italic;font-display:"swap"}@font-face{font-family:"KaTeX_SansSerif";src:url(fonts/KaTeX_SansSerif-Bold.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Bold.woff) format("woff");font-weight:700;font-style:normal;font-display:"swap"}@font-face{font-family:"KaTeX_SansSerif";src:url(fonts/KaTeX_SansSerif-Italic.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Italic.woff) format("woff");font-weight:400;font-style:italic;font-display:"swap"}@font-face{font-family:"KaTeX_SansSerif";src:url(fonts/KaTeX_SansSerif-Regular.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Script;src:url(fonts/KaTeX_Script-Regular.woff2) format("woff2"),url(fonts/KaTeX_Script-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Size1;src:url(fonts/KaTeX_Size1-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size1-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Size2;src:url(fonts/KaTeX_Size2-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size2-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Size3;src:url(fonts/KaTeX_Size3-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size3-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Size4;src:url(fonts/KaTeX_Size4-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size4-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Typewriter;src:url(fonts/KaTeX_Typewriter-Regular.woff2) format("woff2"),url(fonts/KaTeX_Typewriter-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-Regular.woff2) format("woff2"),url(fonts/KaTeX_Main-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Math;src:url(fonts/KaTeX_Math-Italic.woff2) format("woff2"),url(fonts/KaTeX_Math-Italic.woff) format("woff");font-weight:400;font-style:italic;font-display:"swap"}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.ML__mathit{font-family:KaTeX_Math;font-style:italic}.ML__mathrm{font-family:KaTeX_Main;font-style:normal}.ML__mathbf{font-family:KaTeX_Main;font-weight:700}.ML__mathbfit{font-family:KaTeX_Math;font-weight:700;font-style:italic}.ML__ams,.ML__bb{font-family:KaTeX_AMS}.ML__cal{font-family:KaTeX_Caligraphic}.ML__frak{font-family:KaTeX_Fraktur}.ML__tt{font-family:KaTeX_Typewriter}.ML__script{font-family:KaTeX_Script}.ML__sans{font-family:KaTeX_SansSerif}.ML__mainit{font-family:KaTeX_Main;font-style:italic}.ML__series_el,.ML__series_ul{font-weight:100}.ML__series_l{font-weight:200}.ML__series_sl{font-weight:300}.ML__series_sb{font-weight:500}.ML__bold{font-weight:700}.ML__series_eb{font-weight:800}.ML__series_ub{font-weight:900}.ML__series_uc{font-stretch:ultra-condensed}.ML__series_ec{font-stretch:extra-condensed}.ML__series_c{font-stretch:condensed}.ML__series_sc{font-stretch:semi-condensed}.ML__series_sx{font-stretch:semi-expanded}.ML__series_x{font-stretch:expanded}.ML__series_ex{font-stretch:extra-expanded}.ML__series_ux{font-stretch:ultra-expanded}.ML__it{font-style:italic}.ML__shape_ol{-webkit-text-stroke:1px #000;text-stroke:1px #000;color:transparent}.ML__shape_sc{font-variant:small-caps}.ML__shape_sl{font-style:oblique}.ML__emph{color:#bc2612}.ML__emph .ML__emph{color:#0c7f99}.ML__mathlive .highlight{color:#007cb2;background:#edd1b0}.ML__mathlive .reset-textstyle.scriptstyle{font-size:.7em}.ML__mathlive .reset-textstyle.scriptscriptstyle{font-size:.5em}.ML__mathlive .reset-scriptstyle.textstyle{font-size:1.42857em}.ML__mathlive .reset-scriptstyle.scriptscriptstyle{font-size:.71429em}.ML__mathlive .reset-scriptscriptstyle.textstyle{font-size:2em}.ML__mathlive .reset-scriptscriptstyle.scriptstyle{font-size:1.4em}.ML__mathlive .style-wrap{position:relative}.ML__mathlive .vlist{display:inline-block}.ML__mathlive .vlist>span{display:block;height:0;position:relative;line-height:0}.ML__mathlive .vlist>span>span{display:inline-block}.ML__mathlive .msubsup{text-align:left}.ML__mathlive .mfrac>span{text-align:center}.ML__mathlive .mfrac .frac-line{width:100%}.ML__mathlive .mfrac .frac-line:after{content:"";display:block;margin-top:-.04em;border-bottom-style:solid;border-bottom-width:.04em;min-height:.04em}.ML__mathlive .rspace.negativethinspace{margin-right:-.16667em}.ML__mathlive .rspace.thinspace{margin-right:.16667em}.ML__mathlive .rspace.negativemediumspace{margin-right:-.22222em}.ML__mathlive .rspace.mediumspace{margin-right:.22222em}.ML__mathlive .rspace.thickspace{margin-right:.27778em}.ML__mathlive .rspace.sixmuspace{margin-right:.333333em}.ML__mathlive .rspace.eightmuspace{margin-right:.444444em}.ML__mathlive .rspace.enspace{margin-right:.5em}.ML__mathlive .rspace.twelvemuspace{margin-right:.666667em}.ML__mathlive .rspace.quad{margin-right:1em}.ML__mathlive .rspace.qquad{margin-right:2em}.ML__mathlive .mspace{display:inline-block}.ML__mathlive .mspace.negativethinspace{margin-left:-.16667em}.ML__mathlive .mspace.thinspace{width:.16667em}.ML__mathlive .mspace.negativemediumspace{margin-left:-.22222em}.ML__mathlive .mspace.mediumspace{width:.22222em}.ML__mathlive .mspace.thickspace{width:.27778em}.ML__mathlive .mspace.sixmuspace{width:.333333em}.ML__mathlive .mspace.eightmuspace{width:.444444em}.ML__mathlive .mspace.enspace{width:.5em}.ML__mathlive .mspace.twelvemuspace{width:.666667em}.ML__mathlive .mspace.quad{width:1em}.ML__mathlive .mspace.qquad{width:2em}.ML__mathlive .llap,.ML__mathlive .rlap{width:0;position:relative}.ML__mathlive .llap>.inner,.ML__mathlive .rlap>.inner{position:absolute}.ML__mathlive .llap>.fix,.ML__mathlive .rlap>.fix{display:inline-block}.ML__mathlive .llap>.inner{right:0}.ML__mathlive .rlap>.inner{left:0}.ML__mathlive .rule{display:inline-block;border:0 solid;position:relative}.ML__mathlive .overline .overline-line,.ML__mathlive .underline .underline-line{width:100%}.ML__mathlive .overline .overline-line:before,.ML__mathlive .underline .underline-line:before{border-bottom-style:solid;border-bottom-width:.04em;content:"";display:block}.ML__mathlive .overline .overline-line:after,.ML__mathlive .underline .underline-line:after{border-bottom-style:solid;border-bottom-width:.04em;min-height:thin;content:"";display:block;margin-top:-1px}.ML__mathlive .sqrt{display:inline-block}.ML__mathlive .sqrt>.sqrt-sign{font-family:KaTeX_Main;position:relative}.ML__mathlive .sqrt .sqrt-line{height:.04em;width:100%}.ML__mathlive .sqrt .sqrt-line:before{content:"";display:block;margin-top:-.04em;border-bottom-style:solid;border-bottom-width:.04em;min-height:.5px}.ML__mathlive .sqrt .sqrt-line:after{border-bottom-width:1px;content:" ";display:block;margin-top:-.1em}.ML__mathlive .sqrt>.root{margin-left:.27777778em;margin-right:-.55555556em}.ML__mathlive .fontsize-ensurer,.ML__mathlive .sizing{display:inline-block}.ML__mathlive .fontsize-ensurer.reset-size1.size1,.ML__mathlive .sizing.reset-size1.size1{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size1.size2,.ML__mathlive .sizing.reset-size1.size2{font-size:1.4em}.ML__mathlive .fontsize-ensurer.reset-size1.size3,.ML__mathlive .sizing.reset-size1.size3{font-size:1.6em}.ML__mathlive .fontsize-ensurer.reset-size1.size4,.ML__mathlive .sizing.reset-size1.size4{font-size:1.8em}.ML__mathlive .fontsize-ensurer.reset-size1.size5,.ML__mathlive .sizing.reset-size1.size5{font-size:2em}.ML__mathlive .fontsize-ensurer.reset-size1.size6,.ML__mathlive .sizing.reset-size1.size6{font-size:2.4em}.ML__mathlive .fontsize-ensurer.reset-size1.size7,.ML__mathlive .sizing.reset-size1.size7{font-size:2.88em}.ML__mathlive .fontsize-ensurer.reset-size1.size8,.ML__mathlive .sizing.reset-size1.size8{font-size:3.46em}.ML__mathlive .fontsize-ensurer.reset-size1.size9,.ML__mathlive .sizing.reset-size1.size9{font-size:4.14em}.ML__mathlive .fontsize-ensurer.reset-size1.size10,.ML__mathlive .sizing.reset-size1.size10{font-size:4.98em}.ML__mathlive .fontsize-ensurer.reset-size2.size1,.ML__mathlive .sizing.reset-size2.size1{font-size:.71428571em}.ML__mathlive .fontsize-ensurer.reset-size2.size2,.ML__mathlive .sizing.reset-size2.size2{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size2.size3,.ML__mathlive .sizing.reset-size2.size3{font-size:1.14285714em}.ML__mathlive .fontsize-ensurer.reset-size2.size4,.ML__mathlive .sizing.reset-size2.size4{font-size:1.28571429em}.ML__mathlive .fontsize-ensurer.reset-size2.size5,.ML__mathlive .sizing.reset-size2.size5{font-size:1.42857143em}.ML__mathlive .fontsize-ensurer.reset-size2.size6,.ML__mathlive .sizing.reset-size2.size6{font-size:1.71428571em}.ML__mathlive .fontsize-ensurer.reset-size2.size7,.ML__mathlive .sizing.reset-size2.size7{font-size:2.05714286em}.ML__mathlive .fontsize-ensurer.reset-size2.size8,.ML__mathlive .sizing.reset-size2.size8{font-size:2.47142857em}.ML__mathlive .fontsize-ensurer.reset-size2.size9,.ML__mathlive .sizing.reset-size2.size9{font-size:2.95714286em}.ML__mathlive .fontsize-ensurer.reset-size2.size10,.ML__mathlive .sizing.reset-size2.size10{font-size:3.55714286em}.ML__mathlive .fontsize-ensurer.reset-size3.size1,.ML__mathlive .sizing.reset-size3.size1{font-size:.625em}.ML__mathlive .fontsize-ensurer.reset-size3.size2,.ML__mathlive .sizing.reset-size3.size2{font-size:.875em}.ML__mathlive .fontsize-ensurer.reset-size3.size3,.ML__mathlive .sizing.reset-size3.size3{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size3.size4,.ML__mathlive .sizing.reset-size3.size4{font-size:1.125em}.ML__mathlive .fontsize-ensurer.reset-size3.size5,.ML__mathlive .sizing.reset-size3.size5{font-size:1.25em}.ML__mathlive .fontsize-ensurer.reset-size3.size6,.ML__mathlive .sizing.reset-size3.size6{font-size:1.5em}.ML__mathlive .fontsize-ensurer.reset-size3.size7,.ML__mathlive .sizing.reset-size3.size7{font-size:1.8em}.ML__mathlive .fontsize-ensurer.reset-size3.size8,.ML__mathlive .sizing.reset-size3.size8{font-size:2.1625em}.ML__mathlive .fontsize-ensurer.reset-size3.size9,.ML__mathlive .sizing.reset-size3.size9{font-size:2.5875em}.ML__mathlive .fontsize-ensurer.reset-size3.size10,.ML__mathlive .sizing.reset-size3.size10{font-size:3.1125em}.ML__mathlive .fontsize-ensurer.reset-size4.size1,.ML__mathlive .sizing.reset-size4.size1{font-size:.55555556em}.ML__mathlive .fontsize-ensurer.reset-size4.size2,.ML__mathlive .sizing.reset-size4.size2{font-size:.77777778em}.ML__mathlive .fontsize-ensurer.reset-size4.size3,.ML__mathlive .sizing.reset-size4.size3{font-size:.88888889em}.ML__mathlive .fontsize-ensurer.reset-size4.size4,.ML__mathlive .sizing.reset-size4.size4{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size4.size5,.ML__mathlive .sizing.reset-size4.size5{font-size:1.11111111em}.ML__mathlive .fontsize-ensurer.reset-size4.size6,.ML__mathlive .sizing.reset-size4.size6{font-size:1.33333333em}.ML__mathlive .fontsize-ensurer.reset-size4.size7,.ML__mathlive .sizing.reset-size4.size7{font-size:1.6em}.ML__mathlive .fontsize-ensurer.reset-size4.size8,.ML__mathlive .sizing.reset-size4.size8{font-size:1.92222222em}.ML__mathlive .fontsize-ensurer.reset-size4.size9,.ML__mathlive .sizing.reset-size4.size9{font-size:2.3em}.ML__mathlive .fontsize-ensurer.reset-size4.size10,.ML__mathlive .sizing.reset-size4.size10{font-size:2.76666667em}.ML__mathlive .fontsize-ensurer.reset-size5.size1,.ML__mathlive .sizing.reset-size5.size1{font-size:.5em}.ML__mathlive .fontsize-ensurer.reset-size5.size2,.ML__mathlive .sizing.reset-size5.size2{font-size:.7em}.ML__mathlive .fontsize-ensurer.reset-size5.size3,.ML__mathlive .sizing.reset-size5.size3{font-size:.8em}.ML__mathlive .fontsize-ensurer.reset-size5.size4,.ML__mathlive .sizing.reset-size5.size4{font-size:.9em}.ML__mathlive .fontsize-ensurer.reset-size5.size5,.ML__mathlive .sizing.reset-size5.size5{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size5.size6,.ML__mathlive .sizing.reset-size5.size6{font-size:1.2em}.ML__mathlive .fontsize-ensurer.reset-size5.size7,.ML__mathlive .sizing.reset-size5.size7{font-size:1.44em}.ML__mathlive .fontsize-ensurer.reset-size5.size8,.ML__mathlive .sizing.reset-size5.size8{font-size:1.73em}.ML__mathlive .fontsize-ensurer.reset-size5.size9,.ML__mathlive .sizing.reset-size5.size9{font-size:2.07em}.ML__mathlive .fontsize-ensurer.reset-size5.size10,.ML__mathlive .sizing.reset-size5.size10{font-size:2.49em}.ML__mathlive .fontsize-ensurer.reset-size6.size1,.ML__mathlive .sizing.reset-size6.size1{font-size:.41666667em}.ML__mathlive .fontsize-ensurer.reset-size6.size2,.ML__mathlive .sizing.reset-size6.size2{font-size:.58333333em}.ML__mathlive .fontsize-ensurer.reset-size6.size3,.ML__mathlive .sizing.reset-size6.size3{font-size:.66666667em}.ML__mathlive .fontsize-ensurer.reset-size6.size4,.ML__mathlive .sizing.reset-size6.size4{font-size:.75em}.ML__mathlive .fontsize-ensurer.reset-size6.size5,.ML__mathlive .sizing.reset-size6.size5{font-size:.83333333em}.ML__mathlive .fontsize-ensurer.reset-size6.size6,.ML__mathlive .sizing.reset-size6.size6{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size6.size7,.ML__mathlive .sizing.reset-size6.size7{font-size:1.2em}.ML__mathlive .fontsize-ensurer.reset-size6.size8,.ML__mathlive .sizing.reset-size6.size8{font-size:1.44166667em}.ML__mathlive .fontsize-ensurer.reset-size6.size9,.ML__mathlive .sizing.reset-size6.size9{font-size:1.725em}.ML__mathlive .fontsize-ensurer.reset-size6.size10,.ML__mathlive .sizing.reset-size6.size10{font-size:2.075em}.ML__mathlive .fontsize-ensurer.reset-size7.size1,.ML__mathlive .sizing.reset-size7.size1{font-size:.34722222em}.ML__mathlive .fontsize-ensurer.reset-size7.size2,.ML__mathlive .sizing.reset-size7.size2{font-size:.48611111em}.ML__mathlive .fontsize-ensurer.reset-size7.size3,.ML__mathlive .sizing.reset-size7.size3{font-size:.55555556em}.ML__mathlive .fontsize-ensurer.reset-size7.size4,.ML__mathlive .sizing.reset-size7.size4{font-size:.625em}.ML__mathlive .fontsize-ensurer.reset-size7.size5,.ML__mathlive .sizing.reset-size7.size5{font-size:.69444444em}.ML__mathlive .fontsize-ensurer.reset-size7.size6,.ML__mathlive .sizing.reset-size7.size6{font-size:.83333333em}.ML__mathlive .fontsize-ensurer.reset-size7.size7,.ML__mathlive .sizing.reset-size7.size7{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size7.size8,.ML__mathlive .sizing.reset-size7.size8{font-size:1.20138889em}.ML__mathlive .fontsize-ensurer.reset-size7.size9,.ML__mathlive .sizing.reset-size7.size9{font-size:1.4375em}.ML__mathlive .fontsize-ensurer.reset-size7.size10,.ML__mathlive .sizing.reset-size7.size10{font-size:1.72916667em}.ML__mathlive .fontsize-ensurer.reset-size8.size1,.ML__mathlive .sizing.reset-size8.size1{font-size:.28901734em}.ML__mathlive .fontsize-ensurer.reset-size8.size2,.ML__mathlive .sizing.reset-size8.size2{font-size:.40462428em}.ML__mathlive .fontsize-ensurer.reset-size8.size3,.ML__mathlive .sizing.reset-size8.size3{font-size:.46242775em}.ML__mathlive .fontsize-ensurer.reset-size8.size4,.ML__mathlive .sizing.reset-size8.size4{font-size:.52023121em}.ML__mathlive .fontsize-ensurer.reset-size8.size5,.ML__mathlive .sizing.reset-size8.size5{font-size:.57803468em}.ML__mathlive .fontsize-ensurer.reset-size8.size6,.ML__mathlive .sizing.reset-size8.size6{font-size:.69364162em}.ML__mathlive .fontsize-ensurer.reset-size8.size7,.ML__mathlive .sizing.reset-size8.size7{font-size:.83236994em}.ML__mathlive .fontsize-ensurer.reset-size8.size8,.ML__mathlive .sizing.reset-size8.size8{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size8.size9,.ML__mathlive .sizing.reset-size8.size9{font-size:1.19653179em}.ML__mathlive .fontsize-ensurer.reset-size8.size10,.ML__mathlive .sizing.reset-size8.size10{font-size:1.43930636em}.ML__mathlive .fontsize-ensurer.reset-size9.size1,.ML__mathlive .sizing.reset-size9.size1{font-size:.24154589em}.ML__mathlive .fontsize-ensurer.reset-size9.size2,.ML__mathlive .sizing.reset-size9.size2{font-size:.33816425em}.ML__mathlive .fontsize-ensurer.reset-size9.size3,.ML__mathlive .sizing.reset-size9.size3{font-size:.38647343em}.ML__mathlive .fontsize-ensurer.reset-size9.size4,.ML__mathlive .sizing.reset-size9.size4{font-size:.43478261em}.ML__mathlive .fontsize-ensurer.reset-size9.size5,.ML__mathlive .sizing.reset-size9.size5{font-size:.48309179em}.ML__mathlive .fontsize-ensurer.reset-size9.size6,.ML__mathlive .sizing.reset-size9.size6{font-size:.57971014em}.ML__mathlive .fontsize-ensurer.reset-size9.size7,.ML__mathlive .sizing.reset-size9.size7{font-size:.69565217em}.ML__mathlive .fontsize-ensurer.reset-size9.size8,.ML__mathlive .sizing.reset-size9.size8{font-size:.83574879em}.ML__mathlive .fontsize-ensurer.reset-size9.size9,.ML__mathlive .sizing.reset-size9.size9{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size9.size10,.ML__mathlive .sizing.reset-size9.size10{font-size:1.20289855em}.ML__mathlive .fontsize-ensurer.reset-size10.size1,.ML__mathlive .sizing.reset-size10.size1{font-size:.20080321em}.ML__mathlive .fontsize-ensurer.reset-size10.size2,.ML__mathlive .sizing.reset-size10.size2{font-size:.2811245em}.ML__mathlive .fontsize-ensurer.reset-size10.size3,.ML__mathlive .sizing.reset-size10.size3{font-size:.32128514em}.ML__mathlive .fontsize-ensurer.reset-size10.size4,.ML__mathlive .sizing.reset-size10.size4{font-size:.36144578em}.ML__mathlive .fontsize-ensurer.reset-size10.size5,.ML__mathlive .sizing.reset-size10.size5{font-size:.40160643em}.ML__mathlive .fontsize-ensurer.reset-size10.size6,.ML__mathlive .sizing.reset-size10.size6{font-size:.48192771em}.ML__mathlive .fontsize-ensurer.reset-size10.size7,.ML__mathlive .sizing.reset-size10.size7{font-size:.57831325em}.ML__mathlive .fontsize-ensurer.reset-size10.size8,.ML__mathlive .sizing.reset-size10.size8{font-size:.69477912em}.ML__mathlive .fontsize-ensurer.reset-size10.size9,.ML__mathlive .sizing.reset-size10.size9{font-size:.8313253em}.ML__mathlive .fontsize-ensurer.reset-size10.size10,.ML__mathlive .sizing.reset-size10.size10{font-size:1em}.ML__mathlive .delimsizing.size1{font-family:KaTeX_Size1}.ML__mathlive .delimsizing.size2{font-family:KaTeX_Size2}.ML__mathlive .delimsizing.size3{font-family:KaTeX_Size3}.ML__mathlive .delimsizing.size4{font-family:KaTeX_Size4}.ML__mathlive .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1;vertical-align:top}.ML__mathlive .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4;vertical-align:top}.ML__mathlive .nulldelimiter{width:.12em}.ML__mathlive .op-symbol{position:relative}.ML__mathlive .op-symbol.small-op{font-family:KaTeX_Size1}.ML__mathlive .op-symbol.large-op{font-family:KaTeX_Size2}.ML__mathlive .op-limits .vlist>span{text-align:center}.ML__mathlive .accent>.vlist>span{text-align:center}.ML__mathlive .accent .accent-body>span{width:0}.ML__mathlive .accent .accent-body.accent-vec>span{position:relative;left:.326em}.ML__mathlive .mtable .vertical-separator{display:inline-block;margin:0 -.025em;border-right:.05em solid #000}.ML__mathlive .mtable .arraycolsep{display:inline-block}.ML__mathlive .mtable .col-align-c>.vlist{text-align:center}.ML__mathlive .mtable .col-align-l>.vlist{text-align:left}.ML__mathlive .mtable .col-align-r>.vlist{text-align:right}.ML__smart-fence__close{opacity:.5}.ML__selection{background:var(--highlight-inactive);box-sizing:border-box}.ML__focused .ML__selection{background:var(--highlight)!important;color:var(--on-highlight)}.ML__command{font-family:Source Code Pro,Consolas,Roboto Mono,Menlo,Bitstream Vera Sans Mono,DejaVu Sans Mono,Monaco,Courier,monospace;letter-spacing:-1px;font-weight:400;color:var(--primary)}:not(.ML__command)+.ML__command{margin-left:.25em}.ML__command+:not(.ML__command){padding-left:.25em}.ML__keystroke-caption{visibility:hidden;background:var(--secondary);border-color:var(--secondary-border);box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);text-align:center;border-radius:6px;padding:16px;position:absolute;z-index:1;display:flex;flex-direction:row;justify-content:center;--keystroke:#fff;--on-keystroke:#555;--keystroke-border:#f7f7f7}@media (prefers-color-scheme:dark){body:not([theme=light]) .ML__keystroke-caption{--keystroke:hsl(var(--hue),50%,30%);--on-keystroke:#fafafa;--keystroke-border:hsl(var(--hue),50%,25%)}}body[theme=dark] .ML__keystroke-caption{--keystroke:hsl(var(--hue),50%,30%);--on-keystroke:#fafafa;--keystroke-border:hsl(var(--hue),50%,25%)}[data-tooltip]{position:relative}[data-tooltip][data-placement=top]:after{top:inherit;bottom:100%}[data-tooltip]:after{position:absolute;visibility:hidden;content:attr(data-tooltip);display:inline-table;top:110%;width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:200px;padding:8px;background:#616161;color:#fff;text-align:center;z-index:2;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);border-radius:2px;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-weight:400;font-size:12px;opacity:0;-webkit-transform:scale(.5);transform:scale(.5);transition:all .15s cubic-bezier(.4,0,1,1)}@media only screen and (max-width:767px){[data-tooltip]:after{height:32px;padding:4px 16px;font-size:14px}}[data-tooltip]:hover{position:relative}[data-tooltip]:hover:after{visibility:visible;opacity:1;-webkit-transform:scale(1);transform:scale(1)}[data-tooltip][data-delay]:after{transition-delay:0s}[data-tooltip][data-delay]:hover:after{transition-delay:1s}.can-redo [data-command='"redo"']{opacity:1!important}.can-undo [data-command='"undo"']{opacity:1!important}.ML__keyboard{--keyboard-background:rgba(209,213,217,0.95);--keyboard-text:#000;--keyboard-text-active:var(--primary);--keyboard-background-border:#ddd;--keycap-background:#fff;--keycap-background-active:#e5e5e5;--keycap-background-border:#e5e6e9;--keycap-background-border-bottom:#8d8f92;--keycap-text:#000;--keycap-text-active:#fff;--keycap-secondary-text:#000;--keycap-modifier-background:#b9bdc7;--keycap-modifier-border:#c5c9d0;--keycap-modifier-border-bottom:#989da6;--keyboard-alternate-background:#fff;--keyboard-alternate-background-active:#e5e5e5;--keyboard-alternate-text:#000;position:fixed;left:0;bottom:-267px;width:100vw;z-index:105;padding-top:5px;-webkit-transform:translate(0);transform:translate(0);opacity:0;visibility:hidden;transition:.28s cubic-bezier(0,0,.2,1);transition-property:opacity,-webkit-transform;transition-property:transform,opacity;transition-property:transform,opacity,-webkit-transform;-webkit-backdrop-filter:grayscale(50%);backdrop-filter:grayscale(50%);background-color:var(--keyboard-background);border:1px solid var(--keyboard-background-border);font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:16px;font-weight:400;margin:0;text-shadow:none;box-sizing:border-box;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.ML__keyboard.is-visible{-webkit-transform:translateY(-267px);transform:translateY(-267px);opacity:1;visibility:visible;transition-timing-function:cubic-bezier(.4,0,1,1)}.ML__keyboard .tex{font-family:KaTeX_Main,Cambria Math,Asana Math,OpenSymbol,Symbola,STIX,Times,serif!important}.ML__keyboard .tex-math{font-family:KaTeX_Math,Cambria Math,Asana Math,OpenSymbol,Symbola,STIX,Times,serif!important}.ML__keyboard .tt{font-family:Source Code Pro,Consolas,Roboto Mono,Menlo,Bitstream Vera Sans Mono,DejaVu Sans Mono,Monaco,Courier,monospace!important;font-size:30px;font-weight:400}.ML__keyboard.alternate-keys{visibility:hidden;max-width:286px;background-color:var(--keyboard-alternate-background);text-align:center;border-radius:6px;position:fixed;bottom:auto;top:0;box-sizing:content-box;-webkit-transform:none;transform:none;z-index:106;display:flex;flex-direction:row;justify-content:center;align-content:center;box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22);transition:none}@media only screen and (max-height:412px){.ML__keyboard.alternate-keys{max-width:320px}}.ML__keyboard.alternate-keys.is-visible{visibility:visible}.ML__keyboard.alternate-keys ul{list-style:none;margin:3px;padding:0;display:flex;flex-flow:row wrap-reverse;justify-content:center}.ML__keyboard.alternate-keys ul>li{display:flex;flex-flow:column;align-items:center;justify-content:center;font-size:30px;height:70px;width:70px;box-sizing:border-box;margin:0;background:transparent;border:1px solid transparent;border-radius:5px;pointer-events:all;color:var(--keyboard-alternate-text);fill:currentColor}@media only screen and (max-height:412px){.ML__keyboard.alternate-keys ul>li{font-size:24px;height:50px;width:50px}}.ML__keyboard.alternate-keys ul>li.active,.ML__keyboard.alternate-keys ul>li.pressed,.ML__keyboard.alternate-keys ul>li:hover{box-shadow:0 10px 20px rgba(0,0,0,.19),0 6px 6px rgba(0,0,0,.23);background:var(--keyboard-alternate-background-active);color:var(--keyboard-text-active)}.ML__keyboard.alternate-keys ul>li.small{font-size:18px}.ML__keyboard.alternate-keys ul>li.small-button{width:42px;height:42px;margin:2px;background:#fbfbfb}.ML__keyboard.alternate-keys ul>li.small-button:hover{background:var(--keyboard-alternate-background-active)}.ML__keyboard.alternate-keys ul>li.box>div,.ML__keyboard.alternate-keys ul>li.box>span{border:1px dashed rgba(0,0,0,.24)}.ML__keyboard.alternate-keys ul>li .warning{min-height:60px;min-width:60px;background:#cd0030;color:#fff;padding:5px;display:flex;align-items:center;justify-content:center;border-radius:5px}.ML__keyboard.alternate-keys ul>li .warning.active,.ML__keyboard.alternate-keys ul>li .warning.pressed,.ML__keyboard.alternate-keys ul>li .warning:hover{background:red}.ML__keyboard.alternate-keys ul>li .warning svg{width:50px;height:50px}.ML__keyboard.alternate-keys ul>li aside{font-size:12px;line-height:12px;opacity:.78}.ML__keyboard>div.keyboard-layer{display:none;outline:none}.ML__keyboard>div.keyboard-layer.is-visible{display:flex;flex-flow:column}.ML__keyboard>div>div.keyboard-toolbar{align-self:center;display:flex;flex-flow:row;justify-content:space-between;width:736px}@media only screen and (min-width:768px) and (max-width:1024px){.ML__keyboard>div>div.keyboard-toolbar{width:556px}}@media only screen and (max-width:767px){.ML__keyboard>div>div.keyboard-toolbar{width:365px;max-width:100vw}}.ML__keyboard>div>div.keyboard-toolbar svg{height:20px;width:20px}@media only screen and (max-width:767px){.ML__keyboard>div>div.keyboard-toolbar svg{height:13px;width:17px}}.ML__keyboard>div>div.keyboard-toolbar>.left{justify-content:flex-start;display:flex;flex-flow:row}.ML__keyboard>div>div.keyboard-toolbar>.right{justify-content:flex-end;display:flex;flex-flow:row}.ML__keyboard>div>div.keyboard-toolbar>div>div{display:flex;align-items:baseline;justify-content:center;pointer-events:all;color:var(--keyboard-text);fill:currentColor;background:0;font-size:110%;cursor:pointer;min-height:0;padding:4px 10px;margin:7px 4px 6px;box-shadow:none;border:none;border-bottom:2px solid transparent}.ML__keyboard>div>div.keyboard-toolbar>div>div.disabled.pressed svg,.ML__keyboard>div>div.keyboard-toolbar>div>div.disabled:hover svg,.ML__keyboard>div>div.keyboard-toolbar>div>div.disabled svg{color:var(--keyboard-text);opacity:.2}@media only screen and (max-width:414px){.ML__keyboard>div>div.keyboard-toolbar>div>div{font-size:100%;padding:0 6px 0 0}}@media only screen and (max-width:767px){.ML__keyboard>div>div.keyboard-toolbar>div>div{padding-left:4px;padding-right:4px;font-size:90%}}.ML__keyboard>div>div.keyboard-toolbar>div>div.active,.ML__keyboard>div>div.keyboard-toolbar>div>div.pressed,.ML__keyboard>div>div.keyboard-toolbar>div>div:active,.ML__keyboard>div>div.keyboard-toolbar>div>div:hover{color:var(--keyboard-text-active)}.ML__keyboard>div>div.keyboard-toolbar>div>div.selected{color:var(--keyboard-text-active);border-bottom:2px solid var(--keyboard-text-active);margin-bottom:8px;padding-bottom:0}.ML__keyboard div .rows{border:0;border-collapse:separate;clear:both;margin:auto;display:flex;flex-flow:column;align-items:center}.ML__keyboard div .rows>ul{list-style:none;height:40px;margin:0 0 3px;padding:0}.ML__keyboard div .rows>ul>li{display:flex;flex-flow:column;align-items:center;justify-content:center;width:34px;margin-right:2px;height:40px;box-sizing:border-box;padding:8px 0;vertical-align:top;text-align:center;float:left;color:var(--keycap-text);fill:currentColor;font-size:20px;background:var(--keycap-background);border:1px solid var(--keycap-background-border);border-bottom-color:var(--keycap-background-border-bottom);border-radius:5px;pointer-events:all;position:relative;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.ML__keyboard div .rows>ul>li:last-child{margin-right:0}.ML__keyboard div .rows>ul>li.small{font-size:16px}.ML__keyboard div .rows>ul>li.tt{color:var(--keyboard-text-active)}.ML__keyboard div .rows>ul>li.bottom{justify-content:flex-end}.ML__keyboard div .rows>ul>li.left{align-items:flex-start;padding-left:4px}.ML__keyboard div .rows>ul>li.right{align-items:flex-end;padding-right:4px}.ML__keyboard div .rows>ul>li svg{width:20px;height:20px}.ML__keyboard div .rows>ul>li .warning{height:25px;width:25px;min-height:25px;min-width:25px;background:#cd0030;color:#fff;border-radius:100%;padding:5px;display:flex;align-items:center;justify-content:center;margin-bottom:-2px}.ML__keyboard div .rows>ul>li .warning svg{width:16px;height:16px}@media only screen and (max-width:768px){.ML__keyboard div .rows>ul>li .warning{height:16px;width:16px;min-height:16px;min-width:16px}.ML__keyboard div .rows>ul>li .warning svg{width:14px;height:14px}}.ML__keyboard div .rows>ul>li>.w0{width:0}.ML__keyboard div .rows>ul>li>.w5{width:16px}.ML__keyboard div .rows>ul>li>.w15{width:52px}.ML__keyboard div .rows>ul>li>.w20{width:70px}.ML__keyboard div .rows>ul>li>.w50{width:178px}.ML__keyboard div .rows>ul>li.separator{background:transparent;border:none;pointer-events:none}@media only screen and (max-width:560px){.ML__keyboard div .rows>ul>li.if-wide{display:none}}.ML__keyboard div .rows>ul>li.tex-math{font-size:25px}.ML__keyboard div .rows>ul>li.pressed,.ML__keyboard div .rows>ul>li:hover{background:var(--keycap-background-active);color:var(--keyboard-text-active)}.ML__keyboard div .rows>ul>li.action.active,.ML__keyboard div .rows>ul>li.action:active,.ML__keyboard div .rows>ul>li.keycap.active,.ML__keyboard div .rows>ul>li.keycap:active{-webkit-transform:translateY(-20px) scale(1.4);transform:translateY(-20px) scale(1.4);z-index:100;color:var(--keyboard-text-active)}.ML__keyboard div .rows>ul>li.modifier.active,.ML__keyboard div .rows>ul>li.modifier:active{background:var(--keyboard-text-active);color:var(--keycap-text-active)}.ML__keyboard div .rows>ul>li.action.font-glyph,.ML__keyboard div .rows>ul>li.modifier.font-glyph{font-size:18px}@media only screen and (max-width:767px){.ML__keyboard div .rows>ul>li.action.font-glyph,.ML__keyboard div .rows>ul>li.modifier.font-glyph{font-size:16px}}@media only screen and (max-width:767px){.ML__keyboard div .rows>ul>li.bigfnbutton,.ML__keyboard div .rows>ul>li.fnbutton{font-size:12px}}.ML__keyboard div .rows>ul>li.bigfnbutton{font-size:14px}@media only screen and (max-width:767px){.ML__keyboard div .rows>ul>li.bigfnbutton{font-size:9px}}.ML__keyboard div .rows>ul>li.action,.ML__keyboard div .rows>ul>li.modifier{background-color:var(--keycap-modifier-background);border-bottom-color:var(--keycap-modifier-border);border-color:var(--keycap-modifier-border) var(--keycap-modifier-border) var(--keycap-modifier-border-bottom);font-size:65%;font-weight:100}.ML__keyboard div .rows>ul>li.action.selected,.ML__keyboard div .rows>ul>li.modifier.selected{color:var(--keyboard-text-active)}.ML__keyboard div .rows>ul>li.action.selected.active,.ML__keyboard div .rows>ul>li.action.selected.pressed,.ML__keyboard div .rows>ul>li.action.selected:active,.ML__keyboard div .rows>ul>li.action.selected:hover,.ML__keyboard div .rows>ul>li.modifier.selected.active,.ML__keyboard div .rows>ul>li.modifier.selected.pressed,.ML__keyboard div .rows>ul>li.modifier.selected:active,.ML__keyboard div .rows>ul>li.modifier.selected:hover{color:#fff}.ML__keyboard div .rows>ul>li.keycap.w50{font-size:80%;padding-top:10px;font-weight:100}.ML__keyboard div .rows>ul>li small{color:#555}@media only screen and (max-width:767px){.ML__keyboard div .rows>ul>li small{font-size:9px}}.ML__keyboard div .rows>ul>li aside{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:10px;line-height:10px;color:#666}@media only screen and (max-width:767px){.ML__keyboard div .rows>ul>li aside{display:none}}@media only screen and (max-width:414px){.ML__keyboard div .rows>ul>li{width:29px;margin-right:2px}.ML__keyboard div .rows>ul>.w5{width:13.5px}.ML__keyboard div .rows>ul>.w15{width:44.5px}.ML__keyboard div .rows>ul>.w20{width:60px}.ML__keyboard div .rows>ul>.w50{width:153px}}@media only screen and (min-width:415px) and (max-width:768px){.ML__keyboard div .rows>ul>li{width:37px;margin-right:3px}.ML__keyboard div .rows>ul>.w5{width:17px}.ML__keyboard div .rows>ul>.w15{width:57px}.ML__keyboard div .rows>ul>.w20{width:77px}.ML__keyboard div .rows>ul>.w50{width:197px}}@media only screen and (min-width:768px) and (max-width:1024px){.ML__keyboard div .rows>ul{height:52px}.ML__keyboard div .rows>ul>li{height:52px;width:51px;margin-right:4px}.ML__keyboard div .rows>ul>.w5{width:23.5px}.ML__keyboard div .rows>ul>.w15{width:78.5px}.ML__keyboard div .rows>ul>.w20{width:106px}.ML__keyboard div .rows>ul>.w50{width:271px}}@media only screen and (min-width:1025px){.ML__keyboard div .rows>ul{height:52px}.ML__keyboard div .rows>ul>li{height:52px;width:66px;margin-right:6px}.ML__keyboard div .rows>ul>.action,.ML__keyboard div .rows>ul>.modifier{font-size:80%}.ML__keyboard div .rows>ul>.w5{width:30px}.ML__keyboard div .rows>ul>.w15{width:102px}.ML__keyboard div .rows>ul>.w20{width:138px}.ML__keyboard div .rows>ul>.w50{width:354px}}@media (prefers-color-scheme:dark){body:not([theme=light]) .ML__keyboard{--hue:206;--keyboard-background:hsl(var(--hue),19%,38%);--keyboard-text:#f0f0f0;--keyboard-text-active:hsl(var(--hue),100%,60%);--keyboard-background-border:#333;--keycap-background:hsl(var(--hue),25%,39%);--keycap-background-active:hsl(var(--hue),35%,42%);--keycap-background-border:hsl(var(--hue),25%,35%);--keycap-background-border-bottom:#426b8a;--keycap-text:#d0d0d0;--keycap-text-active:#000;--keycap-secondary-text:#fff;--keycap-modifier-background:hsl(var(--hue),35%,40%);--keycap-modifier-border:hsl(var(--hue),35%,35%);--keycap-modifier-border-bottom:hsl(var(--hue),35%,42%);--keyboard-alternate-background:hsl(var(--hue),19%,38%);--keyboard-alternate-background-active:hsl(var(--hue),35%,42%);--keyboard-alternate-text:#d1d1d1}}body[theme=dark] .ML__keyboard{--hue:206;--keyboard-background:hsl(var(--hue),19%,38%);--keyboard-text:#f0f0f0;--keyboard-text-active:hsl(var(--hue),100%,60%);--keyboard-background-border:#333;--keycap-background:hsl(var(--hue),25%,39%);--keycap-background-active:hsl(var(--hue),35%,42%);--keycap-background-border:hsl(var(--hue),25%,35%);--keycap-background-border-bottom:#426b8a;--keycap-text:#d0d0d0;--keycap-text-active:#000;--keycap-secondary-text:#fff;--keycap-modifier-background:hsl(var(--hue),35%,40%);--keycap-modifier-border:hsl(var(--hue),35%,35%);--keycap-modifier-border-bottom:hsl(var(--hue),35%,42%);--keyboard-alternate-background:hsl(var(--hue),19%,38%);--keyboard-alternate-background-active:hsl(var(--hue),35%,42%);--keyboard-alternate-text:#d1d1d1}div.ML__keyboard.material{--keyboard-background:rgba(209,213,217,0.9);--keyboard-background-border:#ddd;--keycap-background:transparent;--keycap-background-active:#cccfd1;--keycap-background-border:transparent;--keyboard-alternate-background:#efefef;--keyboard-alternate-text:#000;font-family:Roboto,sans-serif}div.ML__keyboard.material.alternate-keys{background:var(--keyboard-alternate-background);border:1px solid transparent;border-radius:5px;box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22)}div.ML__keyboard.material.alternate-keys ul li.active,div.ML__keyboard.material.alternate-keys ul li.pressed,div.ML__keyboard.material.alternate-keys ul li:active,div.ML__keyboard.material.alternate-keys ul li:hover{border:1px solid transparent;background:#5f97fc;color:#fff;fill:currentColor}div.ML__keyboard.material .keyboard-toolbar>div>div{font-size:16px}div.ML__keyboard.material .keyboard-toolbar div.div.active,div.ML__keyboard.material .keyboard-toolbar div.div.pressed,div.ML__keyboard.material .keyboard-toolbar div div:active,div.ML__keyboard.material .keyboard-toolbar div div:hover{color:#5f97fc;fill:currentColor}div.ML__keyboard.material .keyboard-toolbar>div>.selected{color:#5f97fc;fill:currentColor;border-bottom:2px solid #5f97fc;margin-bottom:8px;padding-bottom:0}div.ML__keyboard.material div>.rows>ul>.keycap{background:transparent;border:1px solid transparent;border-radius:5px;color:var(--keycap-text);fill:currentColor;transition:none}div.ML__keyboard.material div>.rows>ul>.keycap.tt{color:#5f97fc}div.ML__keyboard.material div>.rows>ul>.keycap[data-key=" "]{margin-top:10px;margin-bottom:10px;height:20px;background:#e0e0e0}div.ML__keyboard.material div>.rows>ul>.keycap[data-key=" "].active,div.ML__keyboard.material div>.rows>ul>.keycap[data-key=" "].pressed,div.ML__keyboard.material div>.rows>ul>.keycap[data-key=" "]:active,div.ML__keyboard.material div>.rows>ul>.keycap[data-key=" "]:hover{background:#d0d0d0;box-shadow:none;-webkit-transform:none;transform:none}div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]):hover{border:1px solid transparent;background:var(--keycap-background-active);box-shadow:none}div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]).active,div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]).pressed,div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]):active{background:var(--keyboard-alternate-background);color:var(--keyboard-alternate-text);box-shadow:0 10px 20px rgba(0,0,0,.19),0 6px 6px rgba(0,0,0,.23)}@media only screen and (max-width:767px){div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]).active,div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]).pressed,div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]):active{box-shadow:0 10px 20px rgba(0,0,0,.19),0 6px 6px rgba(0,0,0,.23);font-size:10px;vertical-align:top;width:19.5px;margin-right:10px;margin-left:10px;-webkit-transform:translateY(-20px) scale(2);transform:translateY(-20px) scale(2);transition:none;justify-content:flex-start;padding:2px 0 0;z-index:100}}@media only screen and (max-width:414px){div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]).active,div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]).pressed,div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]):active{width:16.5px}}@media only screen and (max-width:767px){div.ML__keyboard.material div>.rows>ul>.keycap:last-child.active,div.ML__keyboard.material div>.rows>ul>.keycap:last-child:active{margin-right:0;margin-left:14px}}div.ML__keyboard.material div div.rows ul li.action,div.ML__keyboard.material div div.rows ul li.modifier{background:transparent;border:0;color:#869096;fill:currentColor;font-size:16px;transition:none}div.ML__keyboard.material div div.rows ul li.action.selected,div.ML__keyboard.material div div.rows ul li.modifier.selected{color:#5f97fc;border-radius:0;border-bottom:2px solid #5f97fc}div.ML__keyboard.material div div.rows ul li.action.active,div.ML__keyboard.material div div.rows ul li.action.pressed,div.ML__keyboard.material div div.rows ul li.action:active,div.ML__keyboard.material div div.rows ul li.action:hover,div.ML__keyboard.material div div.rows ul li.modifier.active,div.ML__keyboard.material div div.rows ul li.modifier.pressed,div.ML__keyboard.material div div.rows ul li.modifier:active,div.ML__keyboard.material div div.rows ul li.modifier:hover{border:0;color:var(--keycap-text);background:var(--keycap-background-active);box-shadow:none}div.ML__keyboard.material div div.rows ul li.bigfnbutton,div.ML__keyboard.material div div.rows ul li.fnbutton{background:transparent;border:0}div.ML__keyboard.material div div.rows ul li.bigfnbutton.selected,div.ML__keyboard.material div div.rows ul li.fnbutton.selected{color:#5f97fc;fill:currentColor;border-radius:0;border-bottom:2px solid #5f97fc}div.ML__keyboard.material div div.rows ul li.bigfnbutton.active,div.ML__keyboard.material div div.rows ul li.bigfnbutton.pressed,div.ML__keyboard.material div div.rows ul li.bigfnbutton:active,div.ML__keyboard.material div div.rows ul li.bigfnbutton:hover,div.ML__keyboard.material div div.rows ul li.fnbutton.active,div.ML__keyboard.material div div.rows ul li.fnbutton.pressed,div.ML__keyboard.material div div.rows ul li.fnbutton:active,div.ML__keyboard.material div div.rows ul li.fnbutton:hover{border:0;color:#5f97fc;fill:currentColor;background:var(--keycap-background-active);box-shadow:none}@media (prefers-color-scheme:dark){body:not([theme=light]) div.ML__keyboard.material{--hue:198;--keyboard-background:hsl(var(--hue),19%,18%);--keyboard-text:#d4d6d7;--keyboard-text-active:#5f97fc;--keyboard-background-border:#333;--keycap-background:hsl(var(--hue),25%,39%);--keycap-background-active:#5f97fc;--keycap-background-border:transparent;--keycap-background-border-bottom:transparent;--keycap-text:#d0d0d0;--keycap-text-active:#d4d6d7;--keycap-secondary-text:#5f97fc;--keycap-modifier-background:hsl(var(--hue),35%,40%);--keycap-modifier-border:hsl(var(--hue),35%,35%);--keycap-modifier-border-bottom:hsl(var(--hue),35%,42%);--keyboard-alternate-background:hsl(var(--hue),8%,2%);--keyboard-alternate-background-active:hsl(var(--hue),35%,42%);--keyboard-alternate-text:#d1d1d1}}body[theme=dark] div.ML__keyboard.material{--hue:198;--keyboard-background:hsl(var(--hue),19%,18%);--keyboard-text:#d4d6d7;--keyboard-text-active:#5f97fc;--keyboard-background-border:#333;--keycap-background:hsl(var(--hue),25%,39%);--keycap-background-active:#5f97fc;--keycap-background-border:transparent;--keycap-background-border-bottom:transparent;--keycap-text:#d0d0d0;--keycap-text-active:#d4d6d7;--keycap-secondary-text:#5f97fc;--keycap-modifier-background:hsl(var(--hue),35%,40%);--keycap-modifier-border:hsl(var(--hue),35%,35%);--keycap-modifier-border-bottom:hsl(var(--hue),35%,42%);--keyboard-alternate-background:hsl(var(--hue),8%,2%);--keyboard-alternate-background-active:hsl(var(--hue),35%,42%);--keyboard-alternate-text:#d1d1d1}.ML__error{background-image:radial-gradient(ellipse at center,#cc0041,transparent 70%);background-repeat:repeat-x;background-size:3px 3px;background-position:0 98%}.ML__suggestion{opacity:.5}.ML__placeholder{opacity:.7;padding-left:.5ex;padding-right:.5ex}.ML__keystroke-caption>span{min-width:14px;margin:0 8px 0 0;padding:4px;background-color:var(--keystroke);color:var(--on-keystroke);fill:currentColor;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:1em;border-radius:6px;border:2px solid var(--keystroke-border)}.ML__virtual-keyboard-toggle.pressed{background:hsla(0,0%,70%,.5)}.ML__virtual-keyboard-toggle:focus{outline:none;border-radius:50%;border:2px solid var(--primary)}.ML__virtual-keyboard-toggle.active,.ML__virtual-keyboard-toggle.active:hover{background:hsla(0,0%,70%,.5);color:#000;fill:currentColor}div.ML__popover.is-visible{visibility:visible;-webkit-animation:ML__fade-in .15s cubic-bezier(0,0,.2,1);animation:ML__fade-in .15s cubic-bezier(0,0,.2,1)}@-webkit-keyframes ML__fade-in{0%{opacity:0}to{opacity:1}}@keyframes ML__fade-in{0%{opacity:0}to{opacity:1}}.ML__popover__content{border-radius:6px;padding:2px;cursor:pointer;min-height:100px;display:flex;flex-direction:column;justify-content:center;margin-left:8px;margin-right:8px}.ML__popover__content a{color:#5ea6fd;padding-top:.3em;margin-top:.4em;display:block}.ML__popover__content a:hover{color:#5ea6fd;text-decoration:underline}.ML__popover__content.active,.ML__popover__content.pressed,.ML__popover__content:hover{background:hsla(0,0%,100%,.1)}.ML__popover__command{font-size:1.6rem}.ML__popover__prev-shortcut{height:31px;opacity:.1;cursor:pointer;margin-left:8px;margin-right:8px;padding-top:4px;padding-bottom:2px}.ML__popover__next-shortcut:hover,.ML__popover__prev-shortcut:hover{opacity:.3}.ML__popover__next-shortcut.active,.ML__popover__next-shortcut.pressed,.ML__popover__prev-shortcut.active,.ML__popover__prev-shortcut.pressed{opacity:1}.ML__popover__next-shortcut>span,.ML__popover__prev-shortcut>span{padding:5px;border-radius:50%;width:20px;height:20px;display:inline-block}.ML__popover__prev-shortcut>span>span{margin-top:-2px;display:block}.ML__popover__next-shortcut>span>span{margin-top:2px;display:block}.ML__popover__next-shortcut:hover>span,.ML__popover__prev-shortcut:hover>span{background:hsla(0,0%,100%,.1)}.ML__popover__next-shortcut{height:34px;opacity:.1;cursor:pointer;margin-left:8px;margin-right:8px;padding-top:2px;padding-bottom:4px}.ML__popover__shortcut{font-size:.8em;margin-top:.25em}.ML__popover__note,.ML__popover__shortcut{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;opacity:.7;padding-top:.25em}.ML__popover__note{font-size:.8rem;line-height:1em;padding-left:.5em;padding-right:.5em}.ML__shortcut-join{opacity:.5}.ML__scroller{position:fixed;z-index:1;top:0;height:100vh;width:200px} \ No newline at end of file +@font-face{font-family:KaTeX_AMS;src:url(fonts/KaTeX_AMS-Regular.woff2) format("woff2"),url(fonts/KaTeX_AMS-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Caligraphic;src:url(fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Bold.woff) format("woff");font-weight:700;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Caligraphic;src:url(fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Fraktur;src:url(fonts/KaTeX_Fraktur-Bold.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Bold.woff) format("woff");font-weight:700;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Fraktur;src:url(fonts/KaTeX_Fraktur-Regular.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Main-BoldItalic.woff) format("woff");font-weight:700;font-style:italic;font-display:"swap"}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-Bold.woff2) format("woff2"),url(fonts/KaTeX_Main-Bold.woff) format("woff");font-weight:700;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-Italic.woff2) format("woff2"),url(fonts/KaTeX_Main-Italic.woff) format("woff");font-weight:400;font-style:italic;font-display:"swap"}@font-face{font-family:KaTeX_Math;src:url(fonts/KaTeX_Math-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Math-BoldItalic.woff) format("woff");font-weight:700;font-style:italic;font-display:"swap"}@font-face{font-family:"KaTeX_SansSerif";src:url(fonts/KaTeX_SansSerif-Bold.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Bold.woff) format("woff");font-weight:700;font-style:normal;font-display:"swap"}@font-face{font-family:"KaTeX_SansSerif";src:url(fonts/KaTeX_SansSerif-Italic.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Italic.woff) format("woff");font-weight:400;font-style:italic;font-display:"swap"}@font-face{font-family:"KaTeX_SansSerif";src:url(fonts/KaTeX_SansSerif-Regular.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Script;src:url(fonts/KaTeX_Script-Regular.woff2) format("woff2"),url(fonts/KaTeX_Script-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Size1;src:url(fonts/KaTeX_Size1-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size1-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Size2;src:url(fonts/KaTeX_Size2-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size2-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Size3;src:url(fonts/KaTeX_Size3-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size3-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Size4;src:url(fonts/KaTeX_Size4-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size4-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Typewriter;src:url(fonts/KaTeX_Typewriter-Regular.woff2) format("woff2"),url(fonts/KaTeX_Typewriter-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-Regular.woff2) format("woff2"),url(fonts/KaTeX_Main-Regular.woff) format("woff");font-weight:400;font-style:normal;font-display:"swap"}@font-face{font-family:KaTeX_Math;src:url(fonts/KaTeX_Math-Italic.woff2) format("woff2"),url(fonts/KaTeX_Math-Italic.woff) format("woff");font-weight:400;font-style:italic;font-display:"swap"}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.ML__mathit{font-family:KaTeX_Math;font-style:italic}.ML__mathrm{font-family:KaTeX_Main;font-style:normal}.ML__mathbf{font-family:KaTeX_Main;font-weight:700}.ML__mathbfit{font-family:KaTeX_Math;font-weight:700;font-style:italic}.ML__ams,.ML__bb{font-family:KaTeX_AMS}.ML__cal{font-family:KaTeX_Caligraphic}.ML__frak{font-family:KaTeX_Fraktur}.ML__tt{font-family:KaTeX_Typewriter}.ML__script{font-family:KaTeX_Script}.ML__sans{font-family:KaTeX_SansSerif}.ML__mainit{font-family:KaTeX_Main;font-style:italic}.ML__series_el,.ML__series_ul{font-weight:100}.ML__series_l{font-weight:200}.ML__series_sl{font-weight:300}.ML__series_sb{font-weight:500}.ML__bold{font-weight:700}.ML__series_eb{font-weight:800}.ML__series_ub{font-weight:900}.ML__series_uc{font-stretch:ultra-condensed}.ML__series_ec{font-stretch:extra-condensed}.ML__series_c{font-stretch:condensed}.ML__series_sc{font-stretch:semi-condensed}.ML__series_sx{font-stretch:semi-expanded}.ML__series_x{font-stretch:expanded}.ML__series_ex{font-stretch:extra-expanded}.ML__series_ux{font-stretch:ultra-expanded}.ML__it{font-style:italic}.ML__shape_ol{-webkit-text-stroke:1px #000;text-stroke:1px #000;color:transparent}.ML__shape_sc{font-variant:small-caps}.ML__shape_sl{font-style:oblique}.ML__emph{color:#bc2612}.ML__emph .ML__emph{color:#0c7f99}.ML__mathlive .highlight{color:#007cb2;background:#edd1b0}.ML__mathlive .reset-textstyle.scriptstyle{font-size:.7em}.ML__mathlive .reset-textstyle.scriptscriptstyle{font-size:.5em}.ML__mathlive .reset-scriptstyle.textstyle{font-size:1.42857em}.ML__mathlive .reset-scriptstyle.scriptscriptstyle{font-size:.71429em}.ML__mathlive .reset-scriptscriptstyle.textstyle{font-size:2em}.ML__mathlive .reset-scriptscriptstyle.scriptstyle{font-size:1.4em}.ML__mathlive .style-wrap{position:relative}.ML__mathlive .vlist{display:inline-block}.ML__mathlive .vlist>span{display:block;height:0;position:relative;line-height:0}.ML__mathlive .vlist>span>span{display:inline-block}.ML__mathlive .msubsup{text-align:left}.ML__mathlive .mfrac>span{text-align:center}.ML__mathlive .mfrac .frac-line{width:100%}.ML__mathlive .mfrac .frac-line:after{content:"";display:block;margin-top:-.04em;border-bottom-style:solid;border-bottom-width:.04em;min-height:.04em}.ML__mathlive .rspace.negativethinspace{margin-right:-.16667em}.ML__mathlive .rspace.thinspace{margin-right:.16667em}.ML__mathlive .rspace.negativemediumspace{margin-right:-.22222em}.ML__mathlive .rspace.mediumspace{margin-right:.22222em}.ML__mathlive .rspace.thickspace{margin-right:.27778em}.ML__mathlive .rspace.sixmuspace{margin-right:.333333em}.ML__mathlive .rspace.eightmuspace{margin-right:.444444em}.ML__mathlive .rspace.enspace{margin-right:.5em}.ML__mathlive .rspace.twelvemuspace{margin-right:.666667em}.ML__mathlive .rspace.quad{margin-right:1em}.ML__mathlive .rspace.qquad{margin-right:2em}.ML__mathlive .mspace{display:inline-block}.ML__mathlive .mspace.negativethinspace{margin-left:-.16667em}.ML__mathlive .mspace.thinspace{width:.16667em}.ML__mathlive .mspace.negativemediumspace{margin-left:-.22222em}.ML__mathlive .mspace.mediumspace{width:.22222em}.ML__mathlive .mspace.thickspace{width:.27778em}.ML__mathlive .mspace.sixmuspace{width:.333333em}.ML__mathlive .mspace.eightmuspace{width:.444444em}.ML__mathlive .mspace.enspace{width:.5em}.ML__mathlive .mspace.twelvemuspace{width:.666667em}.ML__mathlive .mspace.quad{width:1em}.ML__mathlive .mspace.qquad{width:2em}.ML__mathlive .llap,.ML__mathlive .rlap{width:0;position:relative}.ML__mathlive .llap>.inner,.ML__mathlive .rlap>.inner{position:absolute}.ML__mathlive .llap>.fix,.ML__mathlive .rlap>.fix{display:inline-block}.ML__mathlive .llap>.inner{right:0}.ML__mathlive .rlap>.inner{left:0}.ML__mathlive .rule{display:inline-block;border:0 solid;position:relative}.ML__mathlive .overline .overline-line,.ML__mathlive .underline .underline-line{width:100%}.ML__mathlive .overline .overline-line:before,.ML__mathlive .underline .underline-line:before{border-bottom-style:solid;border-bottom-width:.04em;content:"";display:block}.ML__mathlive .overline .overline-line:after,.ML__mathlive .underline .underline-line:after{border-bottom-style:solid;border-bottom-width:.04em;min-height:thin;content:"";display:block;margin-top:-1px}.ML__mathlive .sqrt{display:inline-block}.ML__mathlive .sqrt>.sqrt-sign{font-family:KaTeX_Main;position:relative}.ML__mathlive .sqrt .sqrt-line{height:.04em;width:100%}.ML__mathlive .sqrt .sqrt-line:before{content:"";display:block;margin-top:-.04em;border-bottom-style:solid;border-bottom-width:.04em;min-height:.5px}.ML__mathlive .sqrt .sqrt-line:after{border-bottom-width:1px;content:" ";display:block;margin-top:-.1em}.ML__mathlive .sqrt>.root{margin-left:.27777778em;margin-right:-.55555556em}.ML__mathlive .fontsize-ensurer,.ML__mathlive .sizing{display:inline-block}.ML__mathlive .fontsize-ensurer.reset-size1.size1,.ML__mathlive .sizing.reset-size1.size1{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size1.size2,.ML__mathlive .sizing.reset-size1.size2{font-size:1.4em}.ML__mathlive .fontsize-ensurer.reset-size1.size3,.ML__mathlive .sizing.reset-size1.size3{font-size:1.6em}.ML__mathlive .fontsize-ensurer.reset-size1.size4,.ML__mathlive .sizing.reset-size1.size4{font-size:1.8em}.ML__mathlive .fontsize-ensurer.reset-size1.size5,.ML__mathlive .sizing.reset-size1.size5{font-size:2em}.ML__mathlive .fontsize-ensurer.reset-size1.size6,.ML__mathlive .sizing.reset-size1.size6{font-size:2.4em}.ML__mathlive .fontsize-ensurer.reset-size1.size7,.ML__mathlive .sizing.reset-size1.size7{font-size:2.88em}.ML__mathlive .fontsize-ensurer.reset-size1.size8,.ML__mathlive .sizing.reset-size1.size8{font-size:3.46em}.ML__mathlive .fontsize-ensurer.reset-size1.size9,.ML__mathlive .sizing.reset-size1.size9{font-size:4.14em}.ML__mathlive .fontsize-ensurer.reset-size1.size10,.ML__mathlive .sizing.reset-size1.size10{font-size:4.98em}.ML__mathlive .fontsize-ensurer.reset-size2.size1,.ML__mathlive .sizing.reset-size2.size1{font-size:.71428571em}.ML__mathlive .fontsize-ensurer.reset-size2.size2,.ML__mathlive .sizing.reset-size2.size2{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size2.size3,.ML__mathlive .sizing.reset-size2.size3{font-size:1.14285714em}.ML__mathlive .fontsize-ensurer.reset-size2.size4,.ML__mathlive .sizing.reset-size2.size4{font-size:1.28571429em}.ML__mathlive .fontsize-ensurer.reset-size2.size5,.ML__mathlive .sizing.reset-size2.size5{font-size:1.42857143em}.ML__mathlive .fontsize-ensurer.reset-size2.size6,.ML__mathlive .sizing.reset-size2.size6{font-size:1.71428571em}.ML__mathlive .fontsize-ensurer.reset-size2.size7,.ML__mathlive .sizing.reset-size2.size7{font-size:2.05714286em}.ML__mathlive .fontsize-ensurer.reset-size2.size8,.ML__mathlive .sizing.reset-size2.size8{font-size:2.47142857em}.ML__mathlive .fontsize-ensurer.reset-size2.size9,.ML__mathlive .sizing.reset-size2.size9{font-size:2.95714286em}.ML__mathlive .fontsize-ensurer.reset-size2.size10,.ML__mathlive .sizing.reset-size2.size10{font-size:3.55714286em}.ML__mathlive .fontsize-ensurer.reset-size3.size1,.ML__mathlive .sizing.reset-size3.size1{font-size:.625em}.ML__mathlive .fontsize-ensurer.reset-size3.size2,.ML__mathlive .sizing.reset-size3.size2{font-size:.875em}.ML__mathlive .fontsize-ensurer.reset-size3.size3,.ML__mathlive .sizing.reset-size3.size3{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size3.size4,.ML__mathlive .sizing.reset-size3.size4{font-size:1.125em}.ML__mathlive .fontsize-ensurer.reset-size3.size5,.ML__mathlive .sizing.reset-size3.size5{font-size:1.25em}.ML__mathlive .fontsize-ensurer.reset-size3.size6,.ML__mathlive .sizing.reset-size3.size6{font-size:1.5em}.ML__mathlive .fontsize-ensurer.reset-size3.size7,.ML__mathlive .sizing.reset-size3.size7{font-size:1.8em}.ML__mathlive .fontsize-ensurer.reset-size3.size8,.ML__mathlive .sizing.reset-size3.size8{font-size:2.1625em}.ML__mathlive .fontsize-ensurer.reset-size3.size9,.ML__mathlive .sizing.reset-size3.size9{font-size:2.5875em}.ML__mathlive .fontsize-ensurer.reset-size3.size10,.ML__mathlive .sizing.reset-size3.size10{font-size:3.1125em}.ML__mathlive .fontsize-ensurer.reset-size4.size1,.ML__mathlive .sizing.reset-size4.size1{font-size:.55555556em}.ML__mathlive .fontsize-ensurer.reset-size4.size2,.ML__mathlive .sizing.reset-size4.size2{font-size:.77777778em}.ML__mathlive .fontsize-ensurer.reset-size4.size3,.ML__mathlive .sizing.reset-size4.size3{font-size:.88888889em}.ML__mathlive .fontsize-ensurer.reset-size4.size4,.ML__mathlive .sizing.reset-size4.size4{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size4.size5,.ML__mathlive .sizing.reset-size4.size5{font-size:1.11111111em}.ML__mathlive .fontsize-ensurer.reset-size4.size6,.ML__mathlive .sizing.reset-size4.size6{font-size:1.33333333em}.ML__mathlive .fontsize-ensurer.reset-size4.size7,.ML__mathlive .sizing.reset-size4.size7{font-size:1.6em}.ML__mathlive .fontsize-ensurer.reset-size4.size8,.ML__mathlive .sizing.reset-size4.size8{font-size:1.92222222em}.ML__mathlive .fontsize-ensurer.reset-size4.size9,.ML__mathlive .sizing.reset-size4.size9{font-size:2.3em}.ML__mathlive .fontsize-ensurer.reset-size4.size10,.ML__mathlive .sizing.reset-size4.size10{font-size:2.76666667em}.ML__mathlive .fontsize-ensurer.reset-size5.size1,.ML__mathlive .sizing.reset-size5.size1{font-size:.5em}.ML__mathlive .fontsize-ensurer.reset-size5.size2,.ML__mathlive .sizing.reset-size5.size2{font-size:.7em}.ML__mathlive .fontsize-ensurer.reset-size5.size3,.ML__mathlive .sizing.reset-size5.size3{font-size:.8em}.ML__mathlive .fontsize-ensurer.reset-size5.size4,.ML__mathlive .sizing.reset-size5.size4{font-size:.9em}.ML__mathlive .fontsize-ensurer.reset-size5.size5,.ML__mathlive .sizing.reset-size5.size5{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size5.size6,.ML__mathlive .sizing.reset-size5.size6{font-size:1.2em}.ML__mathlive .fontsize-ensurer.reset-size5.size7,.ML__mathlive .sizing.reset-size5.size7{font-size:1.44em}.ML__mathlive .fontsize-ensurer.reset-size5.size8,.ML__mathlive .sizing.reset-size5.size8{font-size:1.73em}.ML__mathlive .fontsize-ensurer.reset-size5.size9,.ML__mathlive .sizing.reset-size5.size9{font-size:2.07em}.ML__mathlive .fontsize-ensurer.reset-size5.size10,.ML__mathlive .sizing.reset-size5.size10{font-size:2.49em}.ML__mathlive .fontsize-ensurer.reset-size6.size1,.ML__mathlive .sizing.reset-size6.size1{font-size:.41666667em}.ML__mathlive .fontsize-ensurer.reset-size6.size2,.ML__mathlive .sizing.reset-size6.size2{font-size:.58333333em}.ML__mathlive .fontsize-ensurer.reset-size6.size3,.ML__mathlive .sizing.reset-size6.size3{font-size:.66666667em}.ML__mathlive .fontsize-ensurer.reset-size6.size4,.ML__mathlive .sizing.reset-size6.size4{font-size:.75em}.ML__mathlive .fontsize-ensurer.reset-size6.size5,.ML__mathlive .sizing.reset-size6.size5{font-size:.83333333em}.ML__mathlive .fontsize-ensurer.reset-size6.size6,.ML__mathlive .sizing.reset-size6.size6{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size6.size7,.ML__mathlive .sizing.reset-size6.size7{font-size:1.2em}.ML__mathlive .fontsize-ensurer.reset-size6.size8,.ML__mathlive .sizing.reset-size6.size8{font-size:1.44166667em}.ML__mathlive .fontsize-ensurer.reset-size6.size9,.ML__mathlive .sizing.reset-size6.size9{font-size:1.725em}.ML__mathlive .fontsize-ensurer.reset-size6.size10,.ML__mathlive .sizing.reset-size6.size10{font-size:2.075em}.ML__mathlive .fontsize-ensurer.reset-size7.size1,.ML__mathlive .sizing.reset-size7.size1{font-size:.34722222em}.ML__mathlive .fontsize-ensurer.reset-size7.size2,.ML__mathlive .sizing.reset-size7.size2{font-size:.48611111em}.ML__mathlive .fontsize-ensurer.reset-size7.size3,.ML__mathlive .sizing.reset-size7.size3{font-size:.55555556em}.ML__mathlive .fontsize-ensurer.reset-size7.size4,.ML__mathlive .sizing.reset-size7.size4{font-size:.625em}.ML__mathlive .fontsize-ensurer.reset-size7.size5,.ML__mathlive .sizing.reset-size7.size5{font-size:.69444444em}.ML__mathlive .fontsize-ensurer.reset-size7.size6,.ML__mathlive .sizing.reset-size7.size6{font-size:.83333333em}.ML__mathlive .fontsize-ensurer.reset-size7.size7,.ML__mathlive .sizing.reset-size7.size7{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size7.size8,.ML__mathlive .sizing.reset-size7.size8{font-size:1.20138889em}.ML__mathlive .fontsize-ensurer.reset-size7.size9,.ML__mathlive .sizing.reset-size7.size9{font-size:1.4375em}.ML__mathlive .fontsize-ensurer.reset-size7.size10,.ML__mathlive .sizing.reset-size7.size10{font-size:1.72916667em}.ML__mathlive .fontsize-ensurer.reset-size8.size1,.ML__mathlive .sizing.reset-size8.size1{font-size:.28901734em}.ML__mathlive .fontsize-ensurer.reset-size8.size2,.ML__mathlive .sizing.reset-size8.size2{font-size:.40462428em}.ML__mathlive .fontsize-ensurer.reset-size8.size3,.ML__mathlive .sizing.reset-size8.size3{font-size:.46242775em}.ML__mathlive .fontsize-ensurer.reset-size8.size4,.ML__mathlive .sizing.reset-size8.size4{font-size:.52023121em}.ML__mathlive .fontsize-ensurer.reset-size8.size5,.ML__mathlive .sizing.reset-size8.size5{font-size:.57803468em}.ML__mathlive .fontsize-ensurer.reset-size8.size6,.ML__mathlive .sizing.reset-size8.size6{font-size:.69364162em}.ML__mathlive .fontsize-ensurer.reset-size8.size7,.ML__mathlive .sizing.reset-size8.size7{font-size:.83236994em}.ML__mathlive .fontsize-ensurer.reset-size8.size8,.ML__mathlive .sizing.reset-size8.size8{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size8.size9,.ML__mathlive .sizing.reset-size8.size9{font-size:1.19653179em}.ML__mathlive .fontsize-ensurer.reset-size8.size10,.ML__mathlive .sizing.reset-size8.size10{font-size:1.43930636em}.ML__mathlive .fontsize-ensurer.reset-size9.size1,.ML__mathlive .sizing.reset-size9.size1{font-size:.24154589em}.ML__mathlive .fontsize-ensurer.reset-size9.size2,.ML__mathlive .sizing.reset-size9.size2{font-size:.33816425em}.ML__mathlive .fontsize-ensurer.reset-size9.size3,.ML__mathlive .sizing.reset-size9.size3{font-size:.38647343em}.ML__mathlive .fontsize-ensurer.reset-size9.size4,.ML__mathlive .sizing.reset-size9.size4{font-size:.43478261em}.ML__mathlive .fontsize-ensurer.reset-size9.size5,.ML__mathlive .sizing.reset-size9.size5{font-size:.48309179em}.ML__mathlive .fontsize-ensurer.reset-size9.size6,.ML__mathlive .sizing.reset-size9.size6{font-size:.57971014em}.ML__mathlive .fontsize-ensurer.reset-size9.size7,.ML__mathlive .sizing.reset-size9.size7{font-size:.69565217em}.ML__mathlive .fontsize-ensurer.reset-size9.size8,.ML__mathlive .sizing.reset-size9.size8{font-size:.83574879em}.ML__mathlive .fontsize-ensurer.reset-size9.size9,.ML__mathlive .sizing.reset-size9.size9{font-size:1em}.ML__mathlive .fontsize-ensurer.reset-size9.size10,.ML__mathlive .sizing.reset-size9.size10{font-size:1.20289855em}.ML__mathlive .fontsize-ensurer.reset-size10.size1,.ML__mathlive .sizing.reset-size10.size1{font-size:.20080321em}.ML__mathlive .fontsize-ensurer.reset-size10.size2,.ML__mathlive .sizing.reset-size10.size2{font-size:.2811245em}.ML__mathlive .fontsize-ensurer.reset-size10.size3,.ML__mathlive .sizing.reset-size10.size3{font-size:.32128514em}.ML__mathlive .fontsize-ensurer.reset-size10.size4,.ML__mathlive .sizing.reset-size10.size4{font-size:.36144578em}.ML__mathlive .fontsize-ensurer.reset-size10.size5,.ML__mathlive .sizing.reset-size10.size5{font-size:.40160643em}.ML__mathlive .fontsize-ensurer.reset-size10.size6,.ML__mathlive .sizing.reset-size10.size6{font-size:.48192771em}.ML__mathlive .fontsize-ensurer.reset-size10.size7,.ML__mathlive .sizing.reset-size10.size7{font-size:.57831325em}.ML__mathlive .fontsize-ensurer.reset-size10.size8,.ML__mathlive .sizing.reset-size10.size8{font-size:.69477912em}.ML__mathlive .fontsize-ensurer.reset-size10.size9,.ML__mathlive .sizing.reset-size10.size9{font-size:.8313253em}.ML__mathlive .fontsize-ensurer.reset-size10.size10,.ML__mathlive .sizing.reset-size10.size10{font-size:1em}.ML__mathlive .delimsizing.size1{font-family:KaTeX_Size1}.ML__mathlive .delimsizing.size2{font-family:KaTeX_Size2}.ML__mathlive .delimsizing.size3{font-family:KaTeX_Size3}.ML__mathlive .delimsizing.size4{font-family:KaTeX_Size4}.ML__mathlive .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1;vertical-align:top}.ML__mathlive .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4;vertical-align:top}.ML__mathlive .nulldelimiter{width:.12em}.ML__mathlive .op-symbol{position:relative}.ML__mathlive .op-symbol.small-op{font-family:KaTeX_Size1}.ML__mathlive .op-symbol.large-op{font-family:KaTeX_Size2}.ML__mathlive .op-limits .vlist>span{text-align:center}.ML__mathlive .accent>.vlist>span{text-align:center}.ML__mathlive .accent .accent-body>span{width:0}.ML__mathlive .accent .accent-body.accent-vec>span{position:relative;left:.326em}.ML__mathlive .mtable .vertical-separator{display:inline-block;margin:0 -.025em;border-right:.05em solid #000}.ML__mathlive .mtable .arraycolsep{display:inline-block}.ML__mathlive .mtable .col-align-c>.vlist{text-align:center}.ML__mathlive .mtable .col-align-l>.vlist{text-align:left}.ML__mathlive .mtable .col-align-r>.vlist{text-align:right}.ML__selection{background:var(--highlight-inactive);box-sizing:border-box}.ML__focused .ML__selection{background:var(--highlight)!important;color:var(--on-highlight)}.ML__command{font-family:Source Code Pro,Consolas,Roboto Mono,Menlo,Bitstream Vera Sans Mono,DejaVu Sans Mono,Monaco,Courier,monospace;letter-spacing:-1px;font-weight:400;color:var(--primary)}:not(.ML__command)+.ML__command{margin-left:.25em}.ML__command+:not(.ML__command){padding-left:.25em}.ML__smart-fence__close{opacity:.5}.ML__keystroke-caption{visibility:hidden;background:var(--secondary);border-color:var(--secondary-border);box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);text-align:center;border-radius:6px;padding:16px;position:absolute;z-index:1;display:flex;flex-direction:row;justify-content:center;--keystroke:#fff;--on-keystroke:#555;--keystroke-border:#f7f7f7}@media (prefers-color-scheme:dark){body:not([theme=light]) .ML__keystroke-caption{--keystroke:hsl(var(--hue),50%,30%);--on-keystroke:#fafafa;--keystroke-border:hsl(var(--hue),50%,25%)}}body[theme=dark] .ML__keystroke-caption{--keystroke:hsl(var(--hue),50%,30%);--on-keystroke:#fafafa;--keystroke-border:hsl(var(--hue),50%,25%)}[data-tooltip]{position:relative}[data-tooltip][data-placement=top]:after{top:inherit;bottom:100%}[data-tooltip]:after{position:absolute;visibility:hidden;content:attr(data-tooltip);display:inline-table;top:110%;width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:200px;padding:8px;background:#616161;color:#fff;text-align:center;z-index:2;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);border-radius:2px;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-weight:400;font-size:12px;opacity:0;transform:scale(.5);transition:all .15s cubic-bezier(.4,0,1,1)}@media only screen and (max-width:767px){[data-tooltip]:after{height:32px;padding:4px 16px;font-size:14px}}[data-tooltip]:hover{position:relative}[data-tooltip]:hover:after{visibility:visible;opacity:1;transform:scale(1)}[data-tooltip][data-delay]:after{transition-delay:0s}[data-tooltip][data-delay]:hover:after{transition-delay:1s}.can-redo [data-command='"redo"']{opacity:1!important}.can-undo [data-command='"undo"']{opacity:1!important}.ML__keyboard{--keyboard-background:rgba(209,213,217,0.95);--keyboard-text:#000;--keyboard-text-active:var(--primary);--keyboard-background-border:#ddd;--keycap-background:#fff;--keycap-background-active:#e5e5e5;--keycap-background-border:#e5e6e9;--keycap-background-border-bottom:#8d8f92;--keycap-text:#000;--keycap-text-active:#fff;--keycap-secondary-text:#000;--keycap-modifier-background:#b9bdc7;--keycap-modifier-border:#c5c9d0;--keycap-modifier-border-bottom:#989da6;--keyboard-alternate-background:#fff;--keyboard-alternate-background-active:#e5e5e5;--keyboard-alternate-text:#000;position:fixed;left:0;bottom:-267px;width:100vw;z-index:105;padding-top:5px;transform:translate(0);opacity:0;visibility:hidden;transition:.28s cubic-bezier(0,0,.2,1);transition-property:transform,opacity;-webkit-backdrop-filter:grayscale(50%);backdrop-filter:grayscale(50%);background-color:var(--keyboard-background);border:1px solid var(--keyboard-background-border);font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:16px;font-weight:400;margin:0;text-shadow:none;box-sizing:border-box;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.ML__keyboard.is-visible{transform:translateY(-267px);opacity:1;visibility:visible;transition-timing-function:cubic-bezier(.4,0,1,1)}.ML__keyboard .tex{font-family:KaTeX_Main,Cambria Math,Asana Math,OpenSymbol,Symbola,STIX,Times,serif!important}.ML__keyboard .tex-math{font-family:KaTeX_Math,Cambria Math,Asana Math,OpenSymbol,Symbola,STIX,Times,serif!important}.ML__keyboard .tt{font-family:Source Code Pro,Consolas,Roboto Mono,Menlo,Bitstream Vera Sans Mono,DejaVu Sans Mono,Monaco,Courier,monospace!important;font-size:30px;font-weight:400}.ML__keyboard.alternate-keys{visibility:hidden;max-width:286px;background-color:var(--keyboard-alternate-background);text-align:center;border-radius:6px;position:fixed;bottom:auto;top:0;box-sizing:content-box;transform:none;z-index:106;display:flex;flex-direction:row;justify-content:center;align-content:center;box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22);transition:none}@media only screen and (max-height:412px){.ML__keyboard.alternate-keys{max-width:320px}}.ML__keyboard.alternate-keys.is-visible{visibility:visible}.ML__keyboard.alternate-keys ul{list-style:none;margin:3px;padding:0;display:flex;flex-flow:row wrap-reverse;justify-content:center}.ML__keyboard.alternate-keys ul>li{display:flex;flex-flow:column;align-items:center;justify-content:center;font-size:30px;height:70px;width:70px;box-sizing:border-box;margin:0;background:transparent;border:1px solid transparent;border-radius:5px;pointer-events:all;color:var(--keyboard-alternate-text);fill:currentColor}@media only screen and (max-height:412px){.ML__keyboard.alternate-keys ul>li{font-size:24px;height:50px;width:50px}}.ML__keyboard.alternate-keys ul>li.active,.ML__keyboard.alternate-keys ul>li.pressed,.ML__keyboard.alternate-keys ul>li:hover{box-shadow:0 10px 20px rgba(0,0,0,.19),0 6px 6px rgba(0,0,0,.23);background:var(--keyboard-alternate-background-active);color:var(--keyboard-text-active)}.ML__keyboard.alternate-keys ul>li.small{font-size:18px}.ML__keyboard.alternate-keys ul>li.small-button{width:42px;height:42px;margin:2px;background:#fbfbfb}.ML__keyboard.alternate-keys ul>li.small-button:hover{background:var(--keyboard-alternate-background-active)}.ML__keyboard.alternate-keys ul>li.box>div,.ML__keyboard.alternate-keys ul>li.box>span{border:1px dashed rgba(0,0,0,.24)}.ML__keyboard.alternate-keys ul>li .warning{min-height:60px;min-width:60px;background:#cd0030;color:#fff;padding:5px;display:flex;align-items:center;justify-content:center;border-radius:5px}.ML__keyboard.alternate-keys ul>li .warning.active,.ML__keyboard.alternate-keys ul>li .warning.pressed,.ML__keyboard.alternate-keys ul>li .warning:hover{background:red}.ML__keyboard.alternate-keys ul>li .warning svg{width:50px;height:50px}.ML__keyboard.alternate-keys ul>li aside{font-size:12px;line-height:12px;opacity:.78}.ML__keyboard>div.keyboard-layer{display:none;outline:none}.ML__keyboard>div.keyboard-layer.is-visible{display:flex;flex-flow:column}.ML__keyboard>div>div.keyboard-toolbar{align-self:center;display:flex;flex-flow:row;justify-content:space-between;width:736px}@media only screen and (min-width:768px) and (max-width:1024px){.ML__keyboard>div>div.keyboard-toolbar{width:556px}}@media only screen and (max-width:767px){.ML__keyboard>div>div.keyboard-toolbar{width:365px;max-width:100vw}}.ML__keyboard>div>div.keyboard-toolbar svg{height:20px;width:20px}@media only screen and (max-width:767px){.ML__keyboard>div>div.keyboard-toolbar svg{height:13px;width:17px}}.ML__keyboard>div>div.keyboard-toolbar>.left{justify-content:flex-start;display:flex;flex-flow:row}.ML__keyboard>div>div.keyboard-toolbar>.right{justify-content:flex-end;display:flex;flex-flow:row}.ML__keyboard>div>div.keyboard-toolbar>div>div{display:flex;align-items:baseline;justify-content:center;pointer-events:all;color:var(--keyboard-text);fill:currentColor;background:0;font-size:110%;cursor:pointer;min-height:0;padding:4px 10px;margin:7px 4px 6px;box-shadow:none;border:none;border-bottom:2px solid transparent}.ML__keyboard>div>div.keyboard-toolbar>div>div.disabled.pressed svg,.ML__keyboard>div>div.keyboard-toolbar>div>div.disabled:hover svg,.ML__keyboard>div>div.keyboard-toolbar>div>div.disabled svg{color:var(--keyboard-text);opacity:.2}@media only screen and (max-width:414px){.ML__keyboard>div>div.keyboard-toolbar>div>div{font-size:100%;padding:0 6px 0 0}}@media only screen and (max-width:767px){.ML__keyboard>div>div.keyboard-toolbar>div>div{padding-left:4px;padding-right:4px;font-size:90%}}.ML__keyboard>div>div.keyboard-toolbar>div>div.active,.ML__keyboard>div>div.keyboard-toolbar>div>div.pressed,.ML__keyboard>div>div.keyboard-toolbar>div>div:active,.ML__keyboard>div>div.keyboard-toolbar>div>div:hover{color:var(--keyboard-text-active)}.ML__keyboard>div>div.keyboard-toolbar>div>div.selected{color:var(--keyboard-text-active);border-bottom:2px solid var(--keyboard-text-active);margin-bottom:8px;padding-bottom:0}.ML__keyboard div .rows{border:0;border-collapse:separate;clear:both;margin:auto;display:flex;flex-flow:column;align-items:center}.ML__keyboard div .rows>ul{list-style:none;height:40px;margin:0 0 3px;padding:0}.ML__keyboard div .rows>ul>li{display:flex;flex-flow:column;align-items:center;justify-content:center;width:34px;margin-right:2px;height:40px;box-sizing:border-box;padding:8px 0;vertical-align:top;text-align:center;float:left;color:var(--keycap-text);fill:currentColor;font-size:20px;background:var(--keycap-background);border:1px solid var(--keycap-background-border);border-bottom-color:var(--keycap-background-border-bottom);border-radius:5px;pointer-events:all;position:relative;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.ML__keyboard div .rows>ul>li:last-child{margin-right:0}.ML__keyboard div .rows>ul>li.small{font-size:16px}.ML__keyboard div .rows>ul>li.tt{color:var(--keyboard-text-active)}.ML__keyboard div .rows>ul>li.bottom{justify-content:flex-end}.ML__keyboard div .rows>ul>li.left{align-items:flex-start;padding-left:4px}.ML__keyboard div .rows>ul>li.right{align-items:flex-end;padding-right:4px}.ML__keyboard div .rows>ul>li svg{width:20px;height:20px}.ML__keyboard div .rows>ul>li .warning{height:25px;width:25px;min-height:25px;min-width:25px;background:#cd0030;color:#fff;border-radius:100%;padding:5px;display:flex;align-items:center;justify-content:center;margin-bottom:-2px}.ML__keyboard div .rows>ul>li .warning svg{width:16px;height:16px}@media only screen and (max-width:768px){.ML__keyboard div .rows>ul>li .warning{height:16px;width:16px;min-height:16px;min-width:16px}.ML__keyboard div .rows>ul>li .warning svg{width:14px;height:14px}}.ML__keyboard div .rows>ul>li>.w0{width:0}.ML__keyboard div .rows>ul>li>.w5{width:16px}.ML__keyboard div .rows>ul>li>.w15{width:52px}.ML__keyboard div .rows>ul>li>.w20{width:70px}.ML__keyboard div .rows>ul>li>.w50{width:178px}.ML__keyboard div .rows>ul>li.separator{background:transparent;border:none;pointer-events:none}@media only screen and (max-width:560px){.ML__keyboard div .rows>ul>li.if-wide{display:none}}.ML__keyboard div .rows>ul>li.tex-math{font-size:25px}.ML__keyboard div .rows>ul>li.pressed,.ML__keyboard div .rows>ul>li:hover{background:var(--keycap-background-active);color:var(--keyboard-text-active)}.ML__keyboard div .rows>ul>li.action.active,.ML__keyboard div .rows>ul>li.action:active,.ML__keyboard div .rows>ul>li.keycap.active,.ML__keyboard div .rows>ul>li.keycap:active{transform:translateY(-20px) scale(1.4);z-index:100;color:var(--keyboard-text-active)}.ML__keyboard div .rows>ul>li.modifier.active,.ML__keyboard div .rows>ul>li.modifier:active{background:var(--keyboard-text-active);color:var(--keycap-text-active)}.ML__keyboard div .rows>ul>li.action.font-glyph,.ML__keyboard div .rows>ul>li.modifier.font-glyph{font-size:18px}@media only screen and (max-width:767px){.ML__keyboard div .rows>ul>li.action.font-glyph,.ML__keyboard div .rows>ul>li.modifier.font-glyph{font-size:16px}}@media only screen and (max-width:767px){.ML__keyboard div .rows>ul>li.bigfnbutton,.ML__keyboard div .rows>ul>li.fnbutton{font-size:12px}}.ML__keyboard div .rows>ul>li.bigfnbutton{font-size:14px}@media only screen and (max-width:767px){.ML__keyboard div .rows>ul>li.bigfnbutton{font-size:9px}}.ML__keyboard div .rows>ul>li.action,.ML__keyboard div .rows>ul>li.modifier{background-color:var(--keycap-modifier-background);border-bottom-color:var(--keycap-modifier-border);border-color:var(--keycap-modifier-border) var(--keycap-modifier-border) var(--keycap-modifier-border-bottom);font-size:65%;font-weight:100}.ML__keyboard div .rows>ul>li.action.selected,.ML__keyboard div .rows>ul>li.modifier.selected{color:var(--keyboard-text-active)}.ML__keyboard div .rows>ul>li.action.selected.active,.ML__keyboard div .rows>ul>li.action.selected.pressed,.ML__keyboard div .rows>ul>li.action.selected:active,.ML__keyboard div .rows>ul>li.action.selected:hover,.ML__keyboard div .rows>ul>li.modifier.selected.active,.ML__keyboard div .rows>ul>li.modifier.selected.pressed,.ML__keyboard div .rows>ul>li.modifier.selected:active,.ML__keyboard div .rows>ul>li.modifier.selected:hover{color:#fff}.ML__keyboard div .rows>ul>li.keycap.w50{font-size:80%;padding-top:10px;font-weight:100}.ML__keyboard div .rows>ul>li small{color:#555}@media only screen and (max-width:767px){.ML__keyboard div .rows>ul>li small{font-size:9px}}.ML__keyboard div .rows>ul>li aside{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:10px;line-height:10px;color:#666}@media only screen and (max-width:767px){.ML__keyboard div .rows>ul>li aside{display:none}}@media only screen and (max-width:414px){.ML__keyboard div .rows>ul>li{width:29px;margin-right:2px}.ML__keyboard div .rows>ul>.w5{width:13.5px}.ML__keyboard div .rows>ul>.w15{width:44.5px}.ML__keyboard div .rows>ul>.w20{width:60px}.ML__keyboard div .rows>ul>.w50{width:153px}}@media only screen and (min-width:415px) and (max-width:768px){.ML__keyboard div .rows>ul>li{width:37px;margin-right:3px}.ML__keyboard div .rows>ul>.w5{width:17px}.ML__keyboard div .rows>ul>.w15{width:57px}.ML__keyboard div .rows>ul>.w20{width:77px}.ML__keyboard div .rows>ul>.w50{width:197px}}@media only screen and (min-width:768px) and (max-width:1024px){.ML__keyboard div .rows>ul{height:52px}.ML__keyboard div .rows>ul>li{height:52px;width:51px;margin-right:4px}.ML__keyboard div .rows>ul>.w5{width:23.5px}.ML__keyboard div .rows>ul>.w15{width:78.5px}.ML__keyboard div .rows>ul>.w20{width:106px}.ML__keyboard div .rows>ul>.w50{width:271px}}@media only screen and (min-width:1025px){.ML__keyboard div .rows>ul{height:52px}.ML__keyboard div .rows>ul>li{height:52px;width:66px;margin-right:6px}.ML__keyboard div .rows>ul>.action,.ML__keyboard div .rows>ul>.modifier{font-size:80%}.ML__keyboard div .rows>ul>.w5{width:30px}.ML__keyboard div .rows>ul>.w15{width:102px}.ML__keyboard div .rows>ul>.w20{width:138px}.ML__keyboard div .rows>ul>.w50{width:354px}}@media (prefers-color-scheme:dark){body:not([theme=light]) .ML__keyboard{--hue:206;--keyboard-background:hsl(var(--hue),19%,38%);--keyboard-text:#f0f0f0;--keyboard-text-active:hsl(var(--hue),100%,60%);--keyboard-background-border:#333;--keycap-background:hsl(var(--hue),25%,39%);--keycap-background-active:hsl(var(--hue),35%,42%);--keycap-background-border:hsl(var(--hue),25%,35%);--keycap-background-border-bottom:#426b8a;--keycap-text:#d0d0d0;--keycap-text-active:#000;--keycap-secondary-text:#fff;--keycap-modifier-background:hsl(var(--hue),35%,40%);--keycap-modifier-border:hsl(var(--hue),35%,35%);--keycap-modifier-border-bottom:hsl(var(--hue),35%,42%);--keyboard-alternate-background:hsl(var(--hue),19%,38%);--keyboard-alternate-background-active:hsl(var(--hue),35%,42%);--keyboard-alternate-text:#d1d1d1}}body[theme=dark] .ML__keyboard{--hue:206;--keyboard-background:hsl(var(--hue),19%,38%);--keyboard-text:#f0f0f0;--keyboard-text-active:hsl(var(--hue),100%,60%);--keyboard-background-border:#333;--keycap-background:hsl(var(--hue),25%,39%);--keycap-background-active:hsl(var(--hue),35%,42%);--keycap-background-border:hsl(var(--hue),25%,35%);--keycap-background-border-bottom:#426b8a;--keycap-text:#d0d0d0;--keycap-text-active:#000;--keycap-secondary-text:#fff;--keycap-modifier-background:hsl(var(--hue),35%,40%);--keycap-modifier-border:hsl(var(--hue),35%,35%);--keycap-modifier-border-bottom:hsl(var(--hue),35%,42%);--keyboard-alternate-background:hsl(var(--hue),19%,38%);--keyboard-alternate-background-active:hsl(var(--hue),35%,42%);--keyboard-alternate-text:#d1d1d1}div.ML__keyboard.material{--keyboard-background:rgba(209,213,217,0.9);--keyboard-background-border:#ddd;--keycap-background:transparent;--keycap-background-active:#cccfd1;--keycap-background-border:transparent;--keyboard-alternate-background:#efefef;--keyboard-alternate-text:#000;font-family:Roboto,sans-serif}div.ML__keyboard.material.alternate-keys{background:var(--keyboard-alternate-background);border:1px solid transparent;border-radius:5px;box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22)}div.ML__keyboard.material.alternate-keys ul li.active,div.ML__keyboard.material.alternate-keys ul li.pressed,div.ML__keyboard.material.alternate-keys ul li:active,div.ML__keyboard.material.alternate-keys ul li:hover{border:1px solid transparent;background:#5f97fc;color:#fff;fill:currentColor}div.ML__keyboard.material .keyboard-toolbar>div>div{font-size:16px}div.ML__keyboard.material .keyboard-toolbar div.div.active,div.ML__keyboard.material .keyboard-toolbar div.div.pressed,div.ML__keyboard.material .keyboard-toolbar div div:active,div.ML__keyboard.material .keyboard-toolbar div div:hover{color:#5f97fc;fill:currentColor}div.ML__keyboard.material .keyboard-toolbar>div>.selected{color:#5f97fc;fill:currentColor;border-bottom:2px solid #5f97fc;margin-bottom:8px;padding-bottom:0}div.ML__keyboard.material div>.rows>ul>.keycap{background:transparent;border:1px solid transparent;border-radius:5px;color:var(--keycap-text);fill:currentColor;transition:none}div.ML__keyboard.material div>.rows>ul>.keycap.tt{color:#5f97fc}div.ML__keyboard.material div>.rows>ul>.keycap[data-key=" "]{margin-top:10px;margin-bottom:10px;height:20px;background:#e0e0e0}div.ML__keyboard.material div>.rows>ul>.keycap[data-key=" "].active,div.ML__keyboard.material div>.rows>ul>.keycap[data-key=" "].pressed,div.ML__keyboard.material div>.rows>ul>.keycap[data-key=" "]:active,div.ML__keyboard.material div>.rows>ul>.keycap[data-key=" "]:hover{background:#d0d0d0;box-shadow:none;transform:none}div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]):hover{border:1px solid transparent;background:var(--keycap-background-active);box-shadow:none}div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]).active,div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]).pressed,div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]):active{background:var(--keyboard-alternate-background);color:var(--keyboard-alternate-text);box-shadow:0 10px 20px rgba(0,0,0,.19),0 6px 6px rgba(0,0,0,.23)}@media only screen and (max-width:767px){div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]).active,div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]).pressed,div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]):active{box-shadow:0 10px 20px rgba(0,0,0,.19),0 6px 6px rgba(0,0,0,.23);font-size:10px;vertical-align:top;width:19.5px;margin-right:10px;margin-left:10px;transform:translateY(-20px) scale(2);transition:none;justify-content:flex-start;padding:2px 0 0;z-index:100}}@media only screen and (max-width:414px){div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]).active,div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]).pressed,div.ML__keyboard.material div>.rows>ul>.keycap:not([data-key=" "]):active{width:16.5px}}@media only screen and (max-width:767px){div.ML__keyboard.material div>.rows>ul>.keycap:last-child.active,div.ML__keyboard.material div>.rows>ul>.keycap:last-child:active{margin-right:0;margin-left:14px}}div.ML__keyboard.material div div.rows ul li.action,div.ML__keyboard.material div div.rows ul li.modifier{background:transparent;border:0;color:#869096;fill:currentColor;font-size:16px;transition:none}div.ML__keyboard.material div div.rows ul li.action.selected,div.ML__keyboard.material div div.rows ul li.modifier.selected{color:#5f97fc;border-radius:0;border-bottom:2px solid #5f97fc}div.ML__keyboard.material div div.rows ul li.action.active,div.ML__keyboard.material div div.rows ul li.action.pressed,div.ML__keyboard.material div div.rows ul li.action:active,div.ML__keyboard.material div div.rows ul li.action:hover,div.ML__keyboard.material div div.rows ul li.modifier.active,div.ML__keyboard.material div div.rows ul li.modifier.pressed,div.ML__keyboard.material div div.rows ul li.modifier:active,div.ML__keyboard.material div div.rows ul li.modifier:hover{border:0;color:var(--keycap-text);background:var(--keycap-background-active);box-shadow:none}div.ML__keyboard.material div div.rows ul li.bigfnbutton,div.ML__keyboard.material div div.rows ul li.fnbutton{background:transparent;border:0}div.ML__keyboard.material div div.rows ul li.bigfnbutton.selected,div.ML__keyboard.material div div.rows ul li.fnbutton.selected{color:#5f97fc;fill:currentColor;border-radius:0;border-bottom:2px solid #5f97fc}div.ML__keyboard.material div div.rows ul li.bigfnbutton.active,div.ML__keyboard.material div div.rows ul li.bigfnbutton.pressed,div.ML__keyboard.material div div.rows ul li.bigfnbutton:active,div.ML__keyboard.material div div.rows ul li.bigfnbutton:hover,div.ML__keyboard.material div div.rows ul li.fnbutton.active,div.ML__keyboard.material div div.rows ul li.fnbutton.pressed,div.ML__keyboard.material div div.rows ul li.fnbutton:active,div.ML__keyboard.material div div.rows ul li.fnbutton:hover{border:0;color:#5f97fc;fill:currentColor;background:var(--keycap-background-active);box-shadow:none}@media (prefers-color-scheme:dark){body:not([theme=light]) div.ML__keyboard.material{--hue:198;--keyboard-background:hsl(var(--hue),19%,18%);--keyboard-text:#d4d6d7;--keyboard-text-active:#5f97fc;--keyboard-background-border:#333;--keycap-background:hsl(var(--hue),25%,39%);--keycap-background-active:#5f97fc;--keycap-background-border:transparent;--keycap-background-border-bottom:transparent;--keycap-text:#d0d0d0;--keycap-text-active:#d4d6d7;--keycap-secondary-text:#5f97fc;--keycap-modifier-background:hsl(var(--hue),35%,40%);--keycap-modifier-border:hsl(var(--hue),35%,35%);--keycap-modifier-border-bottom:hsl(var(--hue),35%,42%);--keyboard-alternate-background:hsl(var(--hue),8%,2%);--keyboard-alternate-background-active:hsl(var(--hue),35%,42%);--keyboard-alternate-text:#d1d1d1}}body[theme=dark] div.ML__keyboard.material{--hue:198;--keyboard-background:hsl(var(--hue),19%,18%);--keyboard-text:#d4d6d7;--keyboard-text-active:#5f97fc;--keyboard-background-border:#333;--keycap-background:hsl(var(--hue),25%,39%);--keycap-background-active:#5f97fc;--keycap-background-border:transparent;--keycap-background-border-bottom:transparent;--keycap-text:#d0d0d0;--keycap-text-active:#d4d6d7;--keycap-secondary-text:#5f97fc;--keycap-modifier-background:hsl(var(--hue),35%,40%);--keycap-modifier-border:hsl(var(--hue),35%,35%);--keycap-modifier-border-bottom:hsl(var(--hue),35%,42%);--keyboard-alternate-background:hsl(var(--hue),8%,2%);--keyboard-alternate-background-active:hsl(var(--hue),35%,42%);--keyboard-alternate-text:#d1d1d1}.ML__error{background-image:radial-gradient(ellipse at center,#cc0041,transparent 70%);background-repeat:repeat-x;background-size:3px 3px;background-position:0 98%}.ML__suggestion{opacity:.5}.ML__placeholder{opacity:.7;padding-left:.5ex;padding-right:.5ex}.ML__keystroke-caption>span{min-width:14px;margin:0 8px 0 0;padding:4px;background-color:var(--keystroke);color:var(--on-keystroke);fill:currentColor;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:1em;border-radius:6px;border:2px solid var(--keystroke-border)}.ML__virtual-keyboard-toggle.pressed{background:hsla(0,0%,70%,.5)}.ML__virtual-keyboard-toggle:focus{outline:none;border-radius:50%;border:2px solid var(--primary)}.ML__virtual-keyboard-toggle.active,.ML__virtual-keyboard-toggle.active:hover{background:hsla(0,0%,70%,.5);color:#000;fill:currentColor}div.ML__popover.is-visible{visibility:visible;-webkit-animation:ML__fade-in .15s cubic-bezier(0,0,.2,1);animation:ML__fade-in .15s cubic-bezier(0,0,.2,1)}@-webkit-keyframes ML__fade-in{0%{opacity:0}to{opacity:1}}@keyframes ML__fade-in{0%{opacity:0}to{opacity:1}}.ML__popover__content{border-radius:6px;padding:2px;cursor:pointer;min-height:100px;display:flex;flex-direction:column;justify-content:center;margin-left:8px;margin-right:8px}.ML__popover__content a{color:#5ea6fd;padding-top:.3em;margin-top:.4em;display:block}.ML__popover__content a:hover{color:#5ea6fd;text-decoration:underline}.ML__popover__content.active,.ML__popover__content.pressed,.ML__popover__content:hover{background:hsla(0,0%,100%,.1)}.ML__popover__command{font-size:1.6rem}.ML__popover__prev-shortcut{height:31px;opacity:.1;cursor:pointer;margin-left:8px;margin-right:8px;padding-top:4px;padding-bottom:2px}.ML__popover__next-shortcut:hover,.ML__popover__prev-shortcut:hover{opacity:.3}.ML__popover__next-shortcut.active,.ML__popover__next-shortcut.pressed,.ML__popover__prev-shortcut.active,.ML__popover__prev-shortcut.pressed{opacity:1}.ML__popover__next-shortcut>span,.ML__popover__prev-shortcut>span{padding:5px;border-radius:50%;width:20px;height:20px;display:inline-block}.ML__popover__prev-shortcut>span>span{margin-top:-2px;display:block}.ML__popover__next-shortcut>span>span{margin-top:2px;display:block}.ML__popover__next-shortcut:hover>span,.ML__popover__prev-shortcut:hover>span{background:hsla(0,0%,100%,.1)}.ML__popover__next-shortcut{height:34px;opacity:.1;cursor:pointer;margin-left:8px;margin-right:8px;padding-top:2px;padding-bottom:4px}.ML__popover__shortcut{font-size:.8em;margin-top:.25em}.ML__popover__note,.ML__popover__shortcut{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;opacity:.7;padding-top:.25em}.ML__popover__note{font-size:.8rem;line-height:1em;padding-left:.5em;padding-right:.5em}.ML__shortcut-join{opacity:.5}.ML__scroller{position:fixed;z-index:1;top:0;height:100vh;width:200px} \ No newline at end of file diff --git a/dist/mathlive.d.ts b/dist/mathlive.d.ts new file mode 100644 index 000000000..5a4ec7e61 --- /dev/null +++ b/dist/mathlive.d.ts @@ -0,0 +1,760 @@ +/** + * @typedef {function} MathFieldCallback + * @param {MathField} mf + * @return {void} + * @global + */ +declare type MathFieldCallback = (mf: MathField) => void; + +/** + @typedef MathFieldConfig + @type {Object} + @property {string} locale? + @property {object} strings? + @property {number} horizontalSpacingScale? + @property {string} namespace? + @property {function} substituteTextArea? + @property {"math" | "text"} defaultMode? + @property {MathFieldCallback} onFocus? + @property {MathFieldCallback} onBlur? + @property {function} onKeystroke? + @property {function} onAnnounce? + @property {boolean} overrideDefaultInlineShortcuts? + @property {object} inlineShortcuts? + @property {number} inlineShortcutTimeout? + @property {boolean} smartFence? + @property {boolean} smartSuperscript? + @property {number} scriptDepth? + @property {boolean} removeExtraneousParentheses? + @property {boolean} ignoreSpacebarInMathMode? + @property {string} virtualKeyboardToggleGlyph? + @property {"manual" | "onfocus" | "off" } virtualKeyboardMode? + @property {"all" | "numeric" | "roman" | "greek" | "functions" | "command" | string} virtualKeyboards? + @property {"qwerty" | "azerty" | "qwertz" | "dvorak" | "colemak"} virtualKeyboardRomanLayout? + @property {object} customVirtualKeyboardLayers? + @property {object} customVirtualKeyboards? + @property {"material" | "apple" | ""} virtualKeyboardTheme? + @property {boolean} keypressVibration? + @property {string} keypressSound? + @property {string} plonkSound? + @property {"mathlive" | "sre"} textToSpeechRules? + @property {"ssml" | "mac"} textToSpeechMarkup? + @property {object} textToSpeechRulesOptions? + @property {"local" | "amazon"} speechEngine? + @property {string} speechEngineVoice? + @property {string} speechEngineRate? + @property {function} onMoveOutOf? + @property {function} onTabOutOf? + @property {MathFieldCallback} onContentWillChange? + @property {MathFieldCallback} onContentDidChange? + @property {MathFieldCallback} onSelectionWillChange? + @property {MathFieldCallback} onSelectionDidChange? + @property {function} onUndoStateWillChange? + @property {function} onUndoStateDidChange? + @property {function} onModeChange? + @property {function} onVirtualKeyboardToggle? + @property {function} onReadAloudStatus? + @property {function} handleSpeak? + @property {function} handleReadAloud? + @global + */ +declare type MathFieldConfig = { + locale?: string; + strings?: { + [key: string]: string; + }; + horizontalSpacingScale?: number; + namespace?: string; + substituteTextArea?: (...params: any[]) => any; + defaultMode?: "math" | "text"; + onFocus?: MathFieldCallback; + onBlur?: MathFieldCallback; + onKeystroke?: (...params: any[]) => any; + onAnnounce?: (...params: any[]) => any; + overrideDefaultInlineShortcuts?: boolean; + inlineShortcuts?: { + [key: string]: string; + }; + inlineShortcutTimeout?: number; + smartFence?: boolean; + smartSuperscript?: boolean; + scriptDepth?: number; + removeExtraneousParentheses?: boolean; + ignoreSpacebarInMathMode?: boolean; + virtualKeyboardToggleGlyph?: string; + virtualKeyboardMode?: "manual" | "onfocus" | "off"; + virtualKeyboards?: "all" | "numeric" | "roman" | "greek" | "functions" | "command" | string; + virtualKeyboardRomanLayout?: "qwerty" | "azerty" | "qwertz" | "dvorak" | "colemak"; + customVirtualKeyboardLayers?: { + [key: string]: string; + }; + customVirtualKeyboards?: { + [key: string]: object; + }; + virtualKeyboardTheme?: "material" | "apple" | ""; + keypressVibration?: boolean; + keypressSound?: string; + plonkSound?: string; + textToSpeechRules?: "mathlive" | "sre"; + textToSpeechMarkup?: "ssml" | "mac"; + textToSpeechRulesOptions?: any; + speechEngine?: "local" | "amazon"; + speechEngineVoice?: string; + speechEngineRate?: string; + onMoveOutOf?: (...params: any[]) => any; + onTabOutOf?: (...params: any[]) => any; + onContentWillChange?: MathFieldCallback; + onContentDidChange?: MathFieldCallback; + onSelectionWillChange?: MathFieldCallback; + onSelectionDidChange?: MathFieldCallback; + onUndoStateWillChange?: (...params: any[]) => any; + onUndoStateDidChange?: (...params: any[]) => any; + onModeChange?: (...params: any[]) => any; + onVirtualKeyboardToggle?: (...params: any[]) => any; + onReadAloudStatus?: (...params: any[]) => any; + handleSpeak?: (...params: any[]) => any; + handleReadAloud?: (...params: any[]) => any; +}; + +/** + * + * @property {HTMLElement} element - The DOM element this mathfield is attached to. + * @property {Object.} config - A set of key/value pairs that can + * be used to customize the behavior of the mathfield + * @property {string} id - A unique ID identifying this mathfield + * @property {boolean} keystrokeCaptionVisible - True if the keystroke caption + * panel is visible + * @property {boolean} virtualKeyboardVisible - True if the virtual keyboard is + * visible + * @property {string} keystrokeBuffer The last few keystrokes, to look out + * for inline shortcuts + * @property {object[]} keystrokeBufferStates The saved state for each of the + * past keystrokes + * @class MathField + * @global + */ +declare class MathField { + /** + * Revert this math field to its original content. After this method has been + * called, no other methods can be called on the MathField object. To turn the + * element back into a MathField, call `MathLive.makeMathField()` on the + * element again to get a new math field object. + * + * @method MathField#$revertToOriginalContent + */ + $revertToOriginalContent(): void; + /** + * @param {string|string[]} command - A selector, or an array whose first element + * is a selector, and whose subsequent elements are arguments to the selector. + * Note that selectors do not include a final "_". They can be passed either + * in camelCase or kebab-case. So: + * ```javascript + * mf.$perform('selectAll'); + * mf.$perform('select-all'); + * ``` + * both calls are valid and invoke the same selector. + * + * @method MathField#$perform + */ + $perform(command: string | string[]): void; + /** + * Return a textual representation of the mathfield. + * @param {string} [format='latex']. One of + * * `'latex'` + * * `'latex-expanded'` : all macros are recursively expanded to their definition + * * `'spoken'` + * * `'spoken-text'` + * * `'spoken-ssml'` + * * `spoken-ssml-withHighlighting` + * * `'mathML'` + * * `'json'` + * @return {string} + * @method MathField#$text + */ + $text(format?: string): string; + /** + * Return a textual representation of the selection in the mathfield. + * @param {string} [format='latex']. One of + * * `'latex'` + * * `'latex-expanded'` : all macros are recursively expanded to their definition + * * `'spoken'` + * * `'spoken-text'` + * * `'spoken-ssml'` + * * `spoken-ssml-withHighlighting` + * * `'mathML'` + * * `'json'` + * @return {string} + * @method MathField#$selectedText + */ + $selectedText(format?: string): string; + /** + * Return true if the length of the selection is 0, that is, if it is a single + * insertion point. + * @return {boolean} + * @method MathField#$selectionIsCollapsed + */ + $selectionIsCollapsed(): boolean; + /** + * Return the depth of the selection group. If the selection is at the root level, + * returns 0. If the selection is a portion of the numerator of a fraction + * which is at the root level, return 1. Note that in that case, the numerator + * would be the "selection group". + * @return {number} + * @method MathField#$selectionDepth + */ + $selectionDepth(): number; + /** + * Return true if the selection starts at the beginning of the selection group. + * @return {boolean} + * @method MathField#$selectionAtStart + */ + $selectionAtStart(): boolean; + /** + * Return true if the selection extends to the end of the selection group. + * @return {boolean} + * @method MathField#$selectionAtEnd + */ + $selectionAtEnd(): boolean; + /** + * If `text` is not empty, sets the content of the mathfield to the + * text interpreted as a LaTeX expression. + * If `text` is empty (or omitted), return the content of the mahtfield as a + * LaTeX expression. + * @param {string} text + * + * @param {Object.} options + * @param {boolean} options.suppressChangeNotifications - If true, the + * handlers for the contentWillChange and contentDidChange notifications will + * not be invoked. Default `false`. + * + * @return {string} + * @method MathField#$latex + */ + $latex(text: string, options: { + suppressChangeNotifications: boolean; + }): string; + /** + * Return the DOM element associated with this mathfield. + * + * Note that `this.$el().mathfield = this` + * @return {HTMLElement} + * @method MathField#$el + */ + $el(): HTMLElement; + /** + * This method can be invoked as a selector with {@linkcode MathField#$perform $perform("insert")} + * or called explicitly. + * + * It will insert the specified block of text at the current insertion point, + * according to the insertion mode specified. + * + * After the insertion, the selection will be set according to the `selectionMode`. + * @param {string} s - The text to be inserted + * + * @param {Object.} [options={}] + * + * @param {'placeholder' | 'after' | 'before' | 'item'} options.selectionMode - Describes where the selection + * will be after the insertion: + * * `'placeholder'`: the selection will be the first available placeholder + * in the item that has been inserted (default) + * * `'after'`: the selection will be an insertion point after the item that + * has been inserted, + * * `'before'`: the selection will be an insertion point before + * the item that has been inserted + * * `'item'`: the item that was inserted will be selected + * + * @param {'auto' | 'latex'} options.format - The format of the string `s`: + * * `'auto'`: the string is interpreted as a latex fragment or command) + * (default) + * * `'latex'`: the string is interpreted strictly as a latex fragment + * + * @param {boolean} options.focus - If true, the mathfield will be focused after + * the insertion + * + * @param {boolean} options.feedback - If true, provide audio and haptic feedback + * + * @param {'text' | 'math' | ''} options.mode - 'text' or 'math'. If empty, the current mode + * is used (default) + * + * @param {boolean} options.resetStyle - If true, the style after the insertion + * is the same as the style before (if false, the style after the + * insertion is the style of the last inserted atom). + * + * @method MathField#$insert + */ + $insert(s: string, options?: { + selectionMode: 'placeholder' | 'after' | 'before' | 'item'; + format: 'auto' | 'latex'; + focus: boolean; + feedback: boolean; + mode: 'text' | 'math' | ''; + resetStyle: boolean; + }): void; + /** + * Apply a style (color, bold, italic, etc...). + * + * If there is a selection, the style is applied to the selection + * + * If the selection already has this style, remove it. If the selection + * has the style partially applied (i.e. only some sections), remove it from + * those sections, and apply it to the entire selection. + * + * If there is no selection, the style will apply to the next character typed. + * + * @param {object} style an object with the following properties. All the + * properties are optional, but they can be combined. + * + * @param {string} [style.mode=''] - Either `'math'`, `'text'` or '`command`' + * @param {string} [style.color=''] - The text/fill color, as a CSS RGB value or + * a string for some 'well-known' colors, e.g. 'red', '#f00', etc... + * + * @param {string} [style.backgroundColor=''] - The background color. + * + * @param {string} [style.fontFamily=''] - The font family used to render text. + * This value can the name of a locally available font, or a CSS font stack, e.g. + * "Avenir", "Georgia, serif", etc... + * This can also be one of the following TeX-specific values: + * - 'cmr': Computer Modern Roman, serif + * - 'cmss': Computer Modern Sans-serif, latin characters only + * - 'cmtt': Typewriter, slab, latin characters only + * - 'cal': Calligraphic style, uppercase latin letters and digits only + * - 'frak': Fraktur, gothic, uppercase, lowercase and digits + * - 'bb': Blackboard bold, uppercase only + * - 'scr': Script style, uppercase only + * + * @param {string} [style.series=''] - The font 'series', i.e. weight and + * stretch. The following values can be combined, for example: "ebc": extra-bold, + * condensed. Aside from 'b', these attributes may not have visible effect if the + * font family does not support this attribute: + * - 'ul' ultra-light weight + * - 'el': extra-light + * - 'l': light + * - 'sl': semi-light + * - 'm': medium (default) + * - 'sb': semi-bold + * - 'b': bold + * - 'eb': extra-bold + * - 'ub': ultra-bold + * - 'uc': ultra-condensed + * - 'ec': extra-condensed + * - 'c': condensed + * - 'sc': semi-condensed + * - 'n': normal (default) + * - 'sx': semi-expanded + * - 'x': expanded + * - 'ex': extra-expanded + * - 'ux': ultra-expanded + * + * @param {string} [style.shape=''] - The font 'shape', i.e. italic. + * - 'auto': italic or upright, depending on mode and letter (single letters are + * italic in math mode) + * - 'up': upright + * - 'it': italic + * - 'sl': slanted or oblique (often the same as italic) + * - 'sc': small caps + * - 'ol': outline + * + * @param {string} [style.size=''] - The font size: 'size1'...'size10' + * 'size5' is the default size + * @method MathField#$applyStyle + * + */ + $applyStyle(style: { + mode?: string; + color?: string; + backgroundColor?: string; + fontFamily?: string; + series?: string; + shape?: string; + size?: string; + }): void; + /** + * @param {string} keys - A string representation of a key combination. + * + * For example `'Alt-KeyU'`. + * + * See [W3C UIEvents](https://www.w3.org/TR/uievents/#code-virtual-keyboards) + * @param {Event} evt + * @return {boolean} + * @method MathField#$keystroke + */ + $keystroke(keys: string, evt: Event): boolean; + /** + * Simulate a user typing the keys indicated by text. + * @param {string} text - A sequence of one or more characters. + * @method MathField#$typedText + */ + $typedText(text: string): void; + /** + * + * Update the configuration options for this mathfield. + * + * @param {MathFieldConfig} [config={}] See {@tutorial CONFIG} for details. + * + * @method MathField#$setConfig + */ + $setConfig(config?: MathFieldConfig): void; + /** + * + * Speak some part of the expression, either with or without synchronized highlighting. + * + * @param {string} amount (all, selection, left, right, group, parent) + * @param {object} speakOptions + * @param {boolean} speakOptions.withHighlighting - If true, synchronized highlighting of speech will happen (if possible). Default is false. + * + * @method MathField#speak_ + */ + speak_(amount: string, speakOptions: { + withHighlighting: boolean; + }): void; +} + +/** + * Return an array of potential shortcuts + * @param {string} s + * @param {object} config + * @return {string[]} + */ +declare function startsWithString(s: string, config: any): string[]; + +/** + * + * @param {string} mode + * @param {object[]} siblings atoms preceding this potential shortcut + * @param {string} shortcut + */ +declare function validateShortcut(mode: string, siblings: object[], shortcut: string): void; + +/** + * + * This modules exports the MathLive entry points. + * + * @module mathlive + * @example + * // To invoke the functions in this module, import the MathLive module. + * + * import MathLive from 'dist/mathlive.mjs'; + * + * const markup = MathLive.latexToMarkup('e^{i\\pi}+1=0'); + * + */ +declare module "mathlive" { + /** + * Convert a LaTeX string to a string of HTML markup. + * + * @param {string} text A string of valid LaTeX. It does not have to start + * with a mode token such as `$$` or `\(`. + * + * @param {string} mathstyle If `'displaystyle'` the "display" mode of TeX + * is used to typeset the formula, which is most appropriate for formulas that are + * displayed in a standalone block. If `'textstyle'` is used, the "text" mode + * of TeX is used, which is most appropriate when displaying math "inline" + * with other text (on the same line). + * + * @param {string} [format='html'] For debugging purposes, this function + * can also return a text representation of internal data structures + * used to construct the markup. Valid values include `'mathlist'` and `'span'` + * + * @return {string} + * @function module:mathlive#latexToMarkup + */ + function latexToMarkup(text: string, mathstyle: string, format?: string): string; + /** + * Convert a DOM element into an editable math field. + * + * After the DOM element has been created, the value `element.mathfield` will + * return a reference to the mathfield object. This value is also returned + * by `makeMathField` + * + * @param {HTMLElement|string} element A DOM element, for example as obtained + * by `document.getElementById()`, or the ID of a DOM element as a string. + * + * @param {MathFieldConfig} [config={}] See {@tutorial CONFIG} for details. + * + * + * @return {MathField} + * + * Given the HTML markup: + * ```html + * $f(x)=sin(x)$ + * ``` + * The following code will turn the span into an editable mathfield. + * ``` + * import MathLive from 'dist/mathlive.mjs'; + * MathLive.makeMathField('equation'); + * ``` + * + * @function module:mathlive#makeMathField + */ + function makeMathField(element: HTMLElement | string, config?: MathFieldConfig): MathField; + /** + * Convert a LaTeX string to a string of MathML markup. + * + * @param {string} latex A string of valid LaTeX. It does not have to start + * with a mode token such as a `$$` or `\(`. + * @param {object} options + * @param {boolean} [options.generateID=false] - If true, add an `extid` attribute + * to the MathML nodes with a value matching the `atomID`. + * @return {string} + * @function module:mathlive#latexToMathML + */ + function latexToMathML(latex: string, options: { + generateID?: boolean; + }): string; + /** + * Convert a LaTeX string to an Abstract Syntax Tree + * + * **See:** {@tutorial MASTON} + * + * @param {string} latex A string of valid LaTeX. It does not have to start + * with a mode token such as a `$$` or `\(`. + * + * @return {object} The Abstract Syntax Tree as a JavaScript object. + * @function module:mathlive#latexToAST + */ + function latexToAST(latex: string): any; + /** + * Convert a LaTeX string to a textual representation ready to be spoken + * + * @param {string} latex A string of valid LaTeX. It does not have to start + * with a mode token such as a `$$` or `\(`. + * + * @param {Object.} options - + * + * @param {string} [options.textToSpeechRules='mathlive'] Specify which + * set of text to speech rules to use. + * + * A value of `mathlive` indicates that + * the simple rules built into MathLive should be used. A value of `sre` + * indicates that the Speech Rule Engine from Volker Sorge should be used. + * Note that SRE is not included or loaded by MathLive and for this option to + * work SRE should be loaded separately. + * + * @param {string} [options.textToSpeechMarkup=''] The markup syntax to use + * for the output of conversion to spoken text. + * + * Possible values are `ssml` for + * the SSML markup or `mac` for the MacOS markup (e.g. `[[ltr]]`) + * + * @param {Object.} [options.textToSpeechRulesOptions={}] A set of + * key/value pairs that can be used to configure the speech rule engine. + * + * Which options are available depends on the speech rule engine in use. There + * are no options available with MathLive's built-in engine. The options for + * the SRE engine are documented [here]{@link:https://github.com/zorkow/speech-rule-engine} + * @return {string} The spoken representation of the input LaTeX. + * @example + * console.log(MathLive.latexToSpeakableText('\\frac{1}{2}')); + * // ➡︎'half' + * @function module:mathlive#latexToSpeakableText + */ + function latexToSpeakableText(latex: string, options: { + textToSpeechRules?: string; + textToSpeechMarkup?: string; + textToSpeechRulesOptions?: { + [key: string]: any; + }; + }): string; + /** + * Highlight the span corresponding to the specified atomID + * This is used for TTS with synchronized highlighting (read aloud) + * + * @param {string} atomID + * + */ + function highlightAtomID(atomID: string): void; + /** + * Return the status of a Read Aloud operation (reading with synchronized + * highlighting). + * + * Possible values include: + * - `ready` + * - `playing` + * - `paused` + * - `unavailable` + * + * **See** {@linkcode module:editor-mathfield#speak speak} + * @return {string} + * @function module:mathlive#readAloudStatus + */ + function readAloudStatus(): string; + /** + * If a Read Aloud operation is in progress, stop it. + * + * **See** {@linkcode module:editor/mathfield#speak speak} + * @function module:mathlive#pauseReadAloud + */ + function pauseReadAloud(): void; + /** + * If a Read Aloud operation is paused, resume it + * + * **See** {@linkcode module:editor-mathfield#speak speak} + * @function module:mathlive#resumeReadAloud + */ + function resumeReadAloud(): void; + /** + * If a Read Aloud operation is in progress, read from a specified token + * + * **See** {@linkcode module:editor-mathfield#speak speak} + * + * @param {string} token + * @param {number} [count] + * @function module:mathlive#playReadAloud + */ + function playReadAloud(token: string, count?: number): void; + /** + * Transform all the elements in the document body that contain LaTeX code + * into typeset math. + * + * **Note:** This is a very expensive call, as it needs to parse the entire + * DOM tree to determine which elements need to be processed. In most cases + * this should only be called once per document, once the DOM has been loaded. + * To render a specific element, use {@linkcode module:mathlive#renderMathInElement renderMathInElement()} + * + * **See:** {@tutorial USAGE_GUIDE} + * + * @param {object} [options={}] See {@linkcode module:mathlive#renderMathInElement renderMathInElement()} + * for details + * @example + * import MathLive from 'dist/mathlive.mjs'; + * document.addEventListener("load", () => { + * MathLive.renderMathInDocument(); + * }); + * + */ + function renderMathInDocument(options?: { + [key: string]: any; + }): void; + /** + * Transform all the children of `element`, recursively, that contain LaTeX code + * into typeset math. + * + * **See:** {@tutorial USAGE_GUIDE} + * + * @param {HTMLElement|string} element An HTML DOM element, or a string containing + * the ID of an element. + * @param {object} [options={}] + * + * @param {string} [options.namespace=''] - Namespace that is added to `data-` + * attributes to avoid collisions with other libraries. + * + * It is empty by default. + * + * The namespace should be a string of lowercase letters. + * + * @param {object[]} [options.macros={}] - Custom LaTeX macros + * + * @param {string[]} [options.skipTags=['noscript', 'style', 'textarea', 'pre', 'code', 'annotation', 'annotation-xml'] ] + * an array of tag names whose content will + * not be scanned for delimiters (unless their class matches the `processClass` + * pattern below. + * + * @param {string} [options.ignoreClass='tex2jax_ignore'] a string used as a + * regular expression of class names of elements whose content will not be + * scanned for delimiters + + * @param {string} [options.processClass='tex2jax_process'] a string used as a + * regular expression of class names of elements whose content **will** be + * scanned for delimiters, even if their tag name or parent class name would + * have prevented them from doing so. + * + * @param {string} [options.processScriptType="math/tex"] ` - - - - - - - - - - - - - - -
Fork me on GitHub
- - -
- - - - - - -
- -
- -

MathAtom

- - - - - - - -
- -
- -

- MathAtom -

- - -
- -
-
- - -
- - - -

new MathAtom(mode: string, type: string, body: string | MathAtom[], style?: [string]:any): MathAtomprivate

- - - - - -
-

An atom is an object encapsulating an elementary mathematical unit, -independent of its graphical representation.

-

It keeps track of the content, while the dimensions, position and style -are tracked by Span objects which are created by the decompose() functions.

-
- - - - - - - - - - - - -
- - - -
- - mode - - - - - : -string - - - - - - - - - - -
- - - -
- - type - - - - - : -string - - - - - - - - - - -
- - - -
- - body - - - - - : -string -| - -MathAtom[] - - - - - - - - - - -
- - - -
- - style - - - - - : -[string]:any - - - - - - - - - - - - - - - - - = {} - - - -

-

A set of additional properties to append to -the atom

-

- - -
- - -
- - - - - - → - - - - - : -MathAtom - - -    - - - - - - - - - - - -
Properties
- - - - - - -
- - mode - - - - - : -string - - - - - - - - - -

'display', 'command', etc...

- -
-
- - - -
- - type - - - - - : -string - - - - - - - - - -

Type can be one of:

-
    -
  • mord: ordinary symbol, e.g. x, \alpha
  • -
  • textord: ordinary characters
  • -
  • mop: operators, including special functions, \sin, \sum, \cap.
  • -
  • mbin: binary operator: +, *, etc...
  • -
  • mrel: relational operator: =, \ne, etc...
  • -
  • mpunct: punctuation: ,, :, etc...
  • -
  • mopen: opening fence: (, \langle, etc...
  • -
  • mclose: closing fence: ), \rangle, etc...
  • -
  • minner: special layout cases, overlap, \left...\right
  • -
-

In addition to these basic types, which correspond to the TeX atom types, -some atoms represent more complex compounds, including:

-
    -
  • space and spacing: blank space between atoms
  • -
  • mathstyle: to change the math style used: display or text. -The layout rules are different for each, the latter being more compact and -intended to be incorporated with surrounding non-math text.
  • -
  • root: a group, which has no parent (only one per formula)
  • -
  • group: a simple group of atoms, for example from a {...}
  • -
  • sizing: set the size of the font used
  • -
  • rule: draw a line, for the \rule command
  • -
  • line: used by \overline and \underline commands
  • -
  • box: a border drawn around an expression and change its background color
  • -
  • overlap: display a symbol over another
  • -
  • overunder: displays an annotation above or below a symbol
  • -
  • array: a group, which has children arranged in rows. Used -by environments such as matrix, cases, etc...
  • -
  • genfrac: a generalized fraction: a numerator and denominator, separated -by an optional line, and surrounded by optional fences
  • -
  • surd: a surd, aka root
  • -
  • leftright: used by the \left and \right commands
  • -
  • delim: some delimiter
  • -
  • sizeddelim: a delimiter that can grow
  • -
-

The following types are used by the editor:

-
    -
  • command indicate a command being entered. The text is displayed in -blue in the editor.
  • -
  • error: indicate a command that is unknown, for example \xyzy. The text -is displayed with a wavy red underline in the editor.
  • -
  • placeholder: indicate a temporary item. Placeholders are displayed -as a dashed square in the editor.
  • -
  • first: a special, empty, atom put as the first atom in math lists in -order to be able to position the caret before the first element. Aside from -the caret, they display nothing.
  • -
- -
-
- - - -
- - body - - - - - : -string -| - -MathAtom[] - - - - - - - - - - - - -
- - - -
- - superscript - - - - - : -MathAtom[] - - - - - - - - - - - - -
- - - -
- - subscript - - - - - : -MathAtom[] - - - - - - - - - - - - -
- - - -
- - numer - - - - - : -MathAtom[] - - - - - - - - - - - - -
- - - -
- - denom - - - - - : -MathAtom[] - - - - - - - - - - - - -
- - - -
- - captureSelection - - - - - : -boolean - - - - - - - - - -

if true, this atom does not let its -children be selected. Used by the \enclose annotations, for example.

- -
-
- - - -
- - skipBoundary - - - - - : -boolean - - - - - - - - - -

if true, when the caret reaches the -first position in this element's body, it automatically moves to the -outside of the element. Conversely, when the caret reaches the position -right after this element, it automatically moves to the last position -inside this element.

- -
-
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - -
- - - - - - - - - - - - - - -

Methods

- - - -
- - - -

toSpeakableText(atoms?: MathAtom[], options?: [string]:any)static

- - - - - - - - - - - - - - - - -
- - - -
- - atoms - - - - - : -MathAtom[] - - - - - - - - - - - - - - - - - - -

-

The atoms to represent as speakable text. -If omitted, this is used.

-

- - -
- - - -
- - options - - - - - : -[string]:any - - - - - - - - - - - - - - - - - - -
- - -
- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - -
- - - -

bind(context: Context, span: Span)

- - - - - -
-

Add an ID attribute to both the span and this atom so that the atom -can be retrieved from the span later on (e.g. when the span is clicked on)

-
- - - - - - - - - - - - -
- - - -
- - context - - - - - : -Context - - - - - - - - - - -
- - - -
- - span - - - - - : -Span - - - - - - - - - - -
- - -
- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - -
- - - -

decompose(context: Context, phantomBase?: Span[]): Span[]

- - - - - -
-

Return a representation of this, but decomposed in an array of Spans

-
- - - - - - - - - - - - -
- - - -
- - context - - - - - : -Context - - - - - - - - - - -

-

Font variant, size, color, etc...

-

- - -
- - - -
- - phantomBase - - - - - : -Span[] - - - - - - - - - - - - - - - - - = null - - - -

-

If not null, the spans to use to -calculate the placement of the supsub

-

- - -
- - -
- - - - - - → - - - - - : -Span[] - - -    - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - -
- - - -

decomposeGenfrac()

- - - - - -
-

Gengrac -- Generalized fraction

-

Decompose fractions, binomials, and in general anything made -of two expressions on top of each other, optionally separated by a bar, -and optionally surrounded by fences (parentheses, brackets, etc...)

-

Depending on the type of fraction the mathstyle is either -display math or inline math (which is indicated by 'textstyle'). This value can -also be set to 'auto', which indicates it should use the current mathstyle

-
- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - -
- - - -

decomposeLeftright()

- - - - - -
-

\left....\right

-

Note that we can encounter malformed \left...\right, for example -a \left without a matching \right or vice versa. In that case, the -leftDelim (resp. rightDelim) will be undefined. We still need to handle -those cases.

-
- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - -
- - - -

decomposeLine()

- - - - - -
-

\overline and \underline

-
- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - -
- - - -

decomposeRule()

- - - - - -
-

\rule

-
- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - -
- - - -

filter(): MathAtom[]

- - - - - -
-

Iterate over all the child atoms of this atom, this included, -and return an array of all the atoms for which the predicate callback -is true.

-
- - - - - - - - - - - - - - - → - - - - - : -MathAtom[] - - -    - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - -
- - - -

makeSpan(context: Context, body: string | Span[]): Span

- - - - - -
-

Create a span with the specified body and with a class attribute -equal to the type ('mbin', 'inner', 'spacing', etc...)

-
- - - - - - - - - - - - -
- - - -
- - context - - - - - : -Context - - - - - - - - - - -
- - - -
- - body - - - - - : -string -| - -Span[] - - - - - - - - - - -
- - -
- - - - - - → - - - - - : -Span - - -    - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - -
- - - -

toAST(): object

- - - - - -
-

Return an AST representation of a single atom

-
- - - - - - - - - - - - - - - → - - - - - : -object - - -    - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - -
- - - -

toLatex(expandMacro: boolean): string

- - - - - -
-

Return a LaTeX representation of the atom.

-
- - - - - - - - - - - - -
- - - -
- - expandMacro - - - - - : -boolean - - - - - - - - - - -

-

If true, macros are fully expanded. This will -no longer round-trip.

-

- - -
- - -
- - - - - - → - - - - - : -string - - -    - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
- - - - - - - -
- -
- - - - -
- - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/MathField.html b/docs/MathField.html index fe26f04ce..3b31be55d 100644 --- a/docs/MathField.html +++ b/docs/MathField.html @@ -40,7 +40,7 @@ @@ -85,28 +85,12 @@

-

new MathField(element: Element, config: object)

+

new MathField()

-
-

To create a mathfield, you would typically use MathLive.makeMathField() -instead of invoking directly this constructor.

-

Note

-
    -
  • Method names that begin with $ are public.
  • -
  • Method names that begin with an underbar _ are private and meant -to be used only by the implementation of the class.
  • -
  • Method names that end with an underbar _ are selectors. They can -be invoked by calling MathField.$perform(). Note -that the selector name does not include the underbar.
  • -
-

For example:

-
   mf.$perform('selectAll');
-
-
@@ -114,16 +98,23 @@

new MathField(elementParameters

--> -
-
+ +
Properties
+ + + + + + +
element @@ -131,7 +122,7 @@

new MathField(element : -Element +HTMLElement @@ -141,18 +132,14 @@

new MathField(element -

The DOM element that this mathfield is attached to. -Note that element.mathfield is this object.

-

- - +

The DOM element this mathfield is attached to.

+ +

-
+
config @@ -160,7 +147,7 @@

new MathField(element : -object +[string]:any @@ -170,40 +157,48 @@

new MathField(element -

See Configuration options for details

-

- - +

A set of key/value pairs that can +be used to customize the behavior of the mathfield

+ +

-
- +
+ + id + - + + + : +string - + + -
Properties
+ - + +

A unique ID identifying this mathfield

+ +
+
- element + keystrokeCaptionVisible : -Element +boolean @@ -213,7 +208,8 @@
Properties
-

The DOM element this mathfield is attached to.

+

True if the keystroke caption +panel is visible

@@ -222,13 +218,13 @@
Properties
- config + virtualKeyboardVisible : -[string]:any +boolean @@ -238,8 +234,8 @@
Properties
-

A set of key/value pairs that can -be used to customize the behavior of the mathfield

+

True if the virtual keyboard is +visible

@@ -248,7 +244,7 @@
Properties
- id + keystrokeBuffer @@ -264,7 +260,8 @@
Properties
-

A unique ID identifying this mathfield

+

The last few keystrokes, to look out +for inline shortcuts

@@ -273,13 +270,13 @@
Properties
- keystrokeCaptionVisible + keystrokeBufferStates : -boolean +object[] @@ -289,43 +286,427 @@
Properties
-

True if the keystroke caption -panel is visible

+

The saved state for each of the +past keystrokes

-
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + +

Methods

+ + - virtualKeyboardVisible +
+ + + +

$applyStyle(style: object)

+ + + + + +
+

Apply a style (color, bold, italic, etc...).

+

If there is a selection, the style is applied to the selection

+

If the selection already has this style, remove it. If the selection +has the style partially applied (i.e. only some sections), remove it from +those sections, and apply it to the entire selection.

+

If there is no selection, the style will apply to the next character typed.

+
+ + + + + + + + + + + + +
+ + + +
+ + style : -boolean +object + + + + + + + + + + +

+

an object with the following properties. All the +properties are optional, but they can be combined.

+

+ +
+ + + +
+ + style.mode + + + + + : +string + + + + + + + + + + + = '' + + + +

+

Either 'math', 'text' or 'command'

+

+ + +
+ + + +
+ + style.color + + + + + : +string + + + + + + + + + + + + + + + + = '' + + + +

+

The text/fill color, as a CSS RGB value or +a string for some 'well-known' colors, e.g. 'red', '#f00', etc...

+

+ + +
+ + + +
+ + style.backgroundColor + + + + + : +string + + + + + + + + + + + + + + + + + = '' + + + +

+

The background color.

+

+ + +
+ + + +
+ + style.fontFamily + + + + + : +string + + + + + + + + + + + + + + + + + = '' + + + +

+

The font family used to render text. +This value can the name of a locally available font, or a CSS font stack, e.g. +"Avenir", "Georgia, serif", etc... +This can also be one of the following TeX-specific values:

+
    +
  • 'cmr': Computer Modern Roman, serif
  • +
  • 'cmss': Computer Modern Sans-serif, latin characters only
  • +
  • 'cmtt': Typewriter, slab, latin characters only
  • +
  • 'cal': Calligraphic style, uppercase latin letters and digits only
  • +
  • 'frak': Fraktur, gothic, uppercase, lowercase and digits
  • +
  • 'bb': Blackboard bold, uppercase only
  • +
  • 'scr': Script style, uppercase only
  • +
+

+ + +
+ + + +
+ + style.series + + + + + : +string + + + + + + + + + + + + + + + + + = '' + + + +

+

The font 'series', i.e. weight and +stretch. The following values can be combined, for example: "ebc": extra-bold, +condensed. Aside from 'b', these attributes may not have visible effect if the +font family does not support this attribute:

+
    +
  • 'ul' ultra-light weight
  • +
  • 'el': extra-light
  • +
  • 'l': light
  • +
  • 'sl': semi-light
  • +
  • 'm': medium (default)
  • +
  • 'sb': semi-bold
  • +
  • 'b': bold
  • +
  • 'eb': extra-bold
  • +
  • 'ub': ultra-bold
  • +
  • 'uc': ultra-condensed
  • +
  • 'ec': extra-condensed
  • +
  • 'c': condensed
  • +
  • 'sc': semi-condensed
  • +
  • 'n': normal (default)
  • +
  • 'sx': semi-expanded
  • +
  • 'x': expanded
  • +
  • 'ex': extra-expanded
  • +
  • 'ux': ultra-expanded
  • +
+

+ + +
+ + + +
+ + style.shape + + + + + : +string + + + + + + + + + + + + + + + + + = '' + + + +

+

The font 'shape', i.e. italic.

+
    +
  • 'auto': italic or upright, depending on mode and letter (single letters are +italic in math mode)
  • +
  • 'up': upright
  • +
  • 'it': italic
  • +
  • 'sl': slanted or oblique (often the same as italic)
  • +
  • 'sc': small caps
  • +
  • 'ol': outline
  • +
+

+ - -

True if the virtual keyboard is -visible

- -
-
+
- keystrokeBuffer + style.size @@ -338,49 +719,37 @@
Properties
- - - -

The last few keystrokes, to look out -for inline shortcuts

+ - -
- + -
- - keystrokeBufferStates + + - - : -object[] - - + = '' - - +

+

The font size: 'size1'...'size10' +'size5' is the default size

+

+ - -

The saved state for each of the -past keystrokes

- -
+
- - - -
+ + +
+
@@ -388,9 +757,8 @@
Properties
- - +
@@ -411,8 +779,6 @@
Properties
-
- @@ -425,13 +791,10 @@
Properties
+ - -
- -
@@ -439,14 +802,13 @@
Properties
- - - -

Methods

+ + +
@@ -454,7 +816,7 @@

Methods

-

$el(): Element

+

$el(): HTMLElement

@@ -484,7 +846,7 @@

$el() : -Element +HTMLElement    @@ -655,7 +1017,16 @@

$insert(s : -string +'placeholder' +| + +'after' +| + +'before' +| + +'item' @@ -685,35 +1056,6 @@

$insert(s - - options.mode - - - - - : -string - - - - - - - - - - -

-

The mode ('text' or 'math') to use. Default -is the current mode.

-

- - - - - -
options.format @@ -722,7 +1064,10 @@

$insert(s : -string +'auto' +| + +'latex' @@ -812,7 +1157,13 @@

$insert(s : -string +'text' +| + +'math' +| + +'' @@ -1563,6 +1914,7 @@

$selectedText(format
  • 'spoken'
  • 'spoken-text'
  • 'spoken-ssml'
  • +
  • spoken-ssml-withHighlighting
  • 'mathML'
  • 'json'
  • @@ -2069,7 +2421,7 @@

    $selectionIsCollapsed$setConfig(config?: [string]:any)

    +

    $setConfig(config?: MathFieldConfig)

    @@ -2102,7 +2454,7 @@

    $setConfig(config : -[string]:any +MathFieldConfig @@ -2260,6 +2612,7 @@

    $text(formatcomplete_(options + + + + + + + + + + + + + + + + +

    + + + +
    + + + +

    constructor(element: HTMLElement, config: MathFieldConfig)private

    + + + + + +
    +

    To create a mathfield, you would typically use MathLive.makeMathField() +instead of invoking directly this constructor.

    +
    + + + + + + + + + + + + +
    + + + +
    + + element + + + + + : +HTMLElement + + + + + + + + + + +

    +

    The DOM element that this mathfield is attached to. +Note that element.mathfield is this object.

    +

    + + +
    + + + +
    + + config + + + + + : +MathFieldConfig + + + + + + + + + + +

    +

    See Configuration options for details

    +

    + + +
    + + +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + @@ -3625,7 +4127,7 @@

    speak_(amount -

    If true, synchronized highlighting of speech will happen (if possible)

    +

    If true, synchronized highlighting of speech will happen (if possible). Default is false.

    diff --git a/docs/UndoManager.html b/docs/UndoManager.html index 9f5a6ed0b..4c6fca9bc 100644 --- a/docs/UndoManager.html +++ b/docs/UndoManager.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    @@ -645,11 +645,11 @@

    redo()restore()

    +

    restore()private

    @@ -731,11 +731,11 @@

    restore() +
    -

    save()

    +

    save()private

    @@ -902,11 +902,11 @@

    snapshot() +
    -

    snapshotAndCoalesce(options: [any])

    +

    snapshotAndCoalesce(options: [any])private

    diff --git a/docs/global.html b/docs/global.html new file mode 100644 index 000000000..e3be61f36 --- /dev/null +++ b/docs/global.html @@ -0,0 +1,2197 @@ + + + + Global - MathLive Docs + + + + + + + + + + + + + + + + + + + + + + + + +
    Fork me on GitHub
    + + +
    + + + + + + +
    + +
    + +

    Global

    + + + + + + + +
    + +
    + +

    + +

    + + +
    + +
    +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + +

    Members

    + + + +
    +

    INLINE_SHORTCUTS →[string]:stringprivateconstant

    + + + + +
    +

    These shortcut strings are replaced with the corresponding LaTeX expression +without requiring an escape sequence or command.

    +
    + + + +
    Type
    +
      +
    • + : +[string]:string + + +
    • +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + +
    +

    KEYBOARD_SHORTCUTS →[string]:stringprivateconstant

    + + + + +
    +

    The index of this array is a keystroke combination as returned by the key +field of a JavaScript keyboard event as documented here: +https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values +except for:

    +
      +
    • EscapeEsc
    • +
    • LeftArrow... → Left/Right/Up/Down
    • +
    • DeleteDel
    • +
    • EscapeEsc
    • +
    • ' ' → Spacebar
    • +
    +

    The modifiers are specified before the main key, in the following order:

    +
      +
    1. Ctrl
    2. +
    3. Meta: Command key on Mac OS. On Windows this is the Windows key, +but the system intercepts those key combinations so they are never received
    4. +
    5. Alt: Option key on Mac OS
    6. +
    7. Shift
    8. +
    +

    The keys can be preceded by a context to restrict when the shortcut is +applicable. For example, "math:Ctrl-KeyA" will restrict this shortcut +to only apply in the "math" context (parseMode). Other valid context include +"text" and "command".

    +

    The value of the entries represent the command to perform. +This can be either a single selector, or an array of a selector followed +by its arguments. +Selectors uses the following naming conventions:

    +
      +
    • a 'char' is a math atom (a letter, digit, symbol or compound atom)
    • +
    • a 'word' is a sequence of math atoms of the same type
    • +
    • a 'group' is a sequence of sibling atoms, for example a numerator or +a superscript
    • +
    • the 'MathField' is the entire expression being edited
    • +
    • a 'placeholder' is either an actual placeholder atom or an empty child +list, for example an empty numerator
    • +
    • 'move' changes the position of the insertion point (and collapses the +selection range if necessary)
    • +
    • 'extend' keeps the anchor of the selection, but moves the focus (extends, +or shrinks, the range of selected items)
    • +
    +
    + + + +
    Type
    +
      +
    • + : +[string]:string + + +
    • +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + +
    +

    REVERSE_KEYBOARD_SHORTCUTS →[string]:stringprivateconstant

    + + + + +
    +

    Most commands can be associated to their keyboard shortcuts from the +KEYBOARD_SHORTCUTS table above, for example 'speakSelection' -> 'Ctrl-KeyR' +However, those that contain complex commands are more ambiguous. +For example, '\sqrt' -> 'math:Alt-KeyV'. This table provides the reverse +mapping for those more complex commands. It is used when displaying +keyboard shortcuts for specific commands in the popover.

    +
    + + + +
    Type
    +
      +
    • + : +[string]:string + + +
    • +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + +

    Methods

    + + + +
    + + + +

    startsWithString(s: string, config: object): string[]

    + + + + + +
    +

    Return an array of potential shortcuts

    +
    + + + + + + + + + + + + +
    + + + +
    + + s + + + + + : +string + + + + + + + + + + +
    + + + +
    + + config + + + + + : +object + + + + + + + + + + +
    + + +
    + + + + + + → + + + + + : +string[] + + +    + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + +
    + + + +
    + + + +

    validateShortcut(mode: string, siblings: object[], shortcut: string)

    + + + + + + + + + + + + + + + + +
    + + + +
    + + mode + + + + + : +string + + + + + + + + + + +
    + + + +
    + + siblings + + + + + : +object[] + + + + + + + + + + +

    +

    atoms preceding this potential shortcut

    +

    + + +
    + + + +
    + + shortcut + + + + + : +string + + + + + + + + + + +
    + + +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + +
    + + + + + +

    Type Definitions

    + + + +
    + + + +

    MathFieldCallback(mf: MathField): void

    + + + + + + + + + + + + + + + + +
    + + + +
    + + mf + + + + + : +MathField + + + + + + + + + + +
    + + +
    + + + + + + → + + + + + : +void + + +    + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + +
    + + + +
    +

    MathFieldConfig

    + + + + + + +
    Type
    +
      +
    • + : +Object + + +
    • +
    + + + + + +
    Properties
    + + + + + + +
    + + locale? + + + + + : +string + + + + + + + + + + + + +
    + + + +
    + + strings? + + + + + : +object.<string, string> + + + + + + + + + + + + +
    + + + +
    + + horizontalSpacingScale? + + + + + : +number + + + + + + + + + + + + +
    + + + +
    + + namespace? + + + + + : +string + + + + + + + + + + + + +
    + + + +
    + + substituteTextArea? + + + + + : +function + + + + + + + + + + + + +
    + + + +
    + + defaultMode? + + + + + : +"math" +| + +"text" + + + + + + + + + + + + +
    + + + +
    + + onFocus? + + + + + : +MathFieldCallback + + + + + + + + + + + + +
    + + + +
    + + onBlur? + + + + + : +MathFieldCallback + + + + + + + + + + + + +
    + + + +
    + + onKeystroke? + + + + + : +function + + + + + + + + + + + + +
    + + + +
    + + onAnnounce? + + + + + : +function + + + + + + + + + + + + +
    + + + +
    + + overrideDefaultInlineShortcuts? + + + + + : +boolean + + + + + + + + + + + + +
    + + + +
    + + inlineShortcuts? + + + + + : +object.<string, string> + + + + + + + + + + + + +
    + + + +
    + + inlineShortcutTimeout? + + + + + : +number + + + + + + + + + + + + +
    + + + +
    + + smartFence? + + + + + : +boolean + + + + + + + + + + + + +
    + + + +
    + + smartSuperscript? + + + + + : +boolean + + + + + + + + + + + + +
    + + + +
    + + scriptDepth? + + + + + : +number + + + + + + + + + + + + +
    + + + +
    + + removeExtraneousParentheses? + + + + + : +boolean + + + + + + + + + + + + +
    + + + +
    + + ignoreSpacebarInMathMode? + + + + + : +boolean + + + + + + + + + + + + +
    + + + +
    + + virtualKeyboardToggleGlyph? + + + + + : +string + + + + + + + + + + + + +
    + + + +
    + + virtualKeyboardMode? + + + + + : +"manual" +| + +"onfocus" +| + +"off" + + + + + + + + + + + + +
    + + + +
    + + virtualKeyboards? + + + + + : +"all" +| + +"numeric" +| + +"roman" +| + +"greek" +| + +"functions" +| + +"command" +| + +string + + + + + + + + + + + + +
    + + + +
    + + virtualKeyboardRomanLayout? + + + + + : +"qwerty" +| + +"azerty" +| + +"qwertz" +| + +"dvorak" +| + +"colemak" + + + + + + + + + + + + +
    + + + +
    + + customVirtualKeyboardLayers? + + + + + : +object.<string, string> + + + + + + + + + + + + +
    + + + +
    + + customVirtualKeyboards? + + + + + : +object.<string, object> + + + + + + + + + + + + +
    + + + +
    + + virtualKeyboardTheme? + + + + + : +"material" +| + +"apple" +| + +"" + + + + + + + + + + + + +
    + + + +
    + + keypressVibration? + + + + + : +boolean + + + + + + + + + + + + +
    + + + +
    + + keypressSound? + + + + + : +string + + + + + + + + + + + + +
    + + + +
    + + plonkSound? + + + + + : +string + + + + + + + + + + + + +
    + + + +
    + + textToSpeechRules? + + + + + : +"mathlive" +| + +"sre" + + + + + + + + + + + + +
    + + + +
    + + textToSpeechMarkup? + + + + + : +"ssml" +| + +"mac" + + + + + + + + + + + + +
    + + + +
    + + textToSpeechRulesOptions? + + + + + : +object + + + + + + + + + + + + +
    + + + +
    + + speechEngine? + + + + + : +"local" +| + +"amazon" + + + + + + + + + + + + +
    + + + +
    + + speechEngineVoice? + + + + + : +string + + + + + + + + + + + + +
    + + + +
    + + speechEngineRate? + + + + + : +string + + + + + + + + + + + + +
    + + + +
    + + onMoveOutOf? + + + + + : +function + + + + + + + + + + + + +
    + + + +
    + + onTabOutOf? + + + + + : +function + + + + + + + + + + + + +
    + + + +
    + + onContentWillChange? + + + + + : +MathFieldCallback + + + + + + + + + + + + +
    + + + +
    + + onContentDidChange? + + + + + : +MathFieldCallback + + + + + + + + + + + + +
    + + + +
    + + onSelectionWillChange? + + + + + : +MathFieldCallback + + + + + + + + + + + + +
    + + + +
    + + onSelectionDidChange? + + + + + : +MathFieldCallback + + + + + + + + + + + + +
    + + + +
    + + onUndoStateWillChange? + + + + + : +function + + + + + + + + + + + + +
    + + + +
    + + onUndoStateDidChange? + + + + + : +function + + + + + + + + + + + + +
    + + + +
    + + onModeChange? + + + + + : +function + + + + + + + + + + + + +
    + + + +
    + + onVirtualKeyboardToggle? + + + + + : +function + + + + + + + + + + + + +
    + + + +
    + + onReadAloudStatus? + + + + + : +function + + + + + + + + + + + + +
    + + + +
    + + handleSpeak? + + + + + : +function + + + + + + + + + + + + +
    + + + +
    + + handleReadAloud? + + + + + : +function + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + +
    + +
    + + + + +
    + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 9c4b0206b..d777f7c1a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    @@ -79,7 +79,7 @@

    MathLive Docs

    - +

    Welcome to MathLive, a JavaScript library to render and edit math.

    diff --git a/docs/module-addons_debug.html b/docs/module-addons_debug.html index feb91aa40..2a1fa6b2c 100644 --- a/docs/module-addons_debug.html +++ b/docs/module-addons_debug.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    diff --git a/docs/module-addons_maston.html b/docs/module-addons_maston.html index 4ef565a20..ab7a75cfd 100644 --- a/docs/module-addons_maston.html +++ b/docs/module-addons_maston.html @@ -40,7 +40,7 @@ @@ -180,11 +180,11 @@

    Methods

    -
    +
    -

    asLatex(ast: object): stringinner

    +

    asLatex(ast: object): stringprivateinner

    @@ -450,11 +450,11 @@

    asMachineNumber(n -
    +
    -

    asSymbol(expr: object): stringinner

    +

    asSymbol(expr: object): stringprivateinner

    @@ -585,11 +585,11 @@

    asSymbol(expr +
    -

    escapeText(s: string): stringinner

    +

    escapeText(s: string): stringprivateinner

    @@ -717,11 +717,11 @@

    escapeText(s +
    -

    formatMantissa(m: string, config: [string]:any)inner

    +

    formatMantissa(m: string, config: [string]:any)privateinner

    @@ -856,11 +856,11 @@

    formatMantissa(m +
    -

    getCanonicalName(latex,: string): stringinner

    +

    getCanonicalName(latex,: string): stringprivateinner

    @@ -991,11 +991,11 @@

    getCanonicalName -
    +
    -

    getLatexForSymbol(name: string): stringinner

    +

    getLatexForSymbol(name: string): stringprivateinner

    @@ -1124,11 +1124,11 @@

    getLatexForSymbolgetLatexTemplateForFunction(name: string): stringinner

    +

    getLatexTemplateForFunction(name: string): stringprivateinner

    @@ -1257,11 +1257,11 @@

    getLatexTemplateForFunction +
    -

    getPrecedence(canonicalName,: string): numberinner

    +

    getPrecedence(canonicalName,: string): numberprivateinner

    @@ -1394,11 +1394,11 @@

    getPrecedence(canon -
    +
    -

    isAtom(expr: object, type: string, value: string)inner

    +

    isAtom(expr: object, type: string, value: string)privateinner

    @@ -1554,11 +1554,11 @@

    isAtom(exprnegate(node: object)inner

    +

    negate(node: object)privateinner

    @@ -1669,11 +1669,11 @@

    negate(nodenumberAsLatex(num: string | number): stringinner

    +

    numberAsLatex(num: string | number): stringprivateinner

    @@ -1808,11 +1808,11 @@

    numberAsLatex(num +
    -

    opPrec(atom: object): numberinner

    +

    opPrec(atom: object): numberprivateinner

    @@ -1941,11 +1941,11 @@

    opPrec(atomparse(atoms: Atoms[]): objectinner

    +

    parse(atoms: Atoms[]): objectprivateinner

    @@ -2069,11 +2069,11 @@

    parse(atomsparseDelim()inner

    +

    parseDelim()privateinner

    @@ -2161,11 +2161,11 @@

    parseDelim() +
    -

    parseDigraph()inner

    +

    parseDigraph()privateinner

    @@ -2395,11 +2395,11 @@

    parseExpression(e -
    +
    -

    parsePostfix()inner

    +

    parsePostfix()privateinner

    @@ -2479,11 +2479,11 @@

    parsePostfix() +
    -

    parseSentence(expr: object): objectinner

    +

    parseSentence(expr: object): objectprivateinner

    @@ -2612,11 +2612,11 @@

    parseSentence(expr< -
    +
    -

    parseSupsub(expr: object)inner

    +

    parseSupsub(expr: object)privateinner

    @@ -2728,11 +2728,11 @@

    parseSupsub(expr +
    -

    wrapFence(fence: string): stringinner

    +

    wrapFence(fence: string): stringprivateinner

    @@ -2865,11 +2865,11 @@

    wrapFence(fence +
    -

    wrapFn(functionName: string, …params: object)inner

    +

    wrapFn(functionName: string, …params: object)privateinner

    diff --git a/docs/module-addons_outputLatex.html b/docs/module-addons_outputLatex.html index 8cb88ff39..df5eb2f21 100644 --- a/docs/module-addons_outputLatex.html +++ b/docs/module-addons_outputLatex.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    @@ -81,7 +81,7 @@

    addons/outputLatex

    This module outputs a formula to LaTeX.

    -

    To use it, use the MathAtom.toLatex() method.

    +

    To use it, use the MathAtom.toLatex() method.

    @@ -317,11 +317,11 @@

    latexify(value +
    -

    latexifyArray(parent: MathAtom, atoms: MathAtom[], expandMacro: boolean)inner

    +

    latexifyArray(parent: MathAtom, atoms: MathAtom[], expandMacro: boolean)privateinner

    diff --git a/docs/module-addons_outputMathML.html b/docs/module-addons_outputMathML.html index 7aebfa729..f80994d19 100644 --- a/docs/module-addons_outputMathML.html +++ b/docs/module-addons_outputMathML.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    @@ -179,11 +179,11 @@

    Methods

    -
    +
    -

    isSuperscriptAtom(stream: object)inner

    +

    isSuperscriptAtom(stream: object)privateinner

    diff --git a/docs/module-core_color.html b/docs/module-core_color.html index 1cae1abef..fce8a404f 100644 --- a/docs/module-core_color.html +++ b/docs/module-core_color.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    diff --git a/docs/module-core_definitions.html b/docs/module-core_definitions.html index f2425b79d..84ab888e4 100644 --- a/docs/module-core_definitions.html +++ b/docs/module-core_definitions.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    @@ -304,263 +304,6 @@
    Type
    -

    Methods

    - - - -
    - - - -

    getSimpleString(atoms: object[])inner

    - - - - - -
    -

    If possible, i.e. if they are all simple atoms, return a string made up of -their body

    -
    - - - - - - - - - - - - -
    - - - -
    - - atoms - - - - - : -object[] - - - - - - - - - - -
    - - -
    - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - - - -
    - - - -

    mathVariantToUnicode(char: string, variant: string)inner

    - - - - - -
    -

    Given a character and variant ('bb', 'cal', etc...) -return the corresponding unicode character (a string)

    -
    - - - - - - - - - - - - -
    - - - -
    - - char - - - - - : -string - - - - - - - - - - -
    - - - -
    - - variant - - - - - : -string - - - - - - - - - - -
    - - -
    - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - - - diff --git a/docs/module-core_delimiters.html b/docs/module-core_delimiters.html index 8c51cd3ba..47d9bc8b2 100644 --- a/docs/module-core_delimiters.html +++ b/docs/module-core_delimiters.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    @@ -187,164 +187,7 @@

    core/delimiters

    -

    Members

    - - -
    -

    stackNeverDelimiterSequenceinnerconstant

    - - - - -
    -

    There are three different sequences of delimiter sizes that the delimiters -follow depending on the kind of delimiter. This is used when creating custom -sized delimiters to decide whether to create a small, large, or stacked -delimiter.

    -

    In real TeX, these sequences aren't explicitly defined, but are instead -defined inside the font metrics. Since there are only three sequences that -are possible for the delimiters that TeX defines, it is easier to just encode -them explicitly here.

    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - -
    - - - - -

    Methods

    - - - -
    - - - -

    delimTypeToFont()inner

    - - - - - -
    -

    Get the font used in a delimiter based on what kind of delimiter it is.

    -
    - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - - diff --git a/docs/module-core_fontMetrics.html b/docs/module-core_fontMetrics.html index 06451d6d0..1d460cb27 100644 --- a/docs/module-core_fontMetrics.html +++ b/docs/module-core_fontMetrics.html @@ -40,7 +40,7 @@ @@ -171,71 +171,9 @@

    core/fontMetrics

    - - - - -

    Members

    - - - -
    -

    METRICSstaticconstant

    - - - - -
    -

    This is just a mapping from common names to real metrics

    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - -
    - @@ -243,11 +181,11 @@

    Methods

    -
    +
    -

    convertDimenToEm(value: number | string, unit: string, precision: number)inner

    +

    convertDimenToEm(value: number | string, unit: string, precision: number)privateinner

    diff --git a/docs/module-core_lexer.html b/docs/module-core_lexer.html index 997c3438b..530c6aac2 100644 --- a/docs/module-core_lexer.html +++ b/docs/module-core_lexer.html @@ -40,7 +40,7 @@ diff --git a/docs/module-core_lexer_Lexer.html b/docs/module-core_lexer_Lexer.html index bfbb8dc67..bc14ff465 100644 --- a/docs/module-core_lexer_Lexer.html +++ b/docs/module-core_lexer_Lexer.html @@ -40,7 +40,7 @@ @@ -322,11 +322,11 @@

    end()get(): string

    +

    get(): stringprivate

    diff --git a/docs/module-core_lexer_Token.html b/docs/module-core_lexer_Token.html index b2242d994..413ad6a66 100644 --- a/docs/module-core_lexer_Token.html +++ b/docs/module-core_lexer_Token.html @@ -40,7 +40,7 @@ diff --git a/docs/module-core_mathatom-MathAtom.html b/docs/module-core_mathatom-MathAtom.html index 4804e03ad..a64f6a06a 100644 --- a/docs/module-core_mathatom-MathAtom.html +++ b/docs/module-core_mathatom-MathAtom.html @@ -40,7 +40,7 @@ @@ -76,6 +76,11 @@

    MathAtom

    +

    An atom is an object encapsulating an elementary mathematical unit, +independent of its graphical representation.

    +

    It keeps track of the content, while the dimensions, position and style +are tracked by Span objects which are created by the decompose() functions.

    + @@ -83,11 +88,13 @@

    -
    +
    +

    Constructor

    + -

    new MathAtom(mode: string, type: string, body: string | Array, style: object)

    +

    new MathAtom(mode: string, type: string, body: string | Array, style: object): MathAtomprivate

    @@ -207,9 +214,317 @@

    new MathAtom(mode + + : +MathAtom + + +    + + + + + + + + + + + +
    Properties
    + + + + + + +
    + + mode + + + + + : +string + + + + + + + + + +

    'display', 'command', etc...

    + +
    +
    + + + +
    + + type + + + + + : +string + + + + + + + + + +

    Type can be one of:

    +
      +
    • mord: ordinary symbol, e.g. x, \alpha
    • +
    • textord: ordinary characters
    • +
    • mop: operators, including special functions, \sin, \sum, \cap.
    • +
    • mbin: binary operator: +, *, etc...
    • +
    • mrel: relational operator: =, \ne, etc...
    • +
    • mpunct: punctuation: ,, :, etc...
    • +
    • mopen: opening fence: (, \langle, etc...
    • +
    • mclose: closing fence: ), \rangle, etc...
    • +
    • minner: special layout cases, overlap, \left...\right
    • +
    +

    In addition to these basic types, which correspond to the TeX atom types, +some atoms represent more complex compounds, including:

    +
      +
    • space and spacing: blank space between atoms
    • +
    • mathstyle: to change the math style used: display or text. +The layout rules are different for each, the latter being more compact and +intended to be incorporated with surrounding non-math text.
    • +
    • root: a group, which has no parent (only one per formula)
    • +
    • group: a simple group of atoms, for example from a {...}
    • +
    • sizing: set the size of the font used
    • +
    • rule: draw a line, for the \rule command
    • +
    • line: used by \overline and \underline commands
    • +
    • box: a border drawn around an expression and change its background color
    • +
    • overlap: display a symbol over another
    • +
    • overunder: displays an annotation above or below a symbol
    • +
    • array: a group, which has children arranged in rows. Used +by environments such as matrix, cases, etc...
    • +
    • genfrac: a generalized fraction: a numerator and denominator, separated +by an optional line, and surrounded by optional fences
    • +
    • surd: a surd, aka root
    • +
    • leftright: used by the \left and \right commands
    • +
    • delim: some delimiter
    • +
    • sizeddelim: a delimiter that can grow
    • +
    +

    The following types are used by the editor:

    +
      +
    • command indicate a command being entered. The text is displayed in +blue in the editor.
    • +
    • error: indicate a command that is unknown, for example \xyzy. The text +is displayed with a wavy red underline in the editor.
    • +
    • placeholder: indicate a temporary item. Placeholders are displayed +as a dashed square in the editor.
    • +
    • first: a special, empty, atom put as the first atom in math lists in +order to be able to position the caret before the first element. Aside from +the caret, they display nothing.
    • +
    + +
    +
    + + + +
    + + body + + + + + : +string +| + +MathAtom[] + + + + + + + + + + + + +
    +
    + + superscript + + + + + : +MathAtom[] + + + + + + + + + + + + +
    + + + +
    + + subscript + + + + + : +MathAtom[] + + + + + + + + + + + + +
    + + + +
    + + numer + + + + + : +MathAtom[] + + + + + + + + + + + + +
    + + + +
    + + denom + + + + + : +MathAtom[] + + + + + + + + + + + + +
    + + + +
    + + captureSelection + + + + + : +boolean + + + + + + + + + +

    if true, this atom does not let its +children be selected. Used by the \enclose annotations, for example.

    + +
    +
    + + + +
    + + skipBoundary + + + + + : +boolean + + + + + + + + + +

    if true, when the caret reaches the +first position in this element's body, it automatically moves to the +outside of the element. Conversely, when the caret reaches the position +right after this element, it automatically moves to the last position +inside this element.

    + +
    +
    + + + + +
    diff --git a/docs/module-core_mathatom.html b/docs/module-core_mathatom.html index 97c0f3d43..26ea08bd5 100644 --- a/docs/module-core_mathatom.html +++ b/docs/module-core_mathatom.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    diff --git a/docs/module-core_mathstyle.html b/docs/module-core_mathstyle.html index b647f8fe7..ca18c64d8 100644 --- a/docs/module-core_mathstyle.html +++ b/docs/module-core_mathstyle.html @@ -40,7 +40,7 @@ diff --git a/docs/module-core_mathstyle_Mathstyle.html b/docs/module-core_mathstyle_Mathstyle.html index 823400ee1..cb1a71b3c 100644 --- a/docs/module-core_mathstyle_Mathstyle.html +++ b/docs/module-core_mathstyle_Mathstyle.html @@ -40,7 +40,7 @@ diff --git a/docs/module-core_parser.html b/docs/module-core_parser.html index e51ce16bb..3113649a4 100644 --- a/docs/module-core_parser.html +++ b/docs/module-core_parser.html @@ -40,7 +40,7 @@ diff --git a/docs/module-core_parser_Parser.html b/docs/module-core_parser_Parser.html index bc2aab9b9..b0f045558 100644 --- a/docs/module-core_parser_Parser.html +++ b/docs/module-core_parser_Parser.html @@ -40,7 +40,7 @@ @@ -686,11 +686,11 @@

    end()hasLiteralPattern(pattern: RegEx): boolean

    +

    hasLiteralPattern(pattern: RegEx): booleanprivate

    @@ -817,11 +817,11 @@

    hasLiteralPattern +
    -

    hasToken(type: string): boolean

    +

    hasToken(type: string): booleanprivate

    @@ -914,6 +914,91 @@

    hasToken(type + + + + + + + + + + + + + + + + +

    + + + +
    + + + +

    lastMathAtom()private

    + + + + + +
    +

    Return the last atom of the math list +If there isn't one, insert a msubsup and return it.

    +
    + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + @@ -1363,11 +1448,11 @@

    parseSupSub() +
    -

    parseToken(type: string)

    +

    parseToken(type: string)private

    diff --git a/docs/module-core_span.exports.Span.html b/docs/module-core_span.exports.Span.html new file mode 100644 index 000000000..53fe31a62 --- /dev/null +++ b/docs/module-core_span.exports.Span.html @@ -0,0 +1,535 @@ + + + + Span - MathLive Docs + + + + + + + + + + + + + + + + + + + + + + + + +
    Fork me on GitHub
    + + +
    + + + + + + +
    + +
    + +

    Span

    + + + + + + + +
    + +
    + +

    + core/span. + + Span +

    + +

    A span is the most elementary element that can be rendered. +It is composed of an optional body of text and an optional list +of children (other spans). Each span can be decorated with +CSS classes and style attributes.

    + + +
    + +
    +
    + + +
    + + +

    Constructor

    + + +

    new Span(content: string | Span | Span[], classes: string): voidprivate

    + + + + + + + + + + + + + + + + +
    + + + +
    + + content + + + + + : +string +| + +Span +| + +Span[] + + + + + + + + + + +

    +

    the items 'contained' by this node

    +

    + + +
    + + + +
    + + classes + + + + + : +string + + + + + + + + + + +

    +

    list of classes attributes associated with this node

    +

    + + +
    + + +
    + + + + + + → + + + + + : +void + + +    + + + + + + + + + + + +
    Properties
    + + + + + + +
    + + type + + + + + : +string + + + + + + + + + +

    For example, 'command', 'mrel', etc...

    + +
    +
    + + + +
    + + classes + + + + + : +string + + + + + + + + + +

    A string of space separated CSS classes +associated with this element

    + +
    +
    + + + +
    + + cssID + + + + + : +string + + + + + + + + + +

    A CSS ID assigned to this span (optional)

    + +
    +
    + + + +
    + + children + + + + + : +Span[] + + + + + + + + + +

    An array, potentially empty, of spans which +this span encloses

    + +
    +
    + + + +
    + + body + + + + + : +string + + + + + + + + + +

    Content of this span. Can be empty.

    + +
    +
    + + + +
    + + style + + + + + : +[string]:any + + + + + + + + + +

    A set of key/value pairs specifying CSS properties +associated with this element.

    + +
    +
    + + + +
    + + height + + + + + : +number + + + + + + + + + +

    The measurement from baseline to top, in em.

    + +
    +
    + + + +
    + + depth + + + + + : +number + + + + + + + + + +

    The measurement from baseline to bottom, in em.

    + +
    +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + +
    + + +
    + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/module-core_span.html b/docs/module-core_span.html index 65e3c0a2a..a48060a0e 100644 --- a/docs/module-core_span.html +++ b/docs/module-core_span.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    @@ -88,6 +88,13 @@

    core/span

    +

    Classes

    + +
    +
    Span
    +
    +
    + @@ -1423,11 +1430,11 @@

    toString(arg +
    -

    makeSVG(body: Span, svgMarkup: string)inner

    +

    makeSVG(body: Span, svgMarkup: string)privateinner

    diff --git a/docs/module-editor_editableMathlist.html b/docs/module-editor_editableMathlist.html index 156385d34..363afc5b6 100644 --- a/docs/module-editor_editableMathlist.html +++ b/docs/module-editor_editableMathlist.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    MathLive Docs
    @@ -184,11 +184,11 @@

    Methods

    -
    +
    -

    parseMathString(s: string)static

    +

    parseMathString(s: string)privatestatic

    @@ -334,11 +334,11 @@

    parseMathString(s -
    +
    -

    arrayAdjustRow(array: Array.<Array.<MathAtom>>, colRow: object, dir: number)inner

    +

    arrayAdjustRow(array: Array.<Array.<MathAtom>>, colRow: object, dir: number)privateinner

    @@ -496,11 +496,11 @@

    arrayAdjustRow(arr -
    +
    -

    arrayCell(array: Array.<Array.<MathAtom>>, colrow: number | string | object)inner

    +

    arrayCell(array: Array.<Array.<MathAtom>>, colrow: number | string | object)privateinner

    @@ -640,11 +640,11 @@

    arrayCell(array +
    -

    arrayCellCount(array: Array.<Array.<MathAtom>>)inner

    +

    arrayCellCount(array: Array.<Array.<MathAtom>>)privateinner

    @@ -754,11 +754,11 @@

    arrayCellCount(arr -
    +
    -

    arrayColRow(array: Array.<Array.<MathAtom>>, index: number | string): objectinner

    +

    arrayColRow(array: Array.<Array.<MathAtom>>, index: number | string): objectprivateinner

    @@ -917,11 +917,11 @@

    arrayColRow(array +
    -

    arrayColumnCellCount(array: MathAtom, col: number): numberinner

    +

    arrayColumnCellCount(array: MathAtom, col: number): numberprivateinner

    @@ -1072,11 +1072,11 @@

    arrayColumnCellCountarrayFirstCellByRow(array: Array.<Array.<MathAtom>>): stringinner

    +

    arrayFirstCellByRow(array: Array.<Array.<MathAtom>>): stringprivateinner

    @@ -1204,11 +1204,11 @@

    arrayFirstCellByRowarrayIndex(array: Array.<Array.<MathAtom>>, rowCol: object): numberinner

    +

    arrayIndex(array: Array.<Array.<MathAtom>>, rowCol: object): numberprivateinner

    @@ -1359,11 +1359,11 @@

    arrayIndex(array +
    -

    arrayJoinColumns(row: MathAtom[], separator: string, style: object): MathAtom[]inner

    +

    arrayJoinColumns(row: MathAtom[], separator: string, style: object): MathAtom[]privateinner

    @@ -1537,11 +1537,11 @@

    arrayJoinColumns -
    +
    -

    arrayJoinRows(array: MathAtom, separators: strings, style: object): MathAtom[]inner

    +

    arrayJoinRows(array: MathAtom, separators: strings, style: object): MathAtom[]privateinner

    @@ -1715,11 +1715,11 @@

    arrayJoinRows(array -
    +
    -

    arrayRemoveColumn(array: MathAtom, col: number)inner

    +

    arrayRemoveColumn(array: MathAtom, col: number)privateinner

    @@ -1852,11 +1852,11 @@

    arrayRemoveColumnarrayRemoveRow(atom: MathAtom, row: number)inner

    +

    arrayRemoveRow(atom: MathAtom, row: number)privateinner

    @@ -2143,11 +2143,11 @@

    atomContains(atom +
    -

    isNumber(atom: object)inner

    +

    isNumber(atom: object)privateinner

    @@ -2258,11 +2258,11 @@

    isNumber(atom +
    -

    parseMathArgument(s: string): objectinner

    +

    parseMathArgument(s: string): objectprivateinner

    diff --git a/docs/module-editor_keyboard.html b/docs/module-editor_keyboard.html index c47a1d09f..58ee907b8 100644 --- a/docs/module-editor_keyboard.html +++ b/docs/module-editor_keyboard.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    @@ -183,7 +183,7 @@

    Methods

    -

    delegateKeyboardEvents(textarea: Element, handlers: [string]:any)privatestatic

    +

    delegateKeyboardEvents(textarea: HTMLElement, handlers: [string]:any)privatestatic

    @@ -220,7 +220,7 @@

    delegateKeyboardEvents : -Element +HTMLElement @@ -273,7 +273,7 @@

    delegateKeyboardEvents : -Element +HTMLElement @@ -506,11 +506,11 @@

    delegateKeyboardEvents +
    -

    keyboardEventToString(evt: Event)static

    +

    keyboardEventToString(evt: Event)privatestatic

    diff --git a/docs/module-editor_mathfield.html b/docs/module-editor_mathfield.html index 0aa25aee0..04f04665b 100644 --- a/docs/module-editor_mathfield.html +++ b/docs/module-editor_mathfield.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    @@ -182,7 +182,7 @@

    Methods

    -

    nearestElementFromPoint(el: Element, x: number, y: number)private

    +

    nearestElementFromPoint(el: HTMLElement, x: number, y: number)private

    @@ -215,7 +215,7 @@

    nearestElementFromPoint : -Element +HTMLElement @@ -452,11 +452,11 @@

    _findElementWithCaretvalidateStyle(style: object)inner

    +

    validateStyle(style: object)privateinner

    diff --git a/docs/module-editor_mathpath.html b/docs/module-editor_mathpath.html index f64b56d5b..fef3709fa 100644 --- a/docs/module-editor_mathpath.html +++ b/docs/module-editor_mathpath.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    diff --git a/docs/module-editor_shortcuts.html b/docs/module-editor_shortcuts.html deleted file mode 100644 index bc8e738e0..000000000 --- a/docs/module-editor_shortcuts.html +++ /dev/null @@ -1,1395 +0,0 @@ - - - - editor/shortcuts - MathLive Docs - - - - - - - - - - - - - - - - - - - - - - - - -
    Fork me on GitHub
    - - -
    - - - - - - -
    - -
    - -

    editor/shortcuts

    - - - - - - - -
    - -
    - - - -
    - -
    -
    - - - - - -
    - - - - - - - - - - - - -

    Members

    - - - -
    -

    INLINE_SHORTCUTS →[string]:stringstaticconstant

    - - - - -
    -

    These shortcut strings are replaced with the corresponding LaTeX expression -without requiring an escape sequence or command.

    -
    - - - -
    Type
    -
      -
    • - : -[string]:string - - -
    • -
    - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - -
    - - -
    -

    KEYBOARD_SHORTCUTS →[string]:stringstaticconstant

    - - - - -
    -

    The index of this array is a keystroke combination as returned by the key -field of a JavaScript keyboard event as documented here: -https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values -except for:

    -
      -
    • EscapeEsc
    • -
    • LeftArrow... → Left/Right/Up/Down
    • -
    • DeleteDel
    • -
    • EscapeEsc
    • -
    • ' ' → Spacebar
    • -
    -

    The modifiers are specified before the main key, in the following order:

    -
      -
    1. Ctrl
    2. -
    3. Meta: Command key on Mac OS. On Windows this is the Windows key, -but the system intercepts those key combinations so they are never received
    4. -
    5. Alt: Option key on Mac OS
    6. -
    7. Shift
    8. -
    -

    The keys can be preceded by a context to restrict when the shortcut is -applicable. For example, "math:Ctrl-KeyA" will restrict this shortcut -to only apply in the "math" context (parseMode). Other valid context include -"text" and "command".

    -

    The value of the entries represent the command to perform. -This can be either a single selector, or an array of a selector followed -by its arguments. -Selectors uses the following naming conventions:

    -
      -
    • a 'char' is a math atom (a letter, digit, symbol or compound atom)
    • -
    • a 'word' is a sequence of math atoms of the same type
    • -
    • a 'group' is a sequence of sibling atoms, for example a numerator or -a superscript
    • -
    • the 'MathField' is the entire expression being edited
    • -
    • a 'placeholder' is either an actual placeholder atom or an empty child -list, for example an empty numerator
    • -
    • 'move' changes the position of the insertion point (and collapses the -selection range if necessary)
    • -
    • 'extend' keeps the anchor of the selection, but moves the focus (extends, -or shrinks, the range of selected items)
    • -
    -
    - - - -
    Type
    -
      -
    • - : -[string]:string - - -
    • -
    - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - -
    - - -
    -

    REVERSE_KEYBOARD_SHORTCUTS →[string]:stringstaticconstant

    - - - - -
    -

    Most commands can be associated to their keyboard shortcuts from the -KEYBOARD_SHORTCUTS table above, for example 'speakSelection' -> 'Ctrl-KeyR' -However, those that contain complex commands are more ambiguous. -For example, '\sqrt' -> 'math:Alt-KeyV'. This table provides the reverse -mapping for those more complex commands. It is used when displaying -keyboard shortcuts for specific commands in the popover.

    -
    - - - -
    Type
    -
      -
    • - : -[string]:string - - -
    • -
    - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - -
    - - - - -

    Methods

    - - - -
    - - - -

    forString(mode: string, context: string, s: string, config: object): stringprivatestatic

    - - - - - -
    -

    This function is used to resolve inline shortcuts.

    -
    - - - - - - - - - - - - -
    - - - -
    - - mode - - - - - : -string - - - - - - - - - - -
    - - - -
    - - context - - - - - : -string - - - - - - - - - - -

    -

    atoms preceding the candidate, potentially used -to reduce which shortcuts are applicable. If 'null', no restrictions are -applied.

    -

    - - -
    - - - -
    - - s - - - - - : -string - - - - - - - - - - -

    -

    candidate inline shortcuts (e.g. 'pi')

    -

    - - -
    - - - -
    - - config - - - - - : -object - - - - - - - - - - -
    - - -
    - - - - - - → - - - - - : -string - - -    - - -
      -
    • A replacement string matching the shortcut (e.g. '\pi')
    • -
    - -
    - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - - - -
    - - - -

    platform(p: string): stringprivatestatic

    - - - - - -
    -

    Return p, the platform name if p is the current platform, otherwise -return !p. For example, when running on Windows, platform('mac') returns -'!mac'. -The valid values for p are:

    -
      -
    • 'mac'
    • -
    • 'win'
    • -
    • 'android'
    • -
    • 'ios'
    • -
    • 'chromeos'
    • -
    • 'other' (Linux, etc...)
    • -
    -
    - - - - - - - - - - - - -
    - - - -
    - - p - - - - - : -string - - - - - - - - - - -

    -

    The platform to test against.

    -

    - - -
    - - -
    - - - - - - → - - - - - : -string - - -    - - -

    if we are running on the candidate platform, return it. -Otherwise, return "!" + candidate.

    - -
    - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - - - -
    - - - -

    selectorForKeystroke(mode: string, keystroke: string): stringprivatestatic

    - - - - - -
    -

    Return the selector matching the keystroke.

    -
    - - - - - - - - - - - - -
    - - - -
    - - mode - - - - - : -string - - - - - - - - - - -
    - - - -
    - - keystroke - - - - - : -string - - - - - - - - - - -
    - - -
    - - - - - - → - - - - - : -string - - -    - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - - - -
    - - - -

    stringify(shortcuts: [string]:string, joinnullable: string)privatestatic

    - - - - - -
    -

    Return a human readable representation of an array of shortcut strings

    -
    - - - - - - - - - - - - -
    - - - -
    - - shortcuts - - - - - : -[string]:string - - - - - - - - - - -
    - - - -
    - - join - - - - - : -string - - - - - - - - - - - nullable - - - - - - - - - -

    -

    optional, string in between each shortcut representation

    -

    - - -
    - - -
    - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - - - -
    - - - -

    startsWithString(s: string, config: object): string[]inner

    - - - - - -
    -

    Return an array of potential shortcuts

    -
    - - - - - - - - - - - - -
    - - - -
    - - s - - - - - : -string - - - - - - - - - - -
    - - - -
    - - config - - - - - : -object - - - - - - - - - - -
    - - -
    - - - - - - → - - - - - : -string[] - - -    - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - - - -
    - - - -

    validateShortcut(mode: string, siblings: object[], shortcut: string)inner

    - - - - - - - - - - - - - - - - -
    - - - -
    - - mode - - - - - : -string - - - - - - - - - - -
    - - - -
    - - siblings - - - - - : -object[] - - - - - - - - - - -

    -

    atoms preceding this potential shortcut

    -

    - - -
    - - - -
    - - shortcut - - - - - : -string - - - - - - - - - - -
    - - -
    - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - - - - - - - -
    - -
    - - - - -
    - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/module-editor_virtualKeyboard.html b/docs/module-editor_virtualKeyboard.html index 8d2495f16..0b5d4ca77 100644 --- a/docs/module-editor_virtualKeyboard.html +++ b/docs/module-editor_virtualKeyboard.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    @@ -237,11 +237,11 @@

    expandLayerMarkupmake(mf: object, theme: string)inner

    +

    make(mf: object, theme: string)privateinner

    diff --git a/docs/module-mathlive.html b/docs/module-mathlive.html index 92f93ed4c..3deeb71b8 100644 --- a/docs/module-mathlive.html +++ b/docs/module-mathlive.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    @@ -190,7 +190,7 @@

    Methods

    -

    getOriginalContent(element: string | Element | MathField, options?: object): string

    +

    getOriginalContent(element: string | HTMLElement | MathField, options?: object): string

    @@ -238,7 +238,7 @@

    getOriginalContentstring | -Element +HTMLElement | MathField @@ -1295,7 +1295,7 @@

    latexToSpeakableTextmakeMathField(element: Element | string, config?: [string]:any): MathField

    +

    makeMathField(element: HTMLElement | string, config?: MathFieldConfig): MathField

    @@ -1331,7 +1331,7 @@

    makeMathField(elemen : -Element +HTMLElement | string @@ -1363,7 +1363,7 @@

    makeMathField(elemen : -[string]:any +MathFieldConfig @@ -1493,7 +1493,7 @@

    pauseReadAloud()

    If a Read Aloud operation is in progress, stop it.

    -

    See speakAllWithSynchronizedHighlighting

    +

    See speak

    @@ -1578,7 +1578,7 @@

    playReadAloud(token<

    If a Read Aloud operation is in progress, read from a specified token

    -

    See speakAllWithSynchronizedHighlighting

    +

    See speak

    @@ -1903,7 +1903,7 @@

    readAloudStatus()<
  • paused
  • unavailable
  • -

    See speakAllWithSynchronizedHighlighting

    +

    See speak

    @@ -1998,7 +1998,7 @@

    readAloudStatus()< -

    renderMathInElement(element: Element | string, options?: object, renderToMarkup?: function, renderToMathML?: function, renderToSpeakableText?: function)

    +

    renderMathInElement(element: HTMLElement | string, options?: object, renderToMarkup?: function, renderToMathML?: function, renderToSpeakableText?: function)

    @@ -2033,7 +2033,7 @@

    renderMathInElement : -Element +HTMLElement | string @@ -2448,7 +2448,7 @@

    renderMathInElement

    if true, generate markup that can -be read aloud later using speakAllWithSynchronizedHighlighting

    +be read aloud later using speak

    @@ -2766,7 +2766,7 @@

    resumeReadAloud()<

    If a Read Aloud operation is paused, resume it

    -

    See speakAllWithSynchronizedHighlighting

    +

    See speak

    @@ -2843,7 +2843,7 @@

    resumeReadAloud()< -

    revertToOriginalContent(element: string | Element | MathField, options?: [string]:any)

    +

    revertToOriginalContent(element: string | HTMLElement | MathField, options?: [string]:any)

    @@ -2875,7 +2875,7 @@

    revertToOriginalContentstring | -Element +HTMLElement | MathField @@ -3144,7 +3144,7 @@

    highlightAtomID(a -

    renderMathInDocument(options?: object)inner

    +

    renderMathInDocument(options?: object.<string, any>)inner

    @@ -3183,7 +3183,7 @@

    renderMathInDocument : -object +object.<string, any> diff --git a/docs/tutorial-COMMANDS_REFERENCE.html b/docs/tutorial-COMMANDS_REFERENCE.html index 6a6ae9ee0..0c76dc47c 100644 --- a/docs/tutorial-COMMANDS_REFERENCE.html +++ b/docs/tutorial-COMMANDS_REFERENCE.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    diff --git a/docs/tutorial-CONFIG.html b/docs/tutorial-CONFIG.html index c01305cc6..15ff85531 100644 --- a/docs/tutorial-CONFIG.html +++ b/docs/tutorial-CONFIG.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    @@ -176,9 +176,9 @@

    Configuration options

    (but not limits for sum, integrals, etc...)

    This can make it easier to enter equations that fit what's expected for the domain where the mathfield is used.

    -

    To control the depth of superscript and subscript indepdently, provide an array: +

    To control the depth of superscript and subscript independently, provide an array: the first element indicate the maximum depth for subscript and the second element -the depth of superscript. Thus, a value of [0, 1] would supress the entry of +the depth of superscript. Thus, a value of [0, 1] would suppress the entry of subscripts, and allow one level of superscripts.


    config.removeExtraneousParentheses=true:boolean

    diff --git a/docs/tutorial-CONTRIBUTOR_GUIDE.html b/docs/tutorial-CONTRIBUTOR_GUIDE.html index 00c7a89d4..c13dcedb9 100644 --- a/docs/tutorial-CONTRIBUTOR_GUIDE.html +++ b/docs/tutorial-CONTRIBUTOR_GUIDE.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    diff --git a/docs/tutorial-EXAMPLES.html b/docs/tutorial-EXAMPLES.html index b1a0f4241..70b5afb8b 100644 --- a/docs/tutorial-EXAMPLES.html +++ b/docs/tutorial-EXAMPLES.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    diff --git a/docs/tutorial-MASTON.html b/docs/tutorial-MASTON.html index f1124af3d..f2c8acfd6 100644 --- a/docs/tutorial-MASTON.html +++ b/docs/tutorial-MASTON.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    diff --git a/docs/tutorial-SELECTORS.html b/docs/tutorial-SELECTORS.html index 6c152702c..57c39709f 100644 --- a/docs/tutorial-SELECTORS.html +++ b/docs/tutorial-SELECTORS.html @@ -40,7 +40,7 @@

    Tutorials

    Modules

    Classes

    Globals

    MathLive Docs
    @@ -426,36 +426,8 @@

    Speech

    -speakSelection - - - -speakParent - - - -speakRightSibling - - - -speakLeftSibling - - - -speakGroup - - - -speakAll - - - -speakSelectionWithSynchronizedHighlighting - - - -speakAllWithSynchronizedHighlighting - +speak +speaks the amount specified by the first parameter. diff --git a/docs/tutorial-USAGE_GUIDE.html b/docs/tutorial-USAGE_GUIDE.html index 59c457c6e..e405e8fdd 100644 --- a/docs/tutorial-USAGE_GUIDE.html +++ b/docs/tutorial-USAGE_GUIDE.html @@ -40,7 +40,7 @@ @@ -343,11 +343,17 @@

    Arrays

    Speech

      -
    • speakAll
    • -
    • speakSelection
    • -
    • speakParent
    • -
    • speakGroup
    • -
    • speakLeftSibling, speakRightSibling
    • +
    • speak This selector takes two arguments. The first argument is a string that determines what should be spoken. The valid values are: +
        +
      • all
      • +
      • left
      • +
      • right
      • +
      • selection
      • +
      • parent
      • +
      • group +The second parameter determines whether what is being spoken should be highlighted. It is an object: {withHighlighting: boolean} (default is false). Note: highlighting currently only works when connected to Amazon's AWS speech synthesizer.
      • +
      +

    Virtual Keyboards

    Entry of expressions can be accomplished using a standard keyboard. In addition diff --git a/examples/common.css b/examples/common.css index cdf507697..a310dba4d 100644 --- a/examples/common.css +++ b/examples/common.css @@ -476,7 +476,7 @@ div#content > header h1, div#content > header h2 { text-transform: uppercase; padding-left:1rem; padding-right:1rem; - margin-left: auto; + margin-left: 40px; margin-right: auto; letter-spacing: -.04ex; text-rendering: optimizeLegibility; diff --git a/examples/index.html b/examples/index.html index 06b8462f8..4ab09ec51 100644 --- a/examples/index.html +++ b/examples/index.html @@ -2,7 +2,7 @@ - MathLive examples + MathLive Examples @@ -19,7 +19,15 @@

    MathLive Examples

    Basic

    -

    A very basic example showing how to create and interact with a MathLive mathfield.

    +

    With a few lines of code you can add interactive math fields to your web page.

    +

    Use your keyboard to type math symbols. They will be typeset using the TeX math layout + rules. +

    +

    Press alt/option V to add a root. Use ^ for exponents.

    +

    Edit and navigate formulas using arrow keys. Most of the keyboard shortcuts you would + use to edit a text field work as well. +

    +

    Get started with this basic example.

    - Shortcuts

    + Multiple Mathfields -

    How to customize keystroke and inline shortcuts.

    +

    You can have multiple mathfields on the page. They are navigable and accessible.

    +

    You can customize how each mathfield behaves with regard to their onscreen virtual keyboard: + always off, if you want to implement your own UI, manual, if you + want to display a toggle to show or hide the keyboard, or onfocus + so that the keyboard appears as soon as the mathfield is focused.

    @@ -56,7 +74,10 @@

    VueJS

    -

    How to use the MathLive VueJS wrapper

    +

    MathLive uses native Javascript and does not depend on third-party libraries

    +

    You can use it with the framewok of your choice. This example provides + a wrapper for VueJS. Others are available for React. +