@khanacademy/math-input 16.5.0 → 17.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/components/input/math-wrapper.d.ts +3 -2
- package/dist/components/input/mathquill-helpers.d.ts +3 -4
- package/dist/components/input/mathquill-instance.d.ts +2 -2
- package/dist/components/input/mathquill-types.d.ts +4 -290
- package/dist/es/index.css +209 -104
- package/dist/es/index.js +62 -81
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +209 -104
- package/dist/index.js +62 -81
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/__tests__/integration.test.tsx +25 -13
- package/src/components/input/__tests__/mathquill.test.ts +37 -35
- package/src/components/input/__tests__/test-math-wrapper.ts +1 -2
- package/src/components/input/math-input.tsx +3 -18
- package/src/components/input/math-wrapper.ts +9 -16
- package/src/components/input/mathquill-helpers.ts +15 -26
- package/src/components/input/mathquill-instance.ts +71 -71
- package/src/components/input/mathquill-types.ts +4 -334
- package/src/components/key-handlers/handle-arrow.ts +1 -2
- package/src/components/key-handlers/handle-backspace.ts +6 -7
- package/src/components/key-handlers/handle-exponent.ts +1 -2
- package/src/components/key-handlers/handle-jump-out.ts +1 -2
- package/src/components/key-handlers/key-translator.ts +1 -1
- package/src/components/keypad/__tests__/keypad-v2-mathquill.test.tsx +11 -5
- package/src/components/keypad/keypad-mathquill.stories.tsx +1 -1
- package/tsconfig-build.tsbuildinfo +1 -1
package/dist/es/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import PropTypes from 'prop-types';
|
|
|
17
17
|
|
|
18
18
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
19
19
|
const libName = "@khanacademy/math-input";
|
|
20
|
-
const libVersion = "
|
|
20
|
+
const libVersion = "17.0.0";
|
|
21
21
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
22
22
|
|
|
23
23
|
function _extends() {
|
|
@@ -341,39 +341,37 @@ let CursorContext = /*#__PURE__*/function (CursorContext) {
|
|
|
341
341
|
|
|
342
342
|
// We only need one MathQuill instance (referred to as MQ in the docs)
|
|
343
343
|
// and that contains some MQ constants and the MathField constructor
|
|
344
|
-
const mathQuillInstance = MathQuill.getInterface(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
};
|
|
376
|
-
}
|
|
344
|
+
const mathQuillInstance = MathQuill.getInterface(3);
|
|
345
|
+
const createBaseConfig = () => ({
|
|
346
|
+
// LaTeX commands that, when typed, are immediately replaced by the
|
|
347
|
+
// appropriate symbol. This does not include ln, log, or any of the
|
|
348
|
+
// trig functions; those are always interpreted as commands.
|
|
349
|
+
autoCommands: "pi theta phi sqrt nthroot",
|
|
350
|
+
// Most of these autoOperatorNames are simply the MathQuill defaults.
|
|
351
|
+
// We have to list them all in order to add the `sen` operator (see
|
|
352
|
+
// comment below).
|
|
353
|
+
autoOperatorNames: ["arccos", "arcsin", "arctan", "arg", "cos", "cosh", "cot", "coth", "csc", "deg", "det", "dim", "exp", "gcd", "hom", "inf", "ker", "lg", "lim", "liminf", "limsup", "ln", "log", "max", "min", "Pr", "projlim", "sec",
|
|
354
|
+
// sen is used instead of sin in e.g. Portuguese
|
|
355
|
+
"sen", "sin", "sinh", "sup", "tan", "tanh"].join(" "),
|
|
356
|
+
// Pop the cursor out of super/subscripts on arithmetic operators
|
|
357
|
+
// or (in)equalities.
|
|
358
|
+
charsThatBreakOutOfSupSub: "+-*/=<>≠≤≥",
|
|
359
|
+
// Prevent excessive super/subscripts or fractions from being
|
|
360
|
+
// created without operands, e.g. when somebody holds down a key
|
|
361
|
+
supSubsRequireOperand: true,
|
|
362
|
+
// The name of this option is somewhat misleading, as tabbing in
|
|
363
|
+
// MathQuill breaks you out of a nested context (fraction/script)
|
|
364
|
+
// if you're in one, but moves focus to the next input if you're
|
|
365
|
+
// not. Spaces (with this option enabled) are just ignored in the
|
|
366
|
+
// latter case.
|
|
367
|
+
//
|
|
368
|
+
// TODO(alex): In order to allow inputting mixed numbers, we will
|
|
369
|
+
// have to accept spaces in certain cases. The desired behavior is
|
|
370
|
+
// still to escape nested contexts if currently in one, but to
|
|
371
|
+
// insert a space if not (we don't expect mixed numbers in nested
|
|
372
|
+
// contexts). We should also limit to one consecutive space.
|
|
373
|
+
spaceBehavesLikeTab: true
|
|
374
|
+
});
|
|
377
375
|
|
|
378
376
|
/**
|
|
379
377
|
* Creates a new [MathField](http://docs.mathquill.com/en/latest/Api_Methods/#mqmathfieldhtml_element-config)
|
|
@@ -419,29 +417,27 @@ const Letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M"
|
|
|
419
417
|
// We only consider numerals, variables, and Greek Letters to be proper
|
|
420
418
|
// leaf nodes.
|
|
421
419
|
const ValidLeaves = [...Numerals, ...GreekLetters, ...Letters.map(letter => letter.toLowerCase()), ...Letters.map(letter => letter.toUpperCase())];
|
|
422
|
-
|
|
423
|
-
return mathField.__controller.cursor;
|
|
424
|
-
}
|
|
420
|
+
const mqNodeHasClass = (node, className) => node._el && node._el.classList.contains(className);
|
|
425
421
|
function isFraction(node) {
|
|
426
|
-
return node
|
|
422
|
+
return mqNodeHasClass(node, "mq-fraction");
|
|
427
423
|
}
|
|
428
424
|
function isNumerator(node) {
|
|
429
|
-
return node
|
|
425
|
+
return mqNodeHasClass(node, "mq-numerator");
|
|
430
426
|
}
|
|
431
427
|
function isDenominator(node) {
|
|
432
|
-
return node
|
|
428
|
+
return mqNodeHasClass(node, "mq-denominator");
|
|
433
429
|
}
|
|
434
430
|
function isSubScript(node) {
|
|
435
431
|
// NOTE(charlie): MyScript has a structure whereby its superscripts seem
|
|
436
432
|
// to be represented as a parent node with 'mq-sup-only' containing a
|
|
437
433
|
// single child with 'mq-sup'.
|
|
438
|
-
return node
|
|
434
|
+
return mqNodeHasClass(node, "mq-sub-only") || mqNodeHasClass(node, "mq-sub");
|
|
439
435
|
}
|
|
440
436
|
function isSuperScript(node) {
|
|
441
437
|
// NOTE(charlie): MyScript has a structure whereby its superscripts seem
|
|
442
438
|
// to be represented as a parent node with 'mq-sup-only' containing a
|
|
443
439
|
// single child with 'mq-sup'.
|
|
444
|
-
return node
|
|
440
|
+
return mqNodeHasClass(node, "mq-sup-only") || mqNodeHasClass(node, "mq-sup");
|
|
445
441
|
}
|
|
446
442
|
function isParens(node) {
|
|
447
443
|
return node && node.ctrlSeq === "\\left(";
|
|
@@ -450,17 +446,17 @@ function isLeaf(node) {
|
|
|
450
446
|
return node && node.ctrlSeq && ValidLeaves.includes(node.ctrlSeq.trim());
|
|
451
447
|
}
|
|
452
448
|
function isSquareRoot(node) {
|
|
453
|
-
return node.blocks && node.blocks[0]
|
|
449
|
+
return node.blocks && mqNodeHasClass(node.blocks[0], "mq-sqrt-stem");
|
|
454
450
|
}
|
|
455
451
|
function isNthRoot(node) {
|
|
456
|
-
return node.blocks && node.blocks[0]
|
|
452
|
+
return node.blocks && mqNodeHasClass(node.blocks[0], "mq-nthroot");
|
|
457
453
|
}
|
|
458
454
|
function isNthRootIndex(node) {
|
|
459
|
-
return node
|
|
455
|
+
return mqNodeHasClass(node, "mq-nthroot");
|
|
460
456
|
}
|
|
461
457
|
function isInsideLogIndex(cursor) {
|
|
462
458
|
const grandparent = cursor.parent.parent;
|
|
463
|
-
if (grandparent && grandparent
|
|
459
|
+
if (grandparent && mqNodeHasClass(grandparent, "mq-supsub")) {
|
|
464
460
|
const command = maybeFindCommandBeforeParens(grandparent);
|
|
465
461
|
if (command && command.name === "\\log") {
|
|
466
462
|
return true;
|
|
@@ -584,7 +580,7 @@ function getCursorContext(mathField) {
|
|
|
584
580
|
}
|
|
585
581
|
|
|
586
582
|
// First, try to find any fraction to the right, unimpeded.
|
|
587
|
-
const cursor =
|
|
583
|
+
const cursor = mathField.cursor();
|
|
588
584
|
let visitor = cursor;
|
|
589
585
|
while (visitor[mathQuillInstance.R] !== MathFieldActionType.MQ_END) {
|
|
590
586
|
if (isFraction(visitor[mathQuillInstance.R])) {
|
|
@@ -634,7 +630,7 @@ function handleBackspaceInRootIndex(mathField, cursor) {
|
|
|
634
630
|
const latex = grandparent.latex();
|
|
635
631
|
const reinsertionPoint = grandparent[mathQuillInstance.L];
|
|
636
632
|
selectNode(grandparent, cursor);
|
|
637
|
-
const rootIsEmpty = grandparent.blocks[1].
|
|
633
|
+
const rootIsEmpty = grandparent.blocks[1]._el.textContent === "";
|
|
638
634
|
if (rootIsEmpty) {
|
|
639
635
|
// If there is not content under the root then simply delete
|
|
640
636
|
// the whole thing.
|
|
@@ -651,7 +647,7 @@ function handleBackspaceInRootIndex(mathField, cursor) {
|
|
|
651
647
|
|
|
652
648
|
// Adjust the cursor to be to the left the sqrt.
|
|
653
649
|
if (reinsertionPoint === MathFieldActionType.MQ_END) {
|
|
654
|
-
mathField.
|
|
650
|
+
mathField.moveToLeftEnd();
|
|
655
651
|
} else {
|
|
656
652
|
cursor.insRightOf(reinsertionPoint);
|
|
657
653
|
}
|
|
@@ -677,7 +673,7 @@ function handleBackspaceInLogIndex(mathField, cursor) {
|
|
|
677
673
|
}
|
|
678
674
|
cursor.select();
|
|
679
675
|
cursor.endSelection();
|
|
680
|
-
const isLogBodyEmpty = grandparent[mathQuillInstance.R].
|
|
676
|
+
const isLogBodyEmpty = grandparent[mathQuillInstance.R]._el.textContent === "";
|
|
681
677
|
if (isLogBodyEmpty) {
|
|
682
678
|
// If there's no content inside the log's parens then delete the
|
|
683
679
|
// whole thing.
|
|
@@ -758,7 +754,7 @@ function handleBackspaceInsideParens(mathField, cursor) {
|
|
|
758
754
|
|
|
759
755
|
if (grandparent[mathQuillInstance.L].sub) {
|
|
760
756
|
// if there is a subscript
|
|
761
|
-
if (grandparent[mathQuillInstance.L].sub.
|
|
757
|
+
if (grandparent[mathQuillInstance.L].sub._el.textContent) {
|
|
762
758
|
// and it contains text
|
|
763
759
|
// move the cursor to the right end of the subscript
|
|
764
760
|
cursor.insAtRightEnd(grandparent[mathQuillInstance.L].sub);
|
|
@@ -795,7 +791,7 @@ function handleBackspaceAfterLigaturedSymbol(mathField) {
|
|
|
795
791
|
* See inline comments for precise behavior of different cases.
|
|
796
792
|
*/
|
|
797
793
|
function handleBackspace(mathField) {
|
|
798
|
-
const cursor =
|
|
794
|
+
const cursor = mathField.cursor();
|
|
799
795
|
if (!cursor.selection) {
|
|
800
796
|
const parent = cursor.parent;
|
|
801
797
|
const grandparent = parent.parent;
|
|
@@ -904,7 +900,7 @@ function handleRightArrow(mathField, cursor) {
|
|
|
904
900
|
}
|
|
905
901
|
}
|
|
906
902
|
function handleArrow(mathField, key) {
|
|
907
|
-
const cursor =
|
|
903
|
+
const cursor = mathField.cursor();
|
|
908
904
|
if (key === "LEFT") {
|
|
909
905
|
handleLeftArrow(mathField, cursor);
|
|
910
906
|
} else if (key === "RIGHT") {
|
|
@@ -915,7 +911,7 @@ function handleArrow(mathField, key) {
|
|
|
915
911
|
const ArithmeticOperators = ["+", "-", "\\cdot", "\\times", "\\div"];
|
|
916
912
|
const EqualityOperators = ["=", "\\neq", "<", "\\leq", ">", "\\geq"];
|
|
917
913
|
function handleExponent(mathField, key) {
|
|
918
|
-
const cursor =
|
|
914
|
+
const cursor = mathField.cursor();
|
|
919
915
|
// If there's an invalid operator preceding the cursor (anything that
|
|
920
916
|
// knowingly cannot be raised to a power), add an empty set of
|
|
921
917
|
// parentheses and apply the exponent to that.
|
|
@@ -964,7 +960,7 @@ const KeysForJumpContext = {
|
|
|
964
960
|
* Advances the cursor to the next logical position.
|
|
965
961
|
*/
|
|
966
962
|
function handleJumpOut(mathField, key) {
|
|
967
|
-
const cursor =
|
|
963
|
+
const cursor = mathField.cursor();
|
|
968
964
|
const context = getCursorContext(mathField);
|
|
969
965
|
|
|
970
966
|
// Validate that the current cursor context matches the key's intent.
|
|
@@ -1119,7 +1115,7 @@ const keyToMathquillMap = {
|
|
|
1119
1115
|
},
|
|
1120
1116
|
|
|
1121
1117
|
FRAC_EXCLUSIVE: mathQuill => {
|
|
1122
|
-
const cursor = mathQuill.
|
|
1118
|
+
const cursor = mathQuill.cursor();
|
|
1123
1119
|
// If there's nothing to the left of the cursor, then we want to
|
|
1124
1120
|
// leave the cursor to the left of the fraction after creating it.
|
|
1125
1121
|
const shouldNavigateLeft = cursor[mathQuillInstance.L] === ActionType.MQ_END;
|
|
@@ -1261,18 +1257,16 @@ class MathWrapper {
|
|
|
1261
1257
|
// HACK(charlie): We shouldn't reaching into MathQuill internals like
|
|
1262
1258
|
// this, but it's the easiest way to allow us to manage the focus state
|
|
1263
1259
|
// ourselves.
|
|
1264
|
-
|
|
1265
|
-
controller.cursor.show();
|
|
1260
|
+
this.mathField.cursor().show();
|
|
1266
1261
|
|
|
1267
1262
|
// Set MathQuill's internal state to reflect the focus, otherwise it
|
|
1268
1263
|
// will consistently try to hide the cursor on key-press and introduce
|
|
1269
1264
|
// layout jank.
|
|
1270
|
-
|
|
1265
|
+
this.mathField.focus();
|
|
1271
1266
|
}
|
|
1272
1267
|
blur() {
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
controller.blurred = true;
|
|
1268
|
+
this.mathField.cursor().hide();
|
|
1269
|
+
this.mathField.blur();
|
|
1276
1270
|
}
|
|
1277
1271
|
|
|
1278
1272
|
/**
|
|
@@ -1320,10 +1314,10 @@ class MathWrapper {
|
|
|
1320
1314
|
if (el.hasAttribute("mq-root-block")) {
|
|
1321
1315
|
// If we're in the empty area place the cursor at the right
|
|
1322
1316
|
// end of the expression.
|
|
1323
|
-
cursor.insAtRightEnd(this.mathField.
|
|
1317
|
+
cursor.insAtRightEnd(this.mathField.controller().root);
|
|
1324
1318
|
} else {
|
|
1325
1319
|
// Otherwise place beside the element at x, y.
|
|
1326
|
-
const controller = this.mathField.
|
|
1320
|
+
const controller = this.mathField.controller();
|
|
1327
1321
|
const pageX = x - document.body.scrollLeft;
|
|
1328
1322
|
const pageY = y - document.body.scrollTop;
|
|
1329
1323
|
controller.seek($(el), pageX, pageY).cursor.startSelection();
|
|
@@ -1349,7 +1343,7 @@ class MathWrapper {
|
|
|
1349
1343
|
// note(Matthew): extracted this logic to share it elsewhere,
|
|
1350
1344
|
// but it's part of the public MathWrapper API
|
|
1351
1345
|
getCursor() {
|
|
1352
|
-
return
|
|
1346
|
+
return this.mathField.cursor();
|
|
1353
1347
|
}
|
|
1354
1348
|
|
|
1355
1349
|
// note(Matthew): extracted this logic to keep this file focused,
|
|
@@ -1686,7 +1680,7 @@ class MathInput extends React.Component {
|
|
|
1686
1680
|
// Pre-emptively check if the input has any child nodes; if not, the
|
|
1687
1681
|
// input is empty, so we throw the cursor at the start.
|
|
1688
1682
|
if (!this._root.hasChildNodes()) {
|
|
1689
|
-
cursor.insAtLeftEnd(this.mathField.mathField.
|
|
1683
|
+
cursor.insAtLeftEnd(this.mathField.mathField.controller().root);
|
|
1690
1684
|
return;
|
|
1691
1685
|
}
|
|
1692
1686
|
|
|
@@ -1734,9 +1728,9 @@ class MathInput extends React.Component {
|
|
|
1734
1728
|
// or left of all of the math, so we place the cursor at the end to
|
|
1735
1729
|
// which it's closest.
|
|
1736
1730
|
if (Math.abs(x - right) < Math.abs(x - left)) {
|
|
1737
|
-
cursor.insAtRightEnd(this.mathField.mathField.
|
|
1731
|
+
cursor.insAtRightEnd(this.mathField.mathField.controller().root);
|
|
1738
1732
|
} else {
|
|
1739
|
-
cursor.insAtLeftEnd(this.mathField.mathField.
|
|
1733
|
+
cursor.insAtLeftEnd(this.mathField.mathField.controller().root);
|
|
1740
1734
|
}
|
|
1741
1735
|
// In that event, we need to update the cursor context ourselves.
|
|
1742
1736
|
this.props.keypadElement && this.props.keypadElement.setCursor({
|
|
@@ -2003,19 +1997,6 @@ class MathInput extends React.Component {
|
|
|
2003
1997
|
this.props.keypadElement && this.props.keypadElement.setCursor(cursor);
|
|
2004
1998
|
}
|
|
2005
1999
|
});
|
|
2006
|
-
|
|
2007
|
-
// NOTE(charlie): MathQuill binds this handler to manage its
|
|
2008
|
-
// drag-to-select behavior. For reasons that I can't explain, the event
|
|
2009
|
-
// itself gets triggered even if you tap slightly outside of the
|
|
2010
|
-
// bound container (maybe 5px outside of any boundary). As a result, the
|
|
2011
|
-
// cursor appears when tapping at those locations, even though the input
|
|
2012
|
-
// itself doesn't receive any touch start or mouse down event and, as
|
|
2013
|
-
// such, doesn't focus itself. This makes for a confusing UX, as the
|
|
2014
|
-
// cursor appears, but the keypad does not and the input otherwise
|
|
2015
|
-
// treats itself as unfocused. Thankfully, we don't need this behavior--
|
|
2016
|
-
// we manage all of the cursor interactions ourselves--so we can safely
|
|
2017
|
-
// unbind the handler.
|
|
2018
|
-
this.mathField.mathField.__controller.container.unbind("mousedown.mathquill");
|
|
2019
2000
|
this.mathField.setContent(this.props.value);
|
|
2020
2001
|
this._updateInputPadding();
|
|
2021
2002
|
this._container = ReactDOM.findDOMNode(this);
|