@khanacademy/math-input 16.1.2 → 16.3.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @khanacademy/math-input
2
2
 
3
+ ## 16.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#863](https://github.com/Khan/perseus/pull/863) [`f910bd72`](https://github.com/Khan/perseus/commit/f910bd72fc5cbf88a1a00d57f8aefa8eea2c755d) Thanks [@handeyeco](https://github.com/handeyeco)! - Localize the multiplication symbol in MathInput
8
+
9
+ ## 16.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#853](https://github.com/Khan/perseus/pull/853) [`cbcc0e68`](https://github.com/Khan/perseus/commit/cbcc0e689b6d4640361c14ae112c476fb061d5f4) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Allowing click events on math-input in order to support third party browsers on ChromeOS.
14
+
15
+ * [#859](https://github.com/Khan/perseus/pull/859) [`e7bec961`](https://github.com/Khan/perseus/commit/e7bec961bc5136bcaeb4ebb4b6744b0809f372ec) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Ensure that we're always getting the current keypadBounds
16
+
3
17
  ## 16.1.2
4
18
 
5
19
  ### Patch Changes
@@ -28,6 +28,7 @@ declare class MathInput extends React.Component<Props, State> {
28
28
  mathField: any;
29
29
  recordTouchStartOutside: (arg1: any) => void;
30
30
  blurOnTouchEndOutside: (arg1: any) => void;
31
+ blurOnClickOutside: (arg1: any) => void;
31
32
  dragListener: any;
32
33
  inputRef: HTMLDivElement | null | undefined;
33
34
  _isMounted: boolean | null | undefined;
@@ -35,18 +36,14 @@ declare class MathInput extends React.Component<Props, State> {
35
36
  _container: HTMLDivElement;
36
37
  _root: any;
37
38
  _containerBounds: ClientRect;
38
- _keypadBounds: ClientRect | null | undefined;
39
39
  static defaultProps: DefaultProps;
40
40
  state: State;
41
41
  componentDidMount(): void;
42
- UNSAFE_componentWillReceiveProps(props: Props): void;
43
42
  componentDidUpdate(prevProps: Props, prevState: State): void;
44
43
  componentWillUnmount(): void;
45
- _clearKeypadBoundsCache: () => void;
46
- _cacheKeypadBounds: (arg1: any) => void;
47
44
  _updateInputPadding: () => void;
48
- /** Gets and cache they bounds of the keypadElement */
49
- _getKeypadBounds: () => any;
45
+ /** Returns the current bounds of the keypadElement */
46
+ _getKeypadBounds(): DOMRect | null;
50
47
  _updateCursorHandle: (arg1?: boolean) => void;
51
48
  _hideCursorHandle: () => void;
52
49
  _handleScroll: () => void;
@@ -86,6 +83,7 @@ declare class MathInput extends React.Component<Props, State> {
86
83
  */
87
84
  _insertCursorAtClosestNode: (arg1: number, arg2: number) => void;
88
85
  handleTouchStart: (arg1: React.TouchEvent<HTMLDivElement>) => void;
86
+ handleClick: (e: React.MouseEvent<HTMLDivElement>) => void;
89
87
  handleTouchMove: (arg1: React.TouchEvent<HTMLDivElement>) => void;
90
88
  handleTouchEnd: (arg1: React.TouchEvent<HTMLDivElement>) => void;
91
89
  /**
package/dist/es/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-core';
2
2
  import Color from '@khanacademy/wonder-blocks-color';
3
3
  import * as i18n from '@khanacademy/wonder-blocks-i18n';
4
- import { getDecimalSeparator } from '@khanacademy/wonder-blocks-i18n';
4
+ import { getDecimalSeparator, getLocale } from '@khanacademy/wonder-blocks-i18n';
5
5
  import { entries } from '@khanacademy/wonder-stuff-core';
6
6
  import { StyleSheet, css } from 'aphrodite';
7
7
  import * as React from 'react';
@@ -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.1.2";
20
+ const libVersion = "16.3.0";
21
21
  addLibraryVersionToPerseusDebug(libName, libVersion);
22
22
 
23
23
  function _extends() {
@@ -789,6 +789,30 @@ const DecimalSeparator = {
789
789
  // - Some languages/locales use different decimal separators than the ones
790
790
  // listed here. Much of the Arab world uses U+066C.
791
791
  const decimalSeparator = getDecimalSeparator() === "," ? DecimalSeparator.COMMA : DecimalSeparator.PERIOD;
792
+ const CDOT_ONLY = ["az", "cs", "da", "de", "hu", "hy", "kk", "ky", "lt", "lv", "nb", "sk", "sr", "sv", "uz"];
793
+ const TIMES_ONLY = ["fr", "tr", "pt-pt"];
794
+
795
+ /**
796
+ * convertDotToTimes (aka `times`) is an option the content creators have to
797
+ * use × (TIMES) rather than · (CDOT) for multiplication (for younger learners).
798
+ * Some locales _only_ use one or the other for all multiplication regardless
799
+ * of age.
800
+ *
801
+ * convertDotToTimesByLocale overrides convertDotToTimes for those locales.
802
+ *
803
+ * @param {boolean} convertDotToTimes - the setting set by content creators
804
+ * @returns {boolean} - true to convert to × (TIMES), false to use · (CDOT)
805
+ */
806
+ function convertDotToTimesByLocale(convertDotToTimes) {
807
+ const locale = getLocale();
808
+ if (CDOT_ONLY.includes(locale)) {
809
+ return false;
810
+ }
811
+ if (TIMES_ONLY.includes(locale)) {
812
+ return true;
813
+ }
814
+ return convertDotToTimes;
815
+ }
792
816
 
793
817
  function handleLeftArrow(mathField, cursor) {
794
818
  // If we're inside a function, and just after the left parentheses, we
@@ -1369,6 +1393,8 @@ class MathInput extends React.Component {
1369
1393
  this.recordTouchStartOutside = void 0;
1370
1394
  // @ts-expect-error - TS2564 - Property 'blurOnTouchEndOutside' has no initializer and is not definitely assigned in the constructor.
1371
1395
  this.blurOnTouchEndOutside = void 0;
1396
+ // @ts-expect-error - TS2564 - Property 'blurOnClickOutside' has no initializer and is not definitely assigned in the constructor.
1397
+ this.blurOnClickOutside = void 0;
1372
1398
  this.dragListener = void 0;
1373
1399
  this.inputRef = void 0;
1374
1400
  this._isMounted = void 0;
@@ -1378,7 +1404,6 @@ class MathInput extends React.Component {
1378
1404
  this._root = void 0;
1379
1405
  // @ts-expect-error - TS2564 - Property '_containerBounds' has no initializer and is not definitely assigned in the constructor.
1380
1406
  this._containerBounds = void 0;
1381
- this._keypadBounds = void 0;
1382
1407
  this.state = {
1383
1408
  focused: false,
1384
1409
  handle: {
@@ -1388,12 +1413,6 @@ class MathInput extends React.Component {
1388
1413
  y: 0
1389
1414
  }
1390
1415
  };
1391
- this._clearKeypadBoundsCache = () => {
1392
- this._keypadBounds = null;
1393
- };
1394
- this._cacheKeypadBounds = keypadNode => {
1395
- this._keypadBounds = keypadNode.getBoundingClientRect();
1396
- };
1397
1416
  this._updateInputPadding = () => {
1398
1417
  this._container = ReactDOM.findDOMNode(this);
1399
1418
  this._root = this._container.querySelector(".mq-root-block");
@@ -1402,15 +1421,6 @@ class MathInput extends React.Component {
1402
1421
  this._root.style.padding = `${padding.paddingTop}px ${padding.paddingRight}px` + ` ${padding.paddingBottom}px ${padding.paddingLeft}px`;
1403
1422
  this._root.style.fontSize = `${fontSizePt}pt`;
1404
1423
  };
1405
- /** Gets and cache they bounds of the keypadElement */
1406
- this._getKeypadBounds = () => {
1407
- if (!this._keypadBounds) {
1408
- var _this$props$keypadEle;
1409
- const node = (_this$props$keypadEle = this.props.keypadElement) == null ? void 0 : _this$props$keypadEle.getDOMNode();
1410
- this._cacheKeypadBounds(node);
1411
- }
1412
- return this._keypadBounds;
1413
- };
1414
1424
  this._updateCursorHandle = animateIntoPosition => {
1415
1425
  const containerBounds = this._container.getBoundingClientRect();
1416
1426
  const cursor = this._container.querySelector(".mq-cursor");
@@ -1470,10 +1480,10 @@ class MathInput extends React.Component {
1470
1480
  });
1471
1481
  };
1472
1482
  this.focus = () => {
1473
- var _this$props$keypadEle2, _this$props;
1483
+ var _this$props$keypadEle, _this$props;
1474
1484
  // Pass this component's handleKey method to the keypad so it can call
1475
1485
  // it whenever it needs to trigger a keypress action.
1476
- (_this$props$keypadEle2 = this.props.keypadElement) == null || _this$props$keypadEle2.setKeyHandler(key => {
1486
+ (_this$props$keypadEle = this.props.keypadElement) == null || _this$props$keypadEle.setKeyHandler(key => {
1477
1487
  const cursor = this.mathField.pressKey(key);
1478
1488
 
1479
1489
  // Trigger an `onChange` if the value in the input changed, and hide
@@ -1506,14 +1516,14 @@ class MathInput extends React.Component {
1506
1516
  // Android Browser 4.3.
1507
1517
  setTimeout(() => {
1508
1518
  if (this._isMounted) {
1509
- var _this$props$keypadEle3;
1519
+ var _this$props$keypadEle2;
1510
1520
  // TODO(benkomalo): the keypad is animating at this point,
1511
1521
  // so we can't call _cacheKeypadBounds(), even though
1512
1522
  // it'd be nice to do so. It should probably be the case
1513
1523
  // that the higher level controller tells us when the
1514
1524
  // keypad is settled (then scrollIntoView wouldn't have
1515
1525
  // to make assumptions about that either).
1516
- const maybeKeypadNode = (_this$props$keypadEle3 = this.props.keypadElement) == null ? void 0 : _this$props$keypadEle3.getDOMNode();
1526
+ const maybeKeypadNode = (_this$props$keypadEle2 = this.props.keypadElement) == null ? void 0 : _this$props$keypadEle2.getDOMNode();
1517
1527
  scrollIntoView(this._container, maybeKeypadNode);
1518
1528
  }
1519
1529
  });
@@ -1708,6 +1718,31 @@ class MathInput extends React.Component {
1708
1718
  this.focus();
1709
1719
  }
1710
1720
  };
1721
+ // We want to allow the user to be able to focus the input via click
1722
+ // when using ChromeOS third-party browsers that use mobile user agents,
1723
+ // but don't actually simulate touch events.
1724
+ this.handleClick = e => {
1725
+ e.stopPropagation();
1726
+
1727
+ // Hide the cursor handle on click
1728
+ this._hideCursorHandle();
1729
+
1730
+ // Cache the container bounds, so as to avoid re-computing. If we don't
1731
+ // have any content, then it's not necessary, since the cursor can't be
1732
+ // moved anyway.
1733
+ if (this.mathField.getContent() !== "") {
1734
+ this._containerBounds = this._container.getBoundingClientRect();
1735
+
1736
+ // Make the cursor visible and set the handle-less cursor's
1737
+ // location.
1738
+ this._insertCursorAtClosestNode(e.clientX, e.clientY);
1739
+ }
1740
+
1741
+ // Trigger a focus event, if we're not already focused.
1742
+ if (!this.state.focused) {
1743
+ this.focus();
1744
+ }
1745
+ };
1711
1746
  this.handleTouchMove = e => {
1712
1747
  e.stopPropagation();
1713
1748
 
@@ -1927,6 +1962,16 @@ class MathInput extends React.Component {
1927
1962
  this._container = ReactDOM.findDOMNode(this);
1928
1963
  this._root = this._container.querySelector(".mq-root-block");
1929
1964
  this._root.addEventListener("scroll", this._handleScroll);
1965
+ const isWithinKeypadBounds = (x, y) => {
1966
+ const bounds = this._getKeypadBounds();
1967
+
1968
+ // If there are no bounds, then the keypad is not mounted, so we
1969
+ // assume that the event is not within the keypad bounds.
1970
+ if (!bounds) {
1971
+ return false;
1972
+ }
1973
+ return bounds.left <= x && bounds.right >= x && bounds.top <= y && bounds.bottom >= y || bounds.bottom < y;
1974
+ };
1930
1975
 
1931
1976
  // Record the initial scroll displacement on touch start. This allows
1932
1977
  // us to detect whether a touch event was a scroll and only blur the
@@ -1944,10 +1989,9 @@ class MathInput extends React.Component {
1944
1989
  if (!this._container.contains(evt.target)) {
1945
1990
  let touchDidStartInOrBelowKeypad = false;
1946
1991
  if (this.props.keypadElement && this.props.keypadElement.getDOMNode()) {
1947
- const bounds = this._getKeypadBounds();
1948
1992
  for (let i = 0; i < evt.changedTouches.length; i++) {
1949
1993
  const [x, y] = [evt.changedTouches[i].clientX, evt.changedTouches[i].clientY];
1950
- if (bounds.left <= x && bounds.right >= x && bounds.top <= y && bounds.bottom >= y || bounds.bottom < y) {
1994
+ if (isWithinKeypadBounds(x, y)) {
1951
1995
  touchDidStartInOrBelowKeypad = true;
1952
1996
  break;
1953
1997
  }
@@ -1983,25 +2027,31 @@ class MathInput extends React.Component {
1983
2027
  this.dragListener.detach();
1984
2028
  }
1985
2029
  };
2030
+
2031
+ // We want to allow the user to blur the input by clicking outside of it
2032
+ // when using ChromeOS third-party browsers that use mobile user agents,
2033
+ // but don't actually simulate touch events.
2034
+ this.blurOnClickOutside = evt => {
2035
+ if (this.state.focused) {
2036
+ if (!this._container.contains(evt.target)) {
2037
+ if (this.props.keypadElement && this.props.keypadElement.getDOMNode()) {
2038
+ const [x, y] = [evt.clientX, evt.clientY];
2039
+ // We only want to blur if the click is above the keypad,
2040
+ // to the left of the keypad, or to the right of the keypad.
2041
+ // The reasoning for not blurring for any clicks below the keypad is
2042
+ // that the keypad may be anchored above the 'Check answer' bottom bar,
2043
+ // in which case we don't want to dismiss the keypad on check.
2044
+ if (!isWithinKeypadBounds(x, y)) {
2045
+ this.blur();
2046
+ }
2047
+ }
2048
+ }
2049
+ }
2050
+ };
1986
2051
  window.addEventListener("touchstart", this.recordTouchStartOutside);
1987
2052
  window.addEventListener("touchend", this.blurOnTouchEndOutside);
1988
2053
  window.addEventListener("touchcancel", this.blurOnTouchEndOutside);
1989
-
1990
- // HACK(benkomalo): if the window resizes, the keypad bounds can
1991
- // change. That's a bit peeking into the internals of the keypad
1992
- // itself, since we know bounds can change only when the viewport
1993
- // changes, but seems like a rare enough thing to get wrong that it's
1994
- // not worth wiring up extra things for the technical "purity" of
1995
- // having the keypad notify of changes to us.
1996
- window.addEventListener("resize", this._clearKeypadBoundsCache);
1997
- window.addEventListener("orientationchange", this._clearKeypadBoundsCache);
1998
- }
1999
-
2000
- // eslint-disable-next-line react/no-unsafe
2001
- UNSAFE_componentWillReceiveProps(props) {
2002
- if (this.props.keypadElement !== props.keypadElement) {
2003
- this._clearKeypadBoundsCache();
2004
- }
2054
+ window.addEventListener("click", this.blurOnClickOutside);
2005
2055
  }
2006
2056
  componentDidUpdate(prevProps, prevState) {
2007
2057
  if (this.mathField.getContent() !== this.props.value) {
@@ -2016,11 +2066,18 @@ class MathInput extends React.Component {
2016
2066
  window.removeEventListener("touchstart", this.recordTouchStartOutside);
2017
2067
  window.removeEventListener("touchend", this.blurOnTouchEndOutside);
2018
2068
  window.removeEventListener("touchcancel", this.blurOnTouchEndOutside);
2019
- // @ts-expect-error - TS2769 - No overload matches this call.
2020
- window.removeEventListener("resize", this._clearKeypadBoundsCache());
2021
- window.removeEventListener("orientationchange",
2022
- // @ts-expect-error - TS2769 - No overload matches this call.
2023
- this._clearKeypadBoundsCache());
2069
+ window.removeEventListener("click", this.blurOnClickOutside);
2070
+ }
2071
+ /** Returns the current bounds of the keypadElement */
2072
+ _getKeypadBounds() {
2073
+ var _this$props$keypadEle3;
2074
+ const keypadNode = (_this$props$keypadEle3 = this.props.keypadElement) == null ? void 0 : _this$props$keypadEle3.getDOMNode();
2075
+
2076
+ // If the keypad is mounted, return its bounds. Otherwise, return null.
2077
+ if (keypadNode instanceof Element) {
2078
+ return keypadNode.getBoundingClientRect();
2079
+ }
2080
+ return null;
2024
2081
  }
2025
2082
  render() {
2026
2083
  const {
@@ -2049,7 +2106,7 @@ class MathInput extends React.Component {
2049
2106
  onTouchStart: this.handleTouchStart,
2050
2107
  onTouchMove: this.handleTouchMove,
2051
2108
  onTouchEnd: this.handleTouchEnd,
2052
- onClick: e => e.stopPropagation(),
2109
+ onClick: this.handleClick,
2053
2110
  role: "textbox",
2054
2111
  ariaLabel: ariaLabel
2055
2112
  }, /*#__PURE__*/React.createElement("div", {
@@ -4846,7 +4903,7 @@ function SharedKeys(props) {
4846
4903
  coord: fractionCoord,
4847
4904
  secondary: true
4848
4905
  }), /*#__PURE__*/React.createElement(KeypadButton, {
4849
- keyConfig: convertDotToTimes ? KeyConfigs.TIMES : KeyConfigs.CDOT,
4906
+ keyConfig: convertDotToTimesByLocale(!!convertDotToTimes) ? KeyConfigs.TIMES : KeyConfigs.CDOT,
4850
4907
  onClickKey: onClickKey,
4851
4908
  coord: [4, 1],
4852
4909
  secondary: true
@@ -5586,5 +5643,5 @@ let KeypadType = /*#__PURE__*/function (KeypadType) {
5586
5643
  return KeypadType;
5587
5644
  }({});
5588
5645
 
5589
- export { CursorContext, Keypad as DesktopKeypad, KeyArray, KeyConfigs, KeypadContext, MathInput as KeypadInput, KeypadType, MobileKeypad, StatefulKeypadContextProvider, createMathField, getCursorContext, keyToMathquillMap as keyTranslator, keypadElementPropType, libVersion, mathQuillInstance };
5646
+ export { CursorContext, Keypad as DesktopKeypad, KeyArray, KeyConfigs, KeypadContext, MathInput as KeypadInput, KeypadType, MobileKeypad, StatefulKeypadContextProvider, convertDotToTimesByLocale, createMathField, getCursorContext, keyToMathquillMap as keyTranslator, keypadElementPropType, libVersion, mathQuillInstance };
5590
5647
  //# sourceMappingURL=index.js.map