@khanacademy/math-input 0.7.2 → 1.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.
Files changed (176) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/actions/index.d.ts +31 -0
  3. package/dist/actions/index.js.flow +40 -0
  4. package/dist/components/compute-layout-parameters.d.ts +38 -0
  5. package/dist/components/compute-layout-parameters.js.flow +49 -0
  6. package/dist/components/corner-decal.d.ts +12 -0
  7. package/dist/components/corner-decal.js.flow +15 -0
  8. package/dist/components/echo-manager.d.ts +26 -0
  9. package/dist/components/echo-manager.js.flow +29 -0
  10. package/dist/components/empty-keypad-button.d.ts +13 -0
  11. package/dist/components/empty-keypad-button.js.flow +23 -0
  12. package/dist/components/expression-keypad.d.ts +22 -0
  13. package/dist/components/expression-keypad.js.flow +32 -0
  14. package/dist/components/fraction-keypad.d.ts +21 -0
  15. package/dist/components/fraction-keypad.js.flow +30 -0
  16. package/dist/components/gesture-manager.d.ts +74 -0
  17. package/dist/components/gesture-manager.js.flow +82 -0
  18. package/dist/components/gesture-state-machine.d.ts +105 -0
  19. package/dist/components/gesture-state-machine.js.flow +118 -0
  20. package/dist/components/icon.d.ts +15 -0
  21. package/dist/components/icon.js.flow +18 -0
  22. package/dist/components/input/__tests__/test-math-wrapper.d.ts +8 -0
  23. package/dist/components/input/__tests__/test-math-wrapper.js.flow +14 -0
  24. package/dist/components/input/cursor-handle.d.ts +1 -1
  25. package/dist/components/input/cursor-handle.js.flow +1 -1
  26. package/dist/components/input/drag-listener.d.ts +13 -0
  27. package/dist/components/input/drag-listener.js.flow +19 -0
  28. package/dist/components/input/math-input.d.ts +5 -4
  29. package/dist/components/input/math-input.js.flow +5 -4
  30. package/dist/components/input/math-wrapper.d.ts +110 -0
  31. package/dist/components/input/math-wrapper.js.flow +125 -0
  32. package/dist/components/input/scroll-into-view.d.ts +11 -0
  33. package/dist/components/input/scroll-into-view.js.flow +20 -0
  34. package/dist/components/keypad/button-assets.d.ts +4 -3
  35. package/dist/components/keypad/button-assets.js.flow +3 -3
  36. package/dist/components/keypad/button.d.ts +1 -2
  37. package/dist/components/keypad/button.js.flow +1 -2
  38. package/dist/components/keypad/keypad-page-items.d.ts +15 -10
  39. package/dist/components/keypad/keypad-page-items.js.flow +20 -10
  40. package/dist/components/keypad-button.d.ts +52 -0
  41. package/dist/components/keypad-button.js.flow +79 -0
  42. package/dist/components/keypad-container.d.ts +40 -0
  43. package/dist/components/keypad-container.js.flow +58 -0
  44. package/dist/components/keypad.d.ts +31 -0
  45. package/dist/components/keypad.js.flow +40 -0
  46. package/dist/components/many-keypad-button.d.ts +15 -0
  47. package/dist/components/many-keypad-button.js.flow +17 -0
  48. package/dist/components/math-icon.d.ts +16 -0
  49. package/dist/components/math-icon.js.flow +19 -0
  50. package/dist/components/multi-symbol-grid.d.ts +14 -0
  51. package/dist/components/multi-symbol-grid.js.flow +16 -0
  52. package/dist/components/multi-symbol-popover.d.ts +12 -0
  53. package/dist/components/multi-symbol-popover.js.flow +15 -0
  54. package/dist/components/navigation-pad.d.ts +14 -0
  55. package/dist/components/navigation-pad.js.flow +16 -0
  56. package/dist/components/node-manager.d.ts +53 -0
  57. package/dist/components/node-manager.js.flow +65 -0
  58. package/dist/components/popover-manager.d.ts +13 -0
  59. package/dist/components/popover-manager.js.flow +15 -0
  60. package/dist/components/popover-state-machine.d.ts +75 -0
  61. package/dist/components/popover-state-machine.js.flow +83 -0
  62. package/dist/components/provided-keypad.d.ts +8 -7
  63. package/dist/components/provided-keypad.js.flow +8 -7
  64. package/dist/components/styles.d.ts +6 -0
  65. package/dist/components/styles.js.flow +13 -0
  66. package/dist/components/svg-icon.d.ts +12 -0
  67. package/dist/components/svg-icon.js.flow +15 -0
  68. package/dist/components/tabbar/icons.d.ts +3 -2
  69. package/dist/components/tabbar/icons.js.flow +3 -2
  70. package/dist/components/tabbar/item.d.ts +1 -2
  71. package/dist/components/tabbar/item.js.flow +1 -2
  72. package/dist/components/tabbar/tabbar.d.ts +3 -3
  73. package/dist/components/tabbar/tabbar.js.flow +3 -3
  74. package/dist/components/text-icon.d.ts +13 -0
  75. package/dist/components/text-icon.js.flow +16 -0
  76. package/dist/components/touchable-keypad-button.d.ts +30 -0
  77. package/dist/components/touchable-keypad-button.js.flow +35 -0
  78. package/dist/components/two-page-keypad.d.ts +20 -0
  79. package/dist/components/two-page-keypad.js.flow +30 -0
  80. package/dist/components/velocity-tracker.d.ts +48 -0
  81. package/dist/components/velocity-tracker.js.flow +54 -0
  82. package/dist/es/index.js +938 -1059
  83. package/dist/es/index.js.map +1 -1
  84. package/dist/fake-react-native-web/text.d.ts +2 -1
  85. package/dist/fake-react-native-web/text.js.flow +2 -1
  86. package/dist/fake-react-native-web/view.d.ts +3 -2
  87. package/dist/fake-react-native-web/view.js.flow +3 -2
  88. package/dist/index.d.ts +1 -1
  89. package/dist/index.js +988 -1089
  90. package/dist/index.js.flow +1 -4
  91. package/dist/index.js.map +1 -1
  92. package/dist/store/echo-reducer.d.ts +5 -0
  93. package/dist/store/echo-reducer.js.flow +14 -0
  94. package/dist/store/index.d.ts +46 -1
  95. package/dist/store/index.js.flow +64 -1
  96. package/dist/store/input-reducer.d.ts +7 -0
  97. package/dist/store/input-reducer.js.flow +16 -0
  98. package/dist/store/keypad-reducer.d.ts +9 -0
  99. package/dist/store/keypad-reducer.js.flow +18 -0
  100. package/dist/store/layout-reducer.d.ts +21 -0
  101. package/dist/store/layout-reducer.js.flow +30 -0
  102. package/dist/store/pager-reducer.d.ts +13 -0
  103. package/dist/store/pager-reducer.js.flow +22 -0
  104. package/dist/store/shared.d.ts +6 -0
  105. package/dist/store/shared.js.flow +13 -0
  106. package/dist/store/types.d.ts +57 -0
  107. package/dist/store/types.js.flow +63 -0
  108. package/dist/types.d.ts +50 -0
  109. package/dist/types.js.flow +61 -0
  110. package/package.json +1 -1
  111. package/src/actions/{index.js → index.ts} +5 -5
  112. package/src/components/__tests__/{gesture-state-machine_test.js → gesture-state-machine.test.ts} +5 -1
  113. package/src/components/__tests__/{two-page-keypad_test.js → two-page-keypad.test.tsx} +0 -2
  114. package/src/components/{corner-decal.js → corner-decal.tsx} +6 -5
  115. package/src/components/{echo-manager.js → echo-manager.tsx} +29 -24
  116. package/src/components/{empty-keypad-button.js → empty-keypad-button.tsx} +17 -10
  117. package/src/components/{expression-keypad.js → expression-keypad.tsx} +27 -25
  118. package/src/components/{fraction-keypad.js → fraction-keypad.tsx} +21 -16
  119. package/src/components/{gesture-manager.js → gesture-manager.tsx} +10 -4
  120. package/src/components/{gesture-state-machine.js → gesture-state-machine.ts} +49 -3
  121. package/src/components/{icon.js → icon.tsx} +12 -14
  122. package/src/components/input/cursor-handle.tsx +1 -1
  123. package/src/components/input/{drag-listener.js → drag-listener.ts} +4 -0
  124. package/src/components/input/math-input.tsx +10 -9
  125. package/src/components/input/{math-wrapper.js → math-wrapper.ts} +10 -6
  126. package/src/components/input/{scroll-into-view.js → scroll-into-view.ts} +5 -15
  127. package/src/components/keypad/button-assets.tsx +4 -5
  128. package/src/components/keypad/button.tsx +1 -2
  129. package/src/components/keypad/index.tsx +1 -1
  130. package/src/components/keypad/keypad-page-items.tsx +33 -10
  131. package/src/components/{keypad-button.js → keypad-button.tsx} +42 -37
  132. package/src/components/{keypad-container.js → keypad-container.tsx} +41 -23
  133. package/src/components/{keypad.js → keypad.tsx} +31 -23
  134. package/src/components/{many-keypad-button.js → many-keypad-button.tsx} +8 -6
  135. package/src/components/{math-icon.js → math-icon.tsx} +7 -6
  136. package/src/components/{multi-symbol-grid.js → multi-symbol-grid.tsx} +8 -8
  137. package/src/components/{multi-symbol-popover.js → multi-symbol-popover.tsx} +5 -6
  138. package/src/components/{navigation-pad.js → navigation-pad.tsx} +7 -6
  139. package/src/components/{node-manager.js → node-manager.ts} +16 -4
  140. package/src/components/{popover-manager.js → popover-manager.tsx} +13 -16
  141. package/src/components/{popover-state-machine.js → popover-state-machine.ts} +21 -2
  142. package/src/components/prop-types.js +1 -67
  143. package/src/components/provided-keypad.tsx +14 -12
  144. package/src/components/{svg-icon.js → svg-icon.tsx} +5 -6
  145. package/src/components/tabbar/icons.tsx +4 -2
  146. package/src/components/tabbar/item.tsx +1 -3
  147. package/src/components/tabbar/{tabbar.stories.js → tabbar.stories.tsx} +10 -1
  148. package/src/components/tabbar/tabbar.tsx +3 -3
  149. package/src/components/{text-icon.js → text-icon.tsx} +7 -6
  150. package/src/components/{touchable-keypad-button.js → touchable-keypad-button.tsx} +19 -16
  151. package/src/components/{two-page-keypad.js → two-page-keypad.tsx} +13 -9
  152. package/src/components/{velocity-tracker.js → velocity-tracker.ts} +14 -4
  153. package/src/fake-react-native-web/text.tsx +2 -1
  154. package/src/fake-react-native-web/view.tsx +3 -2
  155. package/src/index.ts +1 -4
  156. package/src/store/echo-reducer.ts +58 -0
  157. package/src/store/index.ts +14 -425
  158. package/src/store/input-reducer.ts +55 -0
  159. package/src/store/keypad-reducer.ts +62 -0
  160. package/src/store/layout-reducer.ts +133 -0
  161. package/src/store/pager-reducer.ts +141 -0
  162. package/src/store/shared.ts +12 -0
  163. package/src/store/types.ts +65 -0
  164. package/src/types.ts +69 -0
  165. package/tsconfig.tsbuildinfo +1 -1
  166. package/src/components/app.js +0 -73
  167. package/src/demo.js +0 -9
  168. package/src/native-app.js +0 -85
  169. /package/src/components/__tests__/{node-manager_test.js → node-manager.test.ts} +0 -0
  170. /package/src/components/{compute-layout-parameters.js → compute-layout-parameters.ts} +0 -0
  171. /package/src/components/input/__tests__/{context-tracking_test.js → context-tracking.test.ts} +0 -0
  172. /package/src/components/input/__tests__/{mathquill_test.js → mathquill.test.ts} +0 -0
  173. /package/src/components/input/__tests__/{test-math-wrapper.jsx → test-math-wrapper.ts} +0 -0
  174. /package/src/components/keypad/{button.stories.js → button.stories.tsx} +0 -0
  175. /package/src/components/{styles.js → styles.ts} +0 -0
  176. /package/src/components/tabbar/__tests__/{tabbar_test.js → tabbar.test.tsx} +0 -0
package/dist/index.js CHANGED
@@ -391,6 +391,9 @@ _defineProperty(CursorHandle, "defaultProps", {
391
391
  const touchSlopPx = 8;
392
392
  class DragListener {
393
393
  constructor(onDrag, initialEvent) {
394
+ _defineProperty(this, "_scrollListener", void 0);
395
+ _defineProperty(this, "_moveListener", void 0);
396
+ _defineProperty(this, "_endAndCancelListener", void 0);
394
397
  // We detect drags in two ways. First, by listening for the window
395
398
  // scroll event (we consider any legitimate scroll to be a drag).
396
399
  this._scrollListener = () => {
@@ -549,7 +552,7 @@ const IN_DENOMINATOR = "IN_DENOMINATOR";
549
552
  // write is non-leaf math (numbers and variables).
550
553
  const BEFORE_FRACTION = "BEFORE_FRACTION";
551
554
 
552
- var CursorContexts = /*#__PURE__*/Object.freeze({
555
+ var cursorContexts = /*#__PURE__*/Object.freeze({
553
556
  __proto__: null,
554
557
  NONE: NONE,
555
558
  IN_PARENS: IN_PARENS,
@@ -560,12 +563,6 @@ var CursorContexts = /*#__PURE__*/Object.freeze({
560
563
  BEFORE_FRACTION: BEFORE_FRACTION
561
564
  });
562
565
 
563
- /**
564
- * This file contains a wrapper around MathQuill so that we can provide a
565
- * more regular interface for the functionality we need while insulating us
566
- * from MathQuill changes.
567
- */
568
-
569
566
  // Keeping `window` in place for test suite and GitHub Pages.
570
567
  // If it does not exist, fall back to CommonJS require. - jsatk
571
568
 
@@ -700,8 +697,14 @@ const KeysForJumpContext = {
700
697
  [IN_DENOMINATOR]: Keys.JUMP_OUT_DENOMINATOR
701
698
  };
702
699
  class MathWrapper {
700
+ // MathQuill interface
701
+ // MathQuill input
702
+
703
703
  constructor(element) {
704
704
  let callbacks = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
705
+ _defineProperty(this, "MQ", void 0);
706
+ _defineProperty(this, "mathField", void 0);
707
+ _defineProperty(this, "callbacks", void 0);
705
708
  this.MQ = MathQuill__default["default"].getInterface(2);
706
709
  this.mathField = this.MQ.MathField(element, {
707
710
  // use a span instead of a textarea so that we don't bring up the
@@ -780,7 +783,7 @@ class MathWrapper {
780
783
  this._handleBackspace(cursor);
781
784
  } else if (key === Keys.LEFT) {
782
785
  this._handleLeftArrow(cursor);
783
- } else if (key === Keys.RIGHT || key === Keys.JUMP_OUT) {
786
+ } else if (key === Keys.RIGHT) {
784
787
  this._handleRightArrow(cursor);
785
788
  } else if (/^[a-zA-Z]$/.test(key)) {
786
789
  this.mathField[WRITE](key);
@@ -1279,7 +1282,7 @@ class MathWrapper {
1279
1282
  if (this._isInsideEmptyNode(cursor)) {
1280
1283
  const grandparent = cursor.parent.parent;
1281
1284
  const command = this._maybeFindCommandBeforeParens(grandparent);
1282
- cursor.insLeftOf(command.startNode);
1285
+ cursor.insLeftOf(command === null || command === void 0 ? void 0 : command.startNode);
1283
1286
  cursor.startSelection();
1284
1287
  if (grandparent[this.MQ.R] !== MQ_END) {
1285
1288
  cursor.insRightOf(grandparent[this.MQ.R]);
@@ -1443,18 +1446,6 @@ class MathWrapper {
1443
1446
  * TODO(charlie): Move this scroll logic out of our components and into a higher
1444
1447
  * level in the component tree--perhaps even into webapp, beyond Perseus.
1445
1448
  */
1446
-
1447
- // Taken from https://dev.opera.com/articles/fixing-the-scrolltop-bug/
1448
- function bodyOrHtml() {
1449
- if ("scrollingElement" in document) {
1450
- return document.scrollingElement;
1451
- }
1452
- // Fallback for legacy browsers
1453
- if (navigator.userAgent.indexOf("WebKit") !== -1) {
1454
- return document.body;
1455
- }
1456
- return document.documentElement;
1457
- }
1458
1449
  const scrollIntoView = (containerNode, keypadNode) => {
1459
1450
  // TODO(charlie): There's no need for us to be reading the keypad bounds
1460
1451
  // here, since they're pre-determined by logic in the store. We should
@@ -1464,7 +1455,7 @@ const scrollIntoView = (containerNode, keypadNode) => {
1464
1455
  const containerTopPx = containerBounds.top;
1465
1456
 
1466
1457
  // Get the element that scrolls the document.
1467
- const scrollNode = bodyOrHtml();
1458
+ const scrollNode = document.scrollingElement;
1468
1459
  const desiredMarginPx = 16;
1469
1460
  if (keypadNode) {
1470
1461
  // NOTE(charlie): We can't use the bounding rect of the keypad,
@@ -1483,7 +1474,9 @@ const scrollIntoView = (containerNode, keypadNode) => {
1483
1474
  // the bottom of the input is just above the top of the keypad,
1484
1475
  // taking care not to scroll the input out of view.
1485
1476
  const scrollOffset = Math.min(containerBottomPx - keypadTopPx + desiredMarginPx, containerTopPx);
1486
- scrollNode.scrollTop += scrollOffset;
1477
+ if (scrollNode) {
1478
+ scrollNode.scrollTop += scrollOffset;
1479
+ }
1487
1480
  return;
1488
1481
  }
1489
1482
  }
@@ -1491,7 +1484,7 @@ const scrollIntoView = (containerNode, keypadNode) => {
1491
1484
  // Alternatively, if the input is out of the viewport or nearly out
1492
1485
  // of the viewport, scroll it into view. We can do this regardless
1493
1486
  // of whether the keypad has been provided.
1494
- if (containerTopPx < desiredMarginPx) {
1487
+ if (scrollNode && containerTopPx < desiredMarginPx) {
1495
1488
  scrollNode.scrollTop -= containerBounds.height + desiredMarginPx;
1496
1489
  }
1497
1490
  };
@@ -2199,323 +2192,11 @@ const inlineStyles$1 = {
2199
2192
  };
2200
2193
 
2201
2194
  /**
2202
- * This file contains configuration settings for the buttons in the keypad.
2195
+ * React PropTypes that may be shared between components.
2203
2196
  */
2204
- const KeyConfigs = {
2205
- // Basic math keys.
2206
- [Keys.PLUS]: {
2207
- type: KeyTypes.OPERATOR,
2208
- // I18N: A label for a plus sign.
2209
- ariaLabel: i18n__namespace._("Plus")
2210
- },
2211
- [Keys.MINUS]: {
2212
- type: KeyTypes.OPERATOR,
2213
- // I18N: A label for a minus sign.
2214
- ariaLabel: i18n__namespace._("Minus")
2215
- },
2216
- [Keys.NEGATIVE]: {
2217
- type: KeyTypes.VALUE,
2218
- // I18N: A label for a minus sign.
2219
- ariaLabel: i18n__namespace._("Negative")
2220
- },
2221
- [Keys.TIMES]: {
2222
- type: KeyTypes.OPERATOR,
2223
- // I18N: A label for a multiplication sign (represented with an 'x').
2224
- ariaLabel: i18n__namespace._("Multiply")
2225
- },
2226
- [Keys.DIVIDE]: {
2227
- type: KeyTypes.OPERATOR,
2228
- // I18N: A label for a division sign.
2229
- ariaLabel: i18n__namespace._("Divide")
2230
- },
2231
- [Keys.DECIMAL]: {
2232
- type: KeyTypes.VALUE,
2233
- // I18N: A label for a decimal symbol.
2234
- ariaLabel: i18n__namespace._("Decimal"),
2235
- icon: decimalSeparator === DecimalSeparators.COMMA ? {
2236
- // TODO(charlie): Get an SVG icon for the comma, or verify with
2237
- // design that the text-rendered version is acceptable.
2238
- type: IconTypes.TEXT,
2239
- data: ","
2240
- } : {
2241
- type: IconTypes.SVG,
2242
- data: Keys.PERIOD
2243
- }
2244
- },
2245
- [Keys.PERCENT]: {
2246
- type: KeyTypes.OPERATOR,
2247
- // I18N: A label for a percent sign.
2248
- ariaLabel: i18n__namespace._("Percent")
2249
- },
2250
- [Keys.CDOT]: {
2251
- type: KeyTypes.OPERATOR,
2252
- // I18N: A label for a multiplication sign (represented as a dot).
2253
- ariaLabel: i18n__namespace._("Multiply")
2254
- },
2255
- [Keys.EQUAL]: {
2256
- type: KeyTypes.OPERATOR,
2257
- ariaLabel: i18n__namespace._("Equals sign")
2258
- },
2259
- [Keys.NEQ]: {
2260
- type: KeyTypes.OPERATOR,
2261
- ariaLabel: i18n__namespace._("Not-equals sign")
2262
- },
2263
- [Keys.GT]: {
2264
- type: KeyTypes.OPERATOR,
2265
- // I18N: A label for a 'greater than' sign (represented as '>').
2266
- ariaLabel: i18n__namespace._("Greater than sign")
2267
- },
2268
- [Keys.LT]: {
2269
- type: KeyTypes.OPERATOR,
2270
- // I18N: A label for a 'less than' sign (represented as '<').
2271
- ariaLabel: i18n__namespace._("Less than sign")
2272
- },
2273
- [Keys.GEQ]: {
2274
- type: KeyTypes.OPERATOR,
2275
- ariaLabel: i18n__namespace._("Greater than or equal to sign")
2276
- },
2277
- [Keys.LEQ]: {
2278
- type: KeyTypes.OPERATOR,
2279
- ariaLabel: i18n__namespace._("Less than or equal to sign")
2280
- },
2281
- // mobile native
2282
- [Keys.FRAC_INCLUSIVE]: {
2283
- type: KeyTypes.OPERATOR,
2284
- // I18N: A label for a button that creates a new fraction and puts the
2285
- // current expression in the numerator of that fraction.
2286
- ariaLabel: i18n__namespace._("Fraction, with current expression in numerator")
2287
- },
2288
- // mobile native
2289
- [Keys.FRAC_EXCLUSIVE]: {
2290
- type: KeyTypes.OPERATOR,
2291
- // I18N: A label for a button that creates a new fraction next to the
2292
- // cursor.
2293
- ariaLabel: i18n__namespace._("Fraction, excluding the current expression")
2294
- },
2295
- // mobile web
2296
- [Keys.FRAC]: {
2297
- type: KeyTypes.OPERATOR,
2298
- // I18N: A label for a button that creates a new fraction next to the
2299
- // cursor.
2300
- ariaLabel: i18n__namespace._("Fraction, excluding the current expression")
2301
- },
2302
- [Keys.EXP]: {
2303
- type: KeyTypes.OPERATOR,
2304
- // I18N: A label for a button that will allow the user to input a custom
2305
- // exponent.
2306
- ariaLabel: i18n__namespace._("Custom exponent")
2307
- },
2308
- [Keys.EXP_2]: {
2309
- type: KeyTypes.OPERATOR,
2310
- // I18N: A label for a button that will square (take to the second
2311
- // power) some math.
2312
- ariaLabel: i18n__namespace._("Square")
2313
- },
2314
- [Keys.EXP_3]: {
2315
- type: KeyTypes.OPERATOR,
2316
- // I18N: A label for a button that will cube (take to the third power)
2317
- // some math.
2318
- ariaLabel: i18n__namespace._("Cube")
2319
- },
2320
- [Keys.SQRT]: {
2321
- type: KeyTypes.OPERATOR,
2322
- ariaLabel: i18n__namespace._("Square root")
2323
- },
2324
- [Keys.CUBE_ROOT]: {
2325
- type: KeyTypes.OPERATOR,
2326
- ariaLabel: i18n__namespace._("Cube root")
2327
- },
2328
- [Keys.RADICAL]: {
2329
- type: KeyTypes.OPERATOR,
2330
- ariaLabel: i18n__namespace._("Radical with custom root")
2331
- },
2332
- [Keys.LEFT_PAREN]: {
2333
- type: KeyTypes.OPERATOR,
2334
- ariaLabel: i18n__namespace._("Left parenthesis")
2335
- },
2336
- [Keys.RIGHT_PAREN]: {
2337
- type: KeyTypes.OPERATOR,
2338
- ariaLabel: i18n__namespace._("Right parenthesis")
2339
- },
2340
- [Keys.LN]: {
2341
- type: KeyTypes.OPERATOR,
2342
- ariaLabel: i18n__namespace._("Natural logarithm")
2343
- },
2344
- [Keys.LOG]: {
2345
- type: KeyTypes.OPERATOR,
2346
- ariaLabel: i18n__namespace._("Logarithm with base 10")
2347
- },
2348
- [Keys.LOG_N]: {
2349
- type: KeyTypes.OPERATOR,
2350
- ariaLabel: i18n__namespace._("Logarithm with custom base")
2351
- },
2352
- [Keys.SIN]: {
2353
- type: KeyTypes.OPERATOR,
2354
- ariaLabel: i18n__namespace._("Sine")
2355
- },
2356
- [Keys.COS]: {
2357
- type: KeyTypes.OPERATOR,
2358
- ariaLabel: i18n__namespace._("Cosine")
2359
- },
2360
- [Keys.TAN]: {
2361
- type: KeyTypes.OPERATOR,
2362
- ariaLabel: i18n__namespace._("Tangent")
2363
- },
2364
- [Keys.PI]: {
2365
- type: KeyTypes.VALUE,
2366
- ariaLabel: i18n__namespace._("Pi"),
2367
- icon: {
2368
- type: IconTypes.MATH,
2369
- data: "\\pi"
2370
- }
2371
- },
2372
- [Keys.THETA]: {
2373
- type: KeyTypes.VALUE,
2374
- ariaLabel: i18n__namespace._("Theta"),
2375
- icon: {
2376
- type: IconTypes.MATH,
2377
- data: "\\theta"
2378
- }
2379
- },
2380
- [Keys.NOOP]: {
2381
- type: KeyTypes.EMPTY
2382
- },
2383
- // Input navigation keys.
2384
- [Keys.UP]: {
2385
- type: KeyTypes.INPUT_NAVIGATION,
2386
- ariaLabel: i18n__namespace._("Up arrow")
2387
- },
2388
- [Keys.RIGHT]: {
2389
- type: KeyTypes.INPUT_NAVIGATION,
2390
- ariaLabel: i18n__namespace._("Right arrow")
2391
- },
2392
- [Keys.DOWN]: {
2393
- type: KeyTypes.INPUT_NAVIGATION,
2394
- ariaLabel: i18n__namespace._("Down arrow")
2395
- },
2396
- [Keys.LEFT]: {
2397
- type: KeyTypes.INPUT_NAVIGATION,
2398
- ariaLabel: i18n__namespace._("Left arrow")
2399
- },
2400
- [Keys.JUMP_OUT_PARENTHESES]: {
2401
- type: KeyTypes.INPUT_NAVIGATION,
2402
- ariaLabel: i18n__namespace._("Navigate right out of a set of parentheses")
2403
- },
2404
- [Keys.JUMP_OUT_EXPONENT]: {
2405
- type: KeyTypes.INPUT_NAVIGATION,
2406
- ariaLabel: i18n__namespace._("Navigate right out of an exponent")
2407
- },
2408
- [Keys.JUMP_OUT_BASE]: {
2409
- type: KeyTypes.INPUT_NAVIGATION,
2410
- ariaLabel: i18n__namespace._("Navigate right out of a base")
2411
- },
2412
- [Keys.JUMP_INTO_NUMERATOR]: {
2413
- type: KeyTypes.INPUT_NAVIGATION,
2414
- ariaLabel: i18n__namespace._("Navigate right into the numerator of a fraction")
2415
- },
2416
- [Keys.JUMP_OUT_NUMERATOR]: {
2417
- type: KeyTypes.INPUT_NAVIGATION,
2418
- ariaLabel: i18n__namespace._("Navigate right out of the numerator and into the denominator")
2419
- },
2420
- [Keys.JUMP_OUT_DENOMINATOR]: {
2421
- type: KeyTypes.INPUT_NAVIGATION,
2422
- ariaLabel: i18n__namespace._("Navigate right out of the denominator of a fraction")
2423
- },
2424
- [Keys.BACKSPACE]: {
2425
- type: KeyTypes.INPUT_NAVIGATION,
2426
- // I18N: A label for a button that will delete some input.
2427
- ariaLabel: i18n__namespace._("Delete")
2428
- },
2429
- // Keypad navigation keys.
2430
- [Keys.DISMISS]: {
2431
- type: KeyTypes.KEYPAD_NAVIGATION,
2432
- // I18N: A label for a button that will dismiss/hide a keypad.
2433
- ariaLabel: i18n__namespace._("Dismiss")
2434
- }
2435
- };
2436
-
2437
- // Add in any multi-function buttons. By default, these keys will mix in any
2438
- // configuration settings from their default child key (i.e., the first key in
2439
- // the `childKeyIds` array).
2440
- // TODO(charlie): Make the multi-function button's long-press interaction
2441
- // accessible.
2442
- // NOTE(kevinb): This is only used in the mobile native app.
2443
- KeyConfigs[Keys.FRAC_MULTI] = {
2444
- childKeyIds: [Keys.FRAC_INCLUSIVE, Keys.FRAC_EXCLUSIVE]
2445
- };
2446
-
2447
- // TODO(charlie): Use the numeral color for the 'Many' key.
2448
- KeyConfigs[Keys.MANY] = {
2449
- type: KeyTypes.MANY
2450
- // childKeyIds will be configured by the client.
2451
- };
2452
-
2453
- // Add in every numeral.
2454
- const NUMBERS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
2455
- for (const num of NUMBERS) {
2456
- // TODO(charlie): Consider removing the SVG icons that we have for the
2457
- // numeral keys. They can be rendered just as easily with text (though that
2458
- // would mean that we'd be using text beyond the variable key).
2459
- const textRepresentation = "".concat(num);
2460
- KeyConfigs["NUM_".concat(num)] = {
2461
- type: KeyTypes.VALUE,
2462
- ariaLabel: textRepresentation,
2463
- icon: {
2464
- type: IconTypes.TEXT,
2465
- data: textRepresentation
2466
- }
2467
- };
2468
- }
2469
-
2470
- // Add in every variable.
2471
- const LETTERS = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
2472
- for (const letter of LETTERS) {
2473
- const lowerCaseVariable = letter.toLowerCase();
2474
- const upperCaseVariable = letter.toUpperCase();
2475
- for (const textRepresentation of [lowerCaseVariable, upperCaseVariable]) {
2476
- KeyConfigs[textRepresentation] = {
2477
- type: KeyTypes.VALUE,
2478
- ariaLabel: textRepresentation,
2479
- icon: {
2480
- type: IconTypes.MATH,
2481
- data: textRepresentation
2482
- }
2483
- };
2484
- }
2485
- }
2486
- for (const key of Object.keys(KeyConfigs)) {
2487
- KeyConfigs[key] = {
2488
- id: key,
2489
- // Default to an SVG icon indexed by the key name.
2490
- icon: {
2491
- type: IconTypes.SVG,
2492
- data: key
2493
- },
2494
- ...KeyConfigs[key]
2495
- };
2496
- }
2497
-
2498
- /**
2499
- * React PropTypes that may be shared between components.
2500
- */
2501
- const iconPropType = PropTypes__default["default"].shape({
2502
- type: PropTypes__default["default"].oneOf(Object.keys(IconTypes)).isRequired,
2503
- data: PropTypes__default["default"].string.isRequired
2504
- });
2505
- const keyIdPropType = PropTypes__default["default"].oneOf(Object.keys(KeyConfigs));
2506
- const keyConfigPropType = PropTypes__default["default"].shape({
2507
- ariaLabel: PropTypes__default["default"].string,
2508
- id: keyIdPropType.isRequired,
2509
- type: PropTypes__default["default"].oneOf(Object.keys(KeyTypes)).isRequired,
2510
- childKeyIds: PropTypes__default["default"].arrayOf(keyIdPropType),
2511
- icon: iconPropType.isRequired
2512
- });
2513
- const keypadConfigurationPropType = PropTypes__default["default"].shape({
2514
- keypadType: PropTypes__default["default"].oneOf(Object.keys(KeypadTypes)).isRequired,
2515
- extraKeys: PropTypes__default["default"].arrayOf(keyIdPropType)
2516
- });
2517
2197
 
2518
2198
  // NOTE(jared): This is no longer guaranteed to be React element
2199
+ // NOTE(matthewc): only seems to be used in Perseus
2519
2200
  const keypadElementPropType = PropTypes__default["default"].shape({
2520
2201
  activate: PropTypes__default["default"].func.isRequired,
2521
2202
  dismiss: PropTypes__default["default"].func.isRequired,
@@ -2524,29 +2205,6 @@ const keypadElementPropType = PropTypes__default["default"].shape({
2524
2205
  setKeyHandler: PropTypes__default["default"].func.isRequired,
2525
2206
  getDOMNode: PropTypes__default["default"].func.isRequired
2526
2207
  });
2527
- const bordersPropType = PropTypes__default["default"].arrayOf(PropTypes__default["default"].oneOf(Object.keys(BorderDirections)));
2528
- const boundingBoxPropType = PropTypes__default["default"].shape({
2529
- height: PropTypes__default["default"].number,
2530
- width: PropTypes__default["default"].number,
2531
- top: PropTypes__default["default"].number,
2532
- right: PropTypes__default["default"].number,
2533
- bottom: PropTypes__default["default"].number,
2534
- left: PropTypes__default["default"].number
2535
- });
2536
- const echoPropType = PropTypes__default["default"].shape({
2537
- animationId: PropTypes__default["default"].string.isRequired,
2538
- animationType: PropTypes__default["default"].oneOf(Object.keys(EchoAnimationTypes)).isRequired,
2539
- borders: bordersPropType,
2540
- id: keyIdPropType.isRequired,
2541
- initialBounds: boundingBoxPropType.isRequired
2542
- });
2543
- const cursorContextPropType = PropTypes__default["default"].oneOf(Object.keys(CursorContexts));
2544
- const popoverPropType = PropTypes__default["default"].shape({
2545
- parentId: keyIdPropType.isRequired,
2546
- bounds: boundingBoxPropType.isRequired,
2547
- childKeyIds: PropTypes__default["default"].arrayOf(keyIdPropType).isRequired
2548
- });
2549
- PropTypes__default["default"].oneOfType([PropTypes__default["default"].arrayOf(PropTypes__default["default"].node), PropTypes__default["default"].node]);
2550
2208
 
2551
2209
  // naming convention: verb + noun
2552
2210
  // the noun should be one of the other properties in the object that's
@@ -2565,8 +2223,6 @@ const activateKeypad = () => {
2565
2223
 
2566
2224
  /**
2567
2225
  * Configure the keypad with the provided configuration parameters.
2568
- *
2569
- * See: `prop-types.js#keypadConfigurationPropType`.
2570
2226
  */
2571
2227
  const configureKeypad = configuration => {
2572
2228
  return {
@@ -2602,138 +2258,6 @@ const setCursor = cursor => {
2602
2258
  };
2603
2259
  };
2604
2260
 
2605
- /**
2606
- * An algorithm for computing the appropriate layout parameters for the keypad,
2607
- * including the size of the buttons and whether or not to render fullscreen,
2608
- * taking into account a number of factors including the size of the screen, the
2609
- * orientation of the screen, the presence of browser chrome, the presence of
2610
- * other exercise-related chrome, the size of the input box, the parameters that
2611
- * define the keypad (i.e., the number of rows, columns, and pages), and so
2612
- * forth.
2613
- *
2614
- * The computations herein make some strong assumptions about the sizes of
2615
- * various other elements and the situations under which they will be visible
2616
- * (e.g., browser chrome). However, this is just a heuristic--it's not crucial
2617
- * that our buttons are sized in a pixel-perfect manner, but rather, that we
2618
- * make a balanced use of space.
2619
- *
2620
- * Note that one goal of the algorithm is to avoid resizing the keypad in the
2621
- * face of dynamic browser chrome. In order to avoid that awkwardness, we tend
2622
- * to be conservative in our measurements and make things smaller than they
2623
- * might need to be.
2624
- */
2625
- const minButtonHeight = 48;
2626
- const maxButtonSize = 64;
2627
- const minSpaceAboveKeypad = 32;
2628
-
2629
- // These values are taken from an iPhone 5, but should be consistent with the
2630
- // iPhone 4 as well. Regardless, these are meant to be representative of the
2631
- // possible types of browser chrome that could appear in various context, rather
2632
- // than pixel-perfect for every device.
2633
- const safariNavBarWhenShrunk = 44;
2634
- const safariNavBarWhenExpanded = 64;
2635
- const safariToolbar = 44;
2636
-
2637
- // In mobile Safari, the browser chrome is completely hidden in landscape,
2638
- // though a shrunken navbar and full-sized toolbar on scroll. In portrait, the
2639
- // shrunken navbar is always visible, but expands on scroll (and the toolbar
2640
- // appears as well).
2641
- const maxLandscapeBrowserChrome = safariNavBarWhenShrunk + safariToolbar;
2642
- const maxPortraitBrowserChrome = safariToolbar + (safariNavBarWhenExpanded - safariNavBarWhenShrunk);
2643
-
2644
- // This represents the 'worst case' aspect ratio that we care about (for
2645
- // portrait layouts). It's taken from the iPhone 4. The height is computed by
2646
- // taking the height of the device and removing the persistent, shrunken navbar.
2647
- // (We don't need to account for the expanded navbar, since we include the
2648
- // difference when reserving space above the keypad.)
2649
- const worstCaseAspectRatio = 320 / (480 - safariNavBarWhenShrunk);
2650
- const computeLayoutParameters = (_ref, _ref2, _ref3, _ref4) => {
2651
- let {
2652
- numColumns,
2653
- numMaxVisibleRows,
2654
- numPages
2655
- } = _ref;
2656
- let {
2657
- pageWidthPx,
2658
- pageHeightPx
2659
- } = _ref2;
2660
- let {
2661
- deviceOrientation,
2662
- deviceType
2663
- } = _ref3;
2664
- let {
2665
- navigationPadEnabled,
2666
- paginationEnabled,
2667
- toolbarEnabled
2668
- } = _ref4;
2669
- // First, compute some values that will be used in multiple computations.
2670
- const effectiveNumColumns = paginationEnabled ? numColumns : numColumns * numPages;
2671
-
2672
- // Then, compute the button dimensions based on the provided parameters.
2673
- let buttonDimensions;
2674
- if (deviceType === DeviceTypes.PHONE) {
2675
- const isLandscape = deviceOrientation === DeviceOrientations.LANDSCAPE;
2676
-
2677
- // In many cases, the browser chrome will already have been factored
2678
- // into `pageHeightPx`. But we have no way of knowing if that's
2679
- // the case or not. As such, we take a conservative approach and
2680
- // assume that the chrome is _never_ included in `pageHeightPx`.
2681
- const browserChromeHeight = isLandscape ? maxLandscapeBrowserChrome : maxPortraitBrowserChrome;
2682
-
2683
- // Count up all the space that we need to reserve on the page.
2684
- // Namely, we need to account for:
2685
- // 1. Space between the keypad and the top of the page.
2686
- // 2. The presence of the exercise toolbar.
2687
- // 3. The presence of the view pager indicator.
2688
- // 4. Any browser chrome that may appear later.
2689
- const reservedSpace = minSpaceAboveKeypad + browserChromeHeight + (toolbarEnabled ? toolbarHeightPx : 0) + (paginationEnabled ? pageIndicatorHeightPx : 0);
2690
-
2691
- // Next, compute the effective width and height. We can use the page
2692
- // width as the effective width. For the height, though, we take
2693
- // another conservative measure when in portrait by assuming that
2694
- // the device has the worst possible aspect ratio. In other words,
2695
- // we ignore the device height in portrait and assume the worst.
2696
- // This prevents the keypad from changing size when browser chrome
2697
- // appears and disappears.
2698
- const effectiveWidth = pageWidthPx;
2699
- const effectiveHeight = isLandscape ? pageHeightPx : pageWidthPx / worstCaseAspectRatio;
2700
- const maxKeypadHeight = effectiveHeight - reservedSpace;
2701
-
2702
- // Finally, compute the button height and width. In computing the
2703
- // height, accommodate for the maximum number of rows that will ever be
2704
- // visible (since the toggling of popovers can increase the number of
2705
- // visible rows).
2706
- const buttonHeightPx = Math.max(Math.min(maxKeypadHeight / numMaxVisibleRows, maxButtonSize), minButtonHeight);
2707
- let buttonWidthPx;
2708
- if (numPages > 1) {
2709
- const effectiveNumColumns = paginationEnabled ? numColumns : numColumns * numPages;
2710
- buttonWidthPx = effectiveWidth / effectiveNumColumns;
2711
- } else {
2712
- buttonWidthPx = isLandscape ? maxButtonSize : effectiveWidth / numColumns;
2713
- }
2714
- buttonDimensions = {
2715
- widthPx: buttonWidthPx,
2716
- heightPx: buttonHeightPx
2717
- };
2718
- } else if (deviceType === DeviceTypes.TABLET) {
2719
- buttonDimensions = {
2720
- widthPx: maxButtonSize,
2721
- heightPx: maxButtonSize
2722
- };
2723
- } else {
2724
- throw new Error("Invalid device type: " + deviceType);
2725
- }
2726
-
2727
- // Finally, determine whether the keypad should be rendered in the
2728
- // fullscreen layout by determining its resultant width.
2729
- const numSeparators = (navigationPadEnabled ? 1 : 0) + (!paginationEnabled ? numPages - 1 : 0);
2730
- const keypadWidth = effectiveNumColumns * buttonDimensions.widthPx + (navigationPadEnabled ? navigationPadWidthPx : 0) + numSeparators * innerBorderWidthPx;
2731
- return {
2732
- buttonDimensions,
2733
- layoutMode: keypadWidth >= pageWidthPx ? LayoutModes.FULLSCREEN : LayoutModes.COMPACT
2734
- };
2735
- };
2736
-
2737
2261
  /**
2738
2262
  * The state machine that backs our gesture system. In particular, this state
2739
2263
  * machine manages the interplay between focuses, touch ups, and swiping.
@@ -2742,16 +2266,24 @@ const computeLayoutParameters = (_ref, _ref2, _ref3, _ref4) => {
2742
2266
  * multi-touch interactions, tracking gesture state on a per-touch basis.
2743
2267
  */
2744
2268
 
2745
- const defaults = {
2269
+ // exported for tests
2270
+
2271
+ const defaultOptions = {
2746
2272
  longPressWaitTimeMs: 50,
2747
2273
  swipeThresholdPx: 20,
2748
2274
  holdIntervalMs: 250
2749
2275
  };
2750
2276
  class GestureStateMachine {
2751
2277
  constructor(handlers, options, swipeDisabledNodeIds, multiPressableKeys) {
2278
+ _defineProperty(this, "handlers", void 0);
2279
+ _defineProperty(this, "options", void 0);
2280
+ _defineProperty(this, "swipeDisabledNodeIds", void 0);
2281
+ _defineProperty(this, "multiPressableKeys", void 0);
2282
+ _defineProperty(this, "touchState", void 0);
2283
+ _defineProperty(this, "swipeState", void 0);
2752
2284
  this.handlers = handlers;
2753
2285
  this.options = {
2754
- ...defaults,
2286
+ ...defaultOptions,
2755
2287
  ...options
2756
2288
  };
2757
2289
  this.swipeDisabledNodeIds = swipeDisabledNodeIds || [];
@@ -3020,6 +2552,10 @@ class GestureStateMachine {
3020
2552
 
3021
2553
  class NodeManager {
3022
2554
  constructor() {
2555
+ _defineProperty(this, "_nodesById", void 0);
2556
+ _defineProperty(this, "_bordersById", void 0);
2557
+ _defineProperty(this, "_orderedIds", void 0);
2558
+ _defineProperty(this, "_cachedBoundingBoxesById", void 0);
3023
2559
  // A mapping from IDs to DOM nodes.
3024
2560
  this._nodesById = {};
3025
2561
 
@@ -3069,6 +2605,7 @@ class NodeManager {
3069
2605
  const seenIds = {};
3070
2606
  for (const id of allIds) {
3071
2607
  if (!seenIds[id]) {
2608
+ // @ts-expect-error TS2345
3072
2609
  orderedIds.push(id);
3073
2610
  seenIds[id] = true;
3074
2611
  }
@@ -3137,6 +2674,9 @@ class NodeManager {
3137
2674
 
3138
2675
  class PopoverStateMachine {
3139
2676
  constructor(handlers) {
2677
+ _defineProperty(this, "handlers", void 0);
2678
+ _defineProperty(this, "popovers", void 0);
2679
+ _defineProperty(this, "activePopover", void 0);
3140
2680
  this.handlers = handlers;
3141
2681
  this.activePopover = null;
3142
2682
  this.popovers = {};
@@ -3233,8 +2773,8 @@ class PopoverStateMachine {
3233
2773
  this.activePopover = id;
3234
2774
  this.handlers.onActiveNodesChanged({
3235
2775
  popover: {
3236
- parentId: this.activePopover,
3237
- childIds: this.popovers[this.activePopover]
2776
+ parentId: id,
2777
+ childIds: this.popovers[id]
3238
2778
  },
3239
2779
  focus: this._defaultNodeForPopover(this.activePopover)
3240
2780
  });
@@ -3296,16 +2836,16 @@ class PopoverStateMachine {
3296
2836
  }
3297
2837
  }
3298
2838
 
3299
- /**
3300
- * A high-level manager for our gesture system. In particular, this class
3301
- * connects our various bits of logic for managing gestures and interactions,
3302
- * and links them together.
3303
- */
3304
2839
  const coordsForEvent = evt => {
3305
2840
  return [evt.changedTouches[0].clientX, evt.changedTouches[0].clientY];
3306
2841
  };
3307
2842
  class GestureManager {
3308
2843
  constructor(options, handlers, disabledSwipeKeys, multiPressableKeys) {
2844
+ _defineProperty(this, "swipeEnabled", void 0);
2845
+ _defineProperty(this, "trackEvents", void 0);
2846
+ _defineProperty(this, "nodeManager", void 0);
2847
+ _defineProperty(this, "popoverStateMachine", void 0);
2848
+ _defineProperty(this, "gestureStateMachine", void 0);
3309
2849
  const {
3310
2850
  swipeEnabled
3311
2851
  } = options;
@@ -3488,54 +3028,434 @@ class GestureManager {
3488
3028
  }
3489
3029
  }
3490
3030
 
3491
- class CornerDecal extends React__namespace.Component {
3492
- render() {
3493
- const {
3494
- style
3495
- } = this.props;
3496
- const containerStyle = [styles$d.container, ...(Array.isArray(style) ? style : [style])];
3497
- return /*#__PURE__*/React__namespace.createElement(View, {
3498
- style: containerStyle
3499
- }, /*#__PURE__*/React__namespace.createElement("svg", {
3500
- width: triangleSizePx,
3501
- height: triangleSizePx,
3502
- viewBox: "4 4 8 8"
3503
- }, /*#__PURE__*/React__namespace.createElement("path", {
3504
- fill: offBlack,
3505
- opacity: "0.3",
3506
- d: "M5.29289322,5.70710678 L10.2928932,10.7071068 C10.9228581,11.3370716 12,10.8909049 12,10 L12,5 C12,4.44771525 11.5522847,4 11,4 L6,4 C5.10909515,4 4.66292836,5.07714192 5.29289322,5.70710678 Z" // @Nolint
3507
- })));
3508
- }
3509
- }
3510
- _defineProperty(CornerDecal, "propTypes", {
3511
- style: PropTypes__default["default"].any
3512
- });
3513
- const triangleSizePx = 7;
3514
- const styles$d = aphrodite.StyleSheet.create({
3515
- container: {
3516
- position: "absolute",
3517
- top: 0,
3518
- right: 0,
3519
- width: triangleSizePx,
3520
- height: triangleSizePx
3521
- }
3522
- });
3523
-
3524
3031
  /**
3525
- * Common styles shared across components.
3032
+ * This file contains configuration settings for the buttons in the keypad.
3526
3033
  */
3527
- var Styles = aphrodite.StyleSheet.create({
3528
- row: {
3529
- flexDirection: "row"
3034
+ const KeyConfigs = {
3035
+ // Basic math keys.
3036
+ [Keys.PLUS]: {
3037
+ type: KeyTypes.OPERATOR,
3038
+ // I18N: A label for a plus sign.
3039
+ ariaLabel: i18n__namespace._("Plus")
3530
3040
  },
3531
- column: {
3532
- flexDirection: "column"
3041
+ [Keys.MINUS]: {
3042
+ type: KeyTypes.OPERATOR,
3043
+ // I18N: A label for a minus sign.
3044
+ ariaLabel: i18n__namespace._("Minus")
3533
3045
  },
3534
- oneColumn: {
3535
- flexGrow: 1
3046
+ [Keys.NEGATIVE]: {
3047
+ type: KeyTypes.VALUE,
3048
+ // I18N: A label for a minus sign.
3049
+ ariaLabel: i18n__namespace._("Negative")
3536
3050
  },
3537
- fullWidth: {
3538
- width: "100%"
3051
+ [Keys.TIMES]: {
3052
+ type: KeyTypes.OPERATOR,
3053
+ // I18N: A label for a multiplication sign (represented with an 'x').
3054
+ ariaLabel: i18n__namespace._("Multiply")
3055
+ },
3056
+ [Keys.DIVIDE]: {
3057
+ type: KeyTypes.OPERATOR,
3058
+ // I18N: A label for a division sign.
3059
+ ariaLabel: i18n__namespace._("Divide")
3060
+ },
3061
+ [Keys.DECIMAL]: {
3062
+ type: KeyTypes.VALUE,
3063
+ // I18N: A label for a decimal symbol.
3064
+ ariaLabel: i18n__namespace._("Decimal"),
3065
+ icon: decimalSeparator === DecimalSeparators.COMMA ? {
3066
+ // TODO(charlie): Get an SVG icon for the comma, or verify with
3067
+ // design that the text-rendered version is acceptable.
3068
+ type: IconTypes.TEXT,
3069
+ data: ","
3070
+ } : {
3071
+ type: IconTypes.SVG,
3072
+ data: Keys.PERIOD
3073
+ }
3074
+ },
3075
+ [Keys.PERCENT]: {
3076
+ type: KeyTypes.OPERATOR,
3077
+ // I18N: A label for a percent sign.
3078
+ ariaLabel: i18n__namespace._("Percent")
3079
+ },
3080
+ [Keys.CDOT]: {
3081
+ type: KeyTypes.OPERATOR,
3082
+ // I18N: A label for a multiplication sign (represented as a dot).
3083
+ ariaLabel: i18n__namespace._("Multiply")
3084
+ },
3085
+ [Keys.EQUAL]: {
3086
+ type: KeyTypes.OPERATOR,
3087
+ ariaLabel: i18n__namespace._("Equals sign")
3088
+ },
3089
+ [Keys.NEQ]: {
3090
+ type: KeyTypes.OPERATOR,
3091
+ ariaLabel: i18n__namespace._("Not-equals sign")
3092
+ },
3093
+ [Keys.GT]: {
3094
+ type: KeyTypes.OPERATOR,
3095
+ // I18N: A label for a 'greater than' sign (represented as '>').
3096
+ ariaLabel: i18n__namespace._("Greater than sign")
3097
+ },
3098
+ [Keys.LT]: {
3099
+ type: KeyTypes.OPERATOR,
3100
+ // I18N: A label for a 'less than' sign (represented as '<').
3101
+ ariaLabel: i18n__namespace._("Less than sign")
3102
+ },
3103
+ [Keys.GEQ]: {
3104
+ type: KeyTypes.OPERATOR,
3105
+ ariaLabel: i18n__namespace._("Greater than or equal to sign")
3106
+ },
3107
+ [Keys.LEQ]: {
3108
+ type: KeyTypes.OPERATOR,
3109
+ ariaLabel: i18n__namespace._("Less than or equal to sign")
3110
+ },
3111
+ // mobile native
3112
+ [Keys.FRAC_INCLUSIVE]: {
3113
+ type: KeyTypes.OPERATOR,
3114
+ // I18N: A label for a button that creates a new fraction and puts the
3115
+ // current expression in the numerator of that fraction.
3116
+ ariaLabel: i18n__namespace._("Fraction, with current expression in numerator")
3117
+ },
3118
+ // mobile native
3119
+ [Keys.FRAC_EXCLUSIVE]: {
3120
+ type: KeyTypes.OPERATOR,
3121
+ // I18N: A label for a button that creates a new fraction next to the
3122
+ // cursor.
3123
+ ariaLabel: i18n__namespace._("Fraction, excluding the current expression")
3124
+ },
3125
+ // mobile web
3126
+ [Keys.FRAC]: {
3127
+ type: KeyTypes.OPERATOR,
3128
+ // I18N: A label for a button that creates a new fraction next to the
3129
+ // cursor.
3130
+ ariaLabel: i18n__namespace._("Fraction, excluding the current expression")
3131
+ },
3132
+ [Keys.EXP]: {
3133
+ type: KeyTypes.OPERATOR,
3134
+ // I18N: A label for a button that will allow the user to input a custom
3135
+ // exponent.
3136
+ ariaLabel: i18n__namespace._("Custom exponent")
3137
+ },
3138
+ [Keys.EXP_2]: {
3139
+ type: KeyTypes.OPERATOR,
3140
+ // I18N: A label for a button that will square (take to the second
3141
+ // power) some math.
3142
+ ariaLabel: i18n__namespace._("Square")
3143
+ },
3144
+ [Keys.EXP_3]: {
3145
+ type: KeyTypes.OPERATOR,
3146
+ // I18N: A label for a button that will cube (take to the third power)
3147
+ // some math.
3148
+ ariaLabel: i18n__namespace._("Cube")
3149
+ },
3150
+ [Keys.SQRT]: {
3151
+ type: KeyTypes.OPERATOR,
3152
+ ariaLabel: i18n__namespace._("Square root")
3153
+ },
3154
+ [Keys.CUBE_ROOT]: {
3155
+ type: KeyTypes.OPERATOR,
3156
+ ariaLabel: i18n__namespace._("Cube root")
3157
+ },
3158
+ [Keys.RADICAL]: {
3159
+ type: KeyTypes.OPERATOR,
3160
+ ariaLabel: i18n__namespace._("Radical with custom root")
3161
+ },
3162
+ [Keys.LEFT_PAREN]: {
3163
+ type: KeyTypes.OPERATOR,
3164
+ ariaLabel: i18n__namespace._("Left parenthesis")
3165
+ },
3166
+ [Keys.RIGHT_PAREN]: {
3167
+ type: KeyTypes.OPERATOR,
3168
+ ariaLabel: i18n__namespace._("Right parenthesis")
3169
+ },
3170
+ [Keys.LN]: {
3171
+ type: KeyTypes.OPERATOR,
3172
+ ariaLabel: i18n__namespace._("Natural logarithm")
3173
+ },
3174
+ [Keys.LOG]: {
3175
+ type: KeyTypes.OPERATOR,
3176
+ ariaLabel: i18n__namespace._("Logarithm with base 10")
3177
+ },
3178
+ [Keys.LOG_N]: {
3179
+ type: KeyTypes.OPERATOR,
3180
+ ariaLabel: i18n__namespace._("Logarithm with custom base")
3181
+ },
3182
+ [Keys.SIN]: {
3183
+ type: KeyTypes.OPERATOR,
3184
+ ariaLabel: i18n__namespace._("Sine")
3185
+ },
3186
+ [Keys.COS]: {
3187
+ type: KeyTypes.OPERATOR,
3188
+ ariaLabel: i18n__namespace._("Cosine")
3189
+ },
3190
+ [Keys.TAN]: {
3191
+ type: KeyTypes.OPERATOR,
3192
+ ariaLabel: i18n__namespace._("Tangent")
3193
+ },
3194
+ [Keys.PI]: {
3195
+ type: KeyTypes.VALUE,
3196
+ ariaLabel: i18n__namespace._("Pi"),
3197
+ icon: {
3198
+ type: IconTypes.MATH,
3199
+ data: "\\pi"
3200
+ }
3201
+ },
3202
+ [Keys.THETA]: {
3203
+ type: KeyTypes.VALUE,
3204
+ ariaLabel: i18n__namespace._("Theta"),
3205
+ icon: {
3206
+ type: IconTypes.MATH,
3207
+ data: "\\theta"
3208
+ }
3209
+ },
3210
+ [Keys.NOOP]: {
3211
+ type: KeyTypes.EMPTY
3212
+ },
3213
+ // Input navigation keys.
3214
+ [Keys.UP]: {
3215
+ type: KeyTypes.INPUT_NAVIGATION,
3216
+ ariaLabel: i18n__namespace._("Up arrow")
3217
+ },
3218
+ [Keys.RIGHT]: {
3219
+ type: KeyTypes.INPUT_NAVIGATION,
3220
+ ariaLabel: i18n__namespace._("Right arrow")
3221
+ },
3222
+ [Keys.DOWN]: {
3223
+ type: KeyTypes.INPUT_NAVIGATION,
3224
+ ariaLabel: i18n__namespace._("Down arrow")
3225
+ },
3226
+ [Keys.LEFT]: {
3227
+ type: KeyTypes.INPUT_NAVIGATION,
3228
+ ariaLabel: i18n__namespace._("Left arrow")
3229
+ },
3230
+ [Keys.JUMP_OUT_PARENTHESES]: {
3231
+ type: KeyTypes.INPUT_NAVIGATION,
3232
+ ariaLabel: i18n__namespace._("Navigate right out of a set of parentheses")
3233
+ },
3234
+ [Keys.JUMP_OUT_EXPONENT]: {
3235
+ type: KeyTypes.INPUT_NAVIGATION,
3236
+ ariaLabel: i18n__namespace._("Navigate right out of an exponent")
3237
+ },
3238
+ [Keys.JUMP_OUT_BASE]: {
3239
+ type: KeyTypes.INPUT_NAVIGATION,
3240
+ ariaLabel: i18n__namespace._("Navigate right out of a base")
3241
+ },
3242
+ [Keys.JUMP_INTO_NUMERATOR]: {
3243
+ type: KeyTypes.INPUT_NAVIGATION,
3244
+ ariaLabel: i18n__namespace._("Navigate right into the numerator of a fraction")
3245
+ },
3246
+ [Keys.JUMP_OUT_NUMERATOR]: {
3247
+ type: KeyTypes.INPUT_NAVIGATION,
3248
+ ariaLabel: i18n__namespace._("Navigate right out of the numerator and into the denominator")
3249
+ },
3250
+ [Keys.JUMP_OUT_DENOMINATOR]: {
3251
+ type: KeyTypes.INPUT_NAVIGATION,
3252
+ ariaLabel: i18n__namespace._("Navigate right out of the denominator of a fraction")
3253
+ },
3254
+ [Keys.BACKSPACE]: {
3255
+ type: KeyTypes.INPUT_NAVIGATION,
3256
+ // I18N: A label for a button that will delete some input.
3257
+ ariaLabel: i18n__namespace._("Delete")
3258
+ },
3259
+ // Keypad navigation keys.
3260
+ [Keys.DISMISS]: {
3261
+ type: KeyTypes.KEYPAD_NAVIGATION,
3262
+ // I18N: A label for a button that will dismiss/hide a keypad.
3263
+ ariaLabel: i18n__namespace._("Dismiss")
3264
+ }
3265
+ };
3266
+
3267
+ // Add in any multi-function buttons. By default, these keys will mix in any
3268
+ // configuration settings from their default child key (i.e., the first key in
3269
+ // the `childKeyIds` array).
3270
+ // TODO(charlie): Make the multi-function button's long-press interaction
3271
+ // accessible.
3272
+ // NOTE(kevinb): This is only used in the mobile native app.
3273
+ KeyConfigs[Keys.FRAC_MULTI] = {
3274
+ childKeyIds: [Keys.FRAC_INCLUSIVE, Keys.FRAC_EXCLUSIVE]
3275
+ };
3276
+
3277
+ // TODO(charlie): Use the numeral color for the 'Many' key.
3278
+ KeyConfigs[Keys.MANY] = {
3279
+ type: KeyTypes.MANY
3280
+ // childKeyIds will be configured by the client.
3281
+ };
3282
+
3283
+ // Add in every numeral.
3284
+ const NUMBERS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
3285
+ for (const num of NUMBERS) {
3286
+ // TODO(charlie): Consider removing the SVG icons that we have for the
3287
+ // numeral keys. They can be rendered just as easily with text (though that
3288
+ // would mean that we'd be using text beyond the variable key).
3289
+ const textRepresentation = "".concat(num);
3290
+ KeyConfigs["NUM_".concat(num)] = {
3291
+ type: KeyTypes.VALUE,
3292
+ ariaLabel: textRepresentation,
3293
+ icon: {
3294
+ type: IconTypes.TEXT,
3295
+ data: textRepresentation
3296
+ }
3297
+ };
3298
+ }
3299
+
3300
+ // Add in every variable.
3301
+ const LETTERS = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
3302
+ for (const letter of LETTERS) {
3303
+ const lowerCaseVariable = letter.toLowerCase();
3304
+ const upperCaseVariable = letter.toUpperCase();
3305
+ for (const textRepresentation of [lowerCaseVariable, upperCaseVariable]) {
3306
+ KeyConfigs[textRepresentation] = {
3307
+ type: KeyTypes.VALUE,
3308
+ ariaLabel: textRepresentation,
3309
+ icon: {
3310
+ type: IconTypes.MATH,
3311
+ data: textRepresentation
3312
+ }
3313
+ };
3314
+ }
3315
+ }
3316
+ for (const key of Object.keys(KeyConfigs)) {
3317
+ KeyConfigs[key] = {
3318
+ id: key,
3319
+ // Default to an SVG icon indexed by the key name.
3320
+ icon: {
3321
+ type: IconTypes.SVG,
3322
+ data: key
3323
+ },
3324
+ ...KeyConfigs[key]
3325
+ };
3326
+ }
3327
+
3328
+ // Used to generate unique animation IDs for the echo animations. The actual
3329
+ // values are irrelevant as long as they are unique.
3330
+ let _lastAnimationId = 0;
3331
+ const initialEchoState = {
3332
+ echoes: []
3333
+ };
3334
+ const echoReducer = function () {
3335
+ let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialEchoState;
3336
+ let action = arguments.length > 1 ? arguments[1] : undefined;
3337
+ switch (action.type) {
3338
+ case "PressKey":
3339
+ const keyConfig = KeyConfigs[action.key];
3340
+
3341
+ // Add in the echo animation if the user performs a math
3342
+ // operation.
3343
+ if (keyConfig.type === KeyTypes.VALUE || keyConfig.type === KeyTypes.OPERATOR) {
3344
+ return {
3345
+ ...state,
3346
+ echoes: [...state.echoes, {
3347
+ animationId: "" + _lastAnimationId++,
3348
+ animationType: action.inPopover ? EchoAnimationTypes.LONG_FADE_ONLY : EchoAnimationTypes.FADE_ONLY,
3349
+ borders: action.borders,
3350
+ id: keyConfig.id,
3351
+ initialBounds: action.initialBounds
3352
+ }]
3353
+ };
3354
+ }
3355
+ return state;
3356
+ case "RemoveEcho":
3357
+ const remainingEchoes = state.echoes.filter(echo => {
3358
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'animationId' does not exist on type 'never'.
3359
+ return echo.animationId !== action.animationId;
3360
+ });
3361
+ return {
3362
+ ...state,
3363
+ echoes: remainingEchoes
3364
+ };
3365
+ default:
3366
+ return state;
3367
+ }
3368
+ };
3369
+
3370
+ const initialInputState = {
3371
+ keyHandler: null,
3372
+ cursor: {
3373
+ context: NONE
3374
+ }
3375
+ };
3376
+ const inputReducer = function () {
3377
+ let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialInputState;
3378
+ let action = arguments.length > 1 ? arguments[1] : undefined;
3379
+ switch (action.type) {
3380
+ case "SetKeyHandler":
3381
+ return {
3382
+ ...state,
3383
+ keyHandler: action.keyHandler
3384
+ };
3385
+ case "PressKey":
3386
+ const keyConfig = KeyConfigs[action.key];
3387
+ if (keyConfig.type !== KeyTypes.KEYPAD_NAVIGATION) {
3388
+ var _state$keyHandler;
3389
+ // This is probably an anti-pattern but it works for the
3390
+ // case where we don't actually control the state but we
3391
+ // still want to communicate with the other object
3392
+ return {
3393
+ ...state,
3394
+ cursor: (_state$keyHandler = state.keyHandler) === null || _state$keyHandler === void 0 ? void 0 : _state$keyHandler.call(state, keyConfig.id)
3395
+ };
3396
+ }
3397
+
3398
+ // TODO(kevinb) get state from MathQuill and store it?
3399
+ return state;
3400
+ case "SetCursor":
3401
+ return {
3402
+ ...state,
3403
+ cursor: action.cursor
3404
+ };
3405
+ default:
3406
+ return state;
3407
+ }
3408
+ };
3409
+
3410
+ /**
3411
+ * A small triangular decal to sit in the corner of a parent component.
3412
+ */
3413
+ class CornerDecal extends React__namespace.Component {
3414
+ render() {
3415
+ const {
3416
+ style
3417
+ } = this.props;
3418
+ const containerStyle = [styles$d.container, ...(Array.isArray(style) ? style : [style])];
3419
+ return /*#__PURE__*/React__namespace.createElement(View, {
3420
+ style: containerStyle
3421
+ }, /*#__PURE__*/React__namespace.createElement("svg", {
3422
+ width: triangleSizePx,
3423
+ height: triangleSizePx,
3424
+ viewBox: "4 4 8 8"
3425
+ }, /*#__PURE__*/React__namespace.createElement("path", {
3426
+ fill: offBlack,
3427
+ opacity: "0.3",
3428
+ d: "M5.29289322,5.70710678 L10.2928932,10.7071068 C10.9228581,11.3370716 12,10.8909049 12,10 L12,5 C12,4.44771525 11.5522847,4 11,4 L6,4 C5.10909515,4 4.66292836,5.07714192 5.29289322,5.70710678 Z" // @Nolint
3429
+ })));
3430
+ }
3431
+ }
3432
+
3433
+ const triangleSizePx = 7;
3434
+ const styles$d = aphrodite.StyleSheet.create({
3435
+ container: {
3436
+ position: "absolute",
3437
+ top: 0,
3438
+ right: 0,
3439
+ width: triangleSizePx,
3440
+ height: triangleSizePx
3441
+ }
3442
+ });
3443
+
3444
+ /**
3445
+ * Common styles shared across components.
3446
+ */
3447
+ var Styles = aphrodite.StyleSheet.create({
3448
+ row: {
3449
+ flexDirection: "row"
3450
+ },
3451
+ column: {
3452
+ flexDirection: "column"
3453
+ },
3454
+ oneColumn: {
3455
+ flexGrow: 1
3456
+ },
3457
+ fullWidth: {
3458
+ width: "100%"
3539
3459
  },
3540
3460
  stretch: {
3541
3461
  alignItems: "stretch"
@@ -3587,10 +3507,6 @@ class MathIcon extends React__namespace.Component {
3587
3507
  });
3588
3508
  }
3589
3509
  }
3590
- _defineProperty(MathIcon, "propTypes", {
3591
- math: PropTypes__default["default"].string.isRequired,
3592
- style: PropTypes__default["default"].any
3593
- });
3594
3510
  const styles$c = aphrodite.StyleSheet.create({
3595
3511
  size: {
3596
3512
  height: iconSizeHeightPx,
@@ -4743,6 +4659,9 @@ var Iconography = /*#__PURE__*/Object.freeze({
4743
4659
  JUMP_OUT_DENOMINATOR: JumpOutDenominator
4744
4660
  });
4745
4661
 
4662
+ /**
4663
+ * A component that renders a single SVG icon.
4664
+ */
4746
4665
  class SvgIcon extends React__namespace.Component {
4747
4666
  render() {
4748
4667
  const {
@@ -4757,11 +4676,10 @@ class SvgIcon extends React__namespace.Component {
4757
4676
  });
4758
4677
  }
4759
4678
  }
4760
- _defineProperty(SvgIcon, "propTypes", {
4761
- color: PropTypes__default["default"].string.isRequired,
4762
- name: PropTypes__default["default"].string.isRequired
4763
- });
4764
4679
 
4680
+ /**
4681
+ * A component that renders a text-based icon.
4682
+ */
4765
4683
  const {
4766
4684
  row: row$6,
4767
4685
  centered: centered$3
@@ -4778,10 +4696,6 @@ class TextIcon extends React__namespace.Component {
4778
4696
  }, /*#__PURE__*/React__namespace.createElement(Text, null, character));
4779
4697
  }
4780
4698
  }
4781
- _defineProperty(TextIcon, "propTypes", {
4782
- character: PropTypes__default["default"].string.isRequired,
4783
- style: PropTypes__default["default"].any
4784
- });
4785
4699
  const styles$b = aphrodite.StyleSheet.create({
4786
4700
  size: {
4787
4701
  height: iconSizeHeightPx,
@@ -4793,6 +4707,9 @@ const styles$b = aphrodite.StyleSheet.create({
4793
4707
  }
4794
4708
  });
4795
4709
 
4710
+ /**
4711
+ * A component that renders an icon for a symbol with the given name.
4712
+ */
4796
4713
  const focusedColor = "#FFF";
4797
4714
  const unfocusedColor = offBlack;
4798
4715
  class Icon extends React__namespace.PureComponent {
@@ -4823,18 +4740,11 @@ class Icon extends React__namespace.PureComponent {
4823
4740
  character: icon.data,
4824
4741
  style: styleWithFocus
4825
4742
  });
4743
+ default:
4744
+ throw new Error("No icon or symbol provided");
4826
4745
  }
4827
- throw new Error("No icon or symbol provided");
4828
4746
  }
4829
4747
  }
4830
- _defineProperty(Icon, "propTypes", {
4831
- focused: PropTypes__default["default"].bool,
4832
- icon: iconPropType.isRequired,
4833
- // An Aphrodite style object, or an array of Aphrodite style objects.
4834
- // Note that custom styles will only be applied to text and math icons
4835
- // (and not SVG icons).
4836
- style: PropTypes__default["default"].any
4837
- });
4838
4748
  const styles$a = aphrodite.StyleSheet.create({
4839
4749
  unfocused: {
4840
4750
  color: unfocusedColor
@@ -4844,6 +4754,10 @@ const styles$a = aphrodite.StyleSheet.create({
4844
4754
  }
4845
4755
  });
4846
4756
 
4757
+ /**
4758
+ * A grid of symbols, rendered as text and positioned based on the number of
4759
+ * symbols provided. Up to four symbols will be shown.
4760
+ */
4847
4761
  const {
4848
4762
  row: row$5,
4849
4763
  column: column$3,
@@ -4925,13 +4839,9 @@ class MultiSymbolGrid extends React__namespace.Component {
4925
4839
  }))));
4926
4840
  }
4927
4841
  }
4928
- throw new Error("Invalid number of icons:", icons.length);
4842
+ throw new Error("Invalid number of icons: ".concat(icons.length));
4929
4843
  }
4930
4844
  }
4931
- _defineProperty(MultiSymbolGrid, "propTypes", {
4932
- focused: PropTypes__default["default"].bool,
4933
- icons: PropTypes__default["default"].arrayOf(iconPropType).isRequired
4934
- });
4935
4845
  const verticalInsetPx = 2;
4936
4846
  const horizontalInsetPx = 4;
4937
4847
  const styles$9 = aphrodite.StyleSheet.create({
@@ -4975,6 +4885,7 @@ const styles$9 = aphrodite.StyleSheet.create({
4975
4885
  class KeypadButton extends React__namespace.PureComponent {
4976
4886
  constructor() {
4977
4887
  super(...arguments);
4888
+ _defineProperty(this, "buttonSizeStyle", void 0);
4978
4889
  _defineProperty(this, "_preInjectStyles", () => {
4979
4890
  // HACK(charlie): Pre-inject all of the possible styles for the button.
4980
4891
  // This avoids a flickering effect in the echo animation whereby the
@@ -5023,10 +4934,12 @@ class KeypadButton extends React__namespace.PureComponent {
5023
4934
  break;
5024
4935
  }
5025
4936
  const borderStyle = [];
5026
- if (borders.indexOf(BorderDirections.LEFT) !== -1) {
4937
+ if (borders.includes(BorderDirections.LEFT)) {
4938
+ // @ts-expect-error TS2345
5027
4939
  borderStyle.push(styles$8.leftBorder);
5028
4940
  }
5029
- if (borders.indexOf(BorderDirections.BOTTOM) !== -1) {
4941
+ if (borders.includes(BorderDirections.BOTTOM)) {
4942
+ // @ts-expect-error TS2345
5030
4943
  borderStyle.push(styles$8.bottomBorder);
5031
4944
  }
5032
4945
  return [styles$8.buttonBase, backgroundStyle, ...borderStyle, type === KeyTypes.ECHO && styles$8.echo, this.buttonSizeStyle,
@@ -5074,7 +4987,7 @@ class KeypadButton extends React__namespace.PureComponent {
5074
4987
  const renderFocused = !disabled && focused || popoverEnabled || type === KeyTypes.ECHO;
5075
4988
  const buttonStyle = this._getButtonStyle(type, borders, style);
5076
4989
  const focusStyle = this._getFocusStyle(type);
5077
- const iconWrapperStyle = [styles$8.iconWrapper, disabled && styles$8.disabled];
4990
+ const iconWrapperStyle = [styles$8.iconWrapper, disabled ? styles$8.disabled : undefined];
5078
4991
  const eventHandlers = {
5079
4992
  onTouchCancel,
5080
4993
  onTouchEnd,
@@ -5125,32 +5038,6 @@ class KeypadButton extends React__namespace.PureComponent {
5125
5038
  }
5126
5039
  }
5127
5040
  }
5128
- _defineProperty(KeypadButton, "propTypes", {
5129
- ariaLabel: PropTypes__default["default"].string,
5130
- // The borders to display on the button. Typically, this should be set
5131
- // using one of the preset `BorderStyles` options.
5132
- borders: bordersPropType,
5133
- // Any additional keys that can be accessed by long-pressing on the
5134
- // button.
5135
- childKeys: PropTypes__default["default"].arrayOf(keyConfigPropType),
5136
- // Whether the button should be rendered in a 'disabled' state, i.e.,
5137
- // without any touch feedback.
5138
- disabled: PropTypes__default["default"].bool,
5139
- focused: PropTypes__default["default"].bool,
5140
- heightPx: PropTypes__default["default"].number.isRequired,
5141
- icon: iconPropType,
5142
- onTouchCancel: PropTypes__default["default"].func,
5143
- onTouchEnd: PropTypes__default["default"].func,
5144
- onTouchMove: PropTypes__default["default"].func,
5145
- onTouchStart: PropTypes__default["default"].func,
5146
- popoverEnabled: PropTypes__default["default"].bool,
5147
- style: PropTypes__default["default"].any,
5148
- type: PropTypes__default["default"].oneOf(Object.keys(KeyTypes)).isRequired,
5149
- // NOTE(charlie): We may want to make this optional for phone layouts
5150
- // (and rely on Flexbox instead), since it might not be pixel perfect
5151
- // with borders and such.
5152
- widthPx: PropTypes__default["default"].number.isRequired
5153
- });
5154
5041
  _defineProperty(KeypadButton, "defaultProps", {
5155
5042
  borders: BorderStyles.ALL,
5156
5043
  childKeys: [],
@@ -5162,8 +5049,6 @@ const focusInsetPx = 4;
5162
5049
  const focusBoxZIndex = 0;
5163
5050
  const styles$8 = aphrodite.StyleSheet.create({
5164
5051
  buttonBase: {
5165
- // HACK(benkomalo): support old style flex box in Android browsers
5166
- "-webkit-box-flex": "1",
5167
5052
  flex: 1,
5168
5053
  cursor: "pointer",
5169
5054
  // Make the text unselectable
@@ -5239,7 +5124,10 @@ const styleForButtonDimensions = (heightPx, widthPx) => {
5239
5124
  }).buttonSize;
5240
5125
  };
5241
5126
  const mapStateToProps$7 = state => {
5242
- return state.layout.buttonDimensions;
5127
+ return {
5128
+ heightPx: state.layout.buttonDimensions.heightPx,
5129
+ widthPx: state.layout.buttonDimensions.widthPx
5130
+ };
5243
5131
  };
5244
5132
  var KeypadButton$1 = reactRedux.connect(mapStateToProps$7, null, null, {
5245
5133
  forwardRef: true
@@ -5265,9 +5153,6 @@ class EmptyKeypadButton extends React__namespace.Component {
5265
5153
  }, KeyConfigs.NOOP, rest));
5266
5154
  }
5267
5155
  }
5268
- _defineProperty(EmptyKeypadButton, "propTypes", {
5269
- gestureManager: PropTypes__default["default"].instanceOf(GestureManager)
5270
- });
5271
5156
  const mapStateToProps$6 = state => {
5272
5157
  const {
5273
5158
  gestures
@@ -5324,17 +5209,6 @@ class TouchableKeypadButton extends React__namespace.Component {
5324
5209
  }, eventHandlers, rest));
5325
5210
  }
5326
5211
  }
5327
- _defineProperty(TouchableKeypadButton, "propTypes", {
5328
- borders: bordersPropType,
5329
- childKeyIds: PropTypes__default["default"].arrayOf(keyIdPropType),
5330
- disabled: PropTypes__default["default"].bool,
5331
- focused: PropTypes__default["default"].bool,
5332
- gestureManager: PropTypes__default["default"].instanceOf(GestureManager),
5333
- id: keyIdPropType.isRequired,
5334
- popoverEnabled: PropTypes__default["default"].bool,
5335
- style: PropTypes__default["default"].any,
5336
- type: PropTypes__default["default"].oneOf(Object.keys(KeyTypes)).isRequired
5337
- });
5338
5212
  const extractProps = keyConfig => {
5339
5213
  const {
5340
5214
  ariaLabel,
@@ -5400,7 +5274,7 @@ class ManyKeypadButton extends React__namespace.Component {
5400
5274
  // one, render a standard button. Otherwise, capture them all in a
5401
5275
  // single button.
5402
5276
  if (keys.length === 0) {
5403
- return /*#__PURE__*/React__namespace.createElement(EmptyKeypadButton$1, rest);
5277
+ return /*#__PURE__*/React__namespace.createElement(EmptyKeypadButton$1, null);
5404
5278
  } else if (keys.length === 1) {
5405
5279
  const keyConfig = KeyConfigs[keys[0]];
5406
5280
  return /*#__PURE__*/React__namespace.createElement(TouchableKeypadButton$1, _extends({
@@ -5418,8 +5292,8 @@ class ManyKeypadButton extends React__namespace.Component {
5418
5292
  }
5419
5293
  }
5420
5294
  }
5421
- _defineProperty(ManyKeypadButton, "propTypes", {
5422
- keys: PropTypes__default["default"].arrayOf(keyIdPropType).isRequired
5295
+ _defineProperty(ManyKeypadButton, "defaultProps", {
5296
+ keys: []
5423
5297
  });
5424
5298
 
5425
5299
  /**
@@ -5468,20 +5342,12 @@ class Echo extends React__namespace.Component {
5468
5342
  return /*#__PURE__*/React__namespace.createElement("div", {
5469
5343
  style: containerStyle
5470
5344
  }, /*#__PURE__*/React__namespace.createElement(KeypadButton$1, {
5471
- name: id,
5472
5345
  icon: icon,
5473
5346
  type: KeyTypes.ECHO,
5474
5347
  borders: borders
5475
5348
  }));
5476
5349
  }
5477
5350
  }
5478
- _defineProperty(Echo, "propTypes", {
5479
- animationDurationMs: PropTypes__default["default"].number.isRequired,
5480
- borders: bordersPropType,
5481
- id: keyIdPropType.isRequired,
5482
- initialBounds: boundingBoxPropType.isRequired,
5483
- onAnimationFinish: PropTypes__default["default"].func.isRequired
5484
- });
5485
5351
  class EchoManager extends React__namespace.Component {
5486
5352
  constructor() {
5487
5353
  super(...arguments);
@@ -5504,7 +5370,7 @@ class EchoManager extends React__namespace.Component {
5504
5370
  animationTransitionName = "echo-long-fade-only";
5505
5371
  break;
5506
5372
  default:
5507
- throw new Error("Invalid echo animation type:", animationType);
5373
+ throw new Error("Invalid echo animation type: ".concat(animationType));
5508
5374
  }
5509
5375
  return {
5510
5376
  animationDurationMs,
@@ -5549,17 +5415,16 @@ class EchoManager extends React__namespace.Component {
5549
5415
  key: animationId
5550
5416
  }, /*#__PURE__*/React__namespace.createElement(Echo, _extends({
5551
5417
  animationDurationMs: animationDurationMs,
5552
- onAnimationFinish: () => onAnimationFinish(animationId)
5418
+ onAnimationFinish: () => onAnimationFinish === null || onAnimationFinish === void 0 ? void 0 : onAnimationFinish(animationId)
5553
5419
  }, echo)));
5554
5420
  }));
5555
5421
  }));
5556
5422
  }
5557
5423
  }
5558
- _defineProperty(EchoManager, "propTypes", {
5559
- echoes: PropTypes__default["default"].arrayOf(echoPropType),
5560
- onAnimationFinish: PropTypes__default["default"].func.isRequired
5561
- });
5562
5424
 
5425
+ /**
5426
+ * A popover that renders a set of keys floating above the page.
5427
+ */
5563
5428
  class MultiSymbolPopover extends React__namespace.Component {
5564
5429
  render() {
5565
5430
  const {
@@ -5579,9 +5444,6 @@ class MultiSymbolPopover extends React__namespace.Component {
5579
5444
  }));
5580
5445
  }
5581
5446
  }
5582
- _defineProperty(MultiSymbolPopover, "propTypes", {
5583
- keys: PropTypes__default["default"].arrayOf(keyConfigPropType)
5584
- });
5585
5447
  const styles$6 = aphrodite.StyleSheet.create({
5586
5448
  container: {
5587
5449
  flexDirection: "column-reverse",
@@ -5598,11 +5460,14 @@ const styles$6 = aphrodite.StyleSheet.create({
5598
5460
  }
5599
5461
  });
5600
5462
 
5463
+ /**
5464
+ * A component that renders and animates the popovers that appear over the
5465
+ * multi-functional keys.
5466
+ */
5601
5467
  // NOTE(charlie): These must be kept in sync with the transition durations and
5602
5468
  // classnames specified in popover.less.
5603
5469
  const animationTransitionName = "popover";
5604
5470
  const animationDurationMs = 200;
5605
-
5606
5471
  // A container component used to position a popover absolutely at a specific
5607
5472
  // position.
5608
5473
  class PopoverContainer extends React__namespace.Component {
@@ -5622,10 +5487,6 @@ class PopoverContainer extends React__namespace.Component {
5622
5487
  }));
5623
5488
  }
5624
5489
  }
5625
- _defineProperty(PopoverContainer, "propTypes", {
5626
- bounds: boundingBoxPropType.isRequired,
5627
- childKeys: PropTypes__default["default"].arrayOf(keyConfigPropType).isRequired
5628
- });
5629
5490
  class PopoverManager extends React__namespace.Component {
5630
5491
  render() {
5631
5492
  const {
@@ -5646,14 +5507,14 @@ class PopoverManager extends React__namespace.Component {
5646
5507
  })) : null;
5647
5508
  }
5648
5509
  }
5649
- _defineProperty(PopoverManager, "propTypes", {
5650
- popover: popoverPropType
5651
- });
5652
5510
 
5653
5511
  // eslint-disable-next-line react/no-unsafe
5654
5512
  class Keypad extends React__namespace.Component {
5655
5513
  constructor() {
5656
5514
  super(...arguments);
5515
+ _defineProperty(this, "_isMounted", void 0);
5516
+ _defineProperty(this, "_resizeTimeout", void 0);
5517
+ _defineProperty(this, "_container", void 0);
5657
5518
  _defineProperty(this, "_computeContainer", () => {
5658
5519
  const domNode = ReactDOM__default["default"].findDOMNode(this);
5659
5520
  this._container = domNode.getBoundingClientRect();
@@ -5675,7 +5536,7 @@ class Keypad extends React__namespace.Component {
5675
5536
  // Throttle resize events -- taken from:
5676
5537
  // https://developer.mozilla.org/en-US/docs/Web/Events/resize
5677
5538
  if (this._resizeTimeout == null) {
5678
- this._resizeTimeout = setTimeout(() => {
5539
+ this._resizeTimeout = window.setTimeout(() => {
5679
5540
  this._resizeTimeout = null;
5680
5541
  if (this._isMounted) {
5681
5542
  this._updateSizeAndPosition();
@@ -5717,9 +5578,13 @@ class Keypad extends React__namespace.Component {
5717
5578
  return {
5718
5579
  ...rest,
5719
5580
  initialBounds: {
5581
+ // @ts-expect-error TS2533
5720
5582
  top: initialBounds.top - this._container.top,
5583
+ // @ts-expect-error TS2533
5721
5584
  right: initialBounds.right - this._container.left,
5585
+ // @ts-expect-error TS2533
5722
5586
  bottom: initialBounds.bottom - this._container.top,
5587
+ // @ts-expect-error TS2533
5723
5588
  left: initialBounds.left - this._container.left,
5724
5589
  width: initialBounds.width,
5725
5590
  height: initialBounds.height
@@ -5733,7 +5598,12 @@ class Keypad extends React__namespace.Component {
5733
5598
  const relativePopover = popover && {
5734
5599
  ...popover,
5735
5600
  bounds: {
5736
- bottom: this._container.height - (popover.bounds.bottom - this._container.top),
5601
+ bottom:
5602
+ // @ts-expect-error TS2533
5603
+ this._container.height - (
5604
+ // @ts-expect-error TS2533
5605
+ popover.bounds.bottom - this._container.top),
5606
+ // @ts-expect-error TS2533
5737
5607
  left: popover.bounds.left - this._container.left,
5738
5608
  width: popover.bounds.width
5739
5609
  }
@@ -5748,21 +5618,9 @@ class Keypad extends React__namespace.Component {
5748
5618
  }));
5749
5619
  }
5750
5620
  }
5751
- _defineProperty(Keypad, "propTypes", {
5752
- children: PropTypes__default["default"].oneOfType([PropTypes__default["default"].arrayOf(PropTypes__default["default"].node), PropTypes__default["default"].node]),
5753
- removeEcho: PropTypes__default["default"].func.isRequired,
5754
- style: PropTypes__default["default"].any,
5755
- // The props below are injected by redux
5756
-
5757
- // Whether the keypad is active, i.e., whether it should be rendered as
5758
- // visible or invisible.
5759
- active: PropTypes__default["default"].bool,
5760
- echoes: PropTypes__default["default"].arrayOf(echoPropType).isRequired,
5761
- popover: popoverPropType
5762
- });
5763
5621
  const mapStateToProps$4 = state => {
5764
5622
  return {
5765
- ...state.echoes,
5623
+ echoes: state.echoes.echoes,
5766
5624
  active: state.keypad.active,
5767
5625
  popover: state.gestures.popover
5768
5626
  };
@@ -6000,12 +5858,6 @@ class TwoPageKeypad extends React__namespace.Component {
6000
5858
  }
6001
5859
  }
6002
5860
  }
6003
- _defineProperty(TwoPageKeypad, "propTypes", {
6004
- currentPage: PropTypes__default["default"].oneOf([0, 1]).isRequired,
6005
- leftPage: PropTypes__default["default"].node.isRequired,
6006
- paginationEnabled: PropTypes__default["default"].bool.isRequired,
6007
- rightPage: PropTypes__default["default"].node.isRequired
6008
- });
6009
5861
  const styles$3 = aphrodite.StyleSheet.create({
6010
5862
  keypad: {
6011
5863
  // Set the background to light grey, so that when the user drags the
@@ -6029,6 +5881,9 @@ var TwoPageKeypad$1 = reactRedux.connect(mapStateToProps$3, null, null, {
6029
5881
  forwardRef: true
6030
5882
  })(TwoPageKeypad);
6031
5883
 
5884
+ /**
5885
+ * A keypad that includes all of the expression symbols.
5886
+ */
6032
5887
  const {
6033
5888
  row: row$3,
6034
5889
  column: column$1,
@@ -6037,11 +5892,16 @@ const {
6037
5892
  roundedTopLeft: roundedTopLeft$2,
6038
5893
  roundedTopRight: roundedTopRight$1
6039
5894
  } = Styles;
5895
+ const expressionKeypadLayout = {
5896
+ rows: 4,
5897
+ columns: 5,
5898
+ numPages: 2,
5899
+ // Since we include a two-key popover in the top-right, when the popover
5900
+ // is visible, the keypad will expand to fill the equivalent of five
5901
+ // rows vertically.
5902
+ maxVisibleRows: 4
5903
+ };
6040
5904
  class ExpressionKeypad extends React__namespace.Component {
6041
- // Though we include an infinite-key popover in the bottom-left, it's
6042
- // assumed that we don't need to accommodate cases in which that key
6043
- // contains more than four children.
6044
-
6045
5905
  render() {
6046
5906
  const {
6047
5907
  currentPage,
@@ -6095,8 +5955,7 @@ class ExpressionKeypad extends React__namespace.Component {
6095
5955
  keyConfig: KeyConfigs.NUM_1,
6096
5956
  borders: BorderStyles.BOTTOM
6097
5957
  }), /*#__PURE__*/React__namespace.createElement(ManyKeypadButton, {
6098
- keys: extraKeys,
6099
- borders: BorderStyles.NONE
5958
+ keys: extraKeys
6100
5959
  })), /*#__PURE__*/React__namespace.createElement(View, {
6101
5960
  style: [column$1, oneColumn]
6102
5961
  }, /*#__PURE__*/React__namespace.createElement(TouchableKeypadButton$1, {
@@ -6234,18 +6093,6 @@ class ExpressionKeypad extends React__namespace.Component {
6234
6093
  });
6235
6094
  }
6236
6095
  }
6237
- _defineProperty(ExpressionKeypad, "propTypes", {
6238
- currentPage: PropTypes__default["default"].number.isRequired,
6239
- cursorContext: cursorContextPropType.isRequired,
6240
- dynamicJumpOut: PropTypes__default["default"].bool,
6241
- extraKeys: PropTypes__default["default"].arrayOf(keyIdPropType),
6242
- roundTopLeft: PropTypes__default["default"].bool,
6243
- roundTopRight: PropTypes__default["default"].bool
6244
- });
6245
- _defineProperty(ExpressionKeypad, "rows", 4);
6246
- _defineProperty(ExpressionKeypad, "columns", 5);
6247
- _defineProperty(ExpressionKeypad, "maxVisibleRows", 4);
6248
- _defineProperty(ExpressionKeypad, "numPages", 2);
6249
6096
  const styles$2 = aphrodite.StyleSheet.create({
6250
6097
  // NOTE(charlie): These backgrounds are applied to as to fill in some
6251
6098
  // unfortunate 'cracks' in the layout. However, not all keys in the first
@@ -6260,9 +6107,10 @@ const styles$2 = aphrodite.StyleSheet.create({
6260
6107
  }
6261
6108
  });
6262
6109
  const mapStateToProps$2 = state => {
6110
+ var _state$input$cursor;
6263
6111
  return {
6264
6112
  currentPage: state.pager.currentPage,
6265
- cursorContext: state.input.cursor.context,
6113
+ cursorContext: (_state$input$cursor = state.input.cursor) === null || _state$input$cursor === void 0 ? void 0 : _state$input$cursor.context,
6266
6114
  dynamicJumpOut: !state.layout.navigationPadEnabled
6267
6115
  };
6268
6116
  };
@@ -6270,16 +6118,25 @@ var ExpressionKeypad$1 = reactRedux.connect(mapStateToProps$2, null, null, {
6270
6118
  forwardRef: true
6271
6119
  })(ExpressionKeypad);
6272
6120
 
6121
+ /**
6122
+ * A keypad that includes the digits, as well as the symbols required to deal
6123
+ * with fractions, decimals, and percents.
6124
+ */
6273
6125
  const {
6274
6126
  row: row$2,
6275
6127
  roundedTopLeft: roundedTopLeft$1,
6276
6128
  roundedTopRight
6277
6129
  } = Styles;
6278
- class FractionKeypad extends React__namespace.Component {
6130
+ const fractionKeypadLayout = {
6131
+ rows: 4,
6132
+ columns: 4,
6133
+ numPages: 1,
6279
6134
  // Since we include a two-key popover in the top-right, when the popover
6280
6135
  // is visible, the keypad will expand to fill the equivalent of five
6281
6136
  // rows vertically.
6282
-
6137
+ maxVisibleRows: 5
6138
+ };
6139
+ class FractionKeypad extends React__namespace.Component {
6283
6140
  render() {
6284
6141
  const {
6285
6142
  cursorContext,
@@ -6382,35 +6239,308 @@ class FractionKeypad extends React__namespace.Component {
6382
6239
  borders: BorderStyles.LEFT
6383
6240
  })));
6384
6241
  }
6385
- }
6386
- _defineProperty(FractionKeypad, "propTypes", {
6387
- cursorContext: cursorContextPropType.isRequired,
6388
- dynamicJumpOut: PropTypes__default["default"].bool,
6389
- roundTopLeft: PropTypes__default["default"].bool,
6390
- roundTopRight: PropTypes__default["default"].bool
6391
- });
6392
- _defineProperty(FractionKeypad, "rows", 4);
6393
- _defineProperty(FractionKeypad, "columns", 4);
6394
- _defineProperty(FractionKeypad, "maxVisibleRows", 5);
6395
- _defineProperty(FractionKeypad, "numPages", 1);
6396
- const mapStateToProps$1 = state => {
6242
+ }
6243
+ const mapStateToProps$1 = state => {
6244
+ var _state$input$cursor;
6245
+ return {
6246
+ cursorContext: (_state$input$cursor = state.input.cursor) === null || _state$input$cursor === void 0 ? void 0 : _state$input$cursor.context,
6247
+ dynamicJumpOut: !state.layout.navigationPadEnabled
6248
+ };
6249
+ };
6250
+ var FractionKeypad$1 = reactRedux.connect(mapStateToProps$1, null, null, {
6251
+ forwardRef: true
6252
+ })(FractionKeypad);
6253
+
6254
+ const defaultKeypadType = KeypadTypes.EXPRESSION;
6255
+ const keypadForType = {
6256
+ [KeypadTypes.FRACTION]: fractionKeypadLayout,
6257
+ [KeypadTypes.EXPRESSION]: expressionKeypadLayout
6258
+ };
6259
+
6260
+ const initialKeypadState = {
6261
+ extraKeys: ["x", "y", Keys.THETA, Keys.PI],
6262
+ keypadType: defaultKeypadType,
6263
+ active: false
6264
+ };
6265
+ const keypadReducer = function () {
6266
+ let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialKeypadState;
6267
+ let action = arguments.length > 1 ? arguments[1] : undefined;
6268
+ switch (action.type) {
6269
+ case "DismissKeypad":
6270
+ return {
6271
+ ...state,
6272
+ active: false
6273
+ };
6274
+ case "ActivateKeypad":
6275
+ return {
6276
+ ...state,
6277
+ active: true
6278
+ };
6279
+ case "ConfigureKeypad":
6280
+ return {
6281
+ ...state,
6282
+ // Default `extraKeys` to the empty array.
6283
+ extraKeys: [],
6284
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'configuration' does not exist on type '{ type: string; }'.
6285
+ ...action.configuration
6286
+ };
6287
+ case "PressKey":
6288
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'key' does not exist on type '{ type: string; }'.
6289
+ const keyConfig = KeyConfigs[action.key];
6290
+ // NOTE(charlie): Our keypad system operates by triggering key
6291
+ // presses with key IDs in a dumb manner, such that the keys
6292
+ // don't know what they can do--instead, the store is
6293
+ // responsible for interpreting key presses and triggering the
6294
+ // right actions when they occur. Hence, we figure off a
6295
+ // dismissal here rather than dispatching a dismiss action in
6296
+ // the first place.
6297
+ if (keyConfig.id === Keys.DISMISS) {
6298
+ return keypadReducer(state, {
6299
+ type: "DismissKeypad"
6300
+ });
6301
+ }
6302
+ return state;
6303
+ default:
6304
+ return state;
6305
+ }
6306
+ };
6307
+
6308
+ /**
6309
+ * An algorithm for computing the appropriate layout parameters for the keypad,
6310
+ * including the size of the buttons and whether or not to render fullscreen,
6311
+ * taking into account a number of factors including the size of the screen, the
6312
+ * orientation of the screen, the presence of browser chrome, the presence of
6313
+ * other exercise-related chrome, the size of the input box, the parameters that
6314
+ * define the keypad (i.e., the number of rows, columns, and pages), and so
6315
+ * forth.
6316
+ *
6317
+ * The computations herein make some strong assumptions about the sizes of
6318
+ * various other elements and the situations under which they will be visible
6319
+ * (e.g., browser chrome). However, this is just a heuristic--it's not crucial
6320
+ * that our buttons are sized in a pixel-perfect manner, but rather, that we
6321
+ * make a balanced use of space.
6322
+ *
6323
+ * Note that one goal of the algorithm is to avoid resizing the keypad in the
6324
+ * face of dynamic browser chrome. In order to avoid that awkwardness, we tend
6325
+ * to be conservative in our measurements and make things smaller than they
6326
+ * might need to be.
6327
+ */
6328
+ const minButtonHeight = 48;
6329
+ const maxButtonSize = 64;
6330
+ const minSpaceAboveKeypad = 32;
6331
+
6332
+ // These values are taken from an iPhone 5, but should be consistent with the
6333
+ // iPhone 4 as well. Regardless, these are meant to be representative of the
6334
+ // possible types of browser chrome that could appear in various context, rather
6335
+ // than pixel-perfect for every device.
6336
+ const safariNavBarWhenShrunk = 44;
6337
+ const safariNavBarWhenExpanded = 64;
6338
+ const safariToolbar = 44;
6339
+
6340
+ // In mobile Safari, the browser chrome is completely hidden in landscape,
6341
+ // though a shrunken navbar and full-sized toolbar on scroll. In portrait, the
6342
+ // shrunken navbar is always visible, but expands on scroll (and the toolbar
6343
+ // appears as well).
6344
+ const maxLandscapeBrowserChrome = safariNavBarWhenShrunk + safariToolbar;
6345
+ const maxPortraitBrowserChrome = safariToolbar + (safariNavBarWhenExpanded - safariNavBarWhenShrunk);
6346
+
6347
+ // This represents the 'worst case' aspect ratio that we care about (for
6348
+ // portrait layouts). It's taken from the iPhone 4. The height is computed by
6349
+ // taking the height of the device and removing the persistent, shrunken navbar.
6350
+ // (We don't need to account for the expanded navbar, since we include the
6351
+ // difference when reserving space above the keypad.)
6352
+ const worstCaseAspectRatio = 320 / (480 - safariNavBarWhenShrunk);
6353
+ const computeLayoutParameters = (_ref, _ref2, _ref3, _ref4) => {
6354
+ let {
6355
+ numColumns,
6356
+ numMaxVisibleRows,
6357
+ numPages
6358
+ } = _ref;
6359
+ let {
6360
+ pageWidthPx,
6361
+ pageHeightPx
6362
+ } = _ref2;
6363
+ let {
6364
+ deviceOrientation,
6365
+ deviceType
6366
+ } = _ref3;
6367
+ let {
6368
+ navigationPadEnabled,
6369
+ paginationEnabled,
6370
+ toolbarEnabled
6371
+ } = _ref4;
6372
+ // First, compute some values that will be used in multiple computations.
6373
+ const effectiveNumColumns = paginationEnabled ? numColumns : numColumns * numPages;
6374
+
6375
+ // Then, compute the button dimensions based on the provided parameters.
6376
+ let buttonDimensions;
6377
+ if (deviceType === DeviceTypes.PHONE) {
6378
+ const isLandscape = deviceOrientation === DeviceOrientations.LANDSCAPE;
6379
+
6380
+ // In many cases, the browser chrome will already have been factored
6381
+ // into `pageHeightPx`. But we have no way of knowing if that's
6382
+ // the case or not. As such, we take a conservative approach and
6383
+ // assume that the chrome is _never_ included in `pageHeightPx`.
6384
+ const browserChromeHeight = isLandscape ? maxLandscapeBrowserChrome : maxPortraitBrowserChrome;
6385
+
6386
+ // Count up all the space that we need to reserve on the page.
6387
+ // Namely, we need to account for:
6388
+ // 1. Space between the keypad and the top of the page.
6389
+ // 2. The presence of the exercise toolbar.
6390
+ // 3. The presence of the view pager indicator.
6391
+ // 4. Any browser chrome that may appear later.
6392
+ const reservedSpace = minSpaceAboveKeypad + browserChromeHeight + (toolbarEnabled ? toolbarHeightPx : 0) + (paginationEnabled ? pageIndicatorHeightPx : 0);
6393
+
6394
+ // Next, compute the effective width and height. We can use the page
6395
+ // width as the effective width. For the height, though, we take
6396
+ // another conservative measure when in portrait by assuming that
6397
+ // the device has the worst possible aspect ratio. In other words,
6398
+ // we ignore the device height in portrait and assume the worst.
6399
+ // This prevents the keypad from changing size when browser chrome
6400
+ // appears and disappears.
6401
+ const effectiveWidth = pageWidthPx;
6402
+ const effectiveHeight = isLandscape ? pageHeightPx : pageWidthPx / worstCaseAspectRatio;
6403
+ const maxKeypadHeight = effectiveHeight - reservedSpace;
6404
+
6405
+ // Finally, compute the button height and width. In computing the
6406
+ // height, accommodate for the maximum number of rows that will ever be
6407
+ // visible (since the toggling of popovers can increase the number of
6408
+ // visible rows).
6409
+ const buttonHeightPx = Math.max(Math.min(maxKeypadHeight / numMaxVisibleRows, maxButtonSize), minButtonHeight);
6410
+ let buttonWidthPx;
6411
+ if (numPages > 1) {
6412
+ const effectiveNumColumns = paginationEnabled ? numColumns : numColumns * numPages;
6413
+ buttonWidthPx = effectiveWidth / effectiveNumColumns;
6414
+ } else {
6415
+ buttonWidthPx = isLandscape ? maxButtonSize : effectiveWidth / numColumns;
6416
+ }
6417
+ buttonDimensions = {
6418
+ widthPx: buttonWidthPx,
6419
+ heightPx: buttonHeightPx
6420
+ };
6421
+ } else if (deviceType === DeviceTypes.TABLET) {
6422
+ buttonDimensions = {
6423
+ widthPx: maxButtonSize,
6424
+ heightPx: maxButtonSize
6425
+ };
6426
+ } else {
6427
+ throw new Error("Invalid device type: " + deviceType);
6428
+ }
6429
+
6430
+ // Finally, determine whether the keypad should be rendered in the
6431
+ // fullscreen layout by determining its resultant width.
6432
+ const numSeparators = (navigationPadEnabled ? 1 : 0) + (!paginationEnabled ? numPages - 1 : 0);
6433
+ const keypadWidth = effectiveNumColumns * buttonDimensions.widthPx + (navigationPadEnabled ? navigationPadWidthPx : 0) + numSeparators * innerBorderWidthPx;
6397
6434
  return {
6398
- cursorContext: state.input.cursor.context,
6399
- dynamicJumpOut: !state.layout.navigationPadEnabled
6435
+ buttonDimensions,
6436
+ layoutMode: keypadWidth >= pageWidthPx ? LayoutModes.FULLSCREEN : LayoutModes.COMPACT
6400
6437
  };
6401
6438
  };
6402
- var FractionKeypad$1 = reactRedux.connect(mapStateToProps$1, null, null, {
6403
- forwardRef: true
6404
- })(FractionKeypad);
6439
+
6440
+ const initialLayoutState = {
6441
+ gridDimensions: {
6442
+ numRows: keypadForType[defaultKeypadType].rows,
6443
+ numColumns: keypadForType[defaultKeypadType].columns,
6444
+ numMaxVisibleRows: keypadForType[defaultKeypadType].maxVisibleRows,
6445
+ numPages: keypadForType[defaultKeypadType].numPages
6446
+ },
6447
+ buttonDimensions: {
6448
+ widthPx: 48,
6449
+ heightPx: 48
6450
+ },
6451
+ pageDimensions: {
6452
+ pageWidthPx: 0,
6453
+ pageHeightPx: 0
6454
+ },
6455
+ layoutMode: LayoutModes.FULLSCREEN,
6456
+ paginationEnabled: false,
6457
+ navigationPadEnabled: false
6458
+ };
6405
6459
 
6406
6460
  /**
6407
- * A system for tracking gesture velocity in a single dimension.
6408
- *
6409
- * Velocity is computed by smoothing linearly over the gestures that have
6410
- * occurred in the last 100 milliseconds.
6461
+ * Compute the additional layout state based on the provided page and grid
6462
+ * dimensions.
6411
6463
  */
6464
+ const layoutParametersForDimensions = (pageDimensions, gridDimensions) => {
6465
+ const {
6466
+ pageWidthPx,
6467
+ pageHeightPx
6468
+ } = pageDimensions;
6469
+
6470
+ // Determine the device type and orientation.
6471
+ const deviceOrientation = pageWidthPx > pageHeightPx ? DeviceOrientations.LANDSCAPE : DeviceOrientations.PORTRAIT;
6472
+ const deviceType = Math.min(pageWidthPx, pageHeightPx) > tabletCutoffPx ? DeviceTypes.TABLET : DeviceTypes.PHONE;
6473
+
6474
+ // Using that information, make some decisions (or assumptions)
6475
+ // about the resulting layout.
6476
+ const navigationPadEnabled = deviceType === DeviceTypes.TABLET;
6477
+ const paginationEnabled = deviceType === DeviceTypes.PHONE && deviceOrientation === DeviceOrientations.PORTRAIT;
6478
+ const deviceInfo = {
6479
+ deviceOrientation,
6480
+ deviceType
6481
+ };
6482
+ const layoutOptions = {
6483
+ navigationPadEnabled,
6484
+ paginationEnabled,
6485
+ // HACK(charlie): It's not great that we're making assumptions about
6486
+ // the toolbar (which is rendered by webapp, and should always be
6487
+ // visible and anchored to the bottom of the page for phone and
6488
+ // tablet exercises). But this is primarily a heuristic (the goal is
6489
+ // to preserve a 'good' amount of space between the top of the
6490
+ // keypad and the top of the page) so we afford to have some margin
6491
+ // of error.
6492
+ toolbarEnabled: true
6493
+ };
6494
+ return {
6495
+ ...computeLayoutParameters(gridDimensions, pageDimensions, deviceInfo, layoutOptions),
6496
+ // Pass along some of the layout information, so that other
6497
+ // components in the heirarchy can adapt appropriately.
6498
+ navigationPadEnabled,
6499
+ paginationEnabled
6500
+ };
6501
+ };
6502
+ const layoutReducer = function () {
6503
+ let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialLayoutState;
6504
+ let action = arguments.length > 1 ? arguments[1] : undefined;
6505
+ switch (action.type) {
6506
+ case "ConfigureKeypad":
6507
+ const {
6508
+ keypadType
6509
+ } = action.configuration;
6510
+ const gridDimensions = {
6511
+ numRows: keypadForType[keypadType].rows,
6512
+ numColumns: keypadForType[keypadType].columns,
6513
+ numMaxVisibleRows: keypadForType[keypadType].maxVisibleRows,
6514
+ numPages: keypadForType[keypadType].numPages
6515
+ };
6516
+ return {
6517
+ ...state,
6518
+ ...layoutParametersForDimensions(state.pageDimensions, gridDimensions),
6519
+ gridDimensions
6520
+ };
6521
+ case "SetPageSize":
6522
+ const {
6523
+ pageWidthPx,
6524
+ pageHeightPx
6525
+ } = action;
6526
+ const pageDimensions = {
6527
+ pageWidthPx,
6528
+ pageHeightPx
6529
+ };
6530
+ return {
6531
+ ...state,
6532
+ ...layoutParametersForDimensions(pageDimensions, state.gridDimensions),
6533
+ pageDimensions
6534
+ };
6535
+ default:
6536
+ return state;
6537
+ }
6538
+ };
6539
+
6412
6540
  class VelocityTracker {
6413
6541
  constructor(options) {
6542
+ _defineProperty(this, "options", void 0);
6543
+ _defineProperty(this, "_events", void 0);
6414
6544
  this.options = {
6415
6545
  velocityTimeout: 100,
6416
6546
  ...options
@@ -6474,216 +6604,128 @@ class VelocityTracker {
6474
6604
  }
6475
6605
  }
6476
6606
 
6477
- const keypadForType = {
6478
- [KeypadTypes.FRACTION]: FractionKeypad$1,
6479
- [KeypadTypes.EXPRESSION]: ExpressionKeypad$1
6607
+ // We default to the right-most page. This is done so-as to enforce a
6608
+ // consistent orientation between the view pager layout and the flattened
6609
+ // layout, where our default page appears on the far right.
6610
+ const getDefaultPage = numPages => numPages - 1;
6611
+ const initialPagerState = {
6612
+ animateToPosition: false,
6613
+ currentPage: getDefaultPage(keypadForType[defaultKeypadType].numPages),
6614
+ // The cumulative differential in the horizontal direction for the
6615
+ // current swipe.
6616
+ dx: 0,
6617
+ numPages: keypadForType[defaultKeypadType].numPages,
6618
+ pageWidthPx: 0,
6619
+ velocityTracker: new VelocityTracker()
6480
6620
  };
6481
- const createStore = () => {
6482
- const initialInputState = {
6483
- keyHandler: null,
6484
- cursor: {
6485
- context: NONE
6486
- }
6487
- };
6488
- const inputReducer = function () {
6489
- let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialInputState;
6490
- let action = arguments.length > 1 ? arguments[1] : undefined;
6491
- switch (action.type) {
6492
- case "SetKeyHandler":
6493
- return {
6494
- ...state,
6495
- keyHandler: action.keyHandler
6496
- };
6497
- case "PressKey":
6498
- const keyConfig = KeyConfigs[action.key];
6499
- if (keyConfig.type !== KeyTypes.KEYPAD_NAVIGATION) {
6500
- // This is probably an anti-pattern but it works for the
6501
- // case where we don't actually control the state but we
6502
- // still want to communicate with the other object
6503
- return {
6504
- ...state,
6505
- cursor: state.keyHandler(keyConfig.id)
6506
- };
6507
- }
6508
-
6509
- // TODO(kevinb) get state from MathQuill and store it?
6510
- return state;
6511
- case "SetCursor":
6512
- return {
6513
- ...state,
6514
- cursor: action.cursor
6515
- };
6516
- default:
6517
- return state;
6518
- }
6519
- };
6520
- const defaultKeypadType = KeypadTypes.EXPRESSION;
6521
- const initialKeypadState = {
6522
- extraKeys: ["x", "y", Keys.THETA, Keys.PI],
6523
- keypadType: defaultKeypadType,
6524
- active: false
6525
- };
6526
- const keypadReducer = function () {
6527
- let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialKeypadState;
6528
- let action = arguments.length > 1 ? arguments[1] : undefined;
6529
- switch (action.type) {
6530
- case "DismissKeypad":
6531
- return {
6532
- ...state,
6533
- active: false
6534
- };
6535
- case "ActivateKeypad":
6536
- return {
6537
- ...state,
6538
- active: true
6539
- };
6540
- case "ConfigureKeypad":
6541
- return {
6542
- ...state,
6543
- // Default `extraKeys` to the empty array.
6544
- extraKeys: [],
6545
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'configuration' does not exist on type '{ type: string; }'.
6546
- ...action.configuration
6547
- };
6548
- case "PressKey":
6549
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'key' does not exist on type '{ type: string; }'.
6550
- const keyConfig = KeyConfigs[action.key];
6551
- // NOTE(charlie): Our keypad system operates by triggering key
6552
- // presses with key IDs in a dumb manner, such that the keys
6553
- // don't know what they can do--instead, the store is
6554
- // responsible for interpreting key presses and triggering the
6555
- // right actions when they occur. Hence, we figure off a
6556
- // dismissal here rather than dispatching a dismiss action in
6557
- // the first place.
6558
- if (keyConfig.id === Keys.DISMISS) {
6559
- return keypadReducer(state, {
6560
- type: "DismissKeypad"
6561
- });
6562
- }
6563
- return state;
6564
- default:
6565
- return state;
6566
- }
6567
- };
6568
-
6569
- // We default to the right-most page. This is done so-as to enforce a
6570
- // consistent orientation between the view pager layout and the flattened
6571
- // layout, where our default page appears on the far right.
6572
- const getDefaultPage = numPages => numPages - 1;
6573
- const initialPagerState = {
6574
- animateToPosition: false,
6575
- currentPage: getDefaultPage(keypadForType[defaultKeypadType].numPages),
6576
- // The cumulative differential in the horizontal direction for the
6577
- // current swipe.
6578
- dx: 0,
6579
- numPages: keypadForType[defaultKeypadType].numPages,
6580
- pageWidthPx: 0,
6581
- velocityTracker: new VelocityTracker()
6582
- };
6583
- const pagerReducer = function () {
6584
- let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialPagerState;
6585
- let action = arguments.length > 1 ? arguments[1] : undefined;
6586
- switch (action.type) {
6587
- case "ConfigureKeypad":
6588
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'configuration' does not exist on type '{ type: string; }'.
6589
- const {
6590
- keypadType
6591
- } = action.configuration;
6592
- const {
6593
- numPages
6594
- } = keypadForType[keypadType];
6595
- return {
6596
- ...state,
6597
- numPages,
6598
- animateToPosition: false,
6599
- currentPage: getDefaultPage(numPages),
6600
- dx: 0
6601
- };
6602
- case "SetPageSize":
6603
- return {
6604
- ...state,
6605
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'pageWidthPx' does not exist on type '{ type: string; }'.
6606
- pageWidthPx: action.pageWidthPx
6607
- };
6608
- case "PressKey":
6609
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'key' does not exist on type '{ type: string; }'.
6610
- const keyConfig = KeyConfigs[action.key];
6611
-
6612
- // Reset the keypad page if the user performs a math operation.
6613
- if (keyConfig.type === KeyTypes.VALUE || keyConfig.type === KeyTypes.OPERATOR) {
6614
- return pagerReducer(state, {
6615
- type: "ResetKeypadPage"
6616
- });
6617
- }
6618
- return state;
6619
- case "ResetKeypadPage":
6620
- return {
6621
- ...state,
6622
- animateToPosition: true,
6623
- // We start at the right-most page.
6624
- currentPage: getDefaultPage(state.numPages),
6625
- dx: 0
6626
- };
6627
- case "PageKeypadRight":
6628
- const nextPage = Math.min(state.currentPage + 1, state.numPages - 1);
6629
- return {
6630
- ...state,
6631
- animateToPosition: true,
6632
- currentPage: nextPage,
6633
- dx: 0
6634
- };
6635
- case "PageKeypadLeft":
6636
- const prevPage = Math.max(state.currentPage - 1, 0);
6637
- return {
6638
- ...state,
6639
- animateToPosition: true,
6640
- currentPage: prevPage,
6641
- dx: 0
6642
- };
6643
- case "OnSwipeChange":
6644
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'dx' does not exist on type '{ type: string; }'.
6645
- state.velocityTracker.push(action.dx);
6646
- return {
6647
- ...state,
6648
- animateToPosition: false,
6649
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'dx' does not exist on type '{ type: string; }'.
6650
- dx: action.dx
6651
- };
6652
- case "OnSwipeEnd":
6653
- const {
6654
- pageWidthPx,
6655
- velocityTracker
6656
- } = state;
6621
+ const pagerReducer = function () {
6622
+ let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialPagerState;
6623
+ let action = arguments.length > 1 ? arguments[1] : undefined;
6624
+ switch (action.type) {
6625
+ case "ConfigureKeypad":
6626
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'configuration' does not exist on type '{ type: string; }'.
6627
+ const {
6628
+ keypadType
6629
+ } = action.configuration;
6630
+ const {
6631
+ numPages
6632
+ } = keypadForType[keypadType];
6633
+ return {
6634
+ ...state,
6635
+ numPages,
6636
+ animateToPosition: false,
6637
+ currentPage: getDefaultPage(numPages),
6638
+ dx: 0
6639
+ };
6640
+ case "SetPageSize":
6641
+ return {
6642
+ ...state,
6643
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'pageWidthPx' does not exist on type '{ type: string; }'.
6644
+ pageWidthPx: action.pageWidthPx
6645
+ };
6646
+ case "PressKey":
6647
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'key' does not exist on type '{ type: string; }'.
6648
+ const keyConfig = KeyConfigs[action.key];
6649
+
6650
+ // Reset the keypad page if the user performs a math operation.
6651
+ if (keyConfig.type === KeyTypes.VALUE || keyConfig.type === KeyTypes.OPERATOR) {
6652
+ return pagerReducer(state, {
6653
+ type: "ResetKeypadPage"
6654
+ });
6655
+ }
6656
+ return state;
6657
+ case "ResetKeypadPage":
6658
+ return {
6659
+ ...state,
6660
+ animateToPosition: true,
6661
+ // We start at the right-most page.
6662
+ currentPage: getDefaultPage(state.numPages),
6663
+ dx: 0
6664
+ };
6665
+ case "PageKeypadRight":
6666
+ const nextPage = Math.min(state.currentPage + 1, state.numPages - 1);
6667
+ return {
6668
+ ...state,
6669
+ animateToPosition: true,
6670
+ currentPage: nextPage,
6671
+ dx: 0
6672
+ };
6673
+ case "PageKeypadLeft":
6674
+ const prevPage = Math.max(state.currentPage - 1, 0);
6675
+ return {
6676
+ ...state,
6677
+ animateToPosition: true,
6678
+ currentPage: prevPage,
6679
+ dx: 0
6680
+ };
6681
+ case "OnSwipeChange":
6682
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'dx' does not exist on type '{ type: string; }'.
6683
+ state.velocityTracker.push(action.dx);
6684
+ return {
6685
+ ...state,
6686
+ animateToPosition: false,
6657
6687
  // @ts-expect-error [FEI-5003] - TS2339 - Property 'dx' does not exist on type '{ type: string; }'.
6658
- const {
6659
- dx
6660
- } = action;
6661
- const velocity = velocityTracker.getVelocity();
6662
-
6663
- // NOTE(charlie): These will need refinement. The velocity comes
6664
- // from Framer.
6665
- const minFlingVelocity = 0.1;
6666
- const minFlingDistance = 10;
6667
- const shouldPageRight = dx < -pageWidthPx / 2 || velocity < -minFlingVelocity && dx < -minFlingDistance;
6668
- const shouldPageLeft = dx > pageWidthPx / 2 || velocity > minFlingVelocity && dx > minFlingDistance;
6669
- if (shouldPageRight) {
6670
- return pagerReducer(state, {
6671
- type: "PageKeypadRight"
6672
- });
6673
- } else if (shouldPageLeft) {
6674
- return pagerReducer(state, {
6675
- type: "PageKeypadLeft"
6676
- });
6677
- }
6678
- return {
6679
- ...state,
6680
- animateToPosition: true,
6681
- dx: 0
6682
- };
6683
- default:
6684
- return state;
6685
- }
6686
- };
6688
+ dx: action.dx
6689
+ };
6690
+ case "OnSwipeEnd":
6691
+ const {
6692
+ pageWidthPx,
6693
+ velocityTracker
6694
+ } = state;
6695
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'dx' does not exist on type '{ type: string; }'.
6696
+ const {
6697
+ dx
6698
+ } = action;
6699
+ const velocity = velocityTracker.getVelocity();
6700
+
6701
+ // NOTE(charlie): These will need refinement. The velocity comes
6702
+ // from Framer.
6703
+ const minFlingVelocity = 0.1;
6704
+ const minFlingDistance = 10;
6705
+ const shouldPageRight = dx < -pageWidthPx / 2 || velocity < -minFlingVelocity && dx < -minFlingDistance;
6706
+ const shouldPageLeft = dx > pageWidthPx / 2 || velocity > minFlingVelocity && dx > minFlingDistance;
6707
+ if (shouldPageRight) {
6708
+ return pagerReducer(state, {
6709
+ type: "PageKeypadRight"
6710
+ });
6711
+ } else if (shouldPageLeft) {
6712
+ return pagerReducer(state, {
6713
+ type: "PageKeypadLeft"
6714
+ });
6715
+ }
6716
+ return {
6717
+ ...state,
6718
+ animateToPosition: true,
6719
+ dx: 0
6720
+ };
6721
+ default:
6722
+ return state;
6723
+ }
6724
+ };
6725
+
6726
+ const createStore = () => {
6727
+ // TODO(matthewc)[LC-752]: gestureReducer can't be moved from this file
6728
+ // because it depends on `store` being in scope (see note below)
6687
6729
  const createGestureManager = swipeEnabled => {
6688
6730
  return new GestureManager({
6689
6731
  swipeEnabled
@@ -6762,147 +6804,6 @@ const createStore = () => {
6762
6804
  return state;
6763
6805
  }
6764
6806
  };
6765
-
6766
- // Used to generate unique animation IDs for the echo animations. The actual
6767
- // values are irrelevant as long as they are unique.
6768
- let _lastAnimationId = 0;
6769
- const initialEchoState = {
6770
- echoes: []
6771
- };
6772
- const echoReducer = function () {
6773
- let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialEchoState;
6774
- let action = arguments.length > 1 ? arguments[1] : undefined;
6775
- switch (action.type) {
6776
- case "PressKey":
6777
- const keyConfig = KeyConfigs[action.key];
6778
-
6779
- // Add in the echo animation if the user performs a math
6780
- // operation.
6781
- if (keyConfig.type === KeyTypes.VALUE || keyConfig.type === KeyTypes.OPERATOR) {
6782
- return {
6783
- ...state,
6784
- echoes: [...state.echoes, {
6785
- animationId: "" + _lastAnimationId++,
6786
- animationType: action.inPopover ? EchoAnimationTypes.LONG_FADE_ONLY : EchoAnimationTypes.FADE_ONLY,
6787
- borders: action.borders,
6788
- id: keyConfig.id,
6789
- initialBounds: action.initialBounds
6790
- }]
6791
- };
6792
- }
6793
- return state;
6794
- case "RemoveEcho":
6795
- const remainingEchoes = state.echoes.filter(echo => {
6796
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'animationId' does not exist on type 'never'.
6797
- return echo.animationId !== action.animationId;
6798
- });
6799
- return {
6800
- ...state,
6801
- echoes: remainingEchoes
6802
- };
6803
- default:
6804
- return state;
6805
- }
6806
- };
6807
- const initialLayoutState = {
6808
- gridDimensions: {
6809
- numRows: keypadForType[defaultKeypadType].rows,
6810
- numColumns: keypadForType[defaultKeypadType].columns,
6811
- numMaxVisibleRows: keypadForType[defaultKeypadType].maxVisibleRows,
6812
- numPages: keypadForType[defaultKeypadType].numPages
6813
- },
6814
- buttonDimensions: {
6815
- widthPx: 48,
6816
- heightPx: 48
6817
- },
6818
- pageDimensions: {
6819
- pageWidthPx: 0,
6820
- pageHeightPx: 0
6821
- },
6822
- layoutMode: LayoutModes.FULLSCREEN,
6823
- paginationEnabled: false,
6824
- navigationPadEnabled: false
6825
- };
6826
-
6827
- /**
6828
- * Compute the additional layout state based on the provided page and grid
6829
- * dimensions.
6830
- */
6831
- const layoutParametersForDimensions = (pageDimensions, gridDimensions) => {
6832
- const {
6833
- pageWidthPx,
6834
- pageHeightPx
6835
- } = pageDimensions;
6836
-
6837
- // Determine the device type and orientation.
6838
- const deviceOrientation = pageWidthPx > pageHeightPx ? DeviceOrientations.LANDSCAPE : DeviceOrientations.PORTRAIT;
6839
- const deviceType = Math.min(pageWidthPx, pageHeightPx) > tabletCutoffPx ? DeviceTypes.TABLET : DeviceTypes.PHONE;
6840
-
6841
- // Using that information, make some decisions (or assumptions)
6842
- // about the resulting layout.
6843
- const navigationPadEnabled = deviceType === DeviceTypes.TABLET;
6844
- const paginationEnabled = deviceType === DeviceTypes.PHONE && deviceOrientation === DeviceOrientations.PORTRAIT;
6845
- const deviceInfo = {
6846
- deviceOrientation,
6847
- deviceType
6848
- };
6849
- const layoutOptions = {
6850
- navigationPadEnabled,
6851
- paginationEnabled,
6852
- // HACK(charlie): It's not great that we're making assumptions about
6853
- // the toolbar (which is rendered by webapp, and should always be
6854
- // visible and anchored to the bottom of the page for phone and
6855
- // tablet exercises). But this is primarily a heuristic (the goal is
6856
- // to preserve a 'good' amount of space between the top of the
6857
- // keypad and the top of the page) so we afford to have some margin
6858
- // of error.
6859
- toolbarEnabled: true
6860
- };
6861
- return {
6862
- ...computeLayoutParameters(gridDimensions, pageDimensions, deviceInfo, layoutOptions),
6863
- // Pass along some of the layout information, so that other
6864
- // components in the heirarchy can adapt appropriately.
6865
- navigationPadEnabled,
6866
- paginationEnabled
6867
- };
6868
- };
6869
- const layoutReducer = function () {
6870
- let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialLayoutState;
6871
- let action = arguments.length > 1 ? arguments[1] : undefined;
6872
- switch (action.type) {
6873
- case "ConfigureKeypad":
6874
- const {
6875
- keypadType
6876
- } = action.configuration;
6877
- const gridDimensions = {
6878
- numRows: keypadForType[keypadType].rows,
6879
- numColumns: keypadForType[keypadType].columns,
6880
- numMaxVisibleRows: keypadForType[keypadType].maxVisibleRows,
6881
- numPages: keypadForType[keypadType].numPages
6882
- };
6883
- return {
6884
- ...state,
6885
- ...layoutParametersForDimensions(state.pageDimensions, gridDimensions),
6886
- gridDimensions
6887
- };
6888
- case "SetPageSize":
6889
- const {
6890
- pageWidthPx,
6891
- pageHeightPx
6892
- } = action;
6893
- const pageDimensions = {
6894
- pageWidthPx,
6895
- pageHeightPx
6896
- };
6897
- return {
6898
- ...state,
6899
- ...layoutParametersForDimensions(pageDimensions, state.gridDimensions),
6900
- pageDimensions
6901
- };
6902
- default:
6903
- return state;
6904
- }
6905
- };
6906
6807
  const reducer = Redux__namespace.combineReducers({
6907
6808
  input: inputReducer,
6908
6809
  keypad: keypadReducer,
@@ -6920,6 +6821,10 @@ const createStore = () => {
6920
6821
  return store;
6921
6822
  };
6922
6823
 
6824
+ /**
6825
+ * A component that renders a navigation pad, which consists of an arrow for
6826
+ * each possible direction.
6827
+ */
6923
6828
  const {
6924
6829
  row: row$1,
6925
6830
  column,
@@ -6965,10 +6870,6 @@ class NavigationPad extends React__namespace.Component {
6965
6870
  })));
6966
6871
  }
6967
6872
  }
6968
- _defineProperty(NavigationPad, "propTypes", {
6969
- roundTopLeft: PropTypes__default["default"].bool,
6970
- style: PropTypes__default["default"].any
6971
- });
6972
6873
  const buttonSizePx = 48;
6973
6874
  const borderRadiusPx = 4;
6974
6875
  const borderWidthPx$1 = 1;
@@ -7027,11 +6928,12 @@ const {
7027
6928
  centered,
7028
6929
  fullWidth
7029
6930
  } = Styles;
7030
-
7031
6931
  // eslint-disable-next-line react/no-unsafe
7032
6932
  class KeypadContainer extends React__namespace.Component {
7033
6933
  constructor() {
7034
6934
  super(...arguments);
6935
+ _defineProperty(this, "_resizeTimeout", void 0);
6936
+ _defineProperty(this, "hasMounted", void 0);
7035
6937
  _defineProperty(this, "state", {
7036
6938
  hasBeenActivated: false,
7037
6939
  viewportWidth: "100vw"
@@ -7040,19 +6942,20 @@ class KeypadContainer extends React__namespace.Component {
7040
6942
  // Throttle the resize callbacks.
7041
6943
  // https://developer.mozilla.org/en-US/docs/Web/Events/resize
7042
6944
  if (this._resizeTimeout == null) {
7043
- this._resizeTimeout = setTimeout(() => {
6945
+ this._resizeTimeout = window.setTimeout(() => {
7044
6946
  this._resizeTimeout = null;
7045
6947
  this._onResize();
7046
6948
  }, 66);
7047
6949
  }
7048
6950
  });
7049
6951
  _defineProperty(this, "_onResize", () => {
6952
+ var _this$props$onPageSiz, _this$props;
7050
6953
  // Whenever the page resizes, we need to force an update, as the button
7051
6954
  // heights and keypad width are computed based on horizontal space.
7052
6955
  this.setState({
7053
6956
  viewportWidth: window.innerWidth
7054
6957
  });
7055
- this.props.onPageSizeChange(window.innerWidth, window.innerHeight);
6958
+ (_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);
7056
6959
  });
7057
6960
  _defineProperty(this, "renderKeypad", () => {
7058
6961
  const {
@@ -7134,10 +7037,15 @@ class KeypadContainer extends React__namespace.Component {
7134
7037
  // NOTE(charlie): We render the transforms as pure inline styles to
7135
7038
  // avoid an Aphrodite bug in mobile Safari.
7136
7039
  // See: https://github.com/Khan/aphrodite/issues/68.
7137
- const dynamicStyle = {
7138
- ...(active ? inlineStyles.active : inlineStyles.hidden),
7139
- ...(!active && !hasBeenActivated ? inlineStyles.invisible : {})
7040
+ let dynamicStyle = {
7041
+ ...(active ? inlineStyles.active : inlineStyles.hidden)
7140
7042
  };
7043
+ if (!active && !hasBeenActivated) {
7044
+ dynamicStyle = {
7045
+ ...dynamicStyle,
7046
+ ...inlineStyles.invisible
7047
+ };
7048
+ }
7141
7049
  const keypadContainerStyle = [row, centered, fullWidth, styles.keypadContainer, ...(Array.isArray(style) ? style : [style])];
7142
7050
  const keypadStyle = [row, styles.keypadBorder, layoutMode === LayoutModes.FULLSCREEN ? styles.fullscreen : styles.compact];
7143
7051
 
@@ -7164,19 +7072,6 @@ class KeypadContainer extends React__namespace.Component {
7164
7072
  }, this.renderKeypad())));
7165
7073
  }
7166
7074
  }
7167
- _defineProperty(KeypadContainer, "propTypes", {
7168
- active: PropTypes__default["default"].bool,
7169
- extraKeys: PropTypes__default["default"].arrayOf(keyIdPropType),
7170
- keypadType: PropTypes__default["default"].oneOf(Object.keys(KeypadTypes)).isRequired,
7171
- layoutMode: PropTypes__default["default"].oneOf(Object.keys(LayoutModes)).isRequired,
7172
- navigationPadEnabled: PropTypes__default["default"].bool.isRequired,
7173
- onDismiss: PropTypes__default["default"].func,
7174
- // A callback that should be triggered with the root React element on
7175
- // mount.
7176
- onElementMounted: PropTypes__default["default"].func,
7177
- onPageSizeChange: PropTypes__default["default"].func.isRequired,
7178
- style: PropTypes__default["default"].any
7179
- });
7180
7075
  const keypadAnimationDurationMs = 300;
7181
7076
  const borderWidthPx = 1;
7182
7077
  const styles = aphrodite.StyleSheet.create({
@@ -7237,7 +7132,9 @@ const inlineStyles = {
7237
7132
  };
7238
7133
  const mapStateToProps = state => {
7239
7134
  return {
7240
- ...state.keypad,
7135
+ extraKeys: state.keypad.extraKeys,
7136
+ keypadType: state.keypad.keypadType,
7137
+ active: state.keypad.active,
7241
7138
  layoutMode: state.layout.layoutMode,
7242
7139
  navigationPadEnabled: state.layout.navigationPadEnabled
7243
7140
  };
@@ -7298,11 +7195,12 @@ class ProvidedKeypad extends React__namespace.Component {
7298
7195
  render() {
7299
7196
  const {
7300
7197
  onElementMounted,
7301
- ...rest
7198
+ onDismiss,
7199
+ style
7302
7200
  } = this.props;
7303
7201
  return /*#__PURE__*/React__namespace.createElement(reactRedux.Provider, {
7304
7202
  store: this.store
7305
- }, /*#__PURE__*/React__namespace.createElement(KeypadContainer$1, _extends({
7203
+ }, /*#__PURE__*/React__namespace.createElement(KeypadContainer$1, {
7306
7204
  onElementMounted: element => {
7307
7205
  // Append the dispatch methods that we want to expose
7308
7206
  // externally to the returned React element.
@@ -7316,16 +7214,17 @@ class ProvidedKeypad extends React__namespace.Component {
7316
7214
  getDOMNode: this.getDOMNode
7317
7215
  };
7318
7216
  onElementMounted && onElementMounted(elementWithDispatchMethods);
7319
- }
7320
- }, rest)));
7217
+ },
7218
+ onDismiss: onDismiss,
7219
+ style: style
7220
+ }));
7321
7221
  }
7322
7222
  }
7323
7223
 
7324
- exports.CursorContexts = CursorContexts;
7224
+ exports.CursorContexts = cursorContexts;
7325
7225
  exports.KeyConfigs = KeyConfigs;
7326
7226
  exports.Keypad = ProvidedKeypad;
7327
7227
  exports.KeypadInput = MathInput;
7328
7228
  exports.KeypadTypes = KeypadTypes;
7329
- exports.keypadConfigurationPropType = keypadConfigurationPropType;
7330
7229
  exports.keypadElementPropType = keypadElementPropType;
7331
7230
  //# sourceMappingURL=index.js.map