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 = '' + 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 = '' + ('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 = '' + 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 = '' + ('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) + '' + tag + '>';
+ 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 += "\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 = '';
- 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 = ""; //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 = ""; //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 += '
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.
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.
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
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.