@khanacademy/math-input 14.2.2 → 15.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/index.js CHANGED
@@ -53,23 +53,9 @@ var Redux__namespace = /*#__PURE__*/_interopNamespace(Redux);
53
53
 
54
54
  // This file is processed by a Rollup plugin (replace) to inject the production
55
55
  const libName = "@khanacademy/math-input";
56
- const libVersion = "14.2.2";
56
+ const libVersion = "15.0.0";
57
57
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
58
58
 
59
- function _defineProperty(obj, key, value) {
60
- key = _toPropertyKey(key);
61
- if (key in obj) {
62
- Object.defineProperty(obj, key, {
63
- value: value,
64
- enumerable: true,
65
- configurable: true,
66
- writable: true
67
- });
68
- } else {
69
- obj[key] = value;
70
- }
71
- return obj;
72
- }
73
59
  function _extends() {
74
60
  _extends = Object.assign ? Object.assign.bind() : function (target) {
75
61
  for (var i = 1; i < arguments.length; i++) {
@@ -84,20 +70,6 @@ function _extends() {
84
70
  };
85
71
  return _extends.apply(this, arguments);
86
72
  }
87
- function _toPrimitive(input, hint) {
88
- if (typeof input !== "object" || input === null) return input;
89
- var prim = input[Symbol.toPrimitive];
90
- if (prim !== undefined) {
91
- var res = prim.call(input, hint || "default");
92
- if (typeof res !== "object") return res;
93
- throw new TypeError("@@toPrimitive must return a primitive value.");
94
- }
95
- return (hint === "string" ? String : Number)(input);
96
- }
97
- function _toPropertyKey(arg) {
98
- var key = _toPrimitive(arg, "string");
99
- return typeof key === "symbol" ? key : String(key);
100
- }
101
73
 
102
74
  class Text extends React__namespace.Component {
103
75
  render() {
@@ -133,8 +105,36 @@ const styles$k = aphrodite.StyleSheet.create({
133
105
  });
134
106
 
135
107
  class View extends React__namespace.Component {
108
+ static styles = aphrodite.StyleSheet.create({
109
+ // From: https://github.com/necolas/react-native-web/blob/master/src/components/View/index.js
110
+ // eslint-disable-next-line react-native/no-unused-styles
111
+ initial: {
112
+ alignItems: "stretch",
113
+ borderWidth: 0,
114
+ borderStyle: "solid",
115
+ boxSizing: "border-box",
116
+ display: "flex",
117
+ flexBasis: "auto",
118
+ flexDirection: "column",
119
+ margin: 0,
120
+ padding: 0,
121
+ position: "relative",
122
+ // button and anchor reset
123
+ backgroundColor: "transparent",
124
+ color: "inherit",
125
+ font: "inherit",
126
+ textAlign: "inherit",
127
+ textDecorationLine: "none",
128
+ // list reset
129
+ listStyle: "none",
130
+ // fix flexbox bugs
131
+ maxWidth: "100%",
132
+ minHeight: 0,
133
+ minWidth: 0
134
+ }
135
+ });
136
136
  render() {
137
- const className = aphrodite.css(View.styles.initial, ...(Array.isArray(this.props.style) ? this.props.style : [this.props.style])) + (this.props.extraClassName ? " ".concat(this.props.extraClassName) : "");
137
+ const className = aphrodite.css(View.styles.initial, ...(Array.isArray(this.props.style) ? this.props.style : [this.props.style])) + (this.props.extraClassName ? ` ${this.props.extraClassName}` : "");
138
138
  return /*#__PURE__*/React__namespace.createElement("div", {
139
139
  className: className,
140
140
  style: this.props.dynamicStyle,
@@ -149,34 +149,6 @@ class View extends React__namespace.Component {
149
149
  }, this.props.children);
150
150
  }
151
151
  }
152
- _defineProperty(View, "styles", aphrodite.StyleSheet.create({
153
- // From: https://github.com/necolas/react-native-web/blob/master/src/components/View/index.js
154
- // eslint-disable-next-line react-native/no-unused-styles
155
- initial: {
156
- alignItems: "stretch",
157
- borderWidth: 0,
158
- borderStyle: "solid",
159
- boxSizing: "border-box",
160
- display: "flex",
161
- flexBasis: "auto",
162
- flexDirection: "column",
163
- margin: 0,
164
- padding: 0,
165
- position: "relative",
166
- // button and anchor reset
167
- backgroundColor: "transparent",
168
- color: "inherit",
169
- font: "inherit",
170
- textAlign: "inherit",
171
- textDecorationLine: "none",
172
- // list reset
173
- listStyle: "none",
174
- // fix flexbox bugs
175
- maxWidth: "100%",
176
- minHeight: 0,
177
- minWidth: 0
178
- }
179
- }));
180
152
 
181
153
  /**
182
154
  * Common parameters used to style components.
@@ -218,6 +190,9 @@ const navigationPadWidthPx = 192;
218
190
  // has settled down.
219
191
  const toolbarHeightPx = 60;
220
192
 
193
+ /**
194
+ * Renders the green tear-shaped handle under the cursor.
195
+ */
221
196
  const touchTargetRadiusPx = 2 * cursorHandleRadiusPx;
222
197
  const touchTargetHeightPx = 2 * touchTargetRadiusPx;
223
198
  const touchTargetWidthPx = 2 * touchTargetRadiusPx;
@@ -225,6 +200,12 @@ const cursorRadiusPx = cursorHandleRadiusPx;
225
200
  const cursorHeightPx = cursorHandleDistanceMultiplier * (cursorRadiusPx * 4);
226
201
  const cursorWidthPx = 4 * cursorRadiusPx;
227
202
  class CursorHandle extends React__namespace.Component {
203
+ static defaultProps = {
204
+ animateIntoPosition: false,
205
+ visible: false,
206
+ x: 0,
207
+ y: 0
208
+ };
228
209
  render() {
229
210
  const {
230
211
  x,
@@ -235,7 +216,7 @@ class CursorHandle extends React__namespace.Component {
235
216
  transitionDuration: "100ms",
236
217
  transitionProperty: "transform"
237
218
  } : {};
238
- const transformString = "translate(".concat(x, "px, ").concat(y, "px)");
219
+ const transformString = `translate(${x}px, ${y}px)`;
239
220
  const outerStyle = {
240
221
  position: "absolute",
241
222
  // This is essentially webapp's interactiveComponent + 1.
@@ -261,7 +242,7 @@ class CursorHandle extends React__namespace.Component {
261
242
  fill: "none",
262
243
  width: cursorWidthPx,
263
244
  height: cursorHeightPx,
264
- viewBox: "0 0 ".concat(cursorWidthPx, " ").concat(cursorHeightPx)
245
+ viewBox: `0 0 ${cursorWidthPx} ${cursorHeightPx}`
265
246
  }, /*#__PURE__*/React__namespace.createElement("filter", {
266
247
  id: "math-input_cursor",
267
248
  colorInterpolationFilters: "sRGB",
@@ -307,12 +288,6 @@ class CursorHandle extends React__namespace.Component {
307
288
  })));
308
289
  }
309
290
  }
310
- _defineProperty(CursorHandle, "defaultProps", {
311
- animateIntoPosition: false,
312
- visible: false,
313
- x: 0,
314
- y: 0
315
- });
316
291
 
317
292
  /**
318
293
  * A gesture recognizer that detects 'drags', crudely defined as either scrolls
@@ -325,9 +300,6 @@ _defineProperty(CursorHandle, "defaultProps", {
325
300
  const touchSlopPx = 8;
326
301
  class DragListener {
327
302
  constructor(onDrag, initialEvent) {
328
- _defineProperty(this, "_scrollListener", void 0);
329
- _defineProperty(this, "_moveListener", void 0);
330
- _defineProperty(this, "_endAndCancelListener", void 0);
331
303
  // We detect drags in two ways. First, by listening for the window
332
304
  // scroll event (we consider any legitimate scroll to be a drag).
333
305
  this._scrollListener = () => {
@@ -727,7 +699,7 @@ function handleBackspaceInLogIndex(mathField, cursor) {
727
699
  if (isInsideEmptyNode(cursor)) {
728
700
  const grandparent = cursor.parent.parent;
729
701
  const command = maybeFindCommandBeforeParens(grandparent);
730
- cursor.insLeftOf(command === null || command === void 0 ? void 0 : command.startNode);
702
+ cursor.insLeftOf(command?.startNode);
731
703
  cursor.startSelection();
732
704
  if (grandparent[mathQuillInstance.R] !== MathFieldActionType.MQ_END) {
733
705
  cursor.insRightOf(grandparent[mathQuillInstance.R]);
@@ -1028,7 +1000,7 @@ function handleExponent(mathField, key) {
1028
1000
  break;
1029
1001
  case "EXP_2":
1030
1002
  case "EXP_3":
1031
- mathField.write("^".concat(key === "EXP_2" ? 2 : 3));
1003
+ mathField.write(`^${key === "EXP_2" ? 2 : 3}`);
1032
1004
 
1033
1005
  // If we enter a square or a cube, we should leave the cursor
1034
1006
  // within the newly inserted parens, if they exist. This takes
@@ -1042,7 +1014,7 @@ function handleExponent(mathField, key) {
1042
1014
  }
1043
1015
  break;
1044
1016
  default:
1045
- throw new Error("Invalid exponent key: ".concat(key));
1017
+ throw new Error(`Invalid exponent key: ${key}`);
1046
1018
  }
1047
1019
  }
1048
1020
 
@@ -1124,7 +1096,7 @@ function handleJumpOut(mathField, key) {
1124
1096
  cursor.insRightOf(cursor.parent.parent);
1125
1097
  break;
1126
1098
  default:
1127
- throw new Error("Attempted to 'Jump Out' from node, but found no " + "appropriate cursor context: ".concat(context));
1099
+ throw new Error(`Attempted to 'Jump Out' from node, but found no ` + `appropriate cursor context: ${context}`);
1128
1100
  }
1129
1101
  }
1130
1102
 
@@ -1160,7 +1132,7 @@ function buildGenericCallback(str) {
1160
1132
  }
1161
1133
  function buildNormalFunctionCallback(command) {
1162
1134
  return function (mathField) {
1163
- mathField.write("\\".concat(command, "\\left(\\right)"));
1135
+ mathField.write(`\\${command}\\left(\\right)`);
1164
1136
  mathField.keystroke("Left");
1165
1137
  };
1166
1138
  }
@@ -1323,6 +1295,7 @@ const keyToMathquillMap = {
1323
1295
  Z: buildGenericCallback("Z")
1324
1296
  };
1325
1297
 
1298
+ // Notes about MathQuill
1326
1299
  const mobileKeyTranslator = {
1327
1300
  ...keyToMathquillMap,
1328
1301
  // note(Matthew): our mobile backspace logic is really complicated
@@ -1344,8 +1317,6 @@ class MathWrapper {
1344
1317
 
1345
1318
  constructor(element) {
1346
1319
  let callbacks = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1347
- _defineProperty(this, "mathField", void 0);
1348
- _defineProperty(this, "callbacks", void 0);
1349
1320
  this.mathField = createMathField(element, () => {
1350
1321
  return {
1351
1322
  // use a span instead of a textarea so that we don't bring up the
@@ -1528,481 +1499,27 @@ const scrollIntoView = (containerNode, keypadNode) => {
1528
1499
  const constrainingFrictionFactor = 0.8;
1529
1500
  // eslint-disable-next-line react/no-unsafe
1530
1501
  class MathInput extends React__namespace.Component {
1531
- constructor() {
1532
- super(...arguments);
1533
- _defineProperty(this, "didTouchOutside", void 0);
1534
- _defineProperty(this, "didScroll", void 0);
1535
- _defineProperty(this, "mathField", void 0);
1536
- _defineProperty(this, "recordTouchStartOutside", void 0);
1537
- _defineProperty(this, "blurOnTouchEndOutside", void 0);
1538
- _defineProperty(this, "dragListener", void 0);
1539
- _defineProperty(this, "inputRef", void 0);
1540
- _defineProperty(this, "_isMounted", void 0);
1541
- _defineProperty(this, "_mathContainer", void 0);
1542
- _defineProperty(this, "_container", void 0);
1543
- _defineProperty(this, "_root", void 0);
1544
- _defineProperty(this, "_containerBounds", void 0);
1545
- _defineProperty(this, "_keypadBounds", void 0);
1546
- _defineProperty(this, "state", {
1547
- focused: false,
1548
- handle: {
1549
- animateIntoPosition: false,
1550
- visible: false,
1551
- x: 0,
1552
- y: 0
1553
- }
1554
- });
1555
- _defineProperty(this, "_clearKeypadBoundsCache", () => {
1556
- this._keypadBounds = null;
1557
- });
1558
- _defineProperty(this, "_cacheKeypadBounds", keypadNode => {
1559
- this._keypadBounds = keypadNode.getBoundingClientRect();
1560
- });
1561
- _defineProperty(this, "_updateInputPadding", () => {
1562
- this._container = ReactDOM__default["default"].findDOMNode(this);
1563
- this._root = this._container.querySelector(".mq-root-block");
1564
- const padding = this.getInputInnerPadding();
1565
- // NOTE(diedra): This overrides the default 2px padding from Mathquil.
1566
- this._root.style.padding = "".concat(padding.paddingTop, "px ").concat(padding.paddingRight, "px") + " ".concat(padding.paddingBottom, "px ").concat(padding.paddingLeft, "px");
1567
- this._root.style.fontSize = "".concat(fontSizePt, "pt");
1568
- });
1569
- _defineProperty(this, "_getKeypadBounds", () => {
1570
- if (!this._keypadBounds) {
1571
- var _this$props$keypadEle;
1572
- const node = (_this$props$keypadEle = this.props.keypadElement) === null || _this$props$keypadEle === void 0 ? void 0 : _this$props$keypadEle.getDOMNode();
1573
- this._cacheKeypadBounds(node);
1574
- }
1575
- return this._keypadBounds;
1576
- });
1577
- _defineProperty(this, "_updateCursorHandle", animateIntoPosition => {
1578
- const containerBounds = this._container.getBoundingClientRect();
1579
- const cursor = this._container.querySelector(".mq-cursor");
1580
- const cursorBounds = cursor.getBoundingClientRect();
1581
- const cursorWidth = cursorBounds.width;
1582
- const gapBelowCursor = 2;
1583
- const inputInnerPadding = this.getInputInnerPadding();
1584
-
1585
- // The cursor should never be further right or left than the edge of the
1586
- // container's values.
1587
- const furthestRightCursorBound = containerBounds.right - cursorWidth - inputInnerPadding.paddingRight;
1588
- const furthestLeftCursorBound = containerBounds.left + cursorWidth + inputInnerPadding.paddingLeft;
1589
- let cursorBoundsLeft = cursorBounds.left;
1590
- if (cursorBounds.left > furthestRightCursorBound) {
1591
- cursorBoundsLeft = furthestRightCursorBound;
1592
- } else if (cursorBounds.left < furthestLeftCursorBound) {
1593
- cursorBoundsLeft = furthestLeftCursorBound;
1594
- }
1595
- this.setState({
1596
- handle: {
1597
- visible: true,
1598
- animateIntoPosition,
1599
- // We subtract containerBounds' left/top to correct for the
1600
- // position of the container within the page.
1601
- x: cursorBoundsLeft + cursorWidth / 2 - containerBounds.left,
1602
- y: cursorBounds.bottom + gapBelowCursor - containerBounds.top
1603
- }
1604
- });
1605
- });
1606
- _defineProperty(this, "_hideCursorHandle", () => {
1607
- this.setState({
1608
- handle: {
1609
- visible: false,
1610
- x: 0,
1611
- y: 0
1612
- }
1613
- });
1614
- });
1615
- _defineProperty(this, "_handleScroll", () => {
1616
- // If animateIntoPosition is false, the user is currently manually positioning
1617
- // the cursor. This is important because the user can scroll the input field
1618
- // with the curor handle, and we don't want to override that ability.
1619
- // But we do want to hide the handle is the user is just scrolling the input field
1620
- // normally, because the handle will not move with the scroll.
1621
- if (this.state.handle.animateIntoPosition !== false) {
1622
- this._hideCursorHandle();
1623
- }
1624
- });
1625
- _defineProperty(this, "blur", () => {
1626
- this.mathField.blur();
1627
- this.props.onBlur && this.props.onBlur();
1628
- this.setState({
1629
- focused: false,
1630
- handle: {
1631
- visible: false
1632
- }
1633
- });
1634
- });
1635
- _defineProperty(this, "focus", () => {
1636
- var _this$props$keypadEle2, _this$props;
1637
- // Pass this component's handleKey method to the keypad so it can call
1638
- // it whenever it needs to trigger a keypress action.
1639
- (_this$props$keypadEle2 = this.props.keypadElement) === null || _this$props$keypadEle2 === void 0 ? void 0 : _this$props$keypadEle2.setKeyHandler(key => {
1640
- const cursor = this.mathField.pressKey(key);
1641
-
1642
- // Trigger an `onChange` if the value in the input changed, and hide
1643
- // the cursor handle whenever the user types a key. If the value
1644
- // changed as a result of a keypress, we need to be careful not to
1645
- // call `setState` until after `onChange` has resolved.
1646
- const hideCursor = () => {
1647
- this.setState({
1648
- handle: {
1649
- visible: false
1650
- }
1651
- });
1652
- };
1653
- const value = this.mathField.getContent();
1654
- if (this.props.value !== value) {
1655
- this.props.onChange(value, hideCursor);
1656
- } else {
1657
- hideCursor();
1658
- }
1659
- return cursor;
1660
- });
1661
- this.mathField.focus();
1662
- (_this$props = this.props) === null || _this$props === void 0 ? void 0 : _this$props.onFocus();
1663
- this.setState({
1664
- focused: true
1665
- }, () => {
1666
- // NOTE(charlie): We use `setTimeout` to allow for a layout pass to
1667
- // occur. Otherwise, the keypad is measured incorrectly. Ideally,
1668
- // we'd use requestAnimationFrame here, but it's unsupported on
1669
- // Android Browser 4.3.
1670
- setTimeout(() => {
1671
- if (this._isMounted) {
1672
- var _this$props$keypadEle3;
1673
- // TODO(benkomalo): the keypad is animating at this point,
1674
- // so we can't call _cacheKeypadBounds(), even though
1675
- // it'd be nice to do so. It should probably be the case
1676
- // that the higher level controller tells us when the
1677
- // keypad is settled (then scrollIntoView wouldn't have
1678
- // to make assumptions about that either).
1679
- const maybeKeypadNode = (_this$props$keypadEle3 = this.props.keypadElement) === null || _this$props$keypadEle3 === void 0 ? void 0 : _this$props$keypadEle3.getDOMNode();
1680
- scrollIntoView(this._container, maybeKeypadNode);
1681
- }
1682
- });
1683
- });
1684
- });
1685
- _defineProperty(this, "_findHitNode", (containerBounds, x, y, dx, dy) => {
1686
- while (y >= containerBounds.top && y <= containerBounds.bottom) {
1687
- y += dy;
1688
- const points = [[x - dx, y], [x, y], [x + dx, y]];
1689
- const elements = points
1690
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
1691
- .map(point => document.elementFromPoint(...point))
1692
- // We exclude the root container itself and any nodes marked
1693
- // as non-leaf which are fractions, parens, and roots. The
1694
- // children of those nodes are included in the list because
1695
- // those are the items we care about placing the cursor next
1696
- // to.
1697
- //
1698
- // MathQuill's mq-non-leaf is not applied to all non-leaf nodes
1699
- // so the naming is a bit confusing. Although fractions are
1700
- // included, neither mq-numerator nor mq-denominator nodes are
1701
- // and neither are subscripts or superscripts.
1702
- .filter(element => element && this._root.contains(element) && (!element.classList.contains("mq-root-block") && !element.classList.contains("mq-non-leaf") || element.classList.contains("mq-empty") || element.classList.contains("mq-hasCursor")));
1703
- let hitNode = null;
1704
-
1705
- // Contains only DOMNodes with child elements.
1706
- const nonLeafElements = [];
1707
- let max = 0;
1708
- const counts = {};
1709
- const elementsById = {};
1710
- for (const element of elements) {
1711
- // @ts-expect-error - TS2531 - Object is possibly 'null'.
1712
- const id = element.getAttribute("mathquill-command-id");
1713
- if (id != null) {
1714
- counts[id] = (counts[id] || 0) + 1;
1715
- elementsById[id] = element;
1716
- } else {
1717
- // @ts-expect-error - TS2345 - Argument of type 'Element | null' is not assignable to parameter of type 'HTMLElement | null'.
1718
- nonLeafElements.push(element);
1719
- }
1720
- }
1721
-
1722
- // When determining which DOMNode to place the cursor beside, we
1723
- // prefer leaf nodes. Hitting a leaf node is a good sign that the
1724
- // cursor is really close to some piece of math that has been
1725
- // rendered because leaf nodes contain text. Non-leaf nodes may
1726
- // contain a lot of whitespace so the cursor may be further away
1727
- // from actual text within the expression.
1728
- //
1729
- // Since we're doing three hit tests per loop it's possible that
1730
- // we hit multiple leaf nodes at the same time. In this case we
1731
- // we prefer the DOMNode with the most hits.
1732
- // TODO(kevinb) consider preferring nodes hit by [x, y].
1733
- for (const [id, count] of wonderStuffCore.entries(counts)) {
1734
- if (count > max) {
1735
- max = count;
1736
- hitNode = elementsById[id];
1737
- }
1738
- }
1739
-
1740
- // It's possible that two non-leaf nodes are right beside each
1741
- // other. We don't bother counting the number of hits for each,
1742
- // b/c this seems like an unlikely situation. Also, ignoring the
1743
- // hit count in the situation should not have serious effects on
1744
- // the overall accuracy of the algorithm.
1745
- if (hitNode == null && nonLeafElements.length > 0) {
1746
- // @ts-expect-error - TS2322 - Type 'HTMLElement | null' is not assignable to type 'null'.
1747
- hitNode = nonLeafElements[0];
1748
- }
1749
- if (hitNode !== null) {
1750
- this.mathField.setCursorPosition(x, y, hitNode);
1751
- return true;
1752
- }
1753
- }
1754
- return false;
1755
- });
1756
- _defineProperty(this, "_insertCursorAtClosestNode", (x, y) => {
1757
- const cursor = this.mathField.getCursor();
1758
-
1759
- // Pre-emptively check if the input has any child nodes; if not, the
1760
- // input is empty, so we throw the cursor at the start.
1761
- if (!this._root.hasChildNodes()) {
1762
- cursor.insAtLeftEnd(this.mathField.mathField.__controller.root);
1763
- return;
1764
- }
1765
-
1766
- // NOTE(diedra): The adding and subtracting of 10 or 15 pixels here accounts
1767
- // for the padding that surrounds the input values.
1768
- if (y > this._containerBounds.bottom) {
1769
- y = this._containerBounds.bottom - 10;
1770
- } else if (y < this._containerBounds.top) {
1771
- y = this._containerBounds.top + 10;
1772
- }
1773
- if (x > this._containerBounds.right) {
1774
- x = this._containerBounds.right - 15;
1775
- } else if (x < this._containerBounds.left) {
1776
- x = this._containerBounds.left + 15;
1777
- }
1778
- let dy;
1779
-
1780
- // Vertical spacing between hit tests
1781
- // dy is negative because we're moving upwards.
1782
- dy = -8;
1783
-
1784
- // Horizontal spacing between hit tests
1785
- // Note: This value depends on the font size. If the gap is too small
1786
- // we end up placing the cursor at the end of the expression when we
1787
- // shouldn't.
1788
- const dx = 5;
1789
- if (this._findHitNode(this._containerBounds, x, y, dx, dy)) {
1790
- return;
1791
- }
1792
-
1793
- // If we haven't found anything start from the top.
1794
- y = this._containerBounds.top;
1795
-
1796
- // dy is positive b/c we're going downwards.
1797
- dy = 8;
1798
- if (this._findHitNode(this._containerBounds, x, y, dx, dy)) {
1799
- return;
1800
- }
1801
- const firstChildBounds = this._root.firstChild.getBoundingClientRect();
1802
- const lastChildBounds = this._root.lastChild.getBoundingClientRect();
1803
- const left = firstChildBounds.left;
1804
- const right = lastChildBounds.right;
1805
-
1806
- // We've exhausted all of the options. We're likely either to the right
1807
- // or left of all of the math, so we place the cursor at the end to
1808
- // which it's closest.
1809
- if (Math.abs(x - right) < Math.abs(x - left)) {
1810
- cursor.insAtRightEnd(this.mathField.mathField.__controller.root);
1811
- } else {
1812
- cursor.insAtLeftEnd(this.mathField.mathField.__controller.root);
1813
- }
1814
- // In that event, we need to update the cursor context ourselves.
1815
- this.props.keypadElement && this.props.keypadElement.setCursor({
1816
- context: this.mathField.contextForCursor()
1817
- });
1818
- });
1819
- _defineProperty(this, "handleTouchStart", e => {
1820
- e.stopPropagation();
1821
-
1822
- // Hide the cursor handle on touch start, if the handle itself isn't
1823
- // handling the touch event.
1824
- this._hideCursorHandle();
1825
-
1826
- // Cache the container bounds, so as to avoid re-computing. If we don't
1827
- // have any content, then it's not necessary, since the cursor can't be
1828
- // moved anyway.
1829
- if (this.mathField.getContent() !== "") {
1830
- this._containerBounds = this._container.getBoundingClientRect();
1831
-
1832
- // Make the cursor visible and set the handle-less cursor's
1833
- // location.
1834
- const touch = e.changedTouches[0];
1835
- this._insertCursorAtClosestNode(touch.clientX, touch.clientY);
1836
- }
1837
-
1838
- // Trigger a focus event, if we're not already focused.
1839
- if (!this.state.focused) {
1840
- this.focus();
1841
- }
1842
- });
1843
- _defineProperty(this, "handleTouchMove", e => {
1844
- e.stopPropagation();
1845
-
1846
- // Update the handle-less cursor's location on move, if there's any
1847
- // content in the box. Note that if the user touched outside the keypad
1848
- // (e.g., with a different finger) during this touch interaction, we
1849
- // may have blurred, in which case we should ignore the touch (since
1850
- // the cursor is no longer visible and the input is no longer
1851
- // highlighted).
1852
- if (this.mathField.getContent() !== "" && this.state.focused) {
1853
- const touch = e.changedTouches[0];
1854
- this._insertCursorAtClosestNode(touch.clientX, touch.clientY);
1855
- }
1856
- });
1857
- _defineProperty(this, "handleTouchEnd", e => {
1858
- e.stopPropagation();
1859
-
1860
- // And on touch-end, reveal the cursor, unless the input is empty. Note
1861
- // that if the user touched outside the keypad (e.g., with a different
1862
- // finger) during this touch interaction, we may have blurred, in which
1863
- // case we should ignore the touch (since the cursor is no longer
1864
- // visible and the input is no longer highlighted).
1865
- if (this.mathField.getContent() !== "" && this.state.focused) {
1866
- this._updateCursorHandle();
1867
- }
1868
- });
1869
- _defineProperty(this, "onCursorHandleTouchStart", e => {
1870
- // NOTE(charlie): The cursor handle is a child of this view, so whenever
1871
- // it receives a touch event, that event would also typically be bubbled
1872
- // up to our own handlers. However, we want the cursor to handle its own
1873
- // touch events, and for this view to only handle touch events that
1874
- // don't affect the cursor. As such, we `stopPropagation` on any touch
1875
- // events that are being handled by the cursor, so as to avoid handling
1876
- // them in our own touch handlers.
1877
- e.stopPropagation();
1878
- e.preventDefault();
1879
-
1880
- // Cache the container bounds, so as to avoid re-computing.
1881
- this._containerBounds = this._container.getBoundingClientRect();
1882
- });
1883
- _defineProperty(this, "_constrainToBound", (value, min, max, friction) => {
1884
- if (value < min) {
1885
- return min + (value - min) * friction;
1886
- } else if (value > max) {
1887
- return max + (value - max) * friction;
1888
- } else {
1889
- return value;
1890
- }
1891
- });
1892
- _defineProperty(this, "onCursorHandleTouchMove", e => {
1893
- e.stopPropagation();
1894
- const x = e.changedTouches[0].clientX;
1895
- const y = e.changedTouches[0].clientY;
1896
- const relativeX = x - this._containerBounds.left;
1897
- const relativeY = y - 2 * cursorHandleRadiusPx * cursorHandleDistanceMultiplier - this._containerBounds.top;
1898
-
1899
- // We subtract the containerBounds left/top to correct for the
1900
- // MathInput's position on the page. On top of that, we subtract an
1901
- // additional 2 x {height of the cursor} so that the bottom of the
1902
- // cursor tracks the user's finger, to make it visible under their
1903
- // touch.
1904
- this.setState({
1905
- handle: {
1906
- animateIntoPosition: false,
1907
- visible: true,
1908
- // TODO(charlie): Use clientX and clientY to avoid the need for
1909
- // scroll offsets. This likely also means that the cursor
1910
- // detection doesn't work when scrolled, since we're not
1911
- // offsetting those values.
1912
- x: this._constrainToBound(relativeX, 0, this._containerBounds.width, constrainingFrictionFactor),
1913
- y: this._constrainToBound(relativeY, 0, this._containerBounds.height, constrainingFrictionFactor)
1914
- }
1915
- });
1502
+ // @ts-expect-error - TS2564 - Property 'recordTouchStartOutside' has no initializer and is not definitely assigned in the constructor.
1916
1503
 
1917
- // Use a y-coordinate that's just above where the user is actually
1918
- // touching because they're dragging the handle which is a little
1919
- // below where the cursor actually is.
1920
- const distanceAboveFingerToTrySelecting = 22;
1921
- const adjustedY = y - distanceAboveFingerToTrySelecting;
1922
- this._insertCursorAtClosestNode(x, adjustedY);
1923
- });
1924
- _defineProperty(this, "onCursorHandleTouchEnd", e => {
1925
- e.stopPropagation();
1926
- this._updateCursorHandle(true);
1927
- });
1928
- _defineProperty(this, "onCursorHandleTouchCancel", e => {
1929
- e.stopPropagation();
1930
- this._updateCursorHandle(true);
1931
- });
1932
- _defineProperty(this, "domKeyToMathQuillKey", key => {
1933
- const keyMap = {
1934
- "+": "PLUS",
1935
- "-": "MINUS",
1936
- "*": "TIMES",
1937
- "/": "DIVIDE",
1938
- ".": "DECIMAL",
1939
- "%": "PERCENT",
1940
- "=": "EQUAL",
1941
- ">": "GT",
1942
- "<": "LT",
1943
- "^": "EXP"
1944
- };
1504
+ // @ts-expect-error - TS2564 - Property 'blurOnTouchEndOutside' has no initializer and is not definitely assigned in the constructor.
1945
1505
 
1946
- // Numbers
1947
- if (["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].includes(key)) {
1948
- return "NUM_".concat(key);
1949
- }
1506
+ // @ts-expect-error - TS2564 - Property '_container' has no initializer and is not definitely assigned in the constructor.
1950
1507
 
1951
- // Movement keys
1952
- else if (key === "Backspace") {
1953
- return "BACKSPACE";
1954
- }
1508
+ // @ts-expect-error - TS2564 - Property '_containerBounds' has no initializer and is not definitely assigned in the constructor.
1955
1509
 
1956
- // Operators
1957
- else if (key in keyMap) {
1958
- return keyMap[key];
1959
- }
1960
-
1961
- // The key pressed doesn't map to any of the math input operators
1962
- return null;
1963
- });
1964
- _defineProperty(this, "handleKeyUp", event => {
1965
- const mathQuillKey = this.domKeyToMathQuillKey(event.key);
1966
- if (mathQuillKey) {
1967
- this.mathField.pressKey(mathQuillKey);
1968
-
1969
- // TODO(diedra): If the new value being added is off-screen to the right
1970
- // due to the max-width of the text box, scroll the box to show the newest
1971
- // value
1972
- const value = this.mathField.getContent();
1973
- if (this.props.value !== value) {
1974
- this.mathField.setContent(this.props.value);
1975
- this.props.onChange(value, false);
1976
- this._hideCursorHandle();
1977
- }
1978
- }
1979
- });
1980
- _defineProperty(this, "getBorderWidthPx", () => {
1981
- // TODO(diedra): Move these to the common style package.
1982
- const normalBorderWidthPx = 1;
1983
- const focusedBorderWidthPx = 2;
1984
- return this.state.focused ? focusedBorderWidthPx : normalBorderWidthPx;
1985
- });
1986
- _defineProperty(this, "getInputInnerPadding", () => {
1987
- const paddingInset = totalDesiredPadding - this.getBorderWidthPx();
1988
-
1989
- // Now, translate that to the appropriate padding for each direction.
1990
- // The complication here is that we want numerals to be centered within
1991
- // the input. However, Symbola (MathQuill's font of choice) renders
1992
- // numerals with approximately 3px of padding below and 1px of padding
1993
- // above (to make room for ascenders and descenders). So we ignore those
1994
- // padding values for the vertical directions.
1995
- const symbolaPaddingBottom = 3;
1996
- const symbolaPaddingTop = 1;
1997
- const padding = {
1998
- paddingTop: paddingInset - symbolaPaddingTop,
1999
- paddingRight: paddingInset,
2000
- paddingBottom: paddingInset - symbolaPaddingBottom,
2001
- paddingLeft: paddingInset
2002
- };
2003
- return padding;
2004
- });
2005
- }
1510
+ static defaultProps = {
1511
+ style: {},
1512
+ value: ""
1513
+ };
1514
+ state = {
1515
+ focused: false,
1516
+ handle: {
1517
+ animateIntoPosition: false,
1518
+ visible: false,
1519
+ x: 0,
1520
+ y: 0
1521
+ }
1522
+ };
2006
1523
  componentDidMount() {
2007
1524
  this._isMounted = true;
2008
1525
  this.mathField = new MathWrapper(this._mathContainer, {
@@ -2126,6 +1643,519 @@ class MathInput extends React__namespace.Component {
2126
1643
  // @ts-expect-error - TS2769 - No overload matches this call.
2127
1644
  this._clearKeypadBoundsCache());
2128
1645
  }
1646
+ _clearKeypadBoundsCache = () => {
1647
+ this._keypadBounds = null;
1648
+ };
1649
+ _cacheKeypadBounds = keypadNode => {
1650
+ this._keypadBounds = keypadNode.getBoundingClientRect();
1651
+ };
1652
+ _updateInputPadding = () => {
1653
+ this._container = ReactDOM__default["default"].findDOMNode(this);
1654
+ this._root = this._container.querySelector(".mq-root-block");
1655
+ const padding = this.getInputInnerPadding();
1656
+ // NOTE(diedra): This overrides the default 2px padding from Mathquil.
1657
+ this._root.style.padding = `${padding.paddingTop}px ${padding.paddingRight}px` + ` ${padding.paddingBottom}px ${padding.paddingLeft}px`;
1658
+ this._root.style.fontSize = `${fontSizePt}pt`;
1659
+ };
1660
+
1661
+ /** Gets and cache they bounds of the keypadElement */
1662
+ _getKeypadBounds = () => {
1663
+ if (!this._keypadBounds) {
1664
+ const node = this.props.keypadElement?.getDOMNode();
1665
+ this._cacheKeypadBounds(node);
1666
+ }
1667
+ return this._keypadBounds;
1668
+ };
1669
+ _updateCursorHandle = animateIntoPosition => {
1670
+ const containerBounds = this._container.getBoundingClientRect();
1671
+ const cursor = this._container.querySelector(".mq-cursor");
1672
+ const cursorBounds = cursor.getBoundingClientRect();
1673
+ const cursorWidth = cursorBounds.width;
1674
+ const gapBelowCursor = 2;
1675
+ const inputInnerPadding = this.getInputInnerPadding();
1676
+
1677
+ // The cursor should never be further right or left than the edge of the
1678
+ // container's values.
1679
+ const furthestRightCursorBound = containerBounds.right - cursorWidth - inputInnerPadding.paddingRight;
1680
+ const furthestLeftCursorBound = containerBounds.left + cursorWidth + inputInnerPadding.paddingLeft;
1681
+ let cursorBoundsLeft = cursorBounds.left;
1682
+ if (cursorBounds.left > furthestRightCursorBound) {
1683
+ cursorBoundsLeft = furthestRightCursorBound;
1684
+ } else if (cursorBounds.left < furthestLeftCursorBound) {
1685
+ cursorBoundsLeft = furthestLeftCursorBound;
1686
+ }
1687
+ this.setState({
1688
+ handle: {
1689
+ visible: true,
1690
+ animateIntoPosition,
1691
+ // We subtract containerBounds' left/top to correct for the
1692
+ // position of the container within the page.
1693
+ x: cursorBoundsLeft + cursorWidth / 2 - containerBounds.left,
1694
+ y: cursorBounds.bottom + gapBelowCursor - containerBounds.top
1695
+ }
1696
+ });
1697
+ };
1698
+ _hideCursorHandle = () => {
1699
+ this.setState({
1700
+ handle: {
1701
+ visible: false,
1702
+ x: 0,
1703
+ y: 0
1704
+ }
1705
+ });
1706
+ };
1707
+ _handleScroll = () => {
1708
+ // If animateIntoPosition is false, the user is currently manually positioning
1709
+ // the cursor. This is important because the user can scroll the input field
1710
+ // with the curor handle, and we don't want to override that ability.
1711
+ // But we do want to hide the handle is the user is just scrolling the input field
1712
+ // normally, because the handle will not move with the scroll.
1713
+ if (this.state.handle.animateIntoPosition !== false) {
1714
+ this._hideCursorHandle();
1715
+ }
1716
+ };
1717
+ blur = () => {
1718
+ this.mathField.blur();
1719
+ this.props.onBlur && this.props.onBlur();
1720
+ this.setState({
1721
+ focused: false,
1722
+ handle: {
1723
+ visible: false
1724
+ }
1725
+ });
1726
+ };
1727
+ focus = () => {
1728
+ // Pass this component's handleKey method to the keypad so it can call
1729
+ // it whenever it needs to trigger a keypress action.
1730
+ this.props.keypadElement?.setKeyHandler(key => {
1731
+ const cursor = this.mathField.pressKey(key);
1732
+
1733
+ // Trigger an `onChange` if the value in the input changed, and hide
1734
+ // the cursor handle whenever the user types a key. If the value
1735
+ // changed as a result of a keypress, we need to be careful not to
1736
+ // call `setState` until after `onChange` has resolved.
1737
+ const hideCursor = () => {
1738
+ this.setState({
1739
+ handle: {
1740
+ visible: false
1741
+ }
1742
+ });
1743
+ };
1744
+ const value = this.mathField.getContent();
1745
+ if (this.props.value !== value) {
1746
+ this.props.onChange(value, hideCursor);
1747
+ } else {
1748
+ hideCursor();
1749
+ }
1750
+ return cursor;
1751
+ });
1752
+ this.mathField.focus();
1753
+ this.props?.onFocus();
1754
+ this.setState({
1755
+ focused: true
1756
+ }, () => {
1757
+ // NOTE(charlie): We use `setTimeout` to allow for a layout pass to
1758
+ // occur. Otherwise, the keypad is measured incorrectly. Ideally,
1759
+ // we'd use requestAnimationFrame here, but it's unsupported on
1760
+ // Android Browser 4.3.
1761
+ setTimeout(() => {
1762
+ if (this._isMounted) {
1763
+ // TODO(benkomalo): the keypad is animating at this point,
1764
+ // so we can't call _cacheKeypadBounds(), even though
1765
+ // it'd be nice to do so. It should probably be the case
1766
+ // that the higher level controller tells us when the
1767
+ // keypad is settled (then scrollIntoView wouldn't have
1768
+ // to make assumptions about that either).
1769
+ const maybeKeypadNode = this.props.keypadElement?.getDOMNode();
1770
+ scrollIntoView(this._container, maybeKeypadNode);
1771
+ }
1772
+ });
1773
+ });
1774
+ };
1775
+
1776
+ /**
1777
+ * Tries to determine which DOM node to place the cursor next to based on
1778
+ * where the user drags the cursor handle. If it finds a node it will
1779
+ * place the cursor next to it, update the handle to be under the cursor,
1780
+ * and return true. If it doesn't find a node, it returns false.
1781
+ *
1782
+ * It searches for nodes by doing it tests at the following points:
1783
+ *
1784
+ * (x - dx, y), (x, y), (x + dx, y)
1785
+ *
1786
+ * If it doesn't find any nodes from the rendered math it will update y
1787
+ * by adding dy.
1788
+ *
1789
+ * The algorithm ends its search when y goes outside the bounds of
1790
+ * containerBounds.
1791
+ *
1792
+ * @param {DOMRect} containerBounds - bounds of the container node
1793
+ * @param {number} x - the initial x coordinate in the viewport
1794
+ * @param {number} y - the initial y coordinate in the viewport
1795
+ * @param {number} dx - horizontal spacing between elementFromPoint calls
1796
+ * @param {number} dy - vertical spacing between elementFromPoint calls,
1797
+ * sign determines direction.
1798
+ * @returns {boolean} - true if a node was hit, false otherwise.
1799
+ */
1800
+ _findHitNode = (containerBounds, x, y, dx, dy) => {
1801
+ while (y >= containerBounds.top && y <= containerBounds.bottom) {
1802
+ y += dy;
1803
+ const points = [[x - dx, y], [x, y], [x + dx, y]];
1804
+ const elements = points
1805
+ // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
1806
+ .map(point => document.elementFromPoint(...point))
1807
+ // We exclude the root container itself and any nodes marked
1808
+ // as non-leaf which are fractions, parens, and roots. The
1809
+ // children of those nodes are included in the list because
1810
+ // those are the items we care about placing the cursor next
1811
+ // to.
1812
+ //
1813
+ // MathQuill's mq-non-leaf is not applied to all non-leaf nodes
1814
+ // so the naming is a bit confusing. Although fractions are
1815
+ // included, neither mq-numerator nor mq-denominator nodes are
1816
+ // and neither are subscripts or superscripts.
1817
+ .filter(element => element && this._root.contains(element) && (!element.classList.contains("mq-root-block") && !element.classList.contains("mq-non-leaf") || element.classList.contains("mq-empty") || element.classList.contains("mq-hasCursor")));
1818
+ let hitNode = null;
1819
+
1820
+ // Contains only DOMNodes with child elements.
1821
+ const nonLeafElements = [];
1822
+ let max = 0;
1823
+ const counts = {};
1824
+ const elementsById = {};
1825
+ for (const element of elements) {
1826
+ // @ts-expect-error - TS2531 - Object is possibly 'null'.
1827
+ const id = element.getAttribute("mathquill-command-id");
1828
+ if (id != null) {
1829
+ counts[id] = (counts[id] || 0) + 1;
1830
+ elementsById[id] = element;
1831
+ } else {
1832
+ // @ts-expect-error - TS2345 - Argument of type 'Element | null' is not assignable to parameter of type 'HTMLElement | null'.
1833
+ nonLeafElements.push(element);
1834
+ }
1835
+ }
1836
+
1837
+ // When determining which DOMNode to place the cursor beside, we
1838
+ // prefer leaf nodes. Hitting a leaf node is a good sign that the
1839
+ // cursor is really close to some piece of math that has been
1840
+ // rendered because leaf nodes contain text. Non-leaf nodes may
1841
+ // contain a lot of whitespace so the cursor may be further away
1842
+ // from actual text within the expression.
1843
+ //
1844
+ // Since we're doing three hit tests per loop it's possible that
1845
+ // we hit multiple leaf nodes at the same time. In this case we
1846
+ // we prefer the DOMNode with the most hits.
1847
+ // TODO(kevinb) consider preferring nodes hit by [x, y].
1848
+ for (const [id, count] of wonderStuffCore.entries(counts)) {
1849
+ if (count > max) {
1850
+ max = count;
1851
+ hitNode = elementsById[id];
1852
+ }
1853
+ }
1854
+
1855
+ // It's possible that two non-leaf nodes are right beside each
1856
+ // other. We don't bother counting the number of hits for each,
1857
+ // b/c this seems like an unlikely situation. Also, ignoring the
1858
+ // hit count in the situation should not have serious effects on
1859
+ // the overall accuracy of the algorithm.
1860
+ if (hitNode == null && nonLeafElements.length > 0) {
1861
+ // @ts-expect-error - TS2322 - Type 'HTMLElement | null' is not assignable to type 'null'.
1862
+ hitNode = nonLeafElements[0];
1863
+ }
1864
+ if (hitNode !== null) {
1865
+ this.mathField.setCursorPosition(x, y, hitNode);
1866
+ return true;
1867
+ }
1868
+ }
1869
+ return false;
1870
+ };
1871
+
1872
+ /**
1873
+ * Inserts the cursor at the DOM node closest to the given coordinates,
1874
+ * based on hit-tests conducted using #_findHitNode.
1875
+ *
1876
+ * @param {number} x - the x coordinate in the viewport
1877
+ * @param {number} y - the y coordinate in the viewport
1878
+ */
1879
+ _insertCursorAtClosestNode = (x, y) => {
1880
+ const cursor = this.mathField.getCursor();
1881
+
1882
+ // Pre-emptively check if the input has any child nodes; if not, the
1883
+ // input is empty, so we throw the cursor at the start.
1884
+ if (!this._root.hasChildNodes()) {
1885
+ cursor.insAtLeftEnd(this.mathField.mathField.__controller.root);
1886
+ return;
1887
+ }
1888
+
1889
+ // NOTE(diedra): The adding and subtracting of 10 or 15 pixels here accounts
1890
+ // for the padding that surrounds the input values.
1891
+ if (y > this._containerBounds.bottom) {
1892
+ y = this._containerBounds.bottom - 10;
1893
+ } else if (y < this._containerBounds.top) {
1894
+ y = this._containerBounds.top + 10;
1895
+ }
1896
+ if (x > this._containerBounds.right) {
1897
+ x = this._containerBounds.right - 15;
1898
+ } else if (x < this._containerBounds.left) {
1899
+ x = this._containerBounds.left + 15;
1900
+ }
1901
+ let dy;
1902
+
1903
+ // Vertical spacing between hit tests
1904
+ // dy is negative because we're moving upwards.
1905
+ dy = -8;
1906
+
1907
+ // Horizontal spacing between hit tests
1908
+ // Note: This value depends on the font size. If the gap is too small
1909
+ // we end up placing the cursor at the end of the expression when we
1910
+ // shouldn't.
1911
+ const dx = 5;
1912
+ if (this._findHitNode(this._containerBounds, x, y, dx, dy)) {
1913
+ return;
1914
+ }
1915
+
1916
+ // If we haven't found anything start from the top.
1917
+ y = this._containerBounds.top;
1918
+
1919
+ // dy is positive b/c we're going downwards.
1920
+ dy = 8;
1921
+ if (this._findHitNode(this._containerBounds, x, y, dx, dy)) {
1922
+ return;
1923
+ }
1924
+ const firstChildBounds = this._root.firstChild.getBoundingClientRect();
1925
+ const lastChildBounds = this._root.lastChild.getBoundingClientRect();
1926
+ const left = firstChildBounds.left;
1927
+ const right = lastChildBounds.right;
1928
+
1929
+ // We've exhausted all of the options. We're likely either to the right
1930
+ // or left of all of the math, so we place the cursor at the end to
1931
+ // which it's closest.
1932
+ if (Math.abs(x - right) < Math.abs(x - left)) {
1933
+ cursor.insAtRightEnd(this.mathField.mathField.__controller.root);
1934
+ } else {
1935
+ cursor.insAtLeftEnd(this.mathField.mathField.__controller.root);
1936
+ }
1937
+ // In that event, we need to update the cursor context ourselves.
1938
+ this.props.keypadElement && this.props.keypadElement.setCursor({
1939
+ context: this.mathField.contextForCursor()
1940
+ });
1941
+ };
1942
+ handleTouchStart = e => {
1943
+ e.stopPropagation();
1944
+
1945
+ // Hide the cursor handle on touch start, if the handle itself isn't
1946
+ // handling the touch event.
1947
+ this._hideCursorHandle();
1948
+
1949
+ // Cache the container bounds, so as to avoid re-computing. If we don't
1950
+ // have any content, then it's not necessary, since the cursor can't be
1951
+ // moved anyway.
1952
+ if (this.mathField.getContent() !== "") {
1953
+ this._containerBounds = this._container.getBoundingClientRect();
1954
+
1955
+ // Make the cursor visible and set the handle-less cursor's
1956
+ // location.
1957
+ const touch = e.changedTouches[0];
1958
+ this._insertCursorAtClosestNode(touch.clientX, touch.clientY);
1959
+ }
1960
+
1961
+ // Trigger a focus event, if we're not already focused.
1962
+ if (!this.state.focused) {
1963
+ this.focus();
1964
+ }
1965
+ };
1966
+ handleTouchMove = e => {
1967
+ e.stopPropagation();
1968
+
1969
+ // Update the handle-less cursor's location on move, if there's any
1970
+ // content in the box. Note that if the user touched outside the keypad
1971
+ // (e.g., with a different finger) during this touch interaction, we
1972
+ // may have blurred, in which case we should ignore the touch (since
1973
+ // the cursor is no longer visible and the input is no longer
1974
+ // highlighted).
1975
+ if (this.mathField.getContent() !== "" && this.state.focused) {
1976
+ const touch = e.changedTouches[0];
1977
+ this._insertCursorAtClosestNode(touch.clientX, touch.clientY);
1978
+ }
1979
+ };
1980
+ handleTouchEnd = e => {
1981
+ e.stopPropagation();
1982
+
1983
+ // And on touch-end, reveal the cursor, unless the input is empty. Note
1984
+ // that if the user touched outside the keypad (e.g., with a different
1985
+ // finger) during this touch interaction, we may have blurred, in which
1986
+ // case we should ignore the touch (since the cursor is no longer
1987
+ // visible and the input is no longer highlighted).
1988
+ if (this.mathField.getContent() !== "" && this.state.focused) {
1989
+ this._updateCursorHandle();
1990
+ }
1991
+ };
1992
+
1993
+ /**
1994
+ * When a touch starts in the cursor handle, we track it so as to avoid
1995
+ * handling any touch events ourself.
1996
+ *
1997
+ * @param {TouchEvent} e - the raw touch event from the browser
1998
+ */
1999
+ onCursorHandleTouchStart = e => {
2000
+ // NOTE(charlie): The cursor handle is a child of this view, so whenever
2001
+ // it receives a touch event, that event would also typically be bubbled
2002
+ // up to our own handlers. However, we want the cursor to handle its own
2003
+ // touch events, and for this view to only handle touch events that
2004
+ // don't affect the cursor. As such, we `stopPropagation` on any touch
2005
+ // events that are being handled by the cursor, so as to avoid handling
2006
+ // them in our own touch handlers.
2007
+ e.stopPropagation();
2008
+ e.preventDefault();
2009
+
2010
+ // Cache the container bounds, so as to avoid re-computing.
2011
+ this._containerBounds = this._container.getBoundingClientRect();
2012
+ };
2013
+ _constrainToBound = (value, min, max, friction) => {
2014
+ if (value < min) {
2015
+ return min + (value - min) * friction;
2016
+ } else if (value > max) {
2017
+ return max + (value - max) * friction;
2018
+ } else {
2019
+ return value;
2020
+ }
2021
+ };
2022
+
2023
+ /**
2024
+ * When the user moves the cursor handle update the position of the cursor
2025
+ * and the handle.
2026
+ *
2027
+ * @param {TouchEvent} e - the raw touch event from the browser
2028
+ */
2029
+ onCursorHandleTouchMove = e => {
2030
+ e.stopPropagation();
2031
+ const x = e.changedTouches[0].clientX;
2032
+ const y = e.changedTouches[0].clientY;
2033
+ const relativeX = x - this._containerBounds.left;
2034
+ const relativeY = y - 2 * cursorHandleRadiusPx * cursorHandleDistanceMultiplier - this._containerBounds.top;
2035
+
2036
+ // We subtract the containerBounds left/top to correct for the
2037
+ // MathInput's position on the page. On top of that, we subtract an
2038
+ // additional 2 x {height of the cursor} so that the bottom of the
2039
+ // cursor tracks the user's finger, to make it visible under their
2040
+ // touch.
2041
+ this.setState({
2042
+ handle: {
2043
+ animateIntoPosition: false,
2044
+ visible: true,
2045
+ // TODO(charlie): Use clientX and clientY to avoid the need for
2046
+ // scroll offsets. This likely also means that the cursor
2047
+ // detection doesn't work when scrolled, since we're not
2048
+ // offsetting those values.
2049
+ x: this._constrainToBound(relativeX, 0, this._containerBounds.width, constrainingFrictionFactor),
2050
+ y: this._constrainToBound(relativeY, 0, this._containerBounds.height, constrainingFrictionFactor)
2051
+ }
2052
+ });
2053
+
2054
+ // Use a y-coordinate that's just above where the user is actually
2055
+ // touching because they're dragging the handle which is a little
2056
+ // below where the cursor actually is.
2057
+ const distanceAboveFingerToTrySelecting = 22;
2058
+ const adjustedY = y - distanceAboveFingerToTrySelecting;
2059
+ this._insertCursorAtClosestNode(x, adjustedY);
2060
+ };
2061
+
2062
+ /**
2063
+ * When the user releases the cursor handle, animate it back into place.
2064
+ *
2065
+ * @param {TouchEvent} e - the raw touch event from the browser
2066
+ */
2067
+ onCursorHandleTouchEnd = e => {
2068
+ e.stopPropagation();
2069
+ this._updateCursorHandle(true);
2070
+ };
2071
+
2072
+ /**
2073
+ * If the gesture is cancelled mid-drag, simply hide it.
2074
+ *
2075
+ * @param {TouchEvent} e - the raw touch event from the browser
2076
+ */
2077
+ onCursorHandleTouchCancel = e => {
2078
+ e.stopPropagation();
2079
+ this._updateCursorHandle(true);
2080
+ };
2081
+ domKeyToMathQuillKey = key => {
2082
+ const keyMap = {
2083
+ "+": "PLUS",
2084
+ "-": "MINUS",
2085
+ "*": "TIMES",
2086
+ "/": "DIVIDE",
2087
+ ".": "DECIMAL",
2088
+ "%": "PERCENT",
2089
+ "=": "EQUAL",
2090
+ ">": "GT",
2091
+ "<": "LT",
2092
+ "^": "EXP"
2093
+ };
2094
+
2095
+ // Numbers
2096
+ if (["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].includes(key)) {
2097
+ return `NUM_${key}`;
2098
+ }
2099
+
2100
+ // Movement keys
2101
+ else if (key === "Backspace") {
2102
+ return "BACKSPACE";
2103
+ }
2104
+
2105
+ // Operators
2106
+ else if (key in keyMap) {
2107
+ return keyMap[key];
2108
+ }
2109
+
2110
+ // The key pressed doesn't map to any of the math input operators
2111
+ return null;
2112
+ };
2113
+ handleKeyUp = event => {
2114
+ const mathQuillKey = this.domKeyToMathQuillKey(event.key);
2115
+ if (mathQuillKey) {
2116
+ this.mathField.pressKey(mathQuillKey);
2117
+
2118
+ // TODO(diedra): If the new value being added is off-screen to the right
2119
+ // due to the max-width of the text box, scroll the box to show the newest
2120
+ // value
2121
+ const value = this.mathField.getContent();
2122
+ if (this.props.value !== value) {
2123
+ this.mathField.setContent(this.props.value);
2124
+ this.props.onChange(value, false);
2125
+ this._hideCursorHandle();
2126
+ }
2127
+ }
2128
+ };
2129
+ getBorderWidthPx = () => {
2130
+ // TODO(diedra): Move these to the common style package.
2131
+ const normalBorderWidthPx = 1;
2132
+ const focusedBorderWidthPx = 2;
2133
+ return this.state.focused ? focusedBorderWidthPx : normalBorderWidthPx;
2134
+ };
2135
+
2136
+ // Calculate the appropriate padding based on the border width (which is
2137
+ // considered 'padding', since we're using 'border-box') and the fact
2138
+ // that MathQuill automatically applies 2px of padding to the inner
2139
+ // input.
2140
+ getInputInnerPadding = () => {
2141
+ const paddingInset = totalDesiredPadding - this.getBorderWidthPx();
2142
+
2143
+ // Now, translate that to the appropriate padding for each direction.
2144
+ // The complication here is that we want numerals to be centered within
2145
+ // the input. However, Symbola (MathQuill's font of choice) renders
2146
+ // numerals with approximately 3px of padding below and 1px of padding
2147
+ // above (to make room for ascenders and descenders). So we ignore those
2148
+ // padding values for the vertical directions.
2149
+ const symbolaPaddingBottom = 3;
2150
+ const symbolaPaddingTop = 1;
2151
+ const padding = {
2152
+ paddingTop: paddingInset - symbolaPaddingTop,
2153
+ paddingRight: paddingInset,
2154
+ paddingBottom: paddingInset - symbolaPaddingBottom,
2155
+ paddingLeft: paddingInset
2156
+ };
2157
+ return padding;
2158
+ };
2129
2159
  render() {
2130
2160
  const {
2131
2161
  focused,
@@ -2181,10 +2211,6 @@ class MathInput extends React__namespace.Component {
2181
2211
  })));
2182
2212
  }
2183
2213
  }
2184
- _defineProperty(MathInput, "defaultProps", {
2185
- style: {},
2186
- value: ""
2187
- });
2188
2214
  const fontSizePt = 18;
2189
2215
  const inputMaxWidth = 128;
2190
2216
 
@@ -2335,12 +2361,12 @@ const styles$i = aphrodite.StyleSheet.create({
2335
2361
  height: 38,
2336
2362
  boxSizing: "border-box",
2337
2363
  borderRadius: 3,
2338
- border: "1px solid transparent",
2364
+ border: `1px solid transparent`,
2339
2365
  marginRight: 1,
2340
2366
  marginLeft: 1
2341
2367
  },
2342
2368
  hovered: {
2343
- background: "linear-gradient(0deg, rgba(24, 101, 242, 0.32), rgba(24, 101, 242, 0.32)), ".concat(Color__default["default"].white),
2369
+ background: `linear-gradient(0deg, rgba(24, 101, 242, 0.32), rgba(24, 101, 242, 0.32)), ${Color__default["default"].white}`,
2344
2370
  border: "1px solid #1865F2"
2345
2371
  },
2346
2372
  pressed: {
@@ -2348,11 +2374,11 @@ const styles$i = aphrodite.StyleSheet.create({
2348
2374
  },
2349
2375
  focused: {
2350
2376
  outline: "none",
2351
- border: "2px solid ".concat(Color__default["default"].blue)
2377
+ border: `2px solid ${Color__default["default"].blue}`
2352
2378
  },
2353
2379
  innerBox: {
2354
2380
  boxSizing: "border-box",
2355
- border: "1px solid transparent",
2381
+ border: `1px solid transparent`,
2356
2382
  borderRadius: 2,
2357
2383
  display: "flex",
2358
2384
  flex: 1,
@@ -2360,7 +2386,7 @@ const styles$i = aphrodite.StyleSheet.create({
2360
2386
  alignItems: "center"
2361
2387
  },
2362
2388
  innerBoxPressed: {
2363
- border: "1px solid ".concat(Color__default["default"].white)
2389
+ border: `1px solid ${Color__default["default"].white}`
2364
2390
  },
2365
2391
  activeIndicator: {
2366
2392
  position: "absolute",
@@ -2372,7 +2398,7 @@ const styles$i = aphrodite.StyleSheet.create({
2372
2398
  },
2373
2399
  clickable: {
2374
2400
  ":focus": {
2375
- outline: "none"
2401
+ outline: `none`
2376
2402
  }
2377
2403
  }
2378
2404
  });
@@ -2452,7 +2478,7 @@ function Tabbar(props) {
2452
2478
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
2453
2479
  style: [styles$h.pages]
2454
2480
  }, items.map(item => /*#__PURE__*/React__namespace.createElement(TabbarItem, {
2455
- key: "tabbar-item-".concat(item),
2481
+ key: `tabbar-item-${item}`,
2456
2482
  itemState: item === selectedItem ? "active" : "inactive",
2457
2483
  itemType: item,
2458
2484
  onClick: () => {
@@ -4652,7 +4678,7 @@ function ButtonAsset(_ref) {
4652
4678
  // this line forces an exhaustive check of all keys;
4653
4679
  // if a key is not handled, the compiler will complain.
4654
4680
  const unhandledKey = id;
4655
- throw new Error("Unhandled key: ".concat(unhandledKey));
4681
+ throw new Error(`Unhandled key: ${unhandledKey}`);
4656
4682
  }
4657
4683
  }
4658
4684
 
@@ -4702,7 +4728,7 @@ const styles$g = aphrodite.StyleSheet.create({
4702
4728
  display: "flex",
4703
4729
  justifyContent: "center",
4704
4730
  alignItems: "center",
4705
- boxShadow: "0px 1px 0px ".concat(Color__default["default"].offBlack32),
4731
+ boxShadow: `0px 1px 0px ${Color__default["default"].offBlack32}`,
4706
4732
  boxSizing: "border-box",
4707
4733
  background: Color__default["default"].white,
4708
4734
  borderRadius: 4,
@@ -4727,7 +4753,7 @@ const styles$g = aphrodite.StyleSheet.create({
4727
4753
  pressed: {
4728
4754
  border: "2px solid #1B50B3",
4729
4755
  padding: 0,
4730
- background: "linear-gradient(0deg, rgba(24, 101, 242, 0.32), rgba(24, 101, 242, 0.32)), ".concat(Color__default["default"].white),
4756
+ background: `linear-gradient(0deg, rgba(24, 101, 242, 0.32), rgba(24, 101, 242, 0.32)), ${Color__default["default"].white}`,
4731
4757
  boxShadow: "none"
4732
4758
  },
4733
4759
  outerBoxBase: {
@@ -4743,7 +4769,7 @@ const styles$g = aphrodite.StyleSheet.create({
4743
4769
  height: "100%",
4744
4770
  boxSizing: "border-box",
4745
4771
  ":focus": {
4746
- outline: "none"
4772
+ outline: `none`
4747
4773
  }
4748
4774
  }
4749
4775
  });
@@ -5037,7 +5063,7 @@ function getStyles(key) {
5037
5063
  case "LEFT":
5038
5064
  return styles$f.left;
5039
5065
  default:
5040
- throw new Error("Invalid key: ".concat(key));
5066
+ throw new Error(`Invalid key: ${key}`);
5041
5067
  }
5042
5068
  }
5043
5069
  function NavigationButton(_ref) {
@@ -5078,7 +5104,7 @@ const styles$f = aphrodite.StyleSheet.create({
5078
5104
  width: "100%",
5079
5105
  height: "100%",
5080
5106
  ":focus": {
5081
- outline: "none"
5107
+ outline: `none`
5082
5108
  }
5083
5109
  },
5084
5110
  outerBoxBase: {
@@ -5086,7 +5112,7 @@ const styles$f = aphrodite.StyleSheet.create({
5086
5112
  width: "100%"
5087
5113
  },
5088
5114
  base: {
5089
- boxShadow: "0px 1px 0px ".concat(Color__default["default"].offBlack32),
5115
+ boxShadow: `0px 1px 0px ${Color__default["default"].offBlack32}`,
5090
5116
  display: "flex",
5091
5117
  justifyContent: "center",
5092
5118
  alignItems: "center",
@@ -5120,7 +5146,7 @@ const styles$f = aphrodite.StyleSheet.create({
5120
5146
  },
5121
5147
  pressed: {
5122
5148
  border: "2px solid #1B50B3",
5123
- background: "linear-gradient(0deg, rgba(24, 101, 242, 0.32), rgba(24, 101, 242, 0.32)), ".concat(Color__default["default"].white),
5149
+ background: `linear-gradient(0deg, rgba(24, 101, 242, 0.32), rgba(24, 101, 242, 0.32)), ${Color__default["default"].white}`,
5124
5150
  boxShadow: "none"
5125
5151
  }
5126
5152
  });
@@ -5238,7 +5264,6 @@ const defaultProps = {
5238
5264
  extraKeys: []
5239
5265
  };
5240
5266
  function getAvailableTabs(props) {
5241
- var _props$extraKeys;
5242
5267
  // We don't want to show any available tabs on the fractions keypad
5243
5268
  if (props.fractionsOnly) {
5244
5269
  return [];
@@ -5252,7 +5277,7 @@ function getAvailableTabs(props) {
5252
5277
  if (props.trigonometry) {
5253
5278
  tabs.push("Geometry");
5254
5279
  }
5255
- if ((_props$extraKeys = props.extraKeys) !== null && _props$extraKeys !== void 0 && _props$extraKeys.length) {
5280
+ if (props.extraKeys?.length) {
5256
5281
  tabs.push("Extras");
5257
5282
  }
5258
5283
  return tabs;
@@ -5465,6 +5490,7 @@ class TransitionChild extends React__namespace.Component {
5465
5490
  // We keep track of all of the current applied classes so that we can remove
5466
5491
  // them before a new transition starts in the case of the current transition
5467
5492
  // being interrupted.
5493
+ _isMounted = false;
5468
5494
 
5469
5495
  // The use of getDerivedStateFromProps here is to avoid an extra call to
5470
5496
  // setState if the component re-enters. This can happen if TransitionGroup
@@ -5483,38 +5509,6 @@ class TransitionChild extends React__namespace.Component {
5483
5509
  }
5484
5510
  constructor(props) {
5485
5511
  super(props);
5486
- _defineProperty(this, "classNameQueue", void 0);
5487
- _defineProperty(this, "appliedClassNames", void 0);
5488
- _defineProperty(this, "_isMounted", false);
5489
- _defineProperty(this, "addClass", (elem, className) => {
5490
- if (className) {
5491
- elem.classList.add(className);
5492
- this.appliedClassNames.add(className);
5493
- }
5494
- });
5495
- _defineProperty(this, "removeClass", (elem, className) => {
5496
- if (className) {
5497
- elem.classList.remove(className);
5498
- this.appliedClassNames.delete(className);
5499
- }
5500
- });
5501
- _defineProperty(this, "flushClassNameQueue", () => {
5502
- if (this._isMounted) {
5503
- const node = ReactDOM__default["default"].findDOMNode(this);
5504
- if (node instanceof Element) {
5505
- this.classNameQueue.forEach(_ref2 => {
5506
- let [removeClassName, addClassName] = _ref2;
5507
- // Remove the old class before adding a new class just
5508
- // in case the new class is the same as the old one.
5509
- this.removeClass(node, removeClassName);
5510
- this.addClass(node, addClassName);
5511
- });
5512
- }
5513
- }
5514
-
5515
- // Remove all items in the Array.
5516
- this.classNameQueue.length = 0;
5517
- });
5518
5512
  this._isMounted = false;
5519
5513
  this.classNameQueue = [];
5520
5514
  this.appliedClassNames = new Set();
@@ -5558,6 +5552,18 @@ class TransitionChild extends React__namespace.Component {
5558
5552
  this.removeClass(node, className);
5559
5553
  }
5560
5554
  }
5555
+ addClass = (elem, className) => {
5556
+ if (className) {
5557
+ elem.classList.add(className);
5558
+ this.appliedClassNames.add(className);
5559
+ }
5560
+ };
5561
+ removeClass = (elem, className) => {
5562
+ if (className) {
5563
+ elem.classList.remove(className);
5564
+ this.appliedClassNames.delete(className);
5565
+ }
5566
+ };
5561
5567
  transition(animationType, duration) {
5562
5568
  const node = ReactDOM__default["default"].findDOMNode(this);
5563
5569
  if (!(node instanceof Element)) {
@@ -5599,6 +5605,23 @@ class TransitionChild extends React__namespace.Component {
5599
5605
  // Queue operation for after the next paint.
5600
5606
  this.props.schedule.timeout(this.flushClassNameQueue, 0);
5601
5607
  }
5608
+ flushClassNameQueue = () => {
5609
+ if (this._isMounted) {
5610
+ const node = ReactDOM__default["default"].findDOMNode(this);
5611
+ if (node instanceof Element) {
5612
+ this.classNameQueue.forEach(_ref2 => {
5613
+ let [removeClassName, addClassName] = _ref2;
5614
+ // Remove the old class before adding a new class just
5615
+ // in case the new class is the same as the old one.
5616
+ this.removeClass(node, removeClassName);
5617
+ this.addClass(node, addClassName);
5618
+ });
5619
+ }
5620
+ }
5621
+
5622
+ // Remove all items in the Array.
5623
+ this.classNameQueue.length = 0;
5624
+ };
5602
5625
  render() {
5603
5626
  const {
5604
5627
  status
@@ -5682,71 +5705,13 @@ const AnimationDurationInMS = 200;
5682
5705
  * can't have methods attached to them).
5683
5706
  */
5684
5707
  class MobileKeypad extends React__namespace.Component {
5685
- constructor() {
5686
- super(...arguments);
5687
- _defineProperty(this, "_containerRef", /*#__PURE__*/React__namespace.createRef());
5688
- _defineProperty(this, "_containerResizeObserver", null);
5689
- _defineProperty(this, "_throttleResize", false);
5690
- _defineProperty(this, "state", {
5691
- containerWidth: 0
5692
- });
5693
- _defineProperty(this, "_resize", () => {
5694
- var _this$_containerRef$c;
5695
- const containerWidth = ((_this$_containerRef$c = this._containerRef.current) === null || _this$_containerRef$c === void 0 ? void 0 : _this$_containerRef$c.clientWidth) || 0;
5696
- this.setState({
5697
- containerWidth
5698
- });
5699
- });
5700
- _defineProperty(this, "_throttleResizeHandler", () => {
5701
- if (this._throttleResize) {
5702
- return;
5703
- }
5704
- this._throttleResize = true;
5705
- setTimeout(() => {
5706
- this._resize();
5707
- this._throttleResize = false;
5708
- }, 100);
5709
- });
5710
- _defineProperty(this, "activate", () => {
5711
- this.props.setKeypadActive(true);
5712
- });
5713
- _defineProperty(this, "dismiss", () => {
5714
- var _this$props$onDismiss, _this$props;
5715
- this.props.setKeypadActive(false);
5716
- (_this$props$onDismiss = (_this$props = this.props).onDismiss) === null || _this$props$onDismiss === void 0 ? void 0 : _this$props$onDismiss.call(_this$props);
5717
- });
5718
- _defineProperty(this, "configure", (configuration, cb) => {
5719
- this.setState({
5720
- keypadConfig: configuration
5721
- });
5722
-
5723
- // TODO(matthewc)[LC-1080]: this was brought in from v1's ProvidedKeypad.
5724
- // We need to investigate whether we still need this.
5725
- // HACK(charlie): In Perseus, triggering a focus causes the keypad to
5726
- // animate into view and re-configure. We'd like to provide the option
5727
- // to re-render the re-configured keypad before animating it into view,
5728
- // to avoid jank in the animation. As such, we support passing a
5729
- // callback into `configureKeypad`. However, implementing this properly
5730
- // would require middleware, etc., so we just hack it on with
5731
- // `setTimeout` for now.
5732
- setTimeout(() => cb && cb());
5733
- });
5734
- _defineProperty(this, "setCursor", cursor => {
5735
- this.setState({
5736
- cursor
5737
- });
5738
- });
5739
- _defineProperty(this, "setKeyHandler", keyHandler => {
5740
- this.setState({
5741
- keyHandler
5742
- });
5743
- });
5744
- _defineProperty(this, "getDOMNode", () => {
5745
- return ReactDOM__default["default"].findDOMNode(this);
5746
- });
5747
- }
5708
+ _containerRef = /*#__PURE__*/React__namespace.createRef();
5709
+ _containerResizeObserver = null;
5710
+ _throttleResize = false;
5711
+ state = {
5712
+ containerWidth: 0
5713
+ };
5748
5714
  componentDidMount() {
5749
- var _this$props$onElement, _this$props2;
5750
5715
  this._resize();
5751
5716
  window.addEventListener("resize", this._throttleResizeHandler);
5752
5717
  window.addEventListener("orientationchange", this._throttleResizeHandler);
@@ -5759,7 +5724,7 @@ class MobileKeypad extends React__namespace.Component {
5759
5724
  this._containerResizeObserver.observe(this._containerRef.current);
5760
5725
  }
5761
5726
  }
5762
- (_this$props$onElement = (_this$props2 = this.props).onElementMounted) === null || _this$props$onElement === void 0 ? void 0 : _this$props$onElement.call(_this$props2, {
5727
+ this.props.onElementMounted?.({
5763
5728
  activate: this.activate,
5764
5729
  dismiss: this.dismiss,
5765
5730
  configure: this.configure,
@@ -5769,18 +5734,68 @@ class MobileKeypad extends React__namespace.Component {
5769
5734
  });
5770
5735
  }
5771
5736
  componentWillUnmount() {
5772
- var _this$_containerResiz;
5773
5737
  window.removeEventListener("resize", this._throttleResizeHandler);
5774
5738
  window.removeEventListener("orientationchange", this._throttleResizeHandler);
5775
- (_this$_containerResiz = this._containerResizeObserver) === null || _this$_containerResiz === void 0 ? void 0 : _this$_containerResiz.disconnect();
5739
+ this._containerResizeObserver?.disconnect();
5776
5740
  }
5741
+ _resize = () => {
5742
+ const containerWidth = this._containerRef.current?.clientWidth || 0;
5743
+ this.setState({
5744
+ containerWidth
5745
+ });
5746
+ };
5747
+ _throttleResizeHandler = () => {
5748
+ if (this._throttleResize) {
5749
+ return;
5750
+ }
5751
+ this._throttleResize = true;
5752
+ setTimeout(() => {
5753
+ this._resize();
5754
+ this._throttleResize = false;
5755
+ }, 100);
5756
+ };
5757
+ activate = () => {
5758
+ this.props.setKeypadActive(true);
5759
+ };
5760
+ dismiss = () => {
5761
+ this.props.setKeypadActive(false);
5762
+ this.props.onDismiss?.();
5763
+ };
5764
+ configure = (configuration, cb) => {
5765
+ this.setState({
5766
+ keypadConfig: configuration
5767
+ });
5768
+
5769
+ // TODO(matthewc)[LC-1080]: this was brought in from v1's ProvidedKeypad.
5770
+ // We need to investigate whether we still need this.
5771
+ // HACK(charlie): In Perseus, triggering a focus causes the keypad to
5772
+ // animate into view and re-configure. We'd like to provide the option
5773
+ // to re-render the re-configured keypad before animating it into view,
5774
+ // to avoid jank in the animation. As such, we support passing a
5775
+ // callback into `configureKeypad`. However, implementing this properly
5776
+ // would require middleware, etc., so we just hack it on with
5777
+ // `setTimeout` for now.
5778
+ setTimeout(() => cb && cb());
5779
+ };
5780
+ setCursor = cursor => {
5781
+ this.setState({
5782
+ cursor
5783
+ });
5784
+ };
5785
+ setKeyHandler = keyHandler => {
5786
+ this.setState({
5787
+ keyHandler
5788
+ });
5789
+ };
5790
+ getDOMNode = () => {
5791
+ return ReactDOM__default["default"].findDOMNode(this);
5792
+ };
5777
5793
  _handleClickKey(key) {
5778
- var _this$state$keyHandle, _this$state;
5779
5794
  if (key === "DISMISS") {
5780
5795
  this.dismiss();
5781
5796
  return;
5782
5797
  }
5783
- const cursor = (_this$state$keyHandle = (_this$state = this.state).keyHandler) === null || _this$state$keyHandle === void 0 ? void 0 : _this$state$keyHandle.call(_this$state, key);
5798
+ const cursor = this.state.keyHandler?.(key);
5784
5799
  this.setState({
5785
5800
  cursor
5786
5801
  });
@@ -5798,35 +5813,35 @@ class MobileKeypad extends React__namespace.Component {
5798
5813
  const containerStyle = [styles$c.keypadContainer,
5799
5814
  // styles passed as props
5800
5815
  ...(Array.isArray(style) ? style : [style])];
5801
- const isExpression = (keypadConfig === null || keypadConfig === void 0 ? void 0 : keypadConfig.keypadType) === "EXPRESSION";
5802
- const convertDotToTimes = keypadConfig === null || keypadConfig === void 0 ? void 0 : keypadConfig.times;
5803
- return /*#__PURE__*/React__namespace.createElement(AphroditeCSSTransitionGroup, {
5816
+ const isExpression = keypadConfig?.keypadType === "EXPRESSION";
5817
+ const convertDotToTimes = keypadConfig?.times;
5818
+ return /*#__PURE__*/React__namespace.createElement(View, {
5819
+ style: containerStyle,
5820
+ forwardRef: this._containerRef
5821
+ }, /*#__PURE__*/React__namespace.createElement(AphroditeCSSTransitionGroup, {
5804
5822
  transitionEnterTimeout: AnimationDurationInMS,
5805
5823
  transitionLeaveTimeout: AnimationDurationInMS,
5806
5824
  transitionStyle: {
5807
5825
  enter: {
5808
5826
  transform: "translate3d(0, 100%, 0)",
5809
- transition: "".concat(AnimationDurationInMS, "ms ease-out")
5827
+ transition: `${AnimationDurationInMS}ms ease-out`
5810
5828
  },
5811
5829
  enterActive: {
5812
5830
  transform: "translate3d(0, 0, 0)"
5813
5831
  },
5814
5832
  leave: {
5815
5833
  transform: "translate3d(0, 0, 0)",
5816
- transition: "".concat(AnimationDurationInMS, "ms ease-out")
5834
+ transition: `${AnimationDurationInMS}ms ease-out`
5817
5835
  },
5818
5836
  leaveActive: {
5819
5837
  transform: "translate3d(0, 100%, 0)"
5820
5838
  }
5821
5839
  }
5822
- }, keypadActive ? /*#__PURE__*/React__namespace.createElement(View, {
5823
- style: containerStyle,
5824
- forwardRef: this._containerRef
5825
- }, /*#__PURE__*/React__namespace.createElement(Keypad$2, {
5840
+ }, keypadActive ? /*#__PURE__*/React__namespace.createElement(Keypad$2, {
5826
5841
  onAnalyticsEvent: this.props.onAnalyticsEvent,
5827
- extraKeys: keypadConfig === null || keypadConfig === void 0 ? void 0 : keypadConfig.extraKeys,
5842
+ extraKeys: keypadConfig?.extraKeys,
5828
5843
  onClickKey: key => this._handleClickKey(key),
5829
- cursorContext: cursor === null || cursor === void 0 ? void 0 : cursor.context,
5844
+ cursorContext: cursor?.context,
5830
5845
  fractionsOnly: !isExpression,
5831
5846
  convertDotToTimes: convertDotToTimes,
5832
5847
  divisionKey: isExpression,
@@ -5837,7 +5852,7 @@ class MobileKeypad extends React__namespace.Component {
5837
5852
  advancedRelations: isExpression,
5838
5853
  expandedView: containerWidth > expandedViewThreshold$1,
5839
5854
  showDismiss: true
5840
- })) : null);
5855
+ }) : null));
5841
5856
  }
5842
5857
  }
5843
5858
  const styles$c = aphrodite.StyleSheet.create({
@@ -5960,20 +5975,14 @@ var Styles = aphrodite.StyleSheet.create({
5960
5975
  }
5961
5976
  });
5962
5977
 
5978
+ /**
5979
+ * A component that renders an icon with math (via KaTeX).
5980
+ */
5963
5981
  const {
5964
5982
  row: row$7,
5965
5983
  centered: centered$4
5966
5984
  } = Styles;
5967
5985
  class MathIcon extends React__namespace.Component {
5968
- constructor() {
5969
- super(...arguments);
5970
- _defineProperty(this, "_renderMath", () => {
5971
- const {
5972
- math
5973
- } = this.props;
5974
- katex__default["default"].render(math, ReactDOM__default["default"].findDOMNode(this));
5975
- });
5976
- }
5977
5986
  componentDidMount() {
5978
5987
  this._renderMath();
5979
5988
  }
@@ -5982,6 +5991,12 @@ class MathIcon extends React__namespace.Component {
5982
5991
  this._renderMath();
5983
5992
  }
5984
5993
  }
5994
+ _renderMath = () => {
5995
+ const {
5996
+ math
5997
+ } = this.props;
5998
+ katex__default["default"].render(math, ReactDOM__default["default"].findDOMNode(this));
5999
+ };
5985
6000
  render() {
5986
6001
  const {
5987
6002
  style
@@ -6002,7 +6017,15 @@ const styles$a = aphrodite.StyleSheet.create({
6002
6017
  }
6003
6018
  });
6004
6019
 
6020
+ /**
6021
+ * An autogenerated component that renders the COS iconograpy in SVG.
6022
+ *
6023
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6024
+ */
6005
6025
  class Cos extends React__namespace.Component {
6026
+ static propTypes = {
6027
+ color: PropTypes__default["default"].string.isRequired
6028
+ };
6006
6029
  render() {
6007
6030
  return /*#__PURE__*/React__namespace.createElement("svg", {
6008
6031
  width: "48",
@@ -6023,11 +6046,16 @@ class Cos extends React__namespace.Component {
6023
6046
  })));
6024
6047
  }
6025
6048
  }
6026
- _defineProperty(Cos, "propTypes", {
6027
- color: PropTypes__default["default"].string.isRequired
6028
- });
6029
6049
 
6050
+ /**
6051
+ * An autogenerated component that renders the LOG iconograpy in SVG.
6052
+ *
6053
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6054
+ */
6030
6055
  class Log extends React__namespace.Component {
6056
+ static propTypes = {
6057
+ color: PropTypes__default["default"].string.isRequired
6058
+ };
6031
6059
  render() {
6032
6060
  return /*#__PURE__*/React__namespace.createElement("svg", {
6033
6061
  width: "48",
@@ -6045,11 +6073,16 @@ class Log extends React__namespace.Component {
6045
6073
  })));
6046
6074
  }
6047
6075
  }
6048
- _defineProperty(Log, "propTypes", {
6049
- color: PropTypes__default["default"].string.isRequired
6050
- });
6051
6076
 
6077
+ /**
6078
+ * An autogenerated component that renders the EQUAL iconograpy in SVG.
6079
+ *
6080
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6081
+ */
6052
6082
  class Equal extends React__namespace.Component {
6083
+ static propTypes = {
6084
+ color: PropTypes__default["default"].string.isRequired
6085
+ };
6053
6086
  render() {
6054
6087
  return /*#__PURE__*/React__namespace.createElement("svg", {
6055
6088
  width: "48",
@@ -6073,9 +6106,6 @@ class Equal extends React__namespace.Component {
6073
6106
  })));
6074
6107
  }
6075
6108
  }
6076
- _defineProperty(Equal, "propTypes", {
6077
- color: PropTypes__default["default"].string.isRequired
6078
- });
6079
6109
 
6080
6110
  /**
6081
6111
  * An autogenerated component that renders the BACKSPACE iconograpy in SVG.
@@ -6105,7 +6135,15 @@ const Backspace = () => {
6105
6135
  })));
6106
6136
  };
6107
6137
 
6138
+ /**
6139
+ * An autogenerated component that renders the SQRT iconograpy in SVG.
6140
+ *
6141
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6142
+ */
6108
6143
  class Sqrt extends React__namespace.Component {
6144
+ static propTypes = {
6145
+ color: PropTypes__default["default"].string.isRequired
6146
+ };
6109
6147
  render() {
6110
6148
  return /*#__PURE__*/React__namespace.createElement("svg", {
6111
6149
  width: "48",
@@ -6126,11 +6164,16 @@ class Sqrt extends React__namespace.Component {
6126
6164
  })));
6127
6165
  }
6128
6166
  }
6129
- _defineProperty(Sqrt, "propTypes", {
6130
- color: PropTypes__default["default"].string.isRequired
6131
- });
6132
6167
 
6168
+ /**
6169
+ * An autogenerated component that renders the EXP iconograpy in SVG.
6170
+ *
6171
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6172
+ */
6133
6173
  class Exp extends React__namespace.Component {
6174
+ static propTypes = {
6175
+ color: PropTypes__default["default"].string.isRequired
6176
+ };
6134
6177
  render() {
6135
6178
  return /*#__PURE__*/React__namespace.createElement("svg", {
6136
6179
  width: "48",
@@ -6148,11 +6191,16 @@ class Exp extends React__namespace.Component {
6148
6191
  })));
6149
6192
  }
6150
6193
  }
6151
- _defineProperty(Exp, "propTypes", {
6152
- color: PropTypes__default["default"].string.isRequired
6153
- });
6154
6194
 
6195
+ /**
6196
+ * An autogenerated component that renders the NEQ iconograpy in SVG.
6197
+ *
6198
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6199
+ */
6155
6200
  class Neq extends React__namespace.Component {
6201
+ static propTypes = {
6202
+ color: PropTypes__default["default"].string.isRequired
6203
+ };
6156
6204
  render() {
6157
6205
  return /*#__PURE__*/React__namespace.createElement("svg", {
6158
6206
  width: "48",
@@ -6176,11 +6224,16 @@ class Neq extends React__namespace.Component {
6176
6224
  })));
6177
6225
  }
6178
6226
  }
6179
- _defineProperty(Neq, "propTypes", {
6180
- color: PropTypes__default["default"].string.isRequired
6181
- });
6182
6227
 
6228
+ /**
6229
+ * An autogenerated component that renders the GEQ iconograpy in SVG.
6230
+ *
6231
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6232
+ */
6183
6233
  class Geq extends React__namespace.Component {
6234
+ static propTypes = {
6235
+ color: PropTypes__default["default"].string.isRequired
6236
+ };
6184
6237
  render() {
6185
6238
  return /*#__PURE__*/React__namespace.createElement("svg", {
6186
6239
  width: "48",
@@ -6204,11 +6257,16 @@ class Geq extends React__namespace.Component {
6204
6257
  })));
6205
6258
  }
6206
6259
  }
6207
- _defineProperty(Geq, "propTypes", {
6208
- color: PropTypes__default["default"].string.isRequired
6209
- });
6210
6260
 
6261
+ /**
6262
+ * An autogenerated component that renders the LN iconograpy in SVG.
6263
+ *
6264
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6265
+ */
6211
6266
  class Ln extends React__namespace.Component {
6267
+ static propTypes = {
6268
+ color: PropTypes__default["default"].string.isRequired
6269
+ };
6212
6270
  render() {
6213
6271
  return /*#__PURE__*/React__namespace.createElement("svg", {
6214
6272
  width: "48",
@@ -6226,9 +6284,6 @@ class Ln extends React__namespace.Component {
6226
6284
  })));
6227
6285
  }
6228
6286
  }
6229
- _defineProperty(Ln, "propTypes", {
6230
- color: PropTypes__default["default"].string.isRequired
6231
- });
6232
6287
 
6233
6288
  /**
6234
6289
  * An autogenerated component that renders the DISMISS iconograpy in SVG.
@@ -6255,7 +6310,15 @@ const Dismiss = () => {
6255
6310
  })));
6256
6311
  };
6257
6312
 
6313
+ /**
6314
+ * An autogenerated component that renders the SIN iconograpy in SVG.
6315
+ *
6316
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6317
+ */
6258
6318
  class Sin extends React__namespace.Component {
6319
+ static propTypes = {
6320
+ color: PropTypes__default["default"].string.isRequired
6321
+ };
6259
6322
  render() {
6260
6323
  return /*#__PURE__*/React__namespace.createElement("svg", {
6261
6324
  width: "48",
@@ -6276,11 +6339,16 @@ class Sin extends React__namespace.Component {
6276
6339
  })));
6277
6340
  }
6278
6341
  }
6279
- _defineProperty(Sin, "propTypes", {
6280
- color: PropTypes__default["default"].string.isRequired
6281
- });
6282
6342
 
6343
+ /**
6344
+ * An autogenerated component that renders the LT iconograpy in SVG.
6345
+ *
6346
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6347
+ */
6283
6348
  class Lt extends React__namespace.Component {
6349
+ static propTypes = {
6350
+ color: PropTypes__default["default"].string.isRequired
6351
+ };
6284
6352
  render() {
6285
6353
  return /*#__PURE__*/React__namespace.createElement("svg", {
6286
6354
  width: "48",
@@ -6304,11 +6372,16 @@ class Lt extends React__namespace.Component {
6304
6372
  })));
6305
6373
  }
6306
6374
  }
6307
- _defineProperty(Lt, "propTypes", {
6308
- color: PropTypes__default["default"].string.isRequired
6309
- });
6310
6375
 
6376
+ /**
6377
+ * An autogenerated component that renders the CUBE_ROOT iconograpy in SVG.
6378
+ *
6379
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6380
+ */
6311
6381
  class CubeRoot extends React__namespace.Component {
6382
+ static propTypes = {
6383
+ color: PropTypes__default["default"].string.isRequired
6384
+ };
6312
6385
  render() {
6313
6386
  return /*#__PURE__*/React__namespace.createElement("svg", {
6314
6387
  width: "48",
@@ -6332,11 +6405,16 @@ class CubeRoot extends React__namespace.Component {
6332
6405
  })));
6333
6406
  }
6334
6407
  }
6335
- _defineProperty(CubeRoot, "propTypes", {
6336
- color: PropTypes__default["default"].string.isRequired
6337
- });
6338
6408
 
6409
+ /**
6410
+ * An autogenerated component that renders the PLUS iconograpy in SVG.
6411
+ *
6412
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6413
+ */
6339
6414
  class Plus extends React__namespace.Component {
6415
+ static propTypes = {
6416
+ color: PropTypes__default["default"].string.isRequired
6417
+ };
6340
6418
  render() {
6341
6419
  return /*#__PURE__*/React__namespace.createElement("svg", {
6342
6420
  width: "48",
@@ -6357,11 +6435,16 @@ class Plus extends React__namespace.Component {
6357
6435
  })));
6358
6436
  }
6359
6437
  }
6360
- _defineProperty(Plus, "propTypes", {
6361
- color: PropTypes__default["default"].string.isRequired
6362
- });
6363
6438
 
6439
+ /**
6440
+ * An autogenerated component that renders the TAN iconograpy in SVG.
6441
+ *
6442
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6443
+ */
6364
6444
  class Tan extends React__namespace.Component {
6445
+ static propTypes = {
6446
+ color: PropTypes__default["default"].string.isRequired
6447
+ };
6365
6448
  render() {
6366
6449
  return /*#__PURE__*/React__namespace.createElement("svg", {
6367
6450
  width: "48",
@@ -6382,9 +6465,6 @@ class Tan extends React__namespace.Component {
6382
6465
  })));
6383
6466
  }
6384
6467
  }
6385
- _defineProperty(Tan, "propTypes", {
6386
- color: PropTypes__default["default"].string.isRequired
6387
- });
6388
6468
 
6389
6469
  const Arrow = props => {
6390
6470
  return /*#__PURE__*/React__namespace.createElement("g", _extends({
@@ -6442,7 +6522,15 @@ const Down = () => {
6442
6522
  }));
6443
6523
  };
6444
6524
 
6525
+ /**
6526
+ * An autogenerated component that renders the LEFT_PAREN iconograpy in SVG.
6527
+ *
6528
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6529
+ */
6445
6530
  class LeftParen extends React__namespace.Component {
6531
+ static propTypes = {
6532
+ color: PropTypes__default["default"].string.isRequired
6533
+ };
6446
6534
  render() {
6447
6535
  return /*#__PURE__*/React__namespace.createElement("svg", {
6448
6536
  width: "48",
@@ -6466,11 +6554,16 @@ class LeftParen extends React__namespace.Component {
6466
6554
  })));
6467
6555
  }
6468
6556
  }
6469
- _defineProperty(LeftParen, "propTypes", {
6470
- color: PropTypes__default["default"].string.isRequired
6471
- });
6472
6557
 
6558
+ /**
6559
+ * An autogenerated component that renders the RIGHT_PAREN iconograpy in SVG.
6560
+ *
6561
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6562
+ */
6473
6563
  class RightParen extends React__namespace.Component {
6564
+ static propTypes = {
6565
+ color: PropTypes__default["default"].string.isRequired
6566
+ };
6474
6567
  render() {
6475
6568
  return /*#__PURE__*/React__namespace.createElement("svg", {
6476
6569
  width: "48",
@@ -6494,11 +6587,16 @@ class RightParen extends React__namespace.Component {
6494
6587
  })));
6495
6588
  }
6496
6589
  }
6497
- _defineProperty(RightParen, "propTypes", {
6498
- color: PropTypes__default["default"].string.isRequired
6499
- });
6500
6590
 
6591
+ /**
6592
+ * An autogenerated component that renders the GT iconograpy in SVG.
6593
+ *
6594
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6595
+ */
6501
6596
  class Gt extends React__namespace.Component {
6597
+ static propTypes = {
6598
+ color: PropTypes__default["default"].string.isRequired
6599
+ };
6502
6600
  render() {
6503
6601
  return /*#__PURE__*/React__namespace.createElement("svg", {
6504
6602
  width: "48",
@@ -6522,11 +6620,16 @@ class Gt extends React__namespace.Component {
6522
6620
  })));
6523
6621
  }
6524
6622
  }
6525
- _defineProperty(Gt, "propTypes", {
6526
- color: PropTypes__default["default"].string.isRequired
6527
- });
6528
6623
 
6624
+ /**
6625
+ * An autogenerated component that renders the DIVIDE iconograpy in SVG.
6626
+ *
6627
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6628
+ */
6529
6629
  class Divide extends React__namespace.Component {
6630
+ static propTypes = {
6631
+ color: PropTypes__default["default"].string.isRequired
6632
+ };
6530
6633
  render() {
6531
6634
  return /*#__PURE__*/React__namespace.createElement("svg", {
6532
6635
  width: "48",
@@ -6557,11 +6660,16 @@ class Divide extends React__namespace.Component {
6557
6660
  })));
6558
6661
  }
6559
6662
  }
6560
- _defineProperty(Divide, "propTypes", {
6561
- color: PropTypes__default["default"].string.isRequired
6562
- });
6563
6663
 
6664
+ /**
6665
+ * An autogenerated component that renders the PERIOD iconograpy in SVG.
6666
+ *
6667
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6668
+ */
6564
6669
  class Period extends React__namespace.Component {
6670
+ static propTypes = {
6671
+ color: PropTypes__default["default"].string.isRequired
6672
+ };
6565
6673
  render() {
6566
6674
  return /*#__PURE__*/React__namespace.createElement("svg", {
6567
6675
  width: "48",
@@ -6581,11 +6689,16 @@ class Period extends React__namespace.Component {
6581
6689
  })));
6582
6690
  }
6583
6691
  }
6584
- _defineProperty(Period, "propTypes", {
6585
- color: PropTypes__default["default"].string.isRequired
6586
- });
6587
6692
 
6693
+ /**
6694
+ * An autogenerated component that renders the PERCENT iconograpy in SVG.
6695
+ *
6696
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6697
+ */
6588
6698
  class Percent extends React__namespace.Component {
6699
+ static propTypes = {
6700
+ color: PropTypes__default["default"].string.isRequired
6701
+ };
6589
6702
  render() {
6590
6703
  return /*#__PURE__*/React__namespace.createElement("svg", {
6591
6704
  width: "48",
@@ -6623,11 +6736,16 @@ class Percent extends React__namespace.Component {
6623
6736
  }))));
6624
6737
  }
6625
6738
  }
6626
- _defineProperty(Percent, "propTypes", {
6627
- color: PropTypes__default["default"].string.isRequired
6628
- });
6629
6739
 
6740
+ /**
6741
+ * An autogenerated component that renders the TIMES iconograpy in SVG.
6742
+ *
6743
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6744
+ */
6630
6745
  class Times extends React__namespace.Component {
6746
+ static propTypes = {
6747
+ color: PropTypes__default["default"].string.isRequired
6748
+ };
6631
6749
  render() {
6632
6750
  return /*#__PURE__*/React__namespace.createElement("svg", {
6633
6751
  width: "48",
@@ -6651,11 +6769,16 @@ class Times extends React__namespace.Component {
6651
6769
  })));
6652
6770
  }
6653
6771
  }
6654
- _defineProperty(Times, "propTypes", {
6655
- color: PropTypes__default["default"].string.isRequired
6656
- });
6657
6772
 
6773
+ /**
6774
+ * An autogenerated component that renders the EXP_3 iconograpy in SVG.
6775
+ *
6776
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6777
+ */
6658
6778
  class Exp3 extends React__namespace.Component {
6779
+ static propTypes = {
6780
+ color: PropTypes__default["default"].string.isRequired
6781
+ };
6659
6782
  render() {
6660
6783
  return /*#__PURE__*/React__namespace.createElement("svg", {
6661
6784
  width: "48",
@@ -6673,11 +6796,16 @@ class Exp3 extends React__namespace.Component {
6673
6796
  })));
6674
6797
  }
6675
6798
  }
6676
- _defineProperty(Exp3, "propTypes", {
6677
- color: PropTypes__default["default"].string.isRequired
6678
- });
6679
6799
 
6800
+ /**
6801
+ * An autogenerated component that renders the EXP_2 iconograpy in SVG.
6802
+ *
6803
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6804
+ */
6680
6805
  class Exp2 extends React__namespace.Component {
6806
+ static propTypes = {
6807
+ color: PropTypes__default["default"].string.isRequired
6808
+ };
6681
6809
  render() {
6682
6810
  return /*#__PURE__*/React__namespace.createElement("svg", {
6683
6811
  width: "48",
@@ -6695,9 +6823,6 @@ class Exp2 extends React__namespace.Component {
6695
6823
  })));
6696
6824
  }
6697
6825
  }
6698
- _defineProperty(Exp2, "propTypes", {
6699
- color: PropTypes__default["default"].string.isRequired
6700
- });
6701
6826
 
6702
6827
  /**
6703
6828
  * A component that renders the RIGHT iconograpy in SVG.
@@ -6712,7 +6837,15 @@ const Right = () => {
6712
6837
  }));
6713
6838
  };
6714
6839
 
6840
+ /**
6841
+ * An autogenerated component that renders the CDOT iconograpy in SVG.
6842
+ *
6843
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6844
+ */
6715
6845
  class Cdot extends React__namespace.Component {
6846
+ static propTypes = {
6847
+ color: PropTypes__default["default"].string.isRequired
6848
+ };
6716
6849
  render() {
6717
6850
  return /*#__PURE__*/React__namespace.createElement("svg", {
6718
6851
  width: "48",
@@ -6737,11 +6870,16 @@ class Cdot extends React__namespace.Component {
6737
6870
  }))));
6738
6871
  }
6739
6872
  }
6740
- _defineProperty(Cdot, "propTypes", {
6741
- color: PropTypes__default["default"].string.isRequired
6742
- });
6743
6873
 
6874
+ /**
6875
+ * An autogenerated component that renders the LOG_N iconograpy in SVG.
6876
+ *
6877
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6878
+ */
6744
6879
  class LogN extends React__namespace.Component {
6880
+ static propTypes = {
6881
+ color: PropTypes__default["default"].string.isRequired
6882
+ };
6745
6883
  render() {
6746
6884
  return /*#__PURE__*/React__namespace.createElement("svg", {
6747
6885
  width: "48",
@@ -6759,11 +6897,16 @@ class LogN extends React__namespace.Component {
6759
6897
  })));
6760
6898
  }
6761
6899
  }
6762
- _defineProperty(LogN, "propTypes", {
6763
- color: PropTypes__default["default"].string.isRequired
6764
- });
6765
6900
 
6901
+ /**
6902
+ * An autogenerated component that renders the LEQ iconograpy in SVG.
6903
+ *
6904
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6905
+ */
6766
6906
  class Leq extends React__namespace.Component {
6907
+ static propTypes = {
6908
+ color: PropTypes__default["default"].string.isRequired
6909
+ };
6767
6910
  render() {
6768
6911
  return /*#__PURE__*/React__namespace.createElement("svg", {
6769
6912
  width: "48",
@@ -6787,11 +6930,16 @@ class Leq extends React__namespace.Component {
6787
6930
  })));
6788
6931
  }
6789
6932
  }
6790
- _defineProperty(Leq, "propTypes", {
6791
- color: PropTypes__default["default"].string.isRequired
6792
- });
6793
6933
 
6934
+ /**
6935
+ * An autogenerated component that renders the MINUS iconograpy in SVG.
6936
+ *
6937
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6938
+ */
6794
6939
  class Minus extends React__namespace.Component {
6940
+ static propTypes = {
6941
+ color: PropTypes__default["default"].string.isRequired
6942
+ };
6795
6943
  render() {
6796
6944
  return /*#__PURE__*/React__namespace.createElement("svg", {
6797
6945
  width: "48",
@@ -6812,11 +6960,16 @@ class Minus extends React__namespace.Component {
6812
6960
  })));
6813
6961
  }
6814
6962
  }
6815
- _defineProperty(Minus, "propTypes", {
6816
- color: PropTypes__default["default"].string.isRequired
6817
- });
6818
6963
 
6964
+ /**
6965
+ * An autogenerated component that renders the RADICAL iconograpy in SVG.
6966
+ *
6967
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
6968
+ */
6819
6969
  class Radical extends React__namespace.Component {
6970
+ static propTypes = {
6971
+ color: PropTypes__default["default"].string.isRequired
6972
+ };
6820
6973
  render() {
6821
6974
  return /*#__PURE__*/React__namespace.createElement("svg", {
6822
6975
  width: "48",
@@ -6840,11 +6993,16 @@ class Radical extends React__namespace.Component {
6840
6993
  })));
6841
6994
  }
6842
6995
  }
6843
- _defineProperty(Radical, "propTypes", {
6844
- color: PropTypes__default["default"].string.isRequired
6845
- });
6846
6996
 
6997
+ /**
6998
+ * An autogenerated component that renders the FRAC iconograpy in SVG.
6999
+ *
7000
+ * Generated with: https://gist.github.com/crm416/3c7abc88e520eaed72347af240b32590.
7001
+ */
6847
7002
  class FracInclusive extends React__namespace.Component {
7003
+ static propTypes = {
7004
+ color: PropTypes__default["default"].string.isRequired
7005
+ };
6848
7006
  render() {
6849
7007
  return /*#__PURE__*/React__namespace.createElement("svg", {
6850
7008
  width: "48",
@@ -6877,9 +7035,6 @@ class FracInclusive extends React__namespace.Component {
6877
7035
  }))));
6878
7036
  }
6879
7037
  }
6880
- _defineProperty(FracInclusive, "propTypes", {
6881
- color: PropTypes__default["default"].string.isRequired
6882
- });
6883
7038
 
6884
7039
  /**
6885
7040
  * An autogenerated component that renders the JUMP_OUT_PARENTHESES iconograpy in SVG.
@@ -7263,7 +7418,7 @@ class MultiSymbolGrid extends React__namespace.Component {
7263
7418
  // some styles coercion and doesn't seem worthwhile right now.
7264
7419
  icons.forEach(icon => {
7265
7420
  if (icon.type !== IconType.MATH) {
7266
- throw new Error("Received invalid icon: type=".concat(icon.type, ", ") + "data=".concat(icon.data));
7421
+ throw new Error(`Received invalid icon: type=${icon.type}, ` + `data=${icon.data}`);
7267
7422
  }
7268
7423
  });
7269
7424
  if (icons.length === 1) {
@@ -7324,7 +7479,7 @@ class MultiSymbolGrid extends React__namespace.Component {
7324
7479
  }))));
7325
7480
  }
7326
7481
  }
7327
- throw new Error("Invalid number of icons: ".concat(icons.length));
7482
+ throw new Error(`Invalid number of icons: ${icons.length}`);
7328
7483
  }
7329
7484
  }
7330
7485
  const verticalInsetPx = 2;
@@ -7368,72 +7523,13 @@ const styles$7 = aphrodite.StyleSheet.create({
7368
7523
 
7369
7524
  // eslint-disable-next-line react/no-unsafe
7370
7525
  class KeypadButton extends React__namespace.PureComponent {
7371
- constructor() {
7372
- super(...arguments);
7373
- _defineProperty(this, "buttonSizeStyle", void 0);
7374
- _defineProperty(this, "_preInjectStyles", () => {
7375
- // HACK(charlie): Pre-inject all of the possible styles for the button.
7376
- // This avoids a flickering effect in the echo animation whereby the
7377
- // echoes vary in size as they animate. Note that we need to account for
7378
- // the "initial" styles that `View` will include, as these styles are
7379
- // applied to `View` components and Aphrodite will consolidate the style
7380
- // object. This method must be called whenever a property that
7381
- // influences the possible outcomes of `this._getFocusStyle` and
7382
- // `this._getButtonStyle` changes (such as `this.buttonSizeStyle`).
7383
- for (const type of KeyTypes) {
7384
- aphrodite.css(View.styles.initial, ...this._getFocusStyle(type));
7385
- for (const borders of Object.values(BorderStyles)) {
7386
- aphrodite.css(View.styles.initial, ...this._getButtonStyle(type, borders));
7387
- }
7388
- }
7389
- });
7390
- _defineProperty(this, "_getFocusStyle", type => {
7391
- let focusBackgroundStyle;
7392
- if (type === "INPUT_NAVIGATION" || type === "KEYPAD_NAVIGATION") {
7393
- focusBackgroundStyle = styles$6.light;
7394
- } else {
7395
- focusBackgroundStyle = styles$6.bright;
7396
- }
7397
- return [styles$6.focusBox, focusBackgroundStyle];
7398
- });
7399
- _defineProperty(this, "_getButtonStyle", (type, borders, style) => {
7400
- // Select the appropriate style for the button.
7401
- let backgroundStyle;
7402
- switch (type) {
7403
- case "EMPTY":
7404
- backgroundStyle = styles$6.empty;
7405
- break;
7406
- case "MANY":
7407
- case "VALUE":
7408
- backgroundStyle = styles$6.value;
7409
- break;
7410
- case "OPERATOR":
7411
- backgroundStyle = styles$6.operator;
7412
- break;
7413
- case "INPUT_NAVIGATION":
7414
- case "KEYPAD_NAVIGATION":
7415
- backgroundStyle = styles$6.control;
7416
- break;
7417
- case "ECHO":
7418
- backgroundStyle = null;
7419
- break;
7420
- }
7421
- const borderStyle = [];
7422
- if (borders.includes(BorderDirection.LEFT)) {
7423
- // @ts-expect-error TS2345
7424
- borderStyle.push(styles$6.leftBorder);
7425
- }
7426
- if (borders.includes(BorderDirection.BOTTOM)) {
7427
- // @ts-expect-error TS2345
7428
- borderStyle.push(styles$6.bottomBorder);
7429
- }
7430
- return [styles$6.buttonBase, backgroundStyle, ...borderStyle, type === "ECHO" && styles$6.echo, this.buttonSizeStyle,
7431
- // React Native allows you to set the 'style' props on user defined
7432
- // components.
7433
- // See: https://facebook.github.io/react-native/docs/style.html
7434
- ...(Array.isArray(style) ? style : [style])];
7435
- });
7436
- }
7526
+ static defaultProps = {
7527
+ borders: BorderStyles.ALL,
7528
+ childKeys: [],
7529
+ disabled: false,
7530
+ focused: false,
7531
+ popoverEnabled: false
7532
+ };
7437
7533
  UNSAFE_componentWillMount() {
7438
7534
  this.buttonSizeStyle = styleForButtonDimensions(this.props.heightPx, this.props.widthPx);
7439
7535
  }
@@ -7450,6 +7546,68 @@ class KeypadButton extends React__namespace.PureComponent {
7450
7546
  this._preInjectStyles();
7451
7547
  }
7452
7548
  }
7549
+ _preInjectStyles = () => {
7550
+ // HACK(charlie): Pre-inject all of the possible styles for the button.
7551
+ // This avoids a flickering effect in the echo animation whereby the
7552
+ // echoes vary in size as they animate. Note that we need to account for
7553
+ // the "initial" styles that `View` will include, as these styles are
7554
+ // applied to `View` components and Aphrodite will consolidate the style
7555
+ // object. This method must be called whenever a property that
7556
+ // influences the possible outcomes of `this._getFocusStyle` and
7557
+ // `this._getButtonStyle` changes (such as `this.buttonSizeStyle`).
7558
+ for (const type of KeyTypes) {
7559
+ aphrodite.css(View.styles.initial, ...this._getFocusStyle(type));
7560
+ for (const borders of Object.values(BorderStyles)) {
7561
+ aphrodite.css(View.styles.initial, ...this._getButtonStyle(type, borders));
7562
+ }
7563
+ }
7564
+ };
7565
+ _getFocusStyle = type => {
7566
+ let focusBackgroundStyle;
7567
+ if (type === "INPUT_NAVIGATION" || type === "KEYPAD_NAVIGATION") {
7568
+ focusBackgroundStyle = styles$6.light;
7569
+ } else {
7570
+ focusBackgroundStyle = styles$6.bright;
7571
+ }
7572
+ return [styles$6.focusBox, focusBackgroundStyle];
7573
+ };
7574
+ _getButtonStyle = (type, borders, style) => {
7575
+ // Select the appropriate style for the button.
7576
+ let backgroundStyle;
7577
+ switch (type) {
7578
+ case "EMPTY":
7579
+ backgroundStyle = styles$6.empty;
7580
+ break;
7581
+ case "MANY":
7582
+ case "VALUE":
7583
+ backgroundStyle = styles$6.value;
7584
+ break;
7585
+ case "OPERATOR":
7586
+ backgroundStyle = styles$6.operator;
7587
+ break;
7588
+ case "INPUT_NAVIGATION":
7589
+ case "KEYPAD_NAVIGATION":
7590
+ backgroundStyle = styles$6.control;
7591
+ break;
7592
+ case "ECHO":
7593
+ backgroundStyle = null;
7594
+ break;
7595
+ }
7596
+ const borderStyle = [];
7597
+ if (borders.includes(BorderDirection.LEFT)) {
7598
+ // @ts-expect-error TS2345
7599
+ borderStyle.push(styles$6.leftBorder);
7600
+ }
7601
+ if (borders.includes(BorderDirection.BOTTOM)) {
7602
+ // @ts-expect-error TS2345
7603
+ borderStyle.push(styles$6.bottomBorder);
7604
+ }
7605
+ return [styles$6.buttonBase, backgroundStyle, ...borderStyle, type === "ECHO" && styles$6.echo, this.buttonSizeStyle,
7606
+ // React Native allows you to set the 'style' props on user defined
7607
+ // components.
7608
+ // See: https://facebook.github.io/react-native/docs/style.html
7609
+ ...(Array.isArray(style) ? style : [style])];
7610
+ };
7453
7611
  render() {
7454
7612
  const {
7455
7613
  ariaLabel,
@@ -7523,13 +7681,6 @@ class KeypadButton extends React__namespace.PureComponent {
7523
7681
  }
7524
7682
  }
7525
7683
  }
7526
- _defineProperty(KeypadButton, "defaultProps", {
7527
- borders: BorderStyles.ALL,
7528
- childKeys: [],
7529
- disabled: false,
7530
- focused: false,
7531
- popoverEnabled: false
7532
- });
7533
7684
  const focusInsetPx = 4;
7534
7685
  const focusBoxZIndex = 0;
7535
7686
  const styles$6 = aphrodite.StyleSheet.create({
@@ -7707,7 +7858,6 @@ const extractProps = keyConfig => {
7707
7858
  };
7708
7859
  };
7709
7860
  const mapStateToProps$5 = (state, ownProps) => {
7710
- var _gestures$popover;
7711
7861
  const {
7712
7862
  gestures
7713
7863
  } = state;
@@ -7732,7 +7882,7 @@ const mapStateToProps$5 = (state, ownProps) => {
7732
7882
  id: id,
7733
7883
  // Add in some gesture state.
7734
7884
  focused: gestures.focus === id,
7735
- popoverEnabled: ((_gestures$popover = gestures.popover) === null || _gestures$popover === void 0 ? void 0 : _gestures$popover.parentId) === id,
7885
+ popoverEnabled: gestures.popover?.parentId === id,
7736
7886
  // Pass down the child keys and any extracted props.
7737
7887
  childKeys,
7738
7888
  ...extractProps(useFirstChildProps ? childKeys[0] : keyConfig)
@@ -7750,6 +7900,9 @@ var TouchableKeypadButton$1 = reactRedux.connect(mapStateToProps$5, null, null,
7750
7900
  })(TouchableKeypadButton);
7751
7901
 
7752
7902
  class ManyKeypadButton extends React__namespace.Component {
7903
+ static defaultProps = {
7904
+ keys: []
7905
+ };
7753
7906
  render() {
7754
7907
  const {
7755
7908
  keys,
@@ -7783,9 +7936,6 @@ class ManyKeypadButton extends React__namespace.Component {
7783
7936
  }
7784
7937
  }
7785
7938
  }
7786
- _defineProperty(ManyKeypadButton, "defaultProps", {
7787
- keys: []
7788
- });
7789
7939
 
7790
7940
  /**
7791
7941
  * This file contains all of the z-index values used throughout the math-input
@@ -7838,35 +7988,32 @@ class Echo extends React__namespace.Component {
7838
7988
  }
7839
7989
  }
7840
7990
  class EchoManager extends React__namespace.Component {
7841
- constructor() {
7842
- super(...arguments);
7843
- _defineProperty(this, "_animationConfigForType", animationType => {
7844
- // NOTE(charlie): These must be kept in sync with the transition
7845
- // durations and classnames specified in echo.css.
7846
- let animationDurationMs;
7847
- let animationTransitionName;
7848
- switch (animationType) {
7849
- case EchoAnimationType.SLIDE_AND_FADE:
7850
- animationDurationMs = 400;
7851
- animationTransitionName = "echo-slide-and-fade";
7852
- break;
7853
- case EchoAnimationType.FADE_ONLY:
7854
- animationDurationMs = 300;
7855
- animationTransitionName = "echo-fade-only";
7856
- break;
7857
- case EchoAnimationType.LONG_FADE_ONLY:
7858
- animationDurationMs = 400;
7859
- animationTransitionName = "echo-long-fade-only";
7860
- break;
7861
- default:
7862
- throw new Error("Invalid echo animation type: ".concat(animationType));
7863
- }
7864
- return {
7865
- animationDurationMs,
7866
- animationTransitionName
7867
- };
7868
- });
7869
- }
7991
+ _animationConfigForType = animationType => {
7992
+ // NOTE(charlie): These must be kept in sync with the transition
7993
+ // durations and classnames specified in echo.css.
7994
+ let animationDurationMs;
7995
+ let animationTransitionName;
7996
+ switch (animationType) {
7997
+ case EchoAnimationType.SLIDE_AND_FADE:
7998
+ animationDurationMs = 400;
7999
+ animationTransitionName = "echo-slide-and-fade";
8000
+ break;
8001
+ case EchoAnimationType.FADE_ONLY:
8002
+ animationDurationMs = 300;
8003
+ animationTransitionName = "echo-fade-only";
8004
+ break;
8005
+ case EchoAnimationType.LONG_FADE_ONLY:
8006
+ animationDurationMs = 400;
8007
+ animationTransitionName = "echo-long-fade-only";
8008
+ break;
8009
+ default:
8010
+ throw new Error(`Invalid echo animation type: ${animationType}`);
8011
+ }
8012
+ return {
8013
+ animationDurationMs,
8014
+ animationTransitionName
8015
+ };
8016
+ };
7870
8017
  render() {
7871
8018
  const {
7872
8019
  echoes,
@@ -7904,7 +8051,7 @@ class EchoManager extends React__namespace.Component {
7904
8051
  key: animationId
7905
8052
  }, /*#__PURE__*/React__namespace.createElement(Echo, _extends({
7906
8053
  animationDurationMs: animationDurationMs,
7907
- onAnimationFinish: () => onAnimationFinish === null || onAnimationFinish === void 0 ? void 0 : onAnimationFinish(animationId)
8054
+ onAnimationFinish: () => onAnimationFinish?.(animationId)
7908
8055
  }, echo)));
7909
8056
  }));
7910
8057
  }));
@@ -7999,7 +8146,6 @@ class PopoverManager extends React__namespace.Component {
7999
8146
  // naming convention: verb + noun
8000
8147
  // the noun should be one of the other properties in the object that's
8001
8148
  // being dispatched
8002
-
8003
8149
  const dismissKeypad = () => {
8004
8150
  return {
8005
8151
  type: "DismissKeypad"
@@ -8069,43 +8215,12 @@ const pressKey = (key, initialBounds, inPopover) => {
8069
8215
  };
8070
8216
  };
8071
8217
 
8218
+ /**
8219
+ * A keypad component that acts as a container for rows or columns of buttons,
8220
+ * and manages the rendering of echo animations on top of those buttons.
8221
+ */
8072
8222
  // eslint-disable-next-line react/no-unsafe
8073
8223
  class Keypad extends React__namespace.Component {
8074
- constructor() {
8075
- super(...arguments);
8076
- _defineProperty(this, "_isMounted", void 0);
8077
- _defineProperty(this, "_resizeTimeout", void 0);
8078
- _defineProperty(this, "_container", void 0);
8079
- _defineProperty(this, "_computeContainer", () => {
8080
- const domNode = ReactDOM__default["default"].findDOMNode(this);
8081
- this._container = domNode.getBoundingClientRect();
8082
- });
8083
- _defineProperty(this, "_updateSizeAndPosition", () => {
8084
- // Mark the container for recalculation next time the keypad is
8085
- // opened.
8086
- // TODO(charlie): Since we're not recalculating the container
8087
- // immediately, if you were to resize the page while a popover were
8088
- // active, you'd likely get unexpected behavior. This seems very
8089
- // difficult to do and, as such, incredibly unlikely, but we may
8090
- // want to reconsider the caching here.
8091
- this._container = null;
8092
- });
8093
- _defineProperty(this, "_onResize", () => {
8094
- // Whenever the page resizes, we need to recompute the container's
8095
- // bounding box. This is the only time that the bounding box can change.
8096
-
8097
- // Throttle resize events -- taken from:
8098
- // https://developer.mozilla.org/en-US/docs/Web/Events/resize
8099
- if (this._resizeTimeout == null) {
8100
- this._resizeTimeout = window.setTimeout(() => {
8101
- this._resizeTimeout = null;
8102
- if (this._isMounted) {
8103
- this._updateSizeAndPosition();
8104
- }
8105
- }, 66);
8106
- }
8107
- });
8108
- }
8109
8224
  componentDidMount() {
8110
8225
  this._isMounted = true;
8111
8226
  window.addEventListener("resize", this._onResize);
@@ -8120,6 +8235,35 @@ class Keypad extends React__namespace.Component {
8120
8235
  this._isMounted = false;
8121
8236
  window.removeEventListener("resize", this._onResize);
8122
8237
  }
8238
+ _computeContainer = () => {
8239
+ const domNode = ReactDOM__default["default"].findDOMNode(this);
8240
+ this._container = domNode.getBoundingClientRect();
8241
+ };
8242
+ _updateSizeAndPosition = () => {
8243
+ // Mark the container for recalculation next time the keypad is
8244
+ // opened.
8245
+ // TODO(charlie): Since we're not recalculating the container
8246
+ // immediately, if you were to resize the page while a popover were
8247
+ // active, you'd likely get unexpected behavior. This seems very
8248
+ // difficult to do and, as such, incredibly unlikely, but we may
8249
+ // want to reconsider the caching here.
8250
+ this._container = null;
8251
+ };
8252
+ _onResize = () => {
8253
+ // Whenever the page resizes, we need to recompute the container's
8254
+ // bounding box. This is the only time that the bounding box can change.
8255
+
8256
+ // Throttle resize events -- taken from:
8257
+ // https://developer.mozilla.org/en-US/docs/Web/Events/resize
8258
+ if (this._resizeTimeout == null) {
8259
+ this._resizeTimeout = window.setTimeout(() => {
8260
+ this._resizeTimeout = null;
8261
+ if (this._isMounted) {
8262
+ this._updateSizeAndPosition();
8263
+ }
8264
+ }, 66);
8265
+ }
8266
+ };
8123
8267
  render() {
8124
8268
  const {
8125
8269
  children,
@@ -8197,18 +8341,18 @@ var Keypad$1 = reactRedux.connect(mapStateToProps$4, mapDispatchToProps$1, null,
8197
8341
  forwardRef: true
8198
8342
  })(Keypad);
8199
8343
 
8344
+ /**
8345
+ * A keypad with two pages of keys.
8346
+ */
8200
8347
  const {
8201
8348
  column: column$2,
8202
8349
  row: row$4,
8203
8350
  fullWidth: fullWidth$2
8204
8351
  } = Styles;
8205
8352
  class TwoPageKeypad extends React__namespace.Component {
8206
- constructor() {
8207
- super(...arguments);
8208
- _defineProperty(this, "state", {
8209
- selectedPage: "Numbers"
8210
- });
8211
- }
8353
+ state = {
8354
+ selectedPage: "Numbers"
8355
+ };
8212
8356
  render() {
8213
8357
  const {
8214
8358
  leftPage,
@@ -8253,16 +8397,16 @@ const styles$3 = aphrodite.StyleSheet.create({
8253
8397
  backgroundColor: offBlack16
8254
8398
  },
8255
8399
  borderTop: {
8256
- borderTop: "".concat(innerBorderWidthPx, "px ").concat(innerBorderStyle, " ") + "".concat(innerBorderColor)
8400
+ borderTop: `${innerBorderWidthPx}px ${innerBorderStyle} ` + `${innerBorderColor}`
8257
8401
  },
8258
8402
  borderLeft: {
8259
- borderLeft: "".concat(innerBorderWidthPx, "px ").concat(innerBorderStyle, " ") + "".concat(innerBorderColor),
8403
+ borderLeft: `${innerBorderWidthPx}px ${innerBorderStyle} ` + `${innerBorderColor}`,
8260
8404
  boxSizing: "content-box"
8261
8405
  },
8262
8406
  tabbar: {
8263
8407
  background: Color__default["default"].offWhite,
8264
- borderTop: "1px solid ".concat(Color__default["default"].offBlack50),
8265
- borderBottom: "1px solid ".concat(Color__default["default"].offBlack50)
8408
+ borderTop: `1px solid ${Color__default["default"].offBlack50}`,
8409
+ borderBottom: `1px solid ${Color__default["default"].offBlack50}`
8266
8410
  }
8267
8411
  });
8268
8412
  const mapStateToProps$3 = state => {
@@ -8498,9 +8642,8 @@ const styles$2 = aphrodite.StyleSheet.create({
8498
8642
  }
8499
8643
  });
8500
8644
  const mapStateToProps$2 = state => {
8501
- var _state$input$cursor;
8502
8645
  return {
8503
- cursorContext: (_state$input$cursor = state.input.cursor) === null || _state$input$cursor === void 0 ? void 0 : _state$input$cursor.context,
8646
+ cursorContext: state.input.cursor?.context,
8504
8647
  dynamicJumpOut: !state.layout.navigationPadEnabled
8505
8648
  };
8506
8649
  };
@@ -8631,9 +8774,8 @@ class FractionKeypad extends React__namespace.Component {
8631
8774
  }
8632
8775
  }
8633
8776
  const mapStateToProps$1 = state => {
8634
- var _state$input$cursor;
8635
8777
  return {
8636
- cursorContext: (_state$input$cursor = state.input.cursor) === null || _state$input$cursor === void 0 ? void 0 : _state$input$cursor.context,
8778
+ cursorContext: state.input.cursor?.context,
8637
8779
  dynamicJumpOut: !state.layout.navigationPadEnabled
8638
8780
  };
8639
8781
  };
@@ -8750,71 +8892,12 @@ const {
8750
8892
  } = Styles;
8751
8893
  // eslint-disable-next-line react/no-unsafe
8752
8894
  class KeypadContainer extends React__namespace.Component {
8753
- constructor() {
8754
- super(...arguments);
8755
- _defineProperty(this, "_containerRef", /*#__PURE__*/React__namespace.createRef());
8756
- _defineProperty(this, "_containerResizeObserver", null);
8757
- _defineProperty(this, "_resizeTimeout", void 0);
8758
- _defineProperty(this, "hasMounted", void 0);
8759
- _defineProperty(this, "state", {
8760
- hasBeenActivated: false,
8761
- viewportWidth: "100vw"
8762
- });
8763
- _defineProperty(this, "_throttleResizeHandler", () => {
8764
- // Throttle the resize callbacks.
8765
- // https://developer.mozilla.org/en-US/docs/Web/Events/resize
8766
- if (this._resizeTimeout == null) {
8767
- this._resizeTimeout = window.setTimeout(() => {
8768
- this._resizeTimeout = null;
8769
- this._onResize();
8770
- }, 66);
8771
- }
8772
- });
8773
- _defineProperty(this, "_onResize", () => {
8774
- var _this$_containerRef$c, _this$_containerRef$c2, _this$props$onPageSiz, _this$props;
8775
- // Whenever the page resizes, we need to force an update, as the button
8776
- // heights and keypad width are computed based on horizontal space.
8777
- this.setState({
8778
- viewportWidth: window.innerWidth
8779
- });
8780
- const containerWidth = ((_this$_containerRef$c = this._containerRef.current) === null || _this$_containerRef$c === void 0 ? void 0 : _this$_containerRef$c.clientWidth) || 0;
8781
- const containerHeight = ((_this$_containerRef$c2 = this._containerRef.current) === null || _this$_containerRef$c2 === void 0 ? void 0 : _this$_containerRef$c2.clientHeight) || 0;
8782
- (_this$props$onPageSiz = (_this$props = this.props).onPageSizeChange) === null || _this$props$onPageSiz === void 0 ? void 0 : _this$props$onPageSiz.call(_this$props, window.innerWidth, window.innerHeight, containerWidth, containerHeight);
8783
- });
8784
- _defineProperty(this, "renderKeypad", () => {
8785
- const {
8786
- extraKeys,
8787
- keypadType,
8788
- layoutMode,
8789
- navigationPadEnabled
8790
- } = this.props;
8791
- const keypadProps = {
8792
- extraKeys,
8793
- // HACK(charlie): In order to properly round the corners of the
8794
- // compact keypad, we need to instruct some of our child views to
8795
- // crop themselves. At least we're colocating all the layout
8796
- // information in this component, though.
8797
- roundTopLeft: layoutMode === LayoutMode.COMPACT && !navigationPadEnabled,
8798
- roundTopRight: layoutMode === LayoutMode.COMPACT
8799
- };
8800
-
8801
- // Select the appropriate keyboard given the type.
8802
- // TODO(charlie): In the future, we might want to move towards a
8803
- // data-driven approach to defining keyboard layouts, and have a
8804
- // generic keyboard that takes some "keyboard data" and renders it.
8805
- // However, the keyboards differ pretty heavily right now and it's not
8806
- // clear what that format would look like exactly. Plus, there aren't
8807
- // very many of them. So to keep us moving, we'll just hardcode.
8808
- switch (keypadType) {
8809
- case KeypadType.FRACTION:
8810
- return /*#__PURE__*/React__namespace.createElement(FractionKeypad$1, keypadProps);
8811
- case KeypadType.EXPRESSION:
8812
- return /*#__PURE__*/React__namespace.createElement(ExpressionKeypad$1, keypadProps);
8813
- default:
8814
- throw new Error("Invalid keypad type: " + keypadType);
8815
- }
8816
- });
8817
- }
8895
+ _containerRef = /*#__PURE__*/React__namespace.createRef();
8896
+ _containerResizeObserver = null;
8897
+ state = {
8898
+ hasBeenActivated: false,
8899
+ viewportWidth: "100vw"
8900
+ };
8818
8901
  UNSAFE_componentWillMount() {
8819
8902
  if (this.props.active) {
8820
8903
  this.setState({
@@ -8852,11 +8935,63 @@ class KeypadContainer extends React__namespace.Component {
8852
8935
  }
8853
8936
  }
8854
8937
  componentWillUnmount() {
8855
- var _this$_containerResiz;
8856
8938
  window.removeEventListener("resize", this._throttleResizeHandler);
8857
8939
  window.removeEventListener("orientationchange", this._throttleResizeHandler);
8858
- (_this$_containerResiz = this._containerResizeObserver) === null || _this$_containerResiz === void 0 ? void 0 : _this$_containerResiz.disconnect();
8859
- }
8940
+ this._containerResizeObserver?.disconnect();
8941
+ }
8942
+ _throttleResizeHandler = () => {
8943
+ // Throttle the resize callbacks.
8944
+ // https://developer.mozilla.org/en-US/docs/Web/Events/resize
8945
+ if (this._resizeTimeout == null) {
8946
+ this._resizeTimeout = window.setTimeout(() => {
8947
+ this._resizeTimeout = null;
8948
+ this._onResize();
8949
+ }, 66);
8950
+ }
8951
+ };
8952
+ _onResize = () => {
8953
+ // Whenever the page resizes, we need to force an update, as the button
8954
+ // heights and keypad width are computed based on horizontal space.
8955
+ this.setState({
8956
+ viewportWidth: window.innerWidth
8957
+ });
8958
+ const containerWidth = this._containerRef.current?.clientWidth || 0;
8959
+ const containerHeight = this._containerRef.current?.clientHeight || 0;
8960
+ this.props.onPageSizeChange?.(window.innerWidth, window.innerHeight, containerWidth, containerHeight);
8961
+ };
8962
+ renderKeypad = () => {
8963
+ const {
8964
+ extraKeys,
8965
+ keypadType,
8966
+ layoutMode,
8967
+ navigationPadEnabled
8968
+ } = this.props;
8969
+ const keypadProps = {
8970
+ extraKeys,
8971
+ // HACK(charlie): In order to properly round the corners of the
8972
+ // compact keypad, we need to instruct some of our child views to
8973
+ // crop themselves. At least we're colocating all the layout
8974
+ // information in this component, though.
8975
+ roundTopLeft: layoutMode === LayoutMode.COMPACT && !navigationPadEnabled,
8976
+ roundTopRight: layoutMode === LayoutMode.COMPACT
8977
+ };
8978
+
8979
+ // Select the appropriate keyboard given the type.
8980
+ // TODO(charlie): In the future, we might want to move towards a
8981
+ // data-driven approach to defining keyboard layouts, and have a
8982
+ // generic keyboard that takes some "keyboard data" and renders it.
8983
+ // However, the keyboards differ pretty heavily right now and it's not
8984
+ // clear what that format would look like exactly. Plus, there aren't
8985
+ // very many of them. So to keep us moving, we'll just hardcode.
8986
+ switch (keypadType) {
8987
+ case KeypadType.FRACTION:
8988
+ return /*#__PURE__*/React__namespace.createElement(FractionKeypad$1, keypadProps);
8989
+ case KeypadType.EXPRESSION:
8990
+ return /*#__PURE__*/React__namespace.createElement(ExpressionKeypad$1, keypadProps);
8991
+ default:
8992
+ throw new Error("Invalid keypad type: " + keypadType);
8993
+ }
8994
+ };
8860
8995
  render() {
8861
8996
  const {
8862
8997
  active,
@@ -8916,7 +9051,7 @@ const styles = aphrodite.StyleSheet.create({
8916
9051
  left: 0,
8917
9052
  right: 0,
8918
9053
  position: "fixed",
8919
- transition: "".concat(keypadAnimationDurationMs, "ms ease-out"),
9054
+ transition: `${keypadAnimationDurationMs}ms ease-out`,
8920
9055
  transitionProperty: "transform",
8921
9056
  zIndex: keypad
8922
9057
  },
@@ -8937,7 +9072,7 @@ const styles = aphrodite.StyleSheet.create({
8937
9072
  },
8938
9073
  navigationPadContainer: {
8939
9074
  // Add a separator between the navigation pad and the keypad.
8940
- borderRight: "".concat(innerBorderWidthPx, "px ").concat(innerBorderStyle, " ") + "".concat(innerBorderColor),
9075
+ borderRight: `${innerBorderWidthPx}px ${innerBorderStyle} ` + `${innerBorderColor}`,
8941
9076
  boxSizing: "content-box"
8942
9077
  },
8943
9078
  // Defer to the navigation pad, such that the navigation pad is always
@@ -8992,10 +9127,7 @@ var KeypadContainer$1 = reactRedux.connect(mapStateToProps, mapDispatchToProps,
8992
9127
  * It is entirely ignorant of the existence of popovers and the positions of
8993
9128
  * DOM nodes, operating solely on IDs. The state machine does accommodate for
8994
9129
  * multi-touch interactions, tracking gesture state on a per-touch basis.
8995
- */
8996
-
8997
- // exported for tests
8998
-
9130
+ */ // exported for tests
8999
9131
  const defaultOptions = {
9000
9132
  longPressWaitTimeMs: 50,
9001
9133
  swipeThresholdPx: 20,
@@ -9003,12 +9135,6 @@ const defaultOptions = {
9003
9135
  };
9004
9136
  class GestureStateMachine {
9005
9137
  constructor(handlers, options, swipeDisabledNodeIds, multiPressableKeys) {
9006
- _defineProperty(this, "handlers", void 0);
9007
- _defineProperty(this, "options", void 0);
9008
- _defineProperty(this, "swipeDisabledNodeIds", void 0);
9009
- _defineProperty(this, "multiPressableKeys", void 0);
9010
- _defineProperty(this, "touchState", void 0);
9011
- _defineProperty(this, "swipeState", void 0);
9012
9138
  this.handlers = handlers;
9013
9139
  this.options = {
9014
9140
  ...defaultOptions,
@@ -9180,8 +9306,7 @@ class GestureStateMachine {
9180
9306
  // Only respect the finger that started a swipe. Any other lingering
9181
9307
  // gestures are ignored.
9182
9308
  if (this.swipeState.touchId === touchId) {
9183
- var _this$handlers$onSwip, _this$handlers;
9184
- (_this$handlers$onSwip = (_this$handlers = this.handlers).onSwipeChange) === null || _this$handlers$onSwip === void 0 ? void 0 : _this$handlers$onSwip.call(_this$handlers, pageX - this.swipeState.startX);
9309
+ this.handlers.onSwipeChange?.(pageX - this.swipeState.startX);
9185
9310
  }
9186
9311
  } else if (this.touchState[touchId]) {
9187
9312
  // It could be touch events started outside the keypad and
@@ -9194,7 +9319,6 @@ class GestureStateMachine {
9194
9319
  const dx = pageX - startX;
9195
9320
  const shouldBeginSwiping = swipeEnabled && !swipeLocked && Math.abs(dx) > this.options.swipeThresholdPx;
9196
9321
  if (shouldBeginSwiping) {
9197
- var _this$handlers$onSwip2, _this$handlers2;
9198
9322
  this._onSwipeStart();
9199
9323
 
9200
9324
  // Trigger the swipe.
@@ -9202,7 +9326,7 @@ class GestureStateMachine {
9202
9326
  touchId,
9203
9327
  startX
9204
9328
  };
9205
- (_this$handlers$onSwip2 = (_this$handlers2 = this.handlers).onSwipeChange) === null || _this$handlers$onSwip2 === void 0 ? void 0 : _this$handlers$onSwip2.call(_this$handlers2, pageX - this.swipeState.startX);
9329
+ this.handlers.onSwipeChange?.(pageX - this.swipeState.startX);
9206
9330
  } else {
9207
9331
  const id = getId();
9208
9332
  if (id !== activeNodeId) {
@@ -9225,8 +9349,7 @@ class GestureStateMachine {
9225
9349
  // Only respect the finger that started a swipe. Any other lingering
9226
9350
  // gestures are ignored.
9227
9351
  if (this.swipeState.touchId === touchId) {
9228
- var _this$handlers$onSwip3, _this$handlers3;
9229
- (_this$handlers$onSwip3 = (_this$handlers3 = this.handlers).onSwipeEnd) === null || _this$handlers$onSwip3 === void 0 ? void 0 : _this$handlers$onSwip3.call(_this$handlers3, pageX - this.swipeState.startX);
9352
+ this.handlers.onSwipeEnd?.(pageX - this.swipeState.startX);
9230
9353
  this.swipeState = null;
9231
9354
  }
9232
9355
  } else if (this.touchState[touchId]) {
@@ -9260,8 +9383,7 @@ class GestureStateMachine {
9260
9383
  // displacement.
9261
9384
  if (this.swipeState) {
9262
9385
  if (this.swipeState.touchId === touchId) {
9263
- var _this$handlers$onSwip4, _this$handlers4;
9264
- (_this$handlers$onSwip4 = (_this$handlers4 = this.handlers).onSwipeEnd) === null || _this$handlers$onSwip4 === void 0 ? void 0 : _this$handlers$onSwip4.call(_this$handlers4, 0);
9386
+ this.handlers.onSwipeEnd?.(0);
9265
9387
  this.swipeState = null;
9266
9388
  }
9267
9389
  } else if (this.touchState[touchId]) {
@@ -9284,9 +9406,6 @@ class GestureStateMachine {
9284
9406
 
9285
9407
  class NodeManager {
9286
9408
  constructor() {
9287
- _defineProperty(this, "_nodesById", void 0);
9288
- _defineProperty(this, "_orderedIds", void 0);
9289
- _defineProperty(this, "_cachedBoundingBoxesById", void 0);
9290
9409
  // A mapping from IDs to DOM nodes.
9291
9410
  this._nodesById = {};
9292
9411
 
@@ -9399,9 +9518,6 @@ class NodeManager {
9399
9518
 
9400
9519
  class PopoverStateMachine {
9401
9520
  constructor(handlers) {
9402
- _defineProperty(this, "handlers", void 0);
9403
- _defineProperty(this, "popovers", void 0);
9404
- _defineProperty(this, "activePopover", void 0);
9405
9521
  this.handlers = handlers;
9406
9522
  this.activePopover = null;
9407
9523
  this.popovers = {};
@@ -9561,16 +9677,16 @@ class PopoverStateMachine {
9561
9677
  }
9562
9678
  }
9563
9679
 
9680
+ /**
9681
+ * A high-level manager for our gesture system. In particular, this class
9682
+ * connects our various bits of logic for managing gestures and interactions,
9683
+ * and links them together.
9684
+ */
9564
9685
  const coordsForEvent = evt => {
9565
9686
  return [evt.changedTouches[0].clientX, evt.changedTouches[0].clientY];
9566
9687
  };
9567
9688
  class GestureManager {
9568
9689
  constructor(options, handlers, disabledSwipeKeys, multiPressableKeys) {
9569
- _defineProperty(this, "swipeEnabled", void 0);
9570
- _defineProperty(this, "trackEvents", void 0);
9571
- _defineProperty(this, "nodeManager", void 0);
9572
- _defineProperty(this, "popoverStateMachine", void 0);
9573
- _defineProperty(this, "gestureStateMachine", void 0);
9574
9690
  const {
9575
9691
  swipeEnabled
9576
9692
  } = options;
@@ -9811,13 +9927,12 @@ const inputReducer = function () {
9811
9927
  case "PressKey":
9812
9928
  const keyConfig = KeyConfigs[action.key];
9813
9929
  if (keyConfig.type !== "KEYPAD_NAVIGATION") {
9814
- var _state$keyHandler;
9815
9930
  // This is probably an anti-pattern but it works for the
9816
9931
  // case where we don't actually control the state but we
9817
9932
  // still want to communicate with the other object
9818
9933
  return {
9819
9934
  ...state,
9820
- cursor: (_state$keyHandler = state.keyHandler) === null || _state$keyHandler === void 0 ? void 0 : _state$keyHandler.call(state, keyConfig.id)
9935
+ cursor: state.keyHandler?.(keyConfig.id)
9821
9936
  };
9822
9937
  }
9823
9938
 
@@ -10186,66 +10301,6 @@ const createStore = () => {
10186
10301
  class ProvidedKeypad extends React__namespace.Component {
10187
10302
  constructor(props) {
10188
10303
  super(props);
10189
- _defineProperty(this, "store", void 0);
10190
- _defineProperty(this, "activate", () => {
10191
- this.props.setKeypadActive(true);
10192
- });
10193
- _defineProperty(this, "dismiss", () => {
10194
- this.props.setKeypadActive(false);
10195
- });
10196
- _defineProperty(this, "configure", (configuration, cb) => {
10197
- this.store.dispatch(configureKeypad(configuration));
10198
-
10199
- // HACK(charlie): In Perseus, triggering a focus causes the keypad to
10200
- // animate into view and re-configure. We'd like to provide the option
10201
- // to re-render the re-configured keypad before animating it into view,
10202
- // to avoid jank in the animation. As such, we support passing a
10203
- // callback into `configureKeypad`. However, implementing this properly
10204
- // would require middleware, etc., so we just hack it on with
10205
- // `setTimeout` for now.
10206
- setTimeout(() => cb && cb());
10207
- });
10208
- _defineProperty(this, "setCursor", cursor => {
10209
- this.store.dispatch(setCursor(cursor));
10210
- });
10211
- _defineProperty(this, "setKeyHandler", keyHandler => {
10212
- this.store.dispatch(setKeyHandler(keyHandler));
10213
- });
10214
- _defineProperty(this, "getDOMNode", () => {
10215
- return ReactDOM__default["default"].findDOMNode(this);
10216
- });
10217
- _defineProperty(this, "onElementMounted", element => {
10218
- var _this$props$onElement, _this$props;
10219
- this.props.onAnalyticsEvent({
10220
- type: "math-input:keypad-opened",
10221
- payload: {
10222
- virtualKeypadVersion: "MATH_INPUT_KEYPAD_V1"
10223
- }
10224
- });
10225
-
10226
- // Append the dispatch methods that we want to expose
10227
- // externally to the returned React element.
10228
- const elementWithDispatchMethods = {
10229
- ...element,
10230
- activate: this.activate,
10231
- dismiss: this.dismiss,
10232
- configure: this.configure,
10233
- setCursor: this.setCursor,
10234
- setKeyHandler: this.setKeyHandler,
10235
- getDOMNode: this.getDOMNode
10236
- };
10237
- (_this$props$onElement = (_this$props = this.props).onElementMounted) === null || _this$props$onElement === void 0 ? void 0 : _this$props$onElement.call(_this$props, elementWithDispatchMethods);
10238
- });
10239
- _defineProperty(this, "onDismiss", () => {
10240
- var _this$props$onDismiss, _this$props2;
10241
- this.props.onAnalyticsEvent({
10242
- type: "math-input:keypad-closed",
10243
- payload: {
10244
- virtualKeypadVersion: "MATH_INPUT_KEYPAD_V1"
10245
- }
10246
- });
10247
- (_this$props$onDismiss = (_this$props2 = this.props).onDismiss) === null || _this$props$onDismiss === void 0 ? void 0 : _this$props$onDismiss.call(_this$props2);
10248
- });
10249
10304
  this.store = createStore();
10250
10305
  }
10251
10306
  componentDidUpdate(prevProps) {
@@ -10256,6 +10311,63 @@ class ProvidedKeypad extends React__namespace.Component {
10256
10311
  this.store.dispatch(dismissKeypad());
10257
10312
  }
10258
10313
  }
10314
+ activate = () => {
10315
+ this.props.setKeypadActive(true);
10316
+ };
10317
+ dismiss = () => {
10318
+ this.props.setKeypadActive(false);
10319
+ };
10320
+ configure = (configuration, cb) => {
10321
+ this.store.dispatch(configureKeypad(configuration));
10322
+
10323
+ // HACK(charlie): In Perseus, triggering a focus causes the keypad to
10324
+ // animate into view and re-configure. We'd like to provide the option
10325
+ // to re-render the re-configured keypad before animating it into view,
10326
+ // to avoid jank in the animation. As such, we support passing a
10327
+ // callback into `configureKeypad`. However, implementing this properly
10328
+ // would require middleware, etc., so we just hack it on with
10329
+ // `setTimeout` for now.
10330
+ setTimeout(() => cb && cb());
10331
+ };
10332
+ setCursor = cursor => {
10333
+ this.store.dispatch(setCursor(cursor));
10334
+ };
10335
+ setKeyHandler = keyHandler => {
10336
+ this.store.dispatch(setKeyHandler(keyHandler));
10337
+ };
10338
+ getDOMNode = () => {
10339
+ return ReactDOM__default["default"].findDOMNode(this);
10340
+ };
10341
+ onElementMounted = element => {
10342
+ this.props.onAnalyticsEvent({
10343
+ type: "math-input:keypad-opened",
10344
+ payload: {
10345
+ virtualKeypadVersion: "MATH_INPUT_KEYPAD_V1"
10346
+ }
10347
+ });
10348
+
10349
+ // Append the dispatch methods that we want to expose
10350
+ // externally to the returned React element.
10351
+ const elementWithDispatchMethods = {
10352
+ ...element,
10353
+ activate: this.activate,
10354
+ dismiss: this.dismiss,
10355
+ configure: this.configure,
10356
+ setCursor: this.setCursor,
10357
+ setKeyHandler: this.setKeyHandler,
10358
+ getDOMNode: this.getDOMNode
10359
+ };
10360
+ this.props.onElementMounted?.(elementWithDispatchMethods);
10361
+ };
10362
+ onDismiss = () => {
10363
+ this.props.onAnalyticsEvent({
10364
+ type: "math-input:keypad-closed",
10365
+ payload: {
10366
+ virtualKeypadVersion: "MATH_INPUT_KEYPAD_V1"
10367
+ }
10368
+ });
10369
+ this.props.onDismiss?.();
10370
+ };
10259
10371
  render() {
10260
10372
  const {
10261
10373
  style