@khanacademy/math-input 16.5.1 → 17.0.1
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 +67 -86
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +209 -104
- package/dist/index.js +67 -86
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- 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__/__snapshots__/keypad.test.tsx.snap +34 -34
- package/src/components/keypad/__tests__/keypad-v2-mathquill.test.tsx +11 -5
- package/src/components/keypad/keypad-mathquill.stories.tsx +1 -1
- package/src/components/keypad/shared-keys.tsx +6 -6
- package/src/full-keypad.stories.tsx +14 -0
- package/tsconfig-build.tsbuildinfo +1 -1
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
|
|
|
48
48
|
|
|
49
49
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
50
50
|
const libName = "@khanacademy/math-input";
|
|
51
|
-
const libVersion = "
|
|
51
|
+
const libVersion = "17.0.1";
|
|
52
52
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
53
53
|
|
|
54
54
|
function _extends() {
|
|
@@ -373,39 +373,37 @@ let CursorContext = /*#__PURE__*/function (CursorContext) {
|
|
|
373
373
|
|
|
374
374
|
// We only need one MathQuill instance (referred to as MQ in the docs)
|
|
375
375
|
// and that contains some MQ constants and the MathField constructor
|
|
376
|
-
const mathQuillInstance = MathQuill__default["default"].getInterface(
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
};
|
|
408
|
-
}
|
|
376
|
+
const mathQuillInstance = MathQuill__default["default"].getInterface(3);
|
|
377
|
+
const createBaseConfig = () => ({
|
|
378
|
+
// LaTeX commands that, when typed, are immediately replaced by the
|
|
379
|
+
// appropriate symbol. This does not include ln, log, or any of the
|
|
380
|
+
// trig functions; those are always interpreted as commands.
|
|
381
|
+
autoCommands: "pi theta phi sqrt nthroot",
|
|
382
|
+
// Most of these autoOperatorNames are simply the MathQuill defaults.
|
|
383
|
+
// We have to list them all in order to add the `sen` operator (see
|
|
384
|
+
// comment below).
|
|
385
|
+
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",
|
|
386
|
+
// sen is used instead of sin in e.g. Portuguese
|
|
387
|
+
"sen", "sin", "sinh", "sup", "tan", "tanh"].join(" "),
|
|
388
|
+
// Pop the cursor out of super/subscripts on arithmetic operators
|
|
389
|
+
// or (in)equalities.
|
|
390
|
+
charsThatBreakOutOfSupSub: "+-*/=<>≠≤≥",
|
|
391
|
+
// Prevent excessive super/subscripts or fractions from being
|
|
392
|
+
// created without operands, e.g. when somebody holds down a key
|
|
393
|
+
supSubsRequireOperand: true,
|
|
394
|
+
// The name of this option is somewhat misleading, as tabbing in
|
|
395
|
+
// MathQuill breaks you out of a nested context (fraction/script)
|
|
396
|
+
// if you're in one, but moves focus to the next input if you're
|
|
397
|
+
// not. Spaces (with this option enabled) are just ignored in the
|
|
398
|
+
// latter case.
|
|
399
|
+
//
|
|
400
|
+
// TODO(alex): In order to allow inputting mixed numbers, we will
|
|
401
|
+
// have to accept spaces in certain cases. The desired behavior is
|
|
402
|
+
// still to escape nested contexts if currently in one, but to
|
|
403
|
+
// insert a space if not (we don't expect mixed numbers in nested
|
|
404
|
+
// contexts). We should also limit to one consecutive space.
|
|
405
|
+
spaceBehavesLikeTab: true
|
|
406
|
+
});
|
|
409
407
|
|
|
410
408
|
/**
|
|
411
409
|
* Creates a new [MathField](http://docs.mathquill.com/en/latest/Api_Methods/#mqmathfieldhtml_element-config)
|
|
@@ -451,29 +449,27 @@ const Letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M"
|
|
|
451
449
|
// We only consider numerals, variables, and Greek Letters to be proper
|
|
452
450
|
// leaf nodes.
|
|
453
451
|
const ValidLeaves = [...Numerals, ...GreekLetters, ...Letters.map(letter => letter.toLowerCase()), ...Letters.map(letter => letter.toUpperCase())];
|
|
454
|
-
|
|
455
|
-
return mathField.__controller.cursor;
|
|
456
|
-
}
|
|
452
|
+
const mqNodeHasClass = (node, className) => node._el && node._el.classList.contains(className);
|
|
457
453
|
function isFraction(node) {
|
|
458
|
-
return node
|
|
454
|
+
return mqNodeHasClass(node, "mq-fraction");
|
|
459
455
|
}
|
|
460
456
|
function isNumerator(node) {
|
|
461
|
-
return node
|
|
457
|
+
return mqNodeHasClass(node, "mq-numerator");
|
|
462
458
|
}
|
|
463
459
|
function isDenominator(node) {
|
|
464
|
-
return node
|
|
460
|
+
return mqNodeHasClass(node, "mq-denominator");
|
|
465
461
|
}
|
|
466
462
|
function isSubScript(node) {
|
|
467
463
|
// NOTE(charlie): MyScript has a structure whereby its superscripts seem
|
|
468
464
|
// to be represented as a parent node with 'mq-sup-only' containing a
|
|
469
465
|
// single child with 'mq-sup'.
|
|
470
|
-
return node
|
|
466
|
+
return mqNodeHasClass(node, "mq-sub-only") || mqNodeHasClass(node, "mq-sub");
|
|
471
467
|
}
|
|
472
468
|
function isSuperScript(node) {
|
|
473
469
|
// NOTE(charlie): MyScript has a structure whereby its superscripts seem
|
|
474
470
|
// to be represented as a parent node with 'mq-sup-only' containing a
|
|
475
471
|
// single child with 'mq-sup'.
|
|
476
|
-
return node
|
|
472
|
+
return mqNodeHasClass(node, "mq-sup-only") || mqNodeHasClass(node, "mq-sup");
|
|
477
473
|
}
|
|
478
474
|
function isParens(node) {
|
|
479
475
|
return node && node.ctrlSeq === "\\left(";
|
|
@@ -482,17 +478,17 @@ function isLeaf(node) {
|
|
|
482
478
|
return node && node.ctrlSeq && ValidLeaves.includes(node.ctrlSeq.trim());
|
|
483
479
|
}
|
|
484
480
|
function isSquareRoot(node) {
|
|
485
|
-
return node.blocks && node.blocks[0]
|
|
481
|
+
return node.blocks && mqNodeHasClass(node.blocks[0], "mq-sqrt-stem");
|
|
486
482
|
}
|
|
487
483
|
function isNthRoot(node) {
|
|
488
|
-
return node.blocks && node.blocks[0]
|
|
484
|
+
return node.blocks && mqNodeHasClass(node.blocks[0], "mq-nthroot");
|
|
489
485
|
}
|
|
490
486
|
function isNthRootIndex(node) {
|
|
491
|
-
return node
|
|
487
|
+
return mqNodeHasClass(node, "mq-nthroot");
|
|
492
488
|
}
|
|
493
489
|
function isInsideLogIndex(cursor) {
|
|
494
490
|
const grandparent = cursor.parent.parent;
|
|
495
|
-
if (grandparent && grandparent
|
|
491
|
+
if (grandparent && mqNodeHasClass(grandparent, "mq-supsub")) {
|
|
496
492
|
const command = maybeFindCommandBeforeParens(grandparent);
|
|
497
493
|
if (command && command.name === "\\log") {
|
|
498
494
|
return true;
|
|
@@ -616,7 +612,7 @@ function getCursorContext(mathField) {
|
|
|
616
612
|
}
|
|
617
613
|
|
|
618
614
|
// First, try to find any fraction to the right, unimpeded.
|
|
619
|
-
const cursor =
|
|
615
|
+
const cursor = mathField.cursor();
|
|
620
616
|
let visitor = cursor;
|
|
621
617
|
while (visitor[mathQuillInstance.R] !== MathFieldActionType.MQ_END) {
|
|
622
618
|
if (isFraction(visitor[mathQuillInstance.R])) {
|
|
@@ -666,7 +662,7 @@ function handleBackspaceInRootIndex(mathField, cursor) {
|
|
|
666
662
|
const latex = grandparent.latex();
|
|
667
663
|
const reinsertionPoint = grandparent[mathQuillInstance.L];
|
|
668
664
|
selectNode(grandparent, cursor);
|
|
669
|
-
const rootIsEmpty = grandparent.blocks[1].
|
|
665
|
+
const rootIsEmpty = grandparent.blocks[1]._el.textContent === "";
|
|
670
666
|
if (rootIsEmpty) {
|
|
671
667
|
// If there is not content under the root then simply delete
|
|
672
668
|
// the whole thing.
|
|
@@ -683,7 +679,7 @@ function handleBackspaceInRootIndex(mathField, cursor) {
|
|
|
683
679
|
|
|
684
680
|
// Adjust the cursor to be to the left the sqrt.
|
|
685
681
|
if (reinsertionPoint === MathFieldActionType.MQ_END) {
|
|
686
|
-
mathField.
|
|
682
|
+
mathField.moveToLeftEnd();
|
|
687
683
|
} else {
|
|
688
684
|
cursor.insRightOf(reinsertionPoint);
|
|
689
685
|
}
|
|
@@ -709,7 +705,7 @@ function handleBackspaceInLogIndex(mathField, cursor) {
|
|
|
709
705
|
}
|
|
710
706
|
cursor.select();
|
|
711
707
|
cursor.endSelection();
|
|
712
|
-
const isLogBodyEmpty = grandparent[mathQuillInstance.R].
|
|
708
|
+
const isLogBodyEmpty = grandparent[mathQuillInstance.R]._el.textContent === "";
|
|
713
709
|
if (isLogBodyEmpty) {
|
|
714
710
|
// If there's no content inside the log's parens then delete the
|
|
715
711
|
// whole thing.
|
|
@@ -790,7 +786,7 @@ function handleBackspaceInsideParens(mathField, cursor) {
|
|
|
790
786
|
|
|
791
787
|
if (grandparent[mathQuillInstance.L].sub) {
|
|
792
788
|
// if there is a subscript
|
|
793
|
-
if (grandparent[mathQuillInstance.L].sub.
|
|
789
|
+
if (grandparent[mathQuillInstance.L].sub._el.textContent) {
|
|
794
790
|
// and it contains text
|
|
795
791
|
// move the cursor to the right end of the subscript
|
|
796
792
|
cursor.insAtRightEnd(grandparent[mathQuillInstance.L].sub);
|
|
@@ -827,7 +823,7 @@ function handleBackspaceAfterLigaturedSymbol(mathField) {
|
|
|
827
823
|
* See inline comments for precise behavior of different cases.
|
|
828
824
|
*/
|
|
829
825
|
function handleBackspace(mathField) {
|
|
830
|
-
const cursor =
|
|
826
|
+
const cursor = mathField.cursor();
|
|
831
827
|
if (!cursor.selection) {
|
|
832
828
|
const parent = cursor.parent;
|
|
833
829
|
const grandparent = parent.parent;
|
|
@@ -936,7 +932,7 @@ function handleRightArrow(mathField, cursor) {
|
|
|
936
932
|
}
|
|
937
933
|
}
|
|
938
934
|
function handleArrow(mathField, key) {
|
|
939
|
-
const cursor =
|
|
935
|
+
const cursor = mathField.cursor();
|
|
940
936
|
if (key === "LEFT") {
|
|
941
937
|
handleLeftArrow(mathField, cursor);
|
|
942
938
|
} else if (key === "RIGHT") {
|
|
@@ -947,7 +943,7 @@ function handleArrow(mathField, key) {
|
|
|
947
943
|
const ArithmeticOperators = ["+", "-", "\\cdot", "\\times", "\\div"];
|
|
948
944
|
const EqualityOperators = ["=", "\\neq", "<", "\\leq", ">", "\\geq"];
|
|
949
945
|
function handleExponent(mathField, key) {
|
|
950
|
-
const cursor =
|
|
946
|
+
const cursor = mathField.cursor();
|
|
951
947
|
// If there's an invalid operator preceding the cursor (anything that
|
|
952
948
|
// knowingly cannot be raised to a power), add an empty set of
|
|
953
949
|
// parentheses and apply the exponent to that.
|
|
@@ -996,7 +992,7 @@ const KeysForJumpContext = {
|
|
|
996
992
|
* Advances the cursor to the next logical position.
|
|
997
993
|
*/
|
|
998
994
|
function handleJumpOut(mathField, key) {
|
|
999
|
-
const cursor =
|
|
995
|
+
const cursor = mathField.cursor();
|
|
1000
996
|
const context = getCursorContext(mathField);
|
|
1001
997
|
|
|
1002
998
|
// Validate that the current cursor context matches the key's intent.
|
|
@@ -1152,7 +1148,7 @@ const keyToMathquillMap = {
|
|
|
1152
1148
|
},
|
|
1153
1149
|
|
|
1154
1150
|
FRAC_EXCLUSIVE: mathQuill => {
|
|
1155
|
-
const cursor = mathQuill.
|
|
1151
|
+
const cursor = mathQuill.cursor();
|
|
1156
1152
|
// If there's nothing to the left of the cursor, then we want to
|
|
1157
1153
|
// leave the cursor to the left of the fraction after creating it.
|
|
1158
1154
|
const shouldNavigateLeft = cursor[mathQuillInstance.L] === ActionType.MQ_END;
|
|
@@ -1296,18 +1292,16 @@ class MathWrapper {
|
|
|
1296
1292
|
// HACK(charlie): We shouldn't reaching into MathQuill internals like
|
|
1297
1293
|
// this, but it's the easiest way to allow us to manage the focus state
|
|
1298
1294
|
// ourselves.
|
|
1299
|
-
|
|
1300
|
-
controller.cursor.show();
|
|
1295
|
+
this.mathField.cursor().show();
|
|
1301
1296
|
|
|
1302
1297
|
// Set MathQuill's internal state to reflect the focus, otherwise it
|
|
1303
1298
|
// will consistently try to hide the cursor on key-press and introduce
|
|
1304
1299
|
// layout jank.
|
|
1305
|
-
|
|
1300
|
+
this.mathField.focus();
|
|
1306
1301
|
}
|
|
1307
1302
|
blur() {
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
controller.blurred = true;
|
|
1303
|
+
this.mathField.cursor().hide();
|
|
1304
|
+
this.mathField.blur();
|
|
1311
1305
|
}
|
|
1312
1306
|
|
|
1313
1307
|
/**
|
|
@@ -1355,10 +1349,10 @@ class MathWrapper {
|
|
|
1355
1349
|
if (el.hasAttribute("mq-root-block")) {
|
|
1356
1350
|
// If we're in the empty area place the cursor at the right
|
|
1357
1351
|
// end of the expression.
|
|
1358
|
-
cursor.insAtRightEnd(this.mathField.
|
|
1352
|
+
cursor.insAtRightEnd(this.mathField.controller().root);
|
|
1359
1353
|
} else {
|
|
1360
1354
|
// Otherwise place beside the element at x, y.
|
|
1361
|
-
const controller = this.mathField.
|
|
1355
|
+
const controller = this.mathField.controller();
|
|
1362
1356
|
const pageX = x - document.body.scrollLeft;
|
|
1363
1357
|
const pageY = y - document.body.scrollTop;
|
|
1364
1358
|
controller.seek($__default["default"](el), pageX, pageY).cursor.startSelection();
|
|
@@ -1384,7 +1378,7 @@ class MathWrapper {
|
|
|
1384
1378
|
// note(Matthew): extracted this logic to share it elsewhere,
|
|
1385
1379
|
// but it's part of the public MathWrapper API
|
|
1386
1380
|
getCursor() {
|
|
1387
|
-
return
|
|
1381
|
+
return this.mathField.cursor();
|
|
1388
1382
|
}
|
|
1389
1383
|
|
|
1390
1384
|
// note(Matthew): extracted this logic to keep this file focused,
|
|
@@ -1503,19 +1497,6 @@ class MathInput extends React__namespace.Component {
|
|
|
1503
1497
|
this.props.keypadElement && this.props.keypadElement.setCursor(cursor);
|
|
1504
1498
|
}
|
|
1505
1499
|
});
|
|
1506
|
-
|
|
1507
|
-
// NOTE(charlie): MathQuill binds this handler to manage its
|
|
1508
|
-
// drag-to-select behavior. For reasons that I can't explain, the event
|
|
1509
|
-
// itself gets triggered even if you tap slightly outside of the
|
|
1510
|
-
// bound container (maybe 5px outside of any boundary). As a result, the
|
|
1511
|
-
// cursor appears when tapping at those locations, even though the input
|
|
1512
|
-
// itself doesn't receive any touch start or mouse down event and, as
|
|
1513
|
-
// such, doesn't focus itself. This makes for a confusing UX, as the
|
|
1514
|
-
// cursor appears, but the keypad does not and the input otherwise
|
|
1515
|
-
// treats itself as unfocused. Thankfully, we don't need this behavior--
|
|
1516
|
-
// we manage all of the cursor interactions ourselves--so we can safely
|
|
1517
|
-
// unbind the handler.
|
|
1518
|
-
this.mathField.mathField.__controller.container.unbind("mousedown.mathquill");
|
|
1519
1500
|
this.mathField.setContent(this.props.value);
|
|
1520
1501
|
this._updateInputPadding();
|
|
1521
1502
|
this._container = ReactDOM__default["default"].findDOMNode(this);
|
|
@@ -1863,7 +1844,7 @@ class MathInput extends React__namespace.Component {
|
|
|
1863
1844
|
// Pre-emptively check if the input has any child nodes; if not, the
|
|
1864
1845
|
// input is empty, so we throw the cursor at the start.
|
|
1865
1846
|
if (!this._root.hasChildNodes()) {
|
|
1866
|
-
cursor.insAtLeftEnd(this.mathField.mathField.
|
|
1847
|
+
cursor.insAtLeftEnd(this.mathField.mathField.controller().root);
|
|
1867
1848
|
return;
|
|
1868
1849
|
}
|
|
1869
1850
|
|
|
@@ -1911,9 +1892,9 @@ class MathInput extends React__namespace.Component {
|
|
|
1911
1892
|
// or left of all of the math, so we place the cursor at the end to
|
|
1912
1893
|
// which it's closest.
|
|
1913
1894
|
if (Math.abs(x - right) < Math.abs(x - left)) {
|
|
1914
|
-
cursor.insAtRightEnd(this.mathField.mathField.
|
|
1895
|
+
cursor.insAtRightEnd(this.mathField.mathField.controller().root);
|
|
1915
1896
|
} else {
|
|
1916
|
-
cursor.insAtLeftEnd(this.mathField.mathField.
|
|
1897
|
+
cursor.insAtLeftEnd(this.mathField.mathField.controller().root);
|
|
1917
1898
|
}
|
|
1918
1899
|
// In that event, we need to update the cursor context ourselves.
|
|
1919
1900
|
this.props.keypadElement && this.props.keypadElement.setCursor({
|
|
@@ -5241,6 +5222,11 @@ function SharedKeys(props) {
|
|
|
5241
5222
|
// Fraction position depends on the page
|
|
5242
5223
|
const fractionCoord = selectedPage === "Numbers" || selectedPage === "Operators" ? [3, 1] : [3, 0];
|
|
5243
5224
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(KeypadButton, {
|
|
5225
|
+
keyConfig: KeyConfigs.FRAC,
|
|
5226
|
+
onClickKey: onClickKey,
|
|
5227
|
+
coord: fractionCoord,
|
|
5228
|
+
secondary: true
|
|
5229
|
+
}), /*#__PURE__*/React__namespace.createElement(KeypadButton, {
|
|
5244
5230
|
keyConfig: KeyConfigs.PLUS,
|
|
5245
5231
|
onClickKey: onClickKey,
|
|
5246
5232
|
coord: [4, 0],
|
|
@@ -5250,11 +5236,6 @@ function SharedKeys(props) {
|
|
|
5250
5236
|
onClickKey: onClickKey,
|
|
5251
5237
|
coord: [5, 0],
|
|
5252
5238
|
secondary: true
|
|
5253
|
-
}), /*#__PURE__*/React__namespace.createElement(KeypadButton, {
|
|
5254
|
-
keyConfig: KeyConfigs.FRAC,
|
|
5255
|
-
onClickKey: onClickKey,
|
|
5256
|
-
coord: fractionCoord,
|
|
5257
|
-
secondary: true
|
|
5258
5239
|
}), /*#__PURE__*/React__namespace.createElement(KeypadButton, {
|
|
5259
5240
|
keyConfig: convertDotToTimesByLocale(!!convertDotToTimes) ? KeyConfigs.TIMES : KeyConfigs.CDOT,
|
|
5260
5241
|
onClickKey: onClickKey,
|