@khanacademy/math-input 16.1.1 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @khanacademy/math-input
2
2
 
3
+ ## 16.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#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.
8
+
9
+ * [#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
10
+
11
+ ## 16.1.2
12
+
13
+ ### Patch Changes
14
+
15
+ - [#856](https://github.com/Khan/perseus/pull/856) [`6c323dc3`](https://github.com/Khan/perseus/commit/6c323dc37e7cc972fe5a1ab7cbf90a23bf4dd3a0) Thanks [@nedredmond](https://github.com/nedredmond)! - Upgrade WB
16
+
17
+ * [#804](https://github.com/Khan/perseus/pull/804) [`2c295e2c`](https://github.com/Khan/perseus/commit/2c295e2c26ff2bf15e1e8e82bcc34e04e4b9bab0) Thanks [@nixterrimus](https://github.com/nixterrimus)! - Upgrade WonderBlocks Popover
18
+
19
+ * Updated dependencies [[`1f4e17ba`](https://github.com/Khan/perseus/commit/1f4e17ba77e1491523813655af18a70285a25989), [`8857950b`](https://github.com/Khan/perseus/commit/8857950bdeeb6e13bc3766b1c6545289b21cbe2a)]:
20
+ - @khanacademy/perseus-core@1.4.1
21
+
3
22
  ## 16.1.1
4
23
 
5
24
  ### 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
@@ -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.1";
20
+ const libVersion = "16.2.0";
21
21
  addLibraryVersionToPerseusDebug(libName, libVersion);
22
22
 
23
23
  function _extends() {
@@ -1369,6 +1369,8 @@ class MathInput extends React.Component {
1369
1369
  this.recordTouchStartOutside = void 0;
1370
1370
  // @ts-expect-error - TS2564 - Property 'blurOnTouchEndOutside' has no initializer and is not definitely assigned in the constructor.
1371
1371
  this.blurOnTouchEndOutside = void 0;
1372
+ // @ts-expect-error - TS2564 - Property 'blurOnClickOutside' has no initializer and is not definitely assigned in the constructor.
1373
+ this.blurOnClickOutside = void 0;
1372
1374
  this.dragListener = void 0;
1373
1375
  this.inputRef = void 0;
1374
1376
  this._isMounted = void 0;
@@ -1378,7 +1380,6 @@ class MathInput extends React.Component {
1378
1380
  this._root = void 0;
1379
1381
  // @ts-expect-error - TS2564 - Property '_containerBounds' has no initializer and is not definitely assigned in the constructor.
1380
1382
  this._containerBounds = void 0;
1381
- this._keypadBounds = void 0;
1382
1383
  this.state = {
1383
1384
  focused: false,
1384
1385
  handle: {
@@ -1388,12 +1389,6 @@ class MathInput extends React.Component {
1388
1389
  y: 0
1389
1390
  }
1390
1391
  };
1391
- this._clearKeypadBoundsCache = () => {
1392
- this._keypadBounds = null;
1393
- };
1394
- this._cacheKeypadBounds = keypadNode => {
1395
- this._keypadBounds = keypadNode.getBoundingClientRect();
1396
- };
1397
1392
  this._updateInputPadding = () => {
1398
1393
  this._container = ReactDOM.findDOMNode(this);
1399
1394
  this._root = this._container.querySelector(".mq-root-block");
@@ -1402,15 +1397,6 @@ class MathInput extends React.Component {
1402
1397
  this._root.style.padding = `${padding.paddingTop}px ${padding.paddingRight}px` + ` ${padding.paddingBottom}px ${padding.paddingLeft}px`;
1403
1398
  this._root.style.fontSize = `${fontSizePt}pt`;
1404
1399
  };
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
1400
  this._updateCursorHandle = animateIntoPosition => {
1415
1401
  const containerBounds = this._container.getBoundingClientRect();
1416
1402
  const cursor = this._container.querySelector(".mq-cursor");
@@ -1470,10 +1456,10 @@ class MathInput extends React.Component {
1470
1456
  });
1471
1457
  };
1472
1458
  this.focus = () => {
1473
- var _this$props$keypadEle2, _this$props;
1459
+ var _this$props$keypadEle, _this$props;
1474
1460
  // Pass this component's handleKey method to the keypad so it can call
1475
1461
  // it whenever it needs to trigger a keypress action.
1476
- (_this$props$keypadEle2 = this.props.keypadElement) == null || _this$props$keypadEle2.setKeyHandler(key => {
1462
+ (_this$props$keypadEle = this.props.keypadElement) == null || _this$props$keypadEle.setKeyHandler(key => {
1477
1463
  const cursor = this.mathField.pressKey(key);
1478
1464
 
1479
1465
  // Trigger an `onChange` if the value in the input changed, and hide
@@ -1506,14 +1492,14 @@ class MathInput extends React.Component {
1506
1492
  // Android Browser 4.3.
1507
1493
  setTimeout(() => {
1508
1494
  if (this._isMounted) {
1509
- var _this$props$keypadEle3;
1495
+ var _this$props$keypadEle2;
1510
1496
  // TODO(benkomalo): the keypad is animating at this point,
1511
1497
  // so we can't call _cacheKeypadBounds(), even though
1512
1498
  // it'd be nice to do so. It should probably be the case
1513
1499
  // that the higher level controller tells us when the
1514
1500
  // keypad is settled (then scrollIntoView wouldn't have
1515
1501
  // to make assumptions about that either).
1516
- const maybeKeypadNode = (_this$props$keypadEle3 = this.props.keypadElement) == null ? void 0 : _this$props$keypadEle3.getDOMNode();
1502
+ const maybeKeypadNode = (_this$props$keypadEle2 = this.props.keypadElement) == null ? void 0 : _this$props$keypadEle2.getDOMNode();
1517
1503
  scrollIntoView(this._container, maybeKeypadNode);
1518
1504
  }
1519
1505
  });
@@ -1708,6 +1694,31 @@ class MathInput extends React.Component {
1708
1694
  this.focus();
1709
1695
  }
1710
1696
  };
1697
+ // We want to allow the user to be able to focus the input via click
1698
+ // when using ChromeOS third-party browsers that use mobile user agents,
1699
+ // but don't actually simulate touch events.
1700
+ this.handleClick = e => {
1701
+ e.stopPropagation();
1702
+
1703
+ // Hide the cursor handle on click
1704
+ this._hideCursorHandle();
1705
+
1706
+ // Cache the container bounds, so as to avoid re-computing. If we don't
1707
+ // have any content, then it's not necessary, since the cursor can't be
1708
+ // moved anyway.
1709
+ if (this.mathField.getContent() !== "") {
1710
+ this._containerBounds = this._container.getBoundingClientRect();
1711
+
1712
+ // Make the cursor visible and set the handle-less cursor's
1713
+ // location.
1714
+ this._insertCursorAtClosestNode(e.clientX, e.clientY);
1715
+ }
1716
+
1717
+ // Trigger a focus event, if we're not already focused.
1718
+ if (!this.state.focused) {
1719
+ this.focus();
1720
+ }
1721
+ };
1711
1722
  this.handleTouchMove = e => {
1712
1723
  e.stopPropagation();
1713
1724
 
@@ -1927,6 +1938,16 @@ class MathInput extends React.Component {
1927
1938
  this._container = ReactDOM.findDOMNode(this);
1928
1939
  this._root = this._container.querySelector(".mq-root-block");
1929
1940
  this._root.addEventListener("scroll", this._handleScroll);
1941
+ const isWithinKeypadBounds = (x, y) => {
1942
+ const bounds = this._getKeypadBounds();
1943
+
1944
+ // If there are no bounds, then the keypad is not mounted, so we
1945
+ // assume that the event is not within the keypad bounds.
1946
+ if (!bounds) {
1947
+ return false;
1948
+ }
1949
+ return bounds.left <= x && bounds.right >= x && bounds.top <= y && bounds.bottom >= y || bounds.bottom < y;
1950
+ };
1930
1951
 
1931
1952
  // Record the initial scroll displacement on touch start. This allows
1932
1953
  // us to detect whether a touch event was a scroll and only blur the
@@ -1944,10 +1965,9 @@ class MathInput extends React.Component {
1944
1965
  if (!this._container.contains(evt.target)) {
1945
1966
  let touchDidStartInOrBelowKeypad = false;
1946
1967
  if (this.props.keypadElement && this.props.keypadElement.getDOMNode()) {
1947
- const bounds = this._getKeypadBounds();
1948
1968
  for (let i = 0; i < evt.changedTouches.length; i++) {
1949
1969
  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) {
1970
+ if (isWithinKeypadBounds(x, y)) {
1951
1971
  touchDidStartInOrBelowKeypad = true;
1952
1972
  break;
1953
1973
  }
@@ -1983,25 +2003,31 @@ class MathInput extends React.Component {
1983
2003
  this.dragListener.detach();
1984
2004
  }
1985
2005
  };
2006
+
2007
+ // We want to allow the user to blur the input by clicking outside of it
2008
+ // when using ChromeOS third-party browsers that use mobile user agents,
2009
+ // but don't actually simulate touch events.
2010
+ this.blurOnClickOutside = evt => {
2011
+ if (this.state.focused) {
2012
+ if (!this._container.contains(evt.target)) {
2013
+ if (this.props.keypadElement && this.props.keypadElement.getDOMNode()) {
2014
+ const [x, y] = [evt.clientX, evt.clientY];
2015
+ // We only want to blur if the click is above the keypad,
2016
+ // to the left of the keypad, or to the right of the keypad.
2017
+ // The reasoning for not blurring for any clicks below the keypad is
2018
+ // that the keypad may be anchored above the 'Check answer' bottom bar,
2019
+ // in which case we don't want to dismiss the keypad on check.
2020
+ if (!isWithinKeypadBounds(x, y)) {
2021
+ this.blur();
2022
+ }
2023
+ }
2024
+ }
2025
+ }
2026
+ };
1986
2027
  window.addEventListener("touchstart", this.recordTouchStartOutside);
1987
2028
  window.addEventListener("touchend", this.blurOnTouchEndOutside);
1988
2029
  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
- }
2030
+ window.addEventListener("click", this.blurOnClickOutside);
2005
2031
  }
2006
2032
  componentDidUpdate(prevProps, prevState) {
2007
2033
  if (this.mathField.getContent() !== this.props.value) {
@@ -2016,11 +2042,18 @@ class MathInput extends React.Component {
2016
2042
  window.removeEventListener("touchstart", this.recordTouchStartOutside);
2017
2043
  window.removeEventListener("touchend", this.blurOnTouchEndOutside);
2018
2044
  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());
2045
+ window.removeEventListener("click", this.blurOnClickOutside);
2046
+ }
2047
+ /** Returns the current bounds of the keypadElement */
2048
+ _getKeypadBounds() {
2049
+ var _this$props$keypadEle3;
2050
+ const keypadNode = (_this$props$keypadEle3 = this.props.keypadElement) == null ? void 0 : _this$props$keypadEle3.getDOMNode();
2051
+
2052
+ // If the keypad is mounted, return its bounds. Otherwise, return null.
2053
+ if (keypadNode instanceof Element) {
2054
+ return keypadNode.getBoundingClientRect();
2055
+ }
2056
+ return null;
2024
2057
  }
2025
2058
  render() {
2026
2059
  const {
@@ -2049,7 +2082,7 @@ class MathInput extends React.Component {
2049
2082
  onTouchStart: this.handleTouchStart,
2050
2083
  onTouchMove: this.handleTouchMove,
2051
2084
  onTouchEnd: this.handleTouchEnd,
2052
- onClick: e => e.stopPropagation(),
2085
+ onClick: this.handleClick,
2053
2086
  role: "textbox",
2054
2087
  ariaLabel: ariaLabel
2055
2088
  }, /*#__PURE__*/React.createElement("div", {