@khanacademy/math-input 16.1.2 → 16.2.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/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 = "16.1.2";
51
+ const libVersion = "16.2.0";
52
52
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
53
53
 
54
54
  function _extends() {
@@ -1399,6 +1399,8 @@ class MathInput extends React__namespace.Component {
1399
1399
 
1400
1400
  // @ts-expect-error - TS2564 - Property 'blurOnTouchEndOutside' has no initializer and is not definitely assigned in the constructor.
1401
1401
 
1402
+ // @ts-expect-error - TS2564 - Property 'blurOnClickOutside' has no initializer and is not definitely assigned in the constructor.
1403
+
1402
1404
  // @ts-expect-error - TS2564 - Property '_container' has no initializer and is not definitely assigned in the constructor.
1403
1405
 
1404
1406
  // @ts-expect-error - TS2564 - Property '_containerBounds' has no initializer and is not definitely assigned in the constructor.
@@ -1446,6 +1448,16 @@ class MathInput extends React__namespace.Component {
1446
1448
  this._container = ReactDOM__default["default"].findDOMNode(this);
1447
1449
  this._root = this._container.querySelector(".mq-root-block");
1448
1450
  this._root.addEventListener("scroll", this._handleScroll);
1451
+ const isWithinKeypadBounds = (x, y) => {
1452
+ const bounds = this._getKeypadBounds();
1453
+
1454
+ // If there are no bounds, then the keypad is not mounted, so we
1455
+ // assume that the event is not within the keypad bounds.
1456
+ if (!bounds) {
1457
+ return false;
1458
+ }
1459
+ return bounds.left <= x && bounds.right >= x && bounds.top <= y && bounds.bottom >= y || bounds.bottom < y;
1460
+ };
1449
1461
 
1450
1462
  // Record the initial scroll displacement on touch start. This allows
1451
1463
  // us to detect whether a touch event was a scroll and only blur the
@@ -1463,10 +1475,9 @@ class MathInput extends React__namespace.Component {
1463
1475
  if (!this._container.contains(evt.target)) {
1464
1476
  let touchDidStartInOrBelowKeypad = false;
1465
1477
  if (this.props.keypadElement && this.props.keypadElement.getDOMNode()) {
1466
- const bounds = this._getKeypadBounds();
1467
1478
  for (let i = 0; i < evt.changedTouches.length; i++) {
1468
1479
  const [x, y] = [evt.changedTouches[i].clientX, evt.changedTouches[i].clientY];
1469
- if (bounds.left <= x && bounds.right >= x && bounds.top <= y && bounds.bottom >= y || bounds.bottom < y) {
1480
+ if (isWithinKeypadBounds(x, y)) {
1470
1481
  touchDidStartInOrBelowKeypad = true;
1471
1482
  break;
1472
1483
  }
@@ -1502,25 +1513,31 @@ class MathInput extends React__namespace.Component {
1502
1513
  this.dragListener.detach();
1503
1514
  }
1504
1515
  };
1516
+
1517
+ // We want to allow the user to blur the input by clicking outside of it
1518
+ // when using ChromeOS third-party browsers that use mobile user agents,
1519
+ // but don't actually simulate touch events.
1520
+ this.blurOnClickOutside = evt => {
1521
+ if (this.state.focused) {
1522
+ if (!this._container.contains(evt.target)) {
1523
+ if (this.props.keypadElement && this.props.keypadElement.getDOMNode()) {
1524
+ const [x, y] = [evt.clientX, evt.clientY];
1525
+ // We only want to blur if the click is above the keypad,
1526
+ // to the left of the keypad, or to the right of the keypad.
1527
+ // The reasoning for not blurring for any clicks below the keypad is
1528
+ // that the keypad may be anchored above the 'Check answer' bottom bar,
1529
+ // in which case we don't want to dismiss the keypad on check.
1530
+ if (!isWithinKeypadBounds(x, y)) {
1531
+ this.blur();
1532
+ }
1533
+ }
1534
+ }
1535
+ }
1536
+ };
1505
1537
  window.addEventListener("touchstart", this.recordTouchStartOutside);
1506
1538
  window.addEventListener("touchend", this.blurOnTouchEndOutside);
1507
1539
  window.addEventListener("touchcancel", this.blurOnTouchEndOutside);
1508
-
1509
- // HACK(benkomalo): if the window resizes, the keypad bounds can
1510
- // change. That's a bit peeking into the internals of the keypad
1511
- // itself, since we know bounds can change only when the viewport
1512
- // changes, but seems like a rare enough thing to get wrong that it's
1513
- // not worth wiring up extra things for the technical "purity" of
1514
- // having the keypad notify of changes to us.
1515
- window.addEventListener("resize", this._clearKeypadBoundsCache);
1516
- window.addEventListener("orientationchange", this._clearKeypadBoundsCache);
1517
- }
1518
-
1519
- // eslint-disable-next-line react/no-unsafe
1520
- UNSAFE_componentWillReceiveProps(props) {
1521
- if (this.props.keypadElement !== props.keypadElement) {
1522
- this._clearKeypadBoundsCache();
1523
- }
1540
+ window.addEventListener("click", this.blurOnClickOutside);
1524
1541
  }
1525
1542
  componentDidUpdate(prevProps, prevState) {
1526
1543
  if (this.mathField.getContent() !== this.props.value) {
@@ -1535,18 +1552,8 @@ class MathInput extends React__namespace.Component {
1535
1552
  window.removeEventListener("touchstart", this.recordTouchStartOutside);
1536
1553
  window.removeEventListener("touchend", this.blurOnTouchEndOutside);
1537
1554
  window.removeEventListener("touchcancel", this.blurOnTouchEndOutside);
1538
- // @ts-expect-error - TS2769 - No overload matches this call.
1539
- window.removeEventListener("resize", this._clearKeypadBoundsCache());
1540
- window.removeEventListener("orientationchange",
1541
- // @ts-expect-error - TS2769 - No overload matches this call.
1542
- this._clearKeypadBoundsCache());
1555
+ window.removeEventListener("click", this.blurOnClickOutside);
1543
1556
  }
1544
- _clearKeypadBoundsCache = () => {
1545
- this._keypadBounds = null;
1546
- };
1547
- _cacheKeypadBounds = keypadNode => {
1548
- this._keypadBounds = keypadNode.getBoundingClientRect();
1549
- };
1550
1557
  _updateInputPadding = () => {
1551
1558
  this._container = ReactDOM__default["default"].findDOMNode(this);
1552
1559
  this._root = this._container.querySelector(".mq-root-block");
@@ -1556,14 +1563,16 @@ class MathInput extends React__namespace.Component {
1556
1563
  this._root.style.fontSize = `${fontSizePt}pt`;
1557
1564
  };
1558
1565
 
1559
- /** Gets and cache they bounds of the keypadElement */
1560
- _getKeypadBounds = () => {
1561
- if (!this._keypadBounds) {
1562
- const node = this.props.keypadElement?.getDOMNode();
1563
- this._cacheKeypadBounds(node);
1566
+ /** Returns the current bounds of the keypadElement */
1567
+ _getKeypadBounds() {
1568
+ const keypadNode = this.props.keypadElement?.getDOMNode();
1569
+
1570
+ // If the keypad is mounted, return its bounds. Otherwise, return null.
1571
+ if (keypadNode instanceof Element) {
1572
+ return keypadNode.getBoundingClientRect();
1564
1573
  }
1565
- return this._keypadBounds;
1566
- };
1574
+ return null;
1575
+ }
1567
1576
  _updateCursorHandle = animateIntoPosition => {
1568
1577
  const containerBounds = this._container.getBoundingClientRect();
1569
1578
  const cursor = this._container.querySelector(".mq-cursor");
@@ -1861,6 +1870,32 @@ class MathInput extends React__namespace.Component {
1861
1870
  this.focus();
1862
1871
  }
1863
1872
  };
1873
+
1874
+ // We want to allow the user to be able to focus the input via click
1875
+ // when using ChromeOS third-party browsers that use mobile user agents,
1876
+ // but don't actually simulate touch events.
1877
+ handleClick = e => {
1878
+ e.stopPropagation();
1879
+
1880
+ // Hide the cursor handle on click
1881
+ this._hideCursorHandle();
1882
+
1883
+ // Cache the container bounds, so as to avoid re-computing. If we don't
1884
+ // have any content, then it's not necessary, since the cursor can't be
1885
+ // moved anyway.
1886
+ if (this.mathField.getContent() !== "") {
1887
+ this._containerBounds = this._container.getBoundingClientRect();
1888
+
1889
+ // Make the cursor visible and set the handle-less cursor's
1890
+ // location.
1891
+ this._insertCursorAtClosestNode(e.clientX, e.clientY);
1892
+ }
1893
+
1894
+ // Trigger a focus event, if we're not already focused.
1895
+ if (!this.state.focused) {
1896
+ this.focus();
1897
+ }
1898
+ };
1864
1899
  handleTouchMove = e => {
1865
1900
  e.stopPropagation();
1866
1901
 
@@ -2084,7 +2119,7 @@ class MathInput extends React__namespace.Component {
2084
2119
  onTouchStart: this.handleTouchStart,
2085
2120
  onTouchMove: this.handleTouchMove,
2086
2121
  onTouchEnd: this.handleTouchEnd,
2087
- onClick: e => e.stopPropagation(),
2122
+ onClick: this.handleClick,
2088
2123
  role: "textbox",
2089
2124
  ariaLabel: ariaLabel
2090
2125
  }, /*#__PURE__*/React__namespace.createElement("div", {