@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/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 = "16.5.0";
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(2);
345
- function createBaseConfig() {
346
- return {
347
- // LaTeX commands that, when typed, are immediately replaced by the
348
- // appropriate symbol. This does not include ln, log, or any of the
349
- // trig functions; those are always interpreted as commands.
350
- autoCommands: "pi theta phi sqrt nthroot",
351
- // Most of these autoOperatorNames are simply the MathQuill defaults.
352
- // We have to list them all in order to add the `sen` operator (see
353
- // comment below).
354
- 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",
355
- // sen is used instead of sin in e.g. Portuguese
356
- "sen", "sin", "sinh", "sup", "tan", "tanh"].join(" "),
357
- // Pop the cursor out of super/subscripts on arithmetic operators
358
- // or (in)equalities.
359
- charsThatBreakOutOfSupSub: "+-*/=<>≠≤≥",
360
- // Prevent excessive super/subscripts or fractions from being
361
- // created without operands, e.g. when somebody holds down a key
362
- supSubsRequireOperand: true,
363
- // The name of this option is somewhat misleading, as tabbing in
364
- // MathQuill breaks you out of a nested context (fraction/script)
365
- // if you're in one, but moves focus to the next input if you're
366
- // not. Spaces (with this option enabled) are just ignored in the
367
- // latter case.
368
- //
369
- // TODO(alex): In order to allow inputting mixed numbers, we will
370
- // have to accept spaces in certain cases. The desired behavior is
371
- // still to escape nested contexts if currently in one, but to
372
- // insert a space if not (we don't expect mixed numbers in nested
373
- // contexts). We should also limit to one consecutive space.
374
- spaceBehavesLikeTab: true
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
- function getCursor(mathField) {
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.jQ && node.jQ.hasClass("mq-fraction");
422
+ return mqNodeHasClass(node, "mq-fraction");
427
423
  }
428
424
  function isNumerator(node) {
429
- return node.jQ && node.jQ.hasClass("mq-numerator");
425
+ return mqNodeHasClass(node, "mq-numerator");
430
426
  }
431
427
  function isDenominator(node) {
432
- return node.jQ && node.jQ.hasClass("mq-denominator");
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.jQ && (node.jQ.hasClass("mq-sub-only") || node.jQ.hasClass("mq-sub"));
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.jQ && (node.jQ.hasClass("mq-sup-only") || node.jQ.hasClass("mq-sup"));
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].jQ && node.blocks[0].jQ.hasClass("mq-sqrt-stem");
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].jQ && node.blocks[0].jQ.hasClass("mq-nthroot");
452
+ return node.blocks && mqNodeHasClass(node.blocks[0], "mq-nthroot");
457
453
  }
458
454
  function isNthRootIndex(node) {
459
- return node.jQ && node.jQ.hasClass("mq-nthroot");
455
+ return mqNodeHasClass(node, "mq-nthroot");
460
456
  }
461
457
  function isInsideLogIndex(cursor) {
462
458
  const grandparent = cursor.parent.parent;
463
- if (grandparent && grandparent.jQ.hasClass("mq-supsub")) {
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 = getCursor(mathField);
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].jQ.text() === "";
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.moveToDirEnd(mathQuillInstance.L);
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].contentjQ.text() === "";
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.jQ.text()) {
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 = getCursor(mathField);
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 = getCursor(mathField);
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 = getCursor(mathField);
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 = getCursor(mathField);
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.__controller.cursor;
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
- const controller = this.mathField.__controller;
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
- controller.blurred = false;
1265
+ this.mathField.focus();
1271
1266
  }
1272
1267
  blur() {
1273
- const controller = this.mathField.__controller;
1274
- controller.cursor.hide();
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.__controller.root);
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.__controller;
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 getCursor(this.mathField);
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.__controller.root);
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.__controller.root);
1731
+ cursor.insAtRightEnd(this.mathField.mathField.controller().root);
1738
1732
  } else {
1739
- cursor.insAtLeftEnd(this.mathField.mathField.__controller.root);
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);