@khanacademy/math-input 0.7.2 → 1.0.1

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 (183) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/components/compute-layout-parameters.d.ts +38 -0
  3. package/dist/components/compute-layout-parameters.js.flow +49 -0
  4. package/dist/components/corner-decal.d.ts +12 -0
  5. package/dist/components/corner-decal.js.flow +15 -0
  6. package/dist/components/echo-manager.d.ts +26 -0
  7. package/dist/components/echo-manager.js.flow +29 -0
  8. package/dist/components/empty-keypad-button.d.ts +13 -0
  9. package/dist/components/empty-keypad-button.js.flow +23 -0
  10. package/dist/components/expression-keypad.d.ts +22 -0
  11. package/dist/components/expression-keypad.js.flow +32 -0
  12. package/dist/components/fraction-keypad.d.ts +21 -0
  13. package/dist/components/fraction-keypad.js.flow +30 -0
  14. package/dist/components/gesture-manager.d.ts +74 -0
  15. package/dist/components/gesture-manager.js.flow +82 -0
  16. package/dist/components/gesture-state-machine.d.ts +105 -0
  17. package/dist/components/gesture-state-machine.js.flow +118 -0
  18. package/dist/components/icon.d.ts +15 -0
  19. package/dist/components/icon.js.flow +18 -0
  20. package/dist/components/input/__tests__/test-math-wrapper.d.ts +8 -0
  21. package/dist/components/input/__tests__/test-math-wrapper.js.flow +14 -0
  22. package/dist/components/input/cursor-handle.d.ts +1 -1
  23. package/dist/components/input/cursor-handle.js.flow +1 -1
  24. package/dist/components/input/drag-listener.d.ts +13 -0
  25. package/dist/components/input/drag-listener.js.flow +19 -0
  26. package/dist/components/input/math-input.d.ts +5 -4
  27. package/dist/components/input/math-input.js.flow +5 -4
  28. package/dist/components/input/math-wrapper.d.ts +110 -0
  29. package/dist/components/input/math-wrapper.js.flow +125 -0
  30. package/dist/components/input/scroll-into-view.d.ts +11 -0
  31. package/dist/components/input/scroll-into-view.js.flow +20 -0
  32. package/dist/components/keypad/button-assets.d.ts +4 -3
  33. package/dist/components/keypad/button-assets.js.flow +3 -3
  34. package/dist/components/keypad/button.d.ts +1 -2
  35. package/dist/components/keypad/button.js.flow +1 -2
  36. package/dist/components/keypad/index.d.ts +1 -1
  37. package/dist/components/keypad/index.js.flow +1 -3
  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 +50 -0
  57. package/dist/components/node-manager.js.flow +62 -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 +68 -0
  61. package/dist/components/popover-state-machine.js.flow +77 -0
  62. package/dist/components/provided-keypad.d.ts +8 -10
  63. package/dist/components/provided-keypad.js.flow +8 -10
  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.css +0 -3
  83. package/dist/es/index.js +933 -1065
  84. package/dist/es/index.js.map +1 -1
  85. package/dist/fake-react-native-web/text.d.ts +2 -1
  86. package/dist/fake-react-native-web/text.js.flow +2 -1
  87. package/dist/fake-react-native-web/view.d.ts +3 -2
  88. package/dist/fake-react-native-web/view.js.flow +3 -2
  89. package/dist/index.css +0 -3
  90. package/dist/index.d.ts +1 -1
  91. package/dist/index.js +977 -1090
  92. package/dist/index.js.flow +1 -4
  93. package/dist/index.js.map +1 -1
  94. package/dist/store/actions.d.ts +64 -0
  95. package/dist/store/actions.js.flow +100 -0
  96. package/dist/store/echo-reducer.d.ts +4 -0
  97. package/dist/store/echo-reducer.js.flow +10 -0
  98. package/dist/store/index.d.ts +10 -1
  99. package/dist/store/index.js.flow +17 -1
  100. package/dist/store/input-reducer.d.ts +4 -0
  101. package/dist/store/input-reducer.js.flow +13 -0
  102. package/dist/store/keypad-reducer.d.ts +4 -0
  103. package/dist/store/keypad-reducer.js.flow +13 -0
  104. package/dist/store/layout-reducer.d.ts +4 -0
  105. package/dist/store/layout-reducer.js.flow +13 -0
  106. package/dist/store/pager-reducer.d.ts +4 -0
  107. package/dist/store/pager-reducer.js.flow +13 -0
  108. package/dist/store/shared.d.ts +6 -0
  109. package/dist/store/shared.js.flow +13 -0
  110. package/dist/store/types.d.ts +58 -0
  111. package/dist/store/types.js.flow +64 -0
  112. package/dist/types.d.ts +63 -0
  113. package/dist/types.js.flow +73 -0
  114. package/less/overrides.less +0 -6
  115. package/package.json +1 -1
  116. package/src/components/__tests__/{gesture-state-machine_test.js → gesture-state-machine.test.ts} +5 -1
  117. package/src/components/__tests__/{two-page-keypad_test.js → two-page-keypad.test.tsx} +0 -2
  118. package/src/components/{corner-decal.js → corner-decal.tsx} +6 -5
  119. package/src/components/{echo-manager.js → echo-manager.tsx} +29 -24
  120. package/src/components/{empty-keypad-button.js → empty-keypad-button.tsx} +17 -10
  121. package/src/components/{expression-keypad.js → expression-keypad.tsx} +27 -25
  122. package/src/components/{fraction-keypad.js → fraction-keypad.tsx} +21 -16
  123. package/src/components/{gesture-manager.js → gesture-manager.ts} +10 -4
  124. package/src/components/{gesture-state-machine.js → gesture-state-machine.ts} +49 -3
  125. package/src/components/{icon.js → icon.tsx} +12 -14
  126. package/src/components/input/cursor-handle.tsx +1 -1
  127. package/src/components/input/{drag-listener.js → drag-listener.ts} +4 -0
  128. package/src/components/input/math-input.tsx +10 -9
  129. package/src/components/input/{math-wrapper.js → math-wrapper.ts} +10 -6
  130. package/src/components/input/{scroll-into-view.js → scroll-into-view.ts} +5 -15
  131. package/src/components/keypad/button-assets.tsx +4 -5
  132. package/src/components/keypad/button.tsx +1 -2
  133. package/src/components/keypad/index.tsx +2 -2
  134. package/src/components/keypad/keypad-page-items.tsx +33 -10
  135. package/src/components/{keypad-button.js → keypad-button.tsx} +42 -37
  136. package/src/components/{keypad-container.js → keypad-container.tsx} +42 -24
  137. package/src/components/{keypad.js → keypad.tsx} +32 -24
  138. package/src/components/{many-keypad-button.js → many-keypad-button.tsx} +8 -6
  139. package/src/components/{math-icon.js → math-icon.tsx} +7 -6
  140. package/src/components/{multi-symbol-grid.js → multi-symbol-grid.tsx} +8 -8
  141. package/src/components/{multi-symbol-popover.js → multi-symbol-popover.tsx} +5 -6
  142. package/src/components/{navigation-pad.js → navigation-pad.tsx} +7 -6
  143. package/src/components/{node-manager.js → node-manager.ts} +16 -4
  144. package/src/components/{popover-manager.js → popover-manager.tsx} +13 -16
  145. package/src/components/{popover-state-machine.js → popover-state-machine.ts} +13 -2
  146. package/src/components/prop-types.js +1 -67
  147. package/src/components/provided-keypad.tsx +16 -23
  148. package/src/components/{svg-icon.js → svg-icon.tsx} +5 -6
  149. package/src/components/tabbar/icons.tsx +4 -2
  150. package/src/components/tabbar/item.tsx +1 -3
  151. package/src/components/tabbar/{tabbar.stories.js → tabbar.stories.tsx} +10 -1
  152. package/src/components/tabbar/tabbar.tsx +3 -3
  153. package/src/components/{text-icon.js → text-icon.tsx} +7 -6
  154. package/src/components/{touchable-keypad-button.js → touchable-keypad-button.tsx} +19 -16
  155. package/src/components/{two-page-keypad.js → two-page-keypad.tsx} +13 -9
  156. package/src/components/{velocity-tracker.js → velocity-tracker.ts} +14 -4
  157. package/src/fake-react-native-web/text.tsx +2 -1
  158. package/src/fake-react-native-web/view.tsx +3 -2
  159. package/src/index.ts +1 -4
  160. package/src/math-input.stories.tsx +67 -0
  161. package/src/store/actions.ts +178 -0
  162. package/src/store/echo-reducer.ts +61 -0
  163. package/src/store/index.ts +39 -449
  164. package/src/store/input-reducer.ts +56 -0
  165. package/src/store/keypad-reducer.ts +59 -0
  166. package/src/store/layout-reducer.ts +134 -0
  167. package/src/store/pager-reducer.ts +125 -0
  168. package/src/store/shared.ts +12 -0
  169. package/src/store/types.ts +82 -0
  170. package/src/types.ts +81 -0
  171. package/tsconfig.tsbuildinfo +1 -1
  172. package/src/actions/index.js +0 -57
  173. package/src/components/app.js +0 -73
  174. package/src/demo.js +0 -9
  175. package/src/native-app.js +0 -85
  176. /package/src/components/__tests__/{node-manager_test.js → node-manager.test.ts} +0 -0
  177. /package/src/components/{compute-layout-parameters.js → compute-layout-parameters.ts} +0 -0
  178. /package/src/components/input/__tests__/{context-tracking_test.js → context-tracking.test.ts} +0 -0
  179. /package/src/components/input/__tests__/{mathquill_test.js → mathquill.test.ts} +0 -0
  180. /package/src/components/input/__tests__/{test-math-wrapper.jsx → test-math-wrapper.ts} +0 -0
  181. /package/src/components/keypad/{button.stories.js → button.stories.tsx} +0 -0
  182. /package/src/components/{styles.js → styles.ts} +0 -0
  183. /package/src/components/tabbar/__tests__/{tabbar_test.js → tabbar.test.tsx} +0 -0
package/dist/es/index.js CHANGED
@@ -330,6 +330,9 @@ CursorHandle.defaultProps = {
330
330
  const touchSlopPx = 8;
331
331
  class DragListener {
332
332
  constructor(onDrag, initialEvent) {
333
+ this._scrollListener = void 0;
334
+ this._moveListener = void 0;
335
+ this._endAndCancelListener = void 0;
333
336
  // We detect drags in two ways. First, by listening for the window
334
337
  // scroll event (we consider any legitimate scroll to be a drag).
335
338
  this._scrollListener = () => {
@@ -488,7 +491,7 @@ const IN_DENOMINATOR = "IN_DENOMINATOR";
488
491
  // write is non-leaf math (numbers and variables).
489
492
  const BEFORE_FRACTION = "BEFORE_FRACTION";
490
493
 
491
- var CursorContexts = /*#__PURE__*/Object.freeze({
494
+ var cursorContexts = /*#__PURE__*/Object.freeze({
492
495
  __proto__: null,
493
496
  NONE: NONE,
494
497
  IN_PARENS: IN_PARENS,
@@ -639,7 +642,13 @@ const KeysForJumpContext = {
639
642
  [IN_DENOMINATOR]: Keys.JUMP_OUT_DENOMINATOR
640
643
  };
641
644
  class MathWrapper {
645
+ // MathQuill interface
646
+ // MathQuill input
647
+
642
648
  constructor(element, options = {}, callbacks = {}) {
649
+ this.MQ = void 0;
650
+ this.mathField = void 0;
651
+ this.callbacks = void 0;
643
652
  this.MQ = MathQuill.getInterface(2);
644
653
  this.mathField = this.MQ.MathField(element, {
645
654
  // use a span instead of a textarea so that we don't bring up the
@@ -718,7 +727,7 @@ class MathWrapper {
718
727
  this._handleBackspace(cursor);
719
728
  } else if (key === Keys.LEFT) {
720
729
  this._handleLeftArrow(cursor);
721
- } else if (key === Keys.RIGHT || key === Keys.JUMP_OUT) {
730
+ } else if (key === Keys.RIGHT) {
722
731
  this._handleRightArrow(cursor);
723
732
  } else if (/^[a-zA-Z]$/.test(key)) {
724
733
  this.mathField[WRITE](key);
@@ -1217,7 +1226,7 @@ class MathWrapper {
1217
1226
  if (this._isInsideEmptyNode(cursor)) {
1218
1227
  const grandparent = cursor.parent.parent;
1219
1228
  const command = this._maybeFindCommandBeforeParens(grandparent);
1220
- cursor.insLeftOf(command.startNode);
1229
+ cursor.insLeftOf(command == null ? void 0 : command.startNode);
1221
1230
  cursor.startSelection();
1222
1231
  if (grandparent[this.MQ.R] !== MQ_END) {
1223
1232
  cursor.insRightOf(grandparent[this.MQ.R]);
@@ -1381,18 +1390,6 @@ class MathWrapper {
1381
1390
  * TODO(charlie): Move this scroll logic out of our components and into a higher
1382
1391
  * level in the component tree--perhaps even into webapp, beyond Perseus.
1383
1392
  */
1384
-
1385
- // Taken from https://dev.opera.com/articles/fixing-the-scrolltop-bug/
1386
- function bodyOrHtml() {
1387
- if ("scrollingElement" in document) {
1388
- return document.scrollingElement;
1389
- }
1390
- // Fallback for legacy browsers
1391
- if (navigator.userAgent.indexOf("WebKit") !== -1) {
1392
- return document.body;
1393
- }
1394
- return document.documentElement;
1395
- }
1396
1393
  const scrollIntoView = (containerNode, keypadNode) => {
1397
1394
  // TODO(charlie): There's no need for us to be reading the keypad bounds
1398
1395
  // here, since they're pre-determined by logic in the store. We should
@@ -1402,7 +1399,7 @@ const scrollIntoView = (containerNode, keypadNode) => {
1402
1399
  const containerTopPx = containerBounds.top;
1403
1400
 
1404
1401
  // Get the element that scrolls the document.
1405
- const scrollNode = bodyOrHtml();
1402
+ const scrollNode = document.scrollingElement;
1406
1403
  const desiredMarginPx = 16;
1407
1404
  if (keypadNode) {
1408
1405
  // NOTE(charlie): We can't use the bounding rect of the keypad,
@@ -1421,7 +1418,9 @@ const scrollIntoView = (containerNode, keypadNode) => {
1421
1418
  // the bottom of the input is just above the top of the keypad,
1422
1419
  // taking care not to scroll the input out of view.
1423
1420
  const scrollOffset = Math.min(containerBottomPx - keypadTopPx + desiredMarginPx, containerTopPx);
1424
- scrollNode.scrollTop += scrollOffset;
1421
+ if (scrollNode) {
1422
+ scrollNode.scrollTop += scrollOffset;
1423
+ }
1425
1424
  return;
1426
1425
  }
1427
1426
  }
@@ -1429,7 +1428,7 @@ const scrollIntoView = (containerNode, keypadNode) => {
1429
1428
  // Alternatively, if the input is out of the viewport or nearly out
1430
1429
  // of the viewport, scroll it into view. We can do this regardless
1431
1430
  // of whether the keypad has been provided.
1432
- if (containerTopPx < desiredMarginPx) {
1431
+ if (scrollNode && containerTopPx < desiredMarginPx) {
1433
1432
  scrollNode.scrollTop -= containerBounds.height + desiredMarginPx;
1434
1433
  }
1435
1434
  };
@@ -2133,320 +2132,12 @@ const inlineStyles$1 = {
2133
2132
  }
2134
2133
  };
2135
2134
 
2136
- const KeyConfigs = {
2137
- // Basic math keys.
2138
- [Keys.PLUS]: {
2139
- type: KeyTypes.OPERATOR,
2140
- // I18N: A label for a plus sign.
2141
- ariaLabel: i18n._("Plus")
2142
- },
2143
- [Keys.MINUS]: {
2144
- type: KeyTypes.OPERATOR,
2145
- // I18N: A label for a minus sign.
2146
- ariaLabel: i18n._("Minus")
2147
- },
2148
- [Keys.NEGATIVE]: {
2149
- type: KeyTypes.VALUE,
2150
- // I18N: A label for a minus sign.
2151
- ariaLabel: i18n._("Negative")
2152
- },
2153
- [Keys.TIMES]: {
2154
- type: KeyTypes.OPERATOR,
2155
- // I18N: A label for a multiplication sign (represented with an 'x').
2156
- ariaLabel: i18n._("Multiply")
2157
- },
2158
- [Keys.DIVIDE]: {
2159
- type: KeyTypes.OPERATOR,
2160
- // I18N: A label for a division sign.
2161
- ariaLabel: i18n._("Divide")
2162
- },
2163
- [Keys.DECIMAL]: {
2164
- type: KeyTypes.VALUE,
2165
- // I18N: A label for a decimal symbol.
2166
- ariaLabel: i18n._("Decimal"),
2167
- icon: decimalSeparator === DecimalSeparators.COMMA ? {
2168
- // TODO(charlie): Get an SVG icon for the comma, or verify with
2169
- // design that the text-rendered version is acceptable.
2170
- type: IconTypes.TEXT,
2171
- data: ","
2172
- } : {
2173
- type: IconTypes.SVG,
2174
- data: Keys.PERIOD
2175
- }
2176
- },
2177
- [Keys.PERCENT]: {
2178
- type: KeyTypes.OPERATOR,
2179
- // I18N: A label for a percent sign.
2180
- ariaLabel: i18n._("Percent")
2181
- },
2182
- [Keys.CDOT]: {
2183
- type: KeyTypes.OPERATOR,
2184
- // I18N: A label for a multiplication sign (represented as a dot).
2185
- ariaLabel: i18n._("Multiply")
2186
- },
2187
- [Keys.EQUAL]: {
2188
- type: KeyTypes.OPERATOR,
2189
- ariaLabel: i18n._("Equals sign")
2190
- },
2191
- [Keys.NEQ]: {
2192
- type: KeyTypes.OPERATOR,
2193
- ariaLabel: i18n._("Not-equals sign")
2194
- },
2195
- [Keys.GT]: {
2196
- type: KeyTypes.OPERATOR,
2197
- // I18N: A label for a 'greater than' sign (represented as '>').
2198
- ariaLabel: i18n._("Greater than sign")
2199
- },
2200
- [Keys.LT]: {
2201
- type: KeyTypes.OPERATOR,
2202
- // I18N: A label for a 'less than' sign (represented as '<').
2203
- ariaLabel: i18n._("Less than sign")
2204
- },
2205
- [Keys.GEQ]: {
2206
- type: KeyTypes.OPERATOR,
2207
- ariaLabel: i18n._("Greater than or equal to sign")
2208
- },
2209
- [Keys.LEQ]: {
2210
- type: KeyTypes.OPERATOR,
2211
- ariaLabel: i18n._("Less than or equal to sign")
2212
- },
2213
- // mobile native
2214
- [Keys.FRAC_INCLUSIVE]: {
2215
- type: KeyTypes.OPERATOR,
2216
- // I18N: A label for a button that creates a new fraction and puts the
2217
- // current expression in the numerator of that fraction.
2218
- ariaLabel: i18n._("Fraction, with current expression in numerator")
2219
- },
2220
- // mobile native
2221
- [Keys.FRAC_EXCLUSIVE]: {
2222
- type: KeyTypes.OPERATOR,
2223
- // I18N: A label for a button that creates a new fraction next to the
2224
- // cursor.
2225
- ariaLabel: i18n._("Fraction, excluding the current expression")
2226
- },
2227
- // mobile web
2228
- [Keys.FRAC]: {
2229
- type: KeyTypes.OPERATOR,
2230
- // I18N: A label for a button that creates a new fraction next to the
2231
- // cursor.
2232
- ariaLabel: i18n._("Fraction, excluding the current expression")
2233
- },
2234
- [Keys.EXP]: {
2235
- type: KeyTypes.OPERATOR,
2236
- // I18N: A label for a button that will allow the user to input a custom
2237
- // exponent.
2238
- ariaLabel: i18n._("Custom exponent")
2239
- },
2240
- [Keys.EXP_2]: {
2241
- type: KeyTypes.OPERATOR,
2242
- // I18N: A label for a button that will square (take to the second
2243
- // power) some math.
2244
- ariaLabel: i18n._("Square")
2245
- },
2246
- [Keys.EXP_3]: {
2247
- type: KeyTypes.OPERATOR,
2248
- // I18N: A label for a button that will cube (take to the third power)
2249
- // some math.
2250
- ariaLabel: i18n._("Cube")
2251
- },
2252
- [Keys.SQRT]: {
2253
- type: KeyTypes.OPERATOR,
2254
- ariaLabel: i18n._("Square root")
2255
- },
2256
- [Keys.CUBE_ROOT]: {
2257
- type: KeyTypes.OPERATOR,
2258
- ariaLabel: i18n._("Cube root")
2259
- },
2260
- [Keys.RADICAL]: {
2261
- type: KeyTypes.OPERATOR,
2262
- ariaLabel: i18n._("Radical with custom root")
2263
- },
2264
- [Keys.LEFT_PAREN]: {
2265
- type: KeyTypes.OPERATOR,
2266
- ariaLabel: i18n._("Left parenthesis")
2267
- },
2268
- [Keys.RIGHT_PAREN]: {
2269
- type: KeyTypes.OPERATOR,
2270
- ariaLabel: i18n._("Right parenthesis")
2271
- },
2272
- [Keys.LN]: {
2273
- type: KeyTypes.OPERATOR,
2274
- ariaLabel: i18n._("Natural logarithm")
2275
- },
2276
- [Keys.LOG]: {
2277
- type: KeyTypes.OPERATOR,
2278
- ariaLabel: i18n._("Logarithm with base 10")
2279
- },
2280
- [Keys.LOG_N]: {
2281
- type: KeyTypes.OPERATOR,
2282
- ariaLabel: i18n._("Logarithm with custom base")
2283
- },
2284
- [Keys.SIN]: {
2285
- type: KeyTypes.OPERATOR,
2286
- ariaLabel: i18n._("Sine")
2287
- },
2288
- [Keys.COS]: {
2289
- type: KeyTypes.OPERATOR,
2290
- ariaLabel: i18n._("Cosine")
2291
- },
2292
- [Keys.TAN]: {
2293
- type: KeyTypes.OPERATOR,
2294
- ariaLabel: i18n._("Tangent")
2295
- },
2296
- [Keys.PI]: {
2297
- type: KeyTypes.VALUE,
2298
- ariaLabel: i18n._("Pi"),
2299
- icon: {
2300
- type: IconTypes.MATH,
2301
- data: "\\pi"
2302
- }
2303
- },
2304
- [Keys.THETA]: {
2305
- type: KeyTypes.VALUE,
2306
- ariaLabel: i18n._("Theta"),
2307
- icon: {
2308
- type: IconTypes.MATH,
2309
- data: "\\theta"
2310
- }
2311
- },
2312
- [Keys.NOOP]: {
2313
- type: KeyTypes.EMPTY
2314
- },
2315
- // Input navigation keys.
2316
- [Keys.UP]: {
2317
- type: KeyTypes.INPUT_NAVIGATION,
2318
- ariaLabel: i18n._("Up arrow")
2319
- },
2320
- [Keys.RIGHT]: {
2321
- type: KeyTypes.INPUT_NAVIGATION,
2322
- ariaLabel: i18n._("Right arrow")
2323
- },
2324
- [Keys.DOWN]: {
2325
- type: KeyTypes.INPUT_NAVIGATION,
2326
- ariaLabel: i18n._("Down arrow")
2327
- },
2328
- [Keys.LEFT]: {
2329
- type: KeyTypes.INPUT_NAVIGATION,
2330
- ariaLabel: i18n._("Left arrow")
2331
- },
2332
- [Keys.JUMP_OUT_PARENTHESES]: {
2333
- type: KeyTypes.INPUT_NAVIGATION,
2334
- ariaLabel: i18n._("Navigate right out of a set of parentheses")
2335
- },
2336
- [Keys.JUMP_OUT_EXPONENT]: {
2337
- type: KeyTypes.INPUT_NAVIGATION,
2338
- ariaLabel: i18n._("Navigate right out of an exponent")
2339
- },
2340
- [Keys.JUMP_OUT_BASE]: {
2341
- type: KeyTypes.INPUT_NAVIGATION,
2342
- ariaLabel: i18n._("Navigate right out of a base")
2343
- },
2344
- [Keys.JUMP_INTO_NUMERATOR]: {
2345
- type: KeyTypes.INPUT_NAVIGATION,
2346
- ariaLabel: i18n._("Navigate right into the numerator of a fraction")
2347
- },
2348
- [Keys.JUMP_OUT_NUMERATOR]: {
2349
- type: KeyTypes.INPUT_NAVIGATION,
2350
- ariaLabel: i18n._("Navigate right out of the numerator and into the denominator")
2351
- },
2352
- [Keys.JUMP_OUT_DENOMINATOR]: {
2353
- type: KeyTypes.INPUT_NAVIGATION,
2354
- ariaLabel: i18n._("Navigate right out of the denominator of a fraction")
2355
- },
2356
- [Keys.BACKSPACE]: {
2357
- type: KeyTypes.INPUT_NAVIGATION,
2358
- // I18N: A label for a button that will delete some input.
2359
- ariaLabel: i18n._("Delete")
2360
- },
2361
- // Keypad navigation keys.
2362
- [Keys.DISMISS]: {
2363
- type: KeyTypes.KEYPAD_NAVIGATION,
2364
- // I18N: A label for a button that will dismiss/hide a keypad.
2365
- ariaLabel: i18n._("Dismiss")
2366
- }
2367
- };
2368
-
2369
- // Add in any multi-function buttons. By default, these keys will mix in any
2370
- // configuration settings from their default child key (i.e., the first key in
2371
- // the `childKeyIds` array).
2372
- // TODO(charlie): Make the multi-function button's long-press interaction
2373
- // accessible.
2374
- // NOTE(kevinb): This is only used in the mobile native app.
2375
- KeyConfigs[Keys.FRAC_MULTI] = {
2376
- childKeyIds: [Keys.FRAC_INCLUSIVE, Keys.FRAC_EXCLUSIVE]
2377
- };
2378
-
2379
- // TODO(charlie): Use the numeral color for the 'Many' key.
2380
- KeyConfigs[Keys.MANY] = {
2381
- type: KeyTypes.MANY
2382
- // childKeyIds will be configured by the client.
2383
- };
2384
-
2385
- // Add in every numeral.
2386
- const NUMBERS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
2387
- for (const num of NUMBERS) {
2388
- // TODO(charlie): Consider removing the SVG icons that we have for the
2389
- // numeral keys. They can be rendered just as easily with text (though that
2390
- // would mean that we'd be using text beyond the variable key).
2391
- const textRepresentation = `${num}`;
2392
- KeyConfigs[`NUM_${num}`] = {
2393
- type: KeyTypes.VALUE,
2394
- ariaLabel: textRepresentation,
2395
- icon: {
2396
- type: IconTypes.TEXT,
2397
- data: textRepresentation
2398
- }
2399
- };
2400
- }
2401
-
2402
- // Add in every variable.
2403
- 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"];
2404
- for (const letter of LETTERS) {
2405
- const lowerCaseVariable = letter.toLowerCase();
2406
- const upperCaseVariable = letter.toUpperCase();
2407
- for (const textRepresentation of [lowerCaseVariable, upperCaseVariable]) {
2408
- KeyConfigs[textRepresentation] = {
2409
- type: KeyTypes.VALUE,
2410
- ariaLabel: textRepresentation,
2411
- icon: {
2412
- type: IconTypes.MATH,
2413
- data: textRepresentation
2414
- }
2415
- };
2416
- }
2417
- }
2418
- for (const key of Object.keys(KeyConfigs)) {
2419
- KeyConfigs[key] = _extends({
2420
- id: key,
2421
- // Default to an SVG icon indexed by the key name.
2422
- icon: {
2423
- type: IconTypes.SVG,
2424
- data: key
2425
- }
2426
- }, KeyConfigs[key]);
2427
- }
2428
-
2429
- /**
2430
- * React PropTypes that may be shared between components.
2431
- */
2432
- const iconPropType = PropTypes.shape({
2433
- type: PropTypes.oneOf(Object.keys(IconTypes)).isRequired,
2434
- data: PropTypes.string.isRequired
2435
- });
2436
- const keyIdPropType = PropTypes.oneOf(Object.keys(KeyConfigs));
2437
- const keyConfigPropType = PropTypes.shape({
2438
- ariaLabel: PropTypes.string,
2439
- id: keyIdPropType.isRequired,
2440
- type: PropTypes.oneOf(Object.keys(KeyTypes)).isRequired,
2441
- childKeyIds: PropTypes.arrayOf(keyIdPropType),
2442
- icon: iconPropType.isRequired
2443
- });
2444
- const keypadConfigurationPropType = PropTypes.shape({
2445
- keypadType: PropTypes.oneOf(Object.keys(KeypadTypes)).isRequired,
2446
- extraKeys: PropTypes.arrayOf(keyIdPropType)
2447
- });
2135
+ /**
2136
+ * React PropTypes that may be shared between components.
2137
+ */
2448
2138
 
2449
2139
  // NOTE(jared): This is no longer guaranteed to be React element
2140
+ // NOTE(matthewc): only seems to be used in Perseus
2450
2141
  const keypadElementPropType = PropTypes.shape({
2451
2142
  activate: PropTypes.func.isRequired,
2452
2143
  dismiss: PropTypes.func.isRequired,
@@ -2455,44 +2146,6 @@ const keypadElementPropType = PropTypes.shape({
2455
2146
  setKeyHandler: PropTypes.func.isRequired,
2456
2147
  getDOMNode: PropTypes.func.isRequired
2457
2148
  });
2458
- const bordersPropType = PropTypes.arrayOf(PropTypes.oneOf(Object.keys(BorderDirections)));
2459
- const boundingBoxPropType = PropTypes.shape({
2460
- height: PropTypes.number,
2461
- width: PropTypes.number,
2462
- top: PropTypes.number,
2463
- right: PropTypes.number,
2464
- bottom: PropTypes.number,
2465
- left: PropTypes.number
2466
- });
2467
- const echoPropType = PropTypes.shape({
2468
- animationId: PropTypes.string.isRequired,
2469
- animationType: PropTypes.oneOf(Object.keys(EchoAnimationTypes)).isRequired,
2470
- borders: bordersPropType,
2471
- id: keyIdPropType.isRequired,
2472
- initialBounds: boundingBoxPropType.isRequired
2473
- });
2474
- const cursorContextPropType = PropTypes.oneOf(Object.keys(CursorContexts));
2475
- const popoverPropType = PropTypes.shape({
2476
- parentId: keyIdPropType.isRequired,
2477
- bounds: boundingBoxPropType.isRequired,
2478
- childKeyIds: PropTypes.arrayOf(keyIdPropType).isRequired
2479
- });
2480
- PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]);
2481
-
2482
- function _objectWithoutPropertiesLoose(source, excluded) {
2483
- if (source == null) return {};
2484
- var target = {};
2485
- var sourceKeys = Object.keys(source);
2486
- var key, i;
2487
-
2488
- for (i = 0; i < sourceKeys.length; i++) {
2489
- key = sourceKeys[i];
2490
- if (excluded.indexOf(key) >= 0) continue;
2491
- target[key] = source[key];
2492
- }
2493
-
2494
- return target;
2495
- }
2496
2149
 
2497
2150
  // naming convention: verb + noun
2498
2151
  // the noun should be one of the other properties in the object that's
@@ -2511,9 +2164,8 @@ const activateKeypad = () => {
2511
2164
 
2512
2165
  /**
2513
2166
  * Configure the keypad with the provided configuration parameters.
2514
- *
2515
- * See: `prop-types.js#keypadConfigurationPropType`.
2516
2167
  */
2168
+
2517
2169
  const configureKeypad = configuration => {
2518
2170
  return {
2519
2171
  type: "ConfigureKeypad",
@@ -2535,6 +2187,7 @@ const removeEcho = animationId => {
2535
2187
  };
2536
2188
 
2537
2189
  // Input-related actions.
2190
+
2538
2191
  const setKeyHandler = keyHandler => {
2539
2192
  return {
2540
2193
  type: "SetKeyHandler",
@@ -2548,133 +2201,50 @@ const setCursor = cursor => {
2548
2201
  };
2549
2202
  };
2550
2203
 
2551
- /**
2552
- * An algorithm for computing the appropriate layout parameters for the keypad,
2553
- * including the size of the buttons and whether or not to render fullscreen,
2554
- * taking into account a number of factors including the size of the screen, the
2555
- * orientation of the screen, the presence of browser chrome, the presence of
2556
- * other exercise-related chrome, the size of the input box, the parameters that
2557
- * define the keypad (i.e., the number of rows, columns, and pages), and so
2558
- * forth.
2559
- *
2560
- * The computations herein make some strong assumptions about the sizes of
2561
- * various other elements and the situations under which they will be visible
2562
- * (e.g., browser chrome). However, this is just a heuristic--it's not crucial
2563
- * that our buttons are sized in a pixel-perfect manner, but rather, that we
2564
- * make a balanced use of space.
2565
- *
2566
- * Note that one goal of the algorithm is to avoid resizing the keypad in the
2567
- * face of dynamic browser chrome. In order to avoid that awkwardness, we tend
2568
- * to be conservative in our measurements and make things smaller than they
2569
- * might need to be.
2570
- */
2571
- const minButtonHeight = 48;
2572
- const maxButtonSize = 64;
2573
- const minSpaceAboveKeypad = 32;
2574
-
2575
- // These values are taken from an iPhone 5, but should be consistent with the
2576
- // iPhone 4 as well. Regardless, these are meant to be representative of the
2577
- // possible types of browser chrome that could appear in various context, rather
2578
- // than pixel-perfect for every device.
2579
- const safariNavBarWhenShrunk = 44;
2580
- const safariNavBarWhenExpanded = 64;
2581
- const safariToolbar = 44;
2582
-
2583
- // In mobile Safari, the browser chrome is completely hidden in landscape,
2584
- // though a shrunken navbar and full-sized toolbar on scroll. In portrait, the
2585
- // shrunken navbar is always visible, but expands on scroll (and the toolbar
2586
- // appears as well).
2587
- const maxLandscapeBrowserChrome = safariNavBarWhenShrunk + safariToolbar;
2588
- const maxPortraitBrowserChrome = safariToolbar + (safariNavBarWhenExpanded - safariNavBarWhenShrunk);
2589
-
2590
- // This represents the 'worst case' aspect ratio that we care about (for
2591
- // portrait layouts). It's taken from the iPhone 4. The height is computed by
2592
- // taking the height of the device and removing the persistent, shrunken navbar.
2593
- // (We don't need to account for the expanded navbar, since we include the
2594
- // difference when reserving space above the keypad.)
2595
- const worstCaseAspectRatio = 320 / (480 - safariNavBarWhenShrunk);
2596
- const computeLayoutParameters = ({
2597
- numColumns,
2598
- numMaxVisibleRows,
2599
- numPages
2600
- }, {
2601
- pageWidthPx,
2602
- pageHeightPx
2603
- }, {
2604
- deviceOrientation,
2605
- deviceType
2606
- }, {
2607
- navigationPadEnabled,
2608
- paginationEnabled,
2609
- toolbarEnabled
2610
- }) => {
2611
- // First, compute some values that will be used in multiple computations.
2612
- const effectiveNumColumns = paginationEnabled ? numColumns : numColumns * numPages;
2613
-
2614
- // Then, compute the button dimensions based on the provided parameters.
2615
- let buttonDimensions;
2616
- if (deviceType === DeviceTypes.PHONE) {
2617
- const isLandscape = deviceOrientation === DeviceOrientations.LANDSCAPE;
2618
-
2619
- // In many cases, the browser chrome will already have been factored
2620
- // into `pageHeightPx`. But we have no way of knowing if that's
2621
- // the case or not. As such, we take a conservative approach and
2622
- // assume that the chrome is _never_ included in `pageHeightPx`.
2623
- const browserChromeHeight = isLandscape ? maxLandscapeBrowserChrome : maxPortraitBrowserChrome;
2204
+ // Gesture actions
2624
2205
 
2625
- // Count up all the space that we need to reserve on the page.
2626
- // Namely, we need to account for:
2627
- // 1. Space between the keypad and the top of the page.
2628
- // 2. The presence of the exercise toolbar.
2629
- // 3. The presence of the view pager indicator.
2630
- // 4. Any browser chrome that may appear later.
2631
- const reservedSpace = minSpaceAboveKeypad + browserChromeHeight + (toolbarEnabled ? toolbarHeightPx : 0) + (paginationEnabled ? pageIndicatorHeightPx : 0);
2206
+ const onSwipeChange = dx => {
2207
+ return {
2208
+ type: "OnSwipeChange",
2209
+ dx
2210
+ };
2211
+ };
2212
+ const onSwipeEnd = dx => {
2213
+ return {
2214
+ type: "OnSwipeEnd",
2215
+ dx
2216
+ };
2217
+ };
2218
+ const setActiveNodes = activeNodes => {
2219
+ return {
2220
+ type: "SetActiveNodes",
2221
+ activeNodes
2222
+ };
2223
+ };
2224
+ const pressKey = (key, borders, initialBounds, inPopover) => {
2225
+ return {
2226
+ type: "PressKey",
2227
+ key,
2228
+ borders,
2229
+ initialBounds,
2230
+ inPopover
2231
+ };
2232
+ };
2632
2233
 
2633
- // Next, compute the effective width and height. We can use the page
2634
- // width as the effective width. For the height, though, we take
2635
- // another conservative measure when in portrait by assuming that
2636
- // the device has the worst possible aspect ratio. In other words,
2637
- // we ignore the device height in portrait and assume the worst.
2638
- // This prevents the keypad from changing size when browser chrome
2639
- // appears and disappears.
2640
- const effectiveWidth = pageWidthPx;
2641
- const effectiveHeight = isLandscape ? pageHeightPx : pageWidthPx / worstCaseAspectRatio;
2642
- const maxKeypadHeight = effectiveHeight - reservedSpace;
2234
+ function _objectWithoutPropertiesLoose(source, excluded) {
2235
+ if (source == null) return {};
2236
+ var target = {};
2237
+ var sourceKeys = Object.keys(source);
2238
+ var key, i;
2643
2239
 
2644
- // Finally, compute the button height and width. In computing the
2645
- // height, accommodate for the maximum number of rows that will ever be
2646
- // visible (since the toggling of popovers can increase the number of
2647
- // visible rows).
2648
- const buttonHeightPx = Math.max(Math.min(maxKeypadHeight / numMaxVisibleRows, maxButtonSize), minButtonHeight);
2649
- let buttonWidthPx;
2650
- if (numPages > 1) {
2651
- const _effectiveNumColumns = paginationEnabled ? numColumns : numColumns * numPages;
2652
- buttonWidthPx = effectiveWidth / _effectiveNumColumns;
2653
- } else {
2654
- buttonWidthPx = isLandscape ? maxButtonSize : effectiveWidth / numColumns;
2655
- }
2656
- buttonDimensions = {
2657
- widthPx: buttonWidthPx,
2658
- heightPx: buttonHeightPx
2659
- };
2660
- } else if (deviceType === DeviceTypes.TABLET) {
2661
- buttonDimensions = {
2662
- widthPx: maxButtonSize,
2663
- heightPx: maxButtonSize
2664
- };
2665
- } else {
2666
- throw new Error("Invalid device type: " + deviceType);
2240
+ for (i = 0; i < sourceKeys.length; i++) {
2241
+ key = sourceKeys[i];
2242
+ if (excluded.indexOf(key) >= 0) continue;
2243
+ target[key] = source[key];
2667
2244
  }
2668
2245
 
2669
- // Finally, determine whether the keypad should be rendered in the
2670
- // fullscreen layout by determining its resultant width.
2671
- const numSeparators = (navigationPadEnabled ? 1 : 0) + (!paginationEnabled ? numPages - 1 : 0);
2672
- const keypadWidth = effectiveNumColumns * buttonDimensions.widthPx + (navigationPadEnabled ? navigationPadWidthPx : 0) + numSeparators * innerBorderWidthPx;
2673
- return {
2674
- buttonDimensions,
2675
- layoutMode: keypadWidth >= pageWidthPx ? LayoutModes.FULLSCREEN : LayoutModes.COMPACT
2676
- };
2677
- };
2246
+ return target;
2247
+ }
2678
2248
 
2679
2249
  /**
2680
2250
  * The state machine that backs our gesture system. In particular, this state
@@ -2684,15 +2254,23 @@ const computeLayoutParameters = ({
2684
2254
  * multi-touch interactions, tracking gesture state on a per-touch basis.
2685
2255
  */
2686
2256
 
2687
- const defaults = {
2257
+ // exported for tests
2258
+
2259
+ const defaultOptions = {
2688
2260
  longPressWaitTimeMs: 50,
2689
2261
  swipeThresholdPx: 20,
2690
2262
  holdIntervalMs: 250
2691
2263
  };
2692
2264
  class GestureStateMachine {
2693
2265
  constructor(handlers, options, swipeDisabledNodeIds, multiPressableKeys) {
2266
+ this.handlers = void 0;
2267
+ this.options = void 0;
2268
+ this.swipeDisabledNodeIds = void 0;
2269
+ this.multiPressableKeys = void 0;
2270
+ this.touchState = void 0;
2271
+ this.swipeState = void 0;
2694
2272
  this.handlers = handlers;
2695
- this.options = _extends({}, defaults, options);
2273
+ this.options = _extends({}, defaultOptions, options);
2696
2274
  this.swipeDisabledNodeIds = swipeDisabledNodeIds || [];
2697
2275
  this.multiPressableKeys = multiPressableKeys || [];
2698
2276
 
@@ -2953,6 +2531,10 @@ class GestureStateMachine {
2953
2531
 
2954
2532
  class NodeManager {
2955
2533
  constructor() {
2534
+ this._nodesById = void 0;
2535
+ this._bordersById = void 0;
2536
+ this._orderedIds = void 0;
2537
+ this._cachedBoundingBoxesById = void 0;
2956
2538
  // A mapping from IDs to DOM nodes.
2957
2539
  this._nodesById = {};
2958
2540
 
@@ -3002,6 +2584,7 @@ class NodeManager {
3002
2584
  const seenIds = {};
3003
2585
  for (const _id of allIds) {
3004
2586
  if (!seenIds[_id]) {
2587
+ // @ts-expect-error TS2345
3005
2588
  orderedIds.push(_id);
3006
2589
  seenIds[_id] = true;
3007
2590
  }
@@ -3070,6 +2653,9 @@ class NodeManager {
3070
2653
 
3071
2654
  class PopoverStateMachine {
3072
2655
  constructor(handlers) {
2656
+ this.handlers = void 0;
2657
+ this.popovers = void 0;
2658
+ this.activePopover = void 0;
3073
2659
  this.handlers = handlers;
3074
2660
  this.activePopover = null;
3075
2661
  this.popovers = {};
@@ -3166,8 +2752,8 @@ class PopoverStateMachine {
3166
2752
  this.activePopover = id;
3167
2753
  this.handlers.onActiveNodesChanged({
3168
2754
  popover: {
3169
- parentId: this.activePopover,
3170
- childIds: this.popovers[this.activePopover]
2755
+ parentId: id,
2756
+ childIds: this.popovers[id]
3171
2757
  },
3172
2758
  focus: this._defaultNodeForPopover(this.activePopover)
3173
2759
  });
@@ -3229,12 +2815,17 @@ class PopoverStateMachine {
3229
2815
  }
3230
2816
  }
3231
2817
 
3232
- const _excluded$5 = ["popover"];
2818
+ const _excluded$4 = ["popover"];
3233
2819
  const coordsForEvent = evt => {
3234
2820
  return [evt.changedTouches[0].clientX, evt.changedTouches[0].clientY];
3235
2821
  };
3236
2822
  class GestureManager {
3237
2823
  constructor(options, handlers, disabledSwipeKeys, multiPressableKeys) {
2824
+ this.swipeEnabled = void 0;
2825
+ this.trackEvents = void 0;
2826
+ this.nodeManager = void 0;
2827
+ this.popoverStateMachine = void 0;
2828
+ this.gestureStateMachine = void 0;
3238
2829
  const {
3239
2830
  swipeEnabled
3240
2831
  } = options;
@@ -3248,7 +2839,7 @@ class GestureManager {
3248
2839
  const {
3249
2840
  popover
3250
2841
  } = activeNodes,
3251
- rest = _objectWithoutPropertiesLoose(activeNodes, _excluded$5);
2842
+ rest = _objectWithoutPropertiesLoose(activeNodes, _excluded$4);
3252
2843
  handlers.onActiveNodesChanged(_extends({
3253
2844
  popover: popover && {
3254
2845
  parentId: popover.parentId,
@@ -3416,43 +3007,405 @@ class GestureManager {
3416
3007
  }
3417
3008
  }
3418
3009
 
3419
- /**
3420
- * A small triangular decal to sit in the corner of a parent component.
3421
- */
3422
- class CornerDecal extends React.Component {
3423
- render() {
3424
- const {
3425
- style
3426
- } = this.props;
3427
- const containerStyle = [styles$d.container, ...(Array.isArray(style) ? style : [style])];
3428
- return /*#__PURE__*/React.createElement(View, {
3429
- style: containerStyle
3430
- }, /*#__PURE__*/React.createElement("svg", {
3431
- width: triangleSizePx,
3432
- height: triangleSizePx,
3433
- viewBox: "4 4 8 8"
3434
- }, /*#__PURE__*/React.createElement("path", {
3435
- fill: offBlack,
3436
- opacity: "0.3",
3437
- 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
3438
- })));
3439
- }
3440
- }
3441
- CornerDecal.propTypes = {
3442
- style: PropTypes.any
3443
- };
3444
- const triangleSizePx = 7;
3445
- const styles$d = StyleSheet.create({
3446
- container: {
3447
- position: "absolute",
3448
- top: 0,
3449
- right: 0,
3450
- width: triangleSizePx,
3451
- height: triangleSizePx
3452
- }
3453
- });
3454
-
3455
- /**
3010
+ const KeyConfigs = {
3011
+ // Basic math keys.
3012
+ [Keys.PLUS]: {
3013
+ type: KeyTypes.OPERATOR,
3014
+ // I18N: A label for a plus sign.
3015
+ ariaLabel: i18n._("Plus")
3016
+ },
3017
+ [Keys.MINUS]: {
3018
+ type: KeyTypes.OPERATOR,
3019
+ // I18N: A label for a minus sign.
3020
+ ariaLabel: i18n._("Minus")
3021
+ },
3022
+ [Keys.NEGATIVE]: {
3023
+ type: KeyTypes.VALUE,
3024
+ // I18N: A label for a minus sign.
3025
+ ariaLabel: i18n._("Negative")
3026
+ },
3027
+ [Keys.TIMES]: {
3028
+ type: KeyTypes.OPERATOR,
3029
+ // I18N: A label for a multiplication sign (represented with an 'x').
3030
+ ariaLabel: i18n._("Multiply")
3031
+ },
3032
+ [Keys.DIVIDE]: {
3033
+ type: KeyTypes.OPERATOR,
3034
+ // I18N: A label for a division sign.
3035
+ ariaLabel: i18n._("Divide")
3036
+ },
3037
+ [Keys.DECIMAL]: {
3038
+ type: KeyTypes.VALUE,
3039
+ // I18N: A label for a decimal symbol.
3040
+ ariaLabel: i18n._("Decimal"),
3041
+ icon: decimalSeparator === DecimalSeparators.COMMA ? {
3042
+ // TODO(charlie): Get an SVG icon for the comma, or verify with
3043
+ // design that the text-rendered version is acceptable.
3044
+ type: IconTypes.TEXT,
3045
+ data: ","
3046
+ } : {
3047
+ type: IconTypes.SVG,
3048
+ data: Keys.PERIOD
3049
+ }
3050
+ },
3051
+ [Keys.PERCENT]: {
3052
+ type: KeyTypes.OPERATOR,
3053
+ // I18N: A label for a percent sign.
3054
+ ariaLabel: i18n._("Percent")
3055
+ },
3056
+ [Keys.CDOT]: {
3057
+ type: KeyTypes.OPERATOR,
3058
+ // I18N: A label for a multiplication sign (represented as a dot).
3059
+ ariaLabel: i18n._("Multiply")
3060
+ },
3061
+ [Keys.EQUAL]: {
3062
+ type: KeyTypes.OPERATOR,
3063
+ ariaLabel: i18n._("Equals sign")
3064
+ },
3065
+ [Keys.NEQ]: {
3066
+ type: KeyTypes.OPERATOR,
3067
+ ariaLabel: i18n._("Not-equals sign")
3068
+ },
3069
+ [Keys.GT]: {
3070
+ type: KeyTypes.OPERATOR,
3071
+ // I18N: A label for a 'greater than' sign (represented as '>').
3072
+ ariaLabel: i18n._("Greater than sign")
3073
+ },
3074
+ [Keys.LT]: {
3075
+ type: KeyTypes.OPERATOR,
3076
+ // I18N: A label for a 'less than' sign (represented as '<').
3077
+ ariaLabel: i18n._("Less than sign")
3078
+ },
3079
+ [Keys.GEQ]: {
3080
+ type: KeyTypes.OPERATOR,
3081
+ ariaLabel: i18n._("Greater than or equal to sign")
3082
+ },
3083
+ [Keys.LEQ]: {
3084
+ type: KeyTypes.OPERATOR,
3085
+ ariaLabel: i18n._("Less than or equal to sign")
3086
+ },
3087
+ // mobile native
3088
+ [Keys.FRAC_INCLUSIVE]: {
3089
+ type: KeyTypes.OPERATOR,
3090
+ // I18N: A label for a button that creates a new fraction and puts the
3091
+ // current expression in the numerator of that fraction.
3092
+ ariaLabel: i18n._("Fraction, with current expression in numerator")
3093
+ },
3094
+ // mobile native
3095
+ [Keys.FRAC_EXCLUSIVE]: {
3096
+ type: KeyTypes.OPERATOR,
3097
+ // I18N: A label for a button that creates a new fraction next to the
3098
+ // cursor.
3099
+ ariaLabel: i18n._("Fraction, excluding the current expression")
3100
+ },
3101
+ // mobile web
3102
+ [Keys.FRAC]: {
3103
+ type: KeyTypes.OPERATOR,
3104
+ // I18N: A label for a button that creates a new fraction next to the
3105
+ // cursor.
3106
+ ariaLabel: i18n._("Fraction, excluding the current expression")
3107
+ },
3108
+ [Keys.EXP]: {
3109
+ type: KeyTypes.OPERATOR,
3110
+ // I18N: A label for a button that will allow the user to input a custom
3111
+ // exponent.
3112
+ ariaLabel: i18n._("Custom exponent")
3113
+ },
3114
+ [Keys.EXP_2]: {
3115
+ type: KeyTypes.OPERATOR,
3116
+ // I18N: A label for a button that will square (take to the second
3117
+ // power) some math.
3118
+ ariaLabel: i18n._("Square")
3119
+ },
3120
+ [Keys.EXP_3]: {
3121
+ type: KeyTypes.OPERATOR,
3122
+ // I18N: A label for a button that will cube (take to the third power)
3123
+ // some math.
3124
+ ariaLabel: i18n._("Cube")
3125
+ },
3126
+ [Keys.SQRT]: {
3127
+ type: KeyTypes.OPERATOR,
3128
+ ariaLabel: i18n._("Square root")
3129
+ },
3130
+ [Keys.CUBE_ROOT]: {
3131
+ type: KeyTypes.OPERATOR,
3132
+ ariaLabel: i18n._("Cube root")
3133
+ },
3134
+ [Keys.RADICAL]: {
3135
+ type: KeyTypes.OPERATOR,
3136
+ ariaLabel: i18n._("Radical with custom root")
3137
+ },
3138
+ [Keys.LEFT_PAREN]: {
3139
+ type: KeyTypes.OPERATOR,
3140
+ ariaLabel: i18n._("Left parenthesis")
3141
+ },
3142
+ [Keys.RIGHT_PAREN]: {
3143
+ type: KeyTypes.OPERATOR,
3144
+ ariaLabel: i18n._("Right parenthesis")
3145
+ },
3146
+ [Keys.LN]: {
3147
+ type: KeyTypes.OPERATOR,
3148
+ ariaLabel: i18n._("Natural logarithm")
3149
+ },
3150
+ [Keys.LOG]: {
3151
+ type: KeyTypes.OPERATOR,
3152
+ ariaLabel: i18n._("Logarithm with base 10")
3153
+ },
3154
+ [Keys.LOG_N]: {
3155
+ type: KeyTypes.OPERATOR,
3156
+ ariaLabel: i18n._("Logarithm with custom base")
3157
+ },
3158
+ [Keys.SIN]: {
3159
+ type: KeyTypes.OPERATOR,
3160
+ ariaLabel: i18n._("Sine")
3161
+ },
3162
+ [Keys.COS]: {
3163
+ type: KeyTypes.OPERATOR,
3164
+ ariaLabel: i18n._("Cosine")
3165
+ },
3166
+ [Keys.TAN]: {
3167
+ type: KeyTypes.OPERATOR,
3168
+ ariaLabel: i18n._("Tangent")
3169
+ },
3170
+ [Keys.PI]: {
3171
+ type: KeyTypes.VALUE,
3172
+ ariaLabel: i18n._("Pi"),
3173
+ icon: {
3174
+ type: IconTypes.MATH,
3175
+ data: "\\pi"
3176
+ }
3177
+ },
3178
+ [Keys.THETA]: {
3179
+ type: KeyTypes.VALUE,
3180
+ ariaLabel: i18n._("Theta"),
3181
+ icon: {
3182
+ type: IconTypes.MATH,
3183
+ data: "\\theta"
3184
+ }
3185
+ },
3186
+ [Keys.NOOP]: {
3187
+ type: KeyTypes.EMPTY
3188
+ },
3189
+ // Input navigation keys.
3190
+ [Keys.UP]: {
3191
+ type: KeyTypes.INPUT_NAVIGATION,
3192
+ ariaLabel: i18n._("Up arrow")
3193
+ },
3194
+ [Keys.RIGHT]: {
3195
+ type: KeyTypes.INPUT_NAVIGATION,
3196
+ ariaLabel: i18n._("Right arrow")
3197
+ },
3198
+ [Keys.DOWN]: {
3199
+ type: KeyTypes.INPUT_NAVIGATION,
3200
+ ariaLabel: i18n._("Down arrow")
3201
+ },
3202
+ [Keys.LEFT]: {
3203
+ type: KeyTypes.INPUT_NAVIGATION,
3204
+ ariaLabel: i18n._("Left arrow")
3205
+ },
3206
+ [Keys.JUMP_OUT_PARENTHESES]: {
3207
+ type: KeyTypes.INPUT_NAVIGATION,
3208
+ ariaLabel: i18n._("Navigate right out of a set of parentheses")
3209
+ },
3210
+ [Keys.JUMP_OUT_EXPONENT]: {
3211
+ type: KeyTypes.INPUT_NAVIGATION,
3212
+ ariaLabel: i18n._("Navigate right out of an exponent")
3213
+ },
3214
+ [Keys.JUMP_OUT_BASE]: {
3215
+ type: KeyTypes.INPUT_NAVIGATION,
3216
+ ariaLabel: i18n._("Navigate right out of a base")
3217
+ },
3218
+ [Keys.JUMP_INTO_NUMERATOR]: {
3219
+ type: KeyTypes.INPUT_NAVIGATION,
3220
+ ariaLabel: i18n._("Navigate right into the numerator of a fraction")
3221
+ },
3222
+ [Keys.JUMP_OUT_NUMERATOR]: {
3223
+ type: KeyTypes.INPUT_NAVIGATION,
3224
+ ariaLabel: i18n._("Navigate right out of the numerator and into the denominator")
3225
+ },
3226
+ [Keys.JUMP_OUT_DENOMINATOR]: {
3227
+ type: KeyTypes.INPUT_NAVIGATION,
3228
+ ariaLabel: i18n._("Navigate right out of the denominator of a fraction")
3229
+ },
3230
+ [Keys.BACKSPACE]: {
3231
+ type: KeyTypes.INPUT_NAVIGATION,
3232
+ // I18N: A label for a button that will delete some input.
3233
+ ariaLabel: i18n._("Delete")
3234
+ },
3235
+ // Keypad navigation keys.
3236
+ [Keys.DISMISS]: {
3237
+ type: KeyTypes.KEYPAD_NAVIGATION,
3238
+ // I18N: A label for a button that will dismiss/hide a keypad.
3239
+ ariaLabel: i18n._("Dismiss")
3240
+ }
3241
+ };
3242
+
3243
+ // Add in any multi-function buttons. By default, these keys will mix in any
3244
+ // configuration settings from their default child key (i.e., the first key in
3245
+ // the `childKeyIds` array).
3246
+ // TODO(charlie): Make the multi-function button's long-press interaction
3247
+ // accessible.
3248
+ // NOTE(kevinb): This is only used in the mobile native app.
3249
+ KeyConfigs[Keys.FRAC_MULTI] = {
3250
+ childKeyIds: [Keys.FRAC_INCLUSIVE, Keys.FRAC_EXCLUSIVE]
3251
+ };
3252
+
3253
+ // TODO(charlie): Use the numeral color for the 'Many' key.
3254
+ KeyConfigs[Keys.MANY] = {
3255
+ type: KeyTypes.MANY
3256
+ // childKeyIds will be configured by the client.
3257
+ };
3258
+
3259
+ // Add in every numeral.
3260
+ const NUMBERS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
3261
+ for (const num of NUMBERS) {
3262
+ // TODO(charlie): Consider removing the SVG icons that we have for the
3263
+ // numeral keys. They can be rendered just as easily with text (though that
3264
+ // would mean that we'd be using text beyond the variable key).
3265
+ const textRepresentation = `${num}`;
3266
+ KeyConfigs[`NUM_${num}`] = {
3267
+ type: KeyTypes.VALUE,
3268
+ ariaLabel: textRepresentation,
3269
+ icon: {
3270
+ type: IconTypes.TEXT,
3271
+ data: textRepresentation
3272
+ }
3273
+ };
3274
+ }
3275
+
3276
+ // Add in every variable.
3277
+ 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"];
3278
+ for (const letter of LETTERS) {
3279
+ const lowerCaseVariable = letter.toLowerCase();
3280
+ const upperCaseVariable = letter.toUpperCase();
3281
+ for (const textRepresentation of [lowerCaseVariable, upperCaseVariable]) {
3282
+ KeyConfigs[textRepresentation] = {
3283
+ type: KeyTypes.VALUE,
3284
+ ariaLabel: textRepresentation,
3285
+ icon: {
3286
+ type: IconTypes.MATH,
3287
+ data: textRepresentation
3288
+ }
3289
+ };
3290
+ }
3291
+ }
3292
+ for (const key of Object.keys(KeyConfigs)) {
3293
+ KeyConfigs[key] = _extends({
3294
+ id: key,
3295
+ // Default to an SVG icon indexed by the key name.
3296
+ icon: {
3297
+ type: IconTypes.SVG,
3298
+ data: key
3299
+ }
3300
+ }, KeyConfigs[key]);
3301
+ }
3302
+
3303
+ // Used to generate unique animation IDs for the echo animations. The actual
3304
+ // values are irrelevant as long as they are unique.
3305
+ let _lastAnimationId = 0;
3306
+ const initialEchoState = {
3307
+ echoes: []
3308
+ };
3309
+ const echoReducer = function echoReducer(state = initialEchoState, action) {
3310
+ switch (action.type) {
3311
+ case "PressKey":
3312
+ const keyConfig = KeyConfigs[action.key];
3313
+
3314
+ // Add in the echo animation if the user performs a math
3315
+ // operation.
3316
+ if (keyConfig.type === KeyTypes.VALUE || keyConfig.type === KeyTypes.OPERATOR) {
3317
+ return _extends({}, state, {
3318
+ echoes: [...state.echoes, {
3319
+ animationId: "" + _lastAnimationId++,
3320
+ animationType: action.inPopover ? EchoAnimationTypes.LONG_FADE_ONLY : EchoAnimationTypes.FADE_ONLY,
3321
+ borders: action.borders,
3322
+ id: keyConfig.id,
3323
+ initialBounds: action.initialBounds
3324
+ }]
3325
+ });
3326
+ }
3327
+ return state;
3328
+ case "RemoveEcho":
3329
+ const remainingEchoes = state.echoes.filter(echo => {
3330
+ return echo.animationId !== action.animationId;
3331
+ });
3332
+ return _extends({}, state, {
3333
+ echoes: remainingEchoes
3334
+ });
3335
+ default:
3336
+ return state;
3337
+ }
3338
+ };
3339
+
3340
+ const initialInputState = {
3341
+ keyHandler: null,
3342
+ cursor: {
3343
+ context: NONE
3344
+ }
3345
+ };
3346
+ const inputReducer = function inputReducer(state = initialInputState, action) {
3347
+ switch (action.type) {
3348
+ case "SetKeyHandler":
3349
+ return _extends({}, state, {
3350
+ keyHandler: action.keyHandler
3351
+ });
3352
+ case "PressKey":
3353
+ const keyConfig = KeyConfigs[action.key];
3354
+ if (keyConfig.type !== KeyTypes.KEYPAD_NAVIGATION) {
3355
+ // This is probably an anti-pattern but it works for the
3356
+ // case where we don't actually control the state but we
3357
+ // still want to communicate with the other object
3358
+ return _extends({}, state, {
3359
+ cursor: state.keyHandler == null ? void 0 : state.keyHandler(keyConfig.id)
3360
+ });
3361
+ }
3362
+
3363
+ // TODO(kevinb) get state from MathQuill and store it?
3364
+ return state;
3365
+ case "SetCursor":
3366
+ return _extends({}, state, {
3367
+ cursor: action.cursor
3368
+ });
3369
+ default:
3370
+ return state;
3371
+ }
3372
+ };
3373
+
3374
+ /**
3375
+ * A small triangular decal to sit in the corner of a parent component.
3376
+ */
3377
+ class CornerDecal extends React.Component {
3378
+ render() {
3379
+ const {
3380
+ style
3381
+ } = this.props;
3382
+ const containerStyle = [styles$d.container, ...(Array.isArray(style) ? style : [style])];
3383
+ return /*#__PURE__*/React.createElement(View, {
3384
+ style: containerStyle
3385
+ }, /*#__PURE__*/React.createElement("svg", {
3386
+ width: triangleSizePx,
3387
+ height: triangleSizePx,
3388
+ viewBox: "4 4 8 8"
3389
+ }, /*#__PURE__*/React.createElement("path", {
3390
+ fill: offBlack,
3391
+ opacity: "0.3",
3392
+ 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
3393
+ })));
3394
+ }
3395
+ }
3396
+
3397
+ const triangleSizePx = 7;
3398
+ const styles$d = StyleSheet.create({
3399
+ container: {
3400
+ position: "absolute",
3401
+ top: 0,
3402
+ right: 0,
3403
+ width: triangleSizePx,
3404
+ height: triangleSizePx
3405
+ }
3406
+ });
3407
+
3408
+ /**
3456
3409
  * Common styles shared across components.
3457
3410
  */
3458
3411
  var Styles = StyleSheet.create({
@@ -3521,10 +3474,6 @@ class MathIcon extends React.Component {
3521
3474
  });
3522
3475
  }
3523
3476
  }
3524
- MathIcon.propTypes = {
3525
- math: PropTypes.string.isRequired,
3526
- style: PropTypes.any
3527
- };
3528
3477
  const styles$c = StyleSheet.create({
3529
3478
  size: {
3530
3479
  height: iconSizeHeightPx,
@@ -4834,10 +4783,6 @@ class SvgIcon extends React.Component {
4834
4783
  });
4835
4784
  }
4836
4785
  }
4837
- SvgIcon.propTypes = {
4838
- color: PropTypes.string.isRequired,
4839
- name: PropTypes.string.isRequired
4840
- };
4841
4786
 
4842
4787
  /**
4843
4788
  * A component that renders a text-based icon.
@@ -4858,10 +4803,6 @@ class TextIcon extends React.Component {
4858
4803
  }, /*#__PURE__*/React.createElement(Text, null, character));
4859
4804
  }
4860
4805
  }
4861
- TextIcon.propTypes = {
4862
- character: PropTypes.string.isRequired,
4863
- style: PropTypes.any
4864
- };
4865
4806
  const styles$b = StyleSheet.create({
4866
4807
  size: {
4867
4808
  height: iconSizeHeightPx,
@@ -4906,18 +4847,11 @@ class Icon extends React.PureComponent {
4906
4847
  character: icon.data,
4907
4848
  style: styleWithFocus
4908
4849
  });
4850
+ default:
4851
+ throw new Error("No icon or symbol provided");
4909
4852
  }
4910
- throw new Error("No icon or symbol provided");
4911
4853
  }
4912
4854
  }
4913
- Icon.propTypes = {
4914
- focused: PropTypes.bool,
4915
- icon: iconPropType.isRequired,
4916
- // An Aphrodite style object, or an array of Aphrodite style objects.
4917
- // Note that custom styles will only be applied to text and math icons
4918
- // (and not SVG icons).
4919
- style: PropTypes.any
4920
- };
4921
4855
  const styles$a = StyleSheet.create({
4922
4856
  unfocused: {
4923
4857
  color: unfocusedColor
@@ -5012,13 +4946,9 @@ class MultiSymbolGrid extends React.Component {
5012
4946
  }))));
5013
4947
  }
5014
4948
  }
5015
- throw new Error("Invalid number of icons:", icons.length);
4949
+ throw new Error(`Invalid number of icons: ${icons.length}`);
5016
4950
  }
5017
4951
  }
5018
- MultiSymbolGrid.propTypes = {
5019
- focused: PropTypes.bool,
5020
- icons: PropTypes.arrayOf(iconPropType).isRequired
5021
- };
5022
4952
  const verticalInsetPx = 2;
5023
4953
  const horizontalInsetPx = 4;
5024
4954
  const styles$9 = StyleSheet.create({
@@ -5062,6 +4992,7 @@ const styles$9 = StyleSheet.create({
5062
4992
  class KeypadButton extends React.PureComponent {
5063
4993
  constructor(...args) {
5064
4994
  super(...args);
4995
+ this.buttonSizeStyle = void 0;
5065
4996
  this._preInjectStyles = () => {
5066
4997
  // HACK(charlie): Pre-inject all of the possible styles for the button.
5067
4998
  // This avoids a flickering effect in the echo animation whereby the
@@ -5110,10 +5041,12 @@ class KeypadButton extends React.PureComponent {
5110
5041
  break;
5111
5042
  }
5112
5043
  const borderStyle = [];
5113
- if (borders.indexOf(BorderDirections.LEFT) !== -1) {
5044
+ if (borders.includes(BorderDirections.LEFT)) {
5045
+ // @ts-expect-error TS2345
5114
5046
  borderStyle.push(styles$8.leftBorder);
5115
5047
  }
5116
- if (borders.indexOf(BorderDirections.BOTTOM) !== -1) {
5048
+ if (borders.includes(BorderDirections.BOTTOM)) {
5049
+ // @ts-expect-error TS2345
5117
5050
  borderStyle.push(styles$8.bottomBorder);
5118
5051
  }
5119
5052
  return [styles$8.buttonBase, backgroundStyle, ...borderStyle, type === KeyTypes.ECHO && styles$8.echo, this.buttonSizeStyle,
@@ -5161,7 +5094,7 @@ class KeypadButton extends React.PureComponent {
5161
5094
  const renderFocused = !disabled && focused || popoverEnabled || type === KeyTypes.ECHO;
5162
5095
  const buttonStyle = this._getButtonStyle(type, borders, style);
5163
5096
  const focusStyle = this._getFocusStyle(type);
5164
- const iconWrapperStyle = [styles$8.iconWrapper, disabled && styles$8.disabled];
5097
+ const iconWrapperStyle = [styles$8.iconWrapper, disabled ? styles$8.disabled : undefined];
5165
5098
  const eventHandlers = {
5166
5099
  onTouchCancel,
5167
5100
  onTouchEnd,
@@ -5212,32 +5145,6 @@ class KeypadButton extends React.PureComponent {
5212
5145
  }
5213
5146
  }
5214
5147
  }
5215
- KeypadButton.propTypes = {
5216
- ariaLabel: PropTypes.string,
5217
- // The borders to display on the button. Typically, this should be set
5218
- // using one of the preset `BorderStyles` options.
5219
- borders: bordersPropType,
5220
- // Any additional keys that can be accessed by long-pressing on the
5221
- // button.
5222
- childKeys: PropTypes.arrayOf(keyConfigPropType),
5223
- // Whether the button should be rendered in a 'disabled' state, i.e.,
5224
- // without any touch feedback.
5225
- disabled: PropTypes.bool,
5226
- focused: PropTypes.bool,
5227
- heightPx: PropTypes.number.isRequired,
5228
- icon: iconPropType,
5229
- onTouchCancel: PropTypes.func,
5230
- onTouchEnd: PropTypes.func,
5231
- onTouchMove: PropTypes.func,
5232
- onTouchStart: PropTypes.func,
5233
- popoverEnabled: PropTypes.bool,
5234
- style: PropTypes.any,
5235
- type: PropTypes.oneOf(Object.keys(KeyTypes)).isRequired,
5236
- // NOTE(charlie): We may want to make this optional for phone layouts
5237
- // (and rely on Flexbox instead), since it might not be pixel perfect
5238
- // with borders and such.
5239
- widthPx: PropTypes.number.isRequired
5240
- };
5241
5148
  KeypadButton.defaultProps = {
5242
5149
  borders: BorderStyles.ALL,
5243
5150
  childKeys: [],
@@ -5249,8 +5156,6 @@ const focusInsetPx = 4;
5249
5156
  const focusBoxZIndex = 0;
5250
5157
  const styles$8 = StyleSheet.create({
5251
5158
  buttonBase: {
5252
- // HACK(benkomalo): support old style flex box in Android browsers
5253
- "-webkit-box-flex": "1",
5254
5159
  flex: 1,
5255
5160
  cursor: "pointer",
5256
5161
  // Make the text unselectable
@@ -5326,20 +5231,23 @@ const styleForButtonDimensions = (heightPx, widthPx) => {
5326
5231
  }).buttonSize;
5327
5232
  };
5328
5233
  const mapStateToProps$7 = state => {
5329
- return state.layout.buttonDimensions;
5234
+ return {
5235
+ heightPx: state.layout.buttonDimensions.heightPx,
5236
+ widthPx: state.layout.buttonDimensions.widthPx
5237
+ };
5330
5238
  };
5331
5239
  var KeypadButton$1 = connect(mapStateToProps$7, null, null, {
5332
5240
  forwardRef: true
5333
5241
  })(KeypadButton);
5334
5242
 
5335
- const _excluded$4 = ["gestureManager"];
5243
+ const _excluded$3 = ["gestureManager"];
5336
5244
  class EmptyKeypadButton extends React.Component {
5337
5245
  render() {
5338
5246
  const _this$props = this.props,
5339
5247
  {
5340
5248
  gestureManager
5341
5249
  } = _this$props,
5342
- rest = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
5250
+ rest = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
5343
5251
 
5344
5252
  // Register touch events on the button, but don't register its DOM node
5345
5253
  // or compute focus state or anything like that. We want the gesture
@@ -5354,9 +5262,6 @@ class EmptyKeypadButton extends React.Component {
5354
5262
  }, KeyConfigs.NOOP, rest));
5355
5263
  }
5356
5264
  }
5357
- EmptyKeypadButton.propTypes = {
5358
- gestureManager: PropTypes.instanceOf(GestureManager)
5359
- };
5360
5265
  const mapStateToProps$6 = state => {
5361
5266
  const {
5362
5267
  gestures
@@ -5369,7 +5274,7 @@ var EmptyKeypadButton$1 = connect(mapStateToProps$6, null, null, {
5369
5274
  forwardRef: true
5370
5275
  })(EmptyKeypadButton);
5371
5276
 
5372
- const _excluded$3 = ["borders", "childKeyIds", "disabled", "gestureManager", "id", "style"],
5277
+ const _excluded$2 = ["borders", "childKeyIds", "disabled", "gestureManager", "id", "style"],
5373
5278
  _excluded2 = ["keyConfig"];
5374
5279
  class TouchableKeypadButton extends React.Component {
5375
5280
  shouldComponentUpdate(newProps) {
@@ -5396,7 +5301,7 @@ class TouchableKeypadButton extends React.Component {
5396
5301
  id,
5397
5302
  style
5398
5303
  } = _this$props,
5399
- rest = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
5304
+ rest = _objectWithoutPropertiesLoose(_this$props, _excluded$2);
5400
5305
 
5401
5306
  // Only bind the relevant event handlers if the key is enabled.
5402
5307
  const eventHandlers = disabled ? {
@@ -5416,17 +5321,6 @@ class TouchableKeypadButton extends React.Component {
5416
5321
  }, eventHandlers, rest));
5417
5322
  }
5418
5323
  }
5419
- TouchableKeypadButton.propTypes = {
5420
- borders: bordersPropType,
5421
- childKeyIds: PropTypes.arrayOf(keyIdPropType),
5422
- disabled: PropTypes.bool,
5423
- focused: PropTypes.bool,
5424
- gestureManager: PropTypes.instanceOf(GestureManager),
5425
- id: keyIdPropType.isRequired,
5426
- popoverEnabled: PropTypes.bool,
5427
- style: PropTypes.any,
5428
- type: PropTypes.oneOf(Object.keys(KeyTypes)).isRequired
5429
- };
5430
5324
  const extractProps = keyConfig => {
5431
5325
  const {
5432
5326
  ariaLabel,
@@ -5479,20 +5373,20 @@ var TouchableKeypadButton$1 = connect(mapStateToProps$5, null, null, {
5479
5373
  forwardRef: true
5480
5374
  })(TouchableKeypadButton);
5481
5375
 
5482
- const _excluded$2 = ["keys"];
5376
+ const _excluded$1 = ["keys"];
5483
5377
  class ManyKeypadButton extends React.Component {
5484
5378
  render() {
5485
5379
  const _this$props = this.props,
5486
5380
  {
5487
5381
  keys
5488
5382
  } = _this$props,
5489
- rest = _objectWithoutPropertiesLoose(_this$props, _excluded$2);
5383
+ rest = _objectWithoutPropertiesLoose(_this$props, _excluded$1);
5490
5384
 
5491
5385
  // If we have no extra symbols, render an empty button. If we have just
5492
5386
  // one, render a standard button. Otherwise, capture them all in a
5493
5387
  // single button.
5494
5388
  if (keys.length === 0) {
5495
- return /*#__PURE__*/React.createElement(EmptyKeypadButton$1, rest);
5389
+ return /*#__PURE__*/React.createElement(EmptyKeypadButton$1, null);
5496
5390
  } else if (keys.length === 1) {
5497
5391
  const keyConfig = KeyConfigs[keys[0]];
5498
5392
  return /*#__PURE__*/React.createElement(TouchableKeypadButton$1, _extends({
@@ -5510,8 +5404,8 @@ class ManyKeypadButton extends React.Component {
5510
5404
  }
5511
5405
  }
5512
5406
  }
5513
- ManyKeypadButton.propTypes = {
5514
- keys: PropTypes.arrayOf(keyIdPropType).isRequired
5407
+ ManyKeypadButton.defaultProps = {
5408
+ keys: []
5515
5409
  };
5516
5410
 
5517
5411
  /**
@@ -5559,20 +5453,12 @@ class Echo extends React.Component {
5559
5453
  return /*#__PURE__*/React.createElement("div", {
5560
5454
  style: containerStyle
5561
5455
  }, /*#__PURE__*/React.createElement(KeypadButton$1, {
5562
- name: id,
5563
5456
  icon: icon,
5564
5457
  type: KeyTypes.ECHO,
5565
5458
  borders: borders
5566
5459
  }));
5567
5460
  }
5568
5461
  }
5569
- Echo.propTypes = {
5570
- animationDurationMs: PropTypes.number.isRequired,
5571
- borders: bordersPropType,
5572
- id: keyIdPropType.isRequired,
5573
- initialBounds: boundingBoxPropType.isRequired,
5574
- onAnimationFinish: PropTypes.func.isRequired
5575
- };
5576
5462
  class EchoManager extends React.Component {
5577
5463
  constructor(...args) {
5578
5464
  super(...args);
@@ -5595,7 +5481,7 @@ class EchoManager extends React.Component {
5595
5481
  animationTransitionName = "echo-long-fade-only";
5596
5482
  break;
5597
5483
  default:
5598
- throw new Error("Invalid echo animation type:", animationType);
5484
+ throw new Error(`Invalid echo animation type: ${animationType}`);
5599
5485
  }
5600
5486
  return {
5601
5487
  animationDurationMs,
@@ -5640,16 +5526,12 @@ class EchoManager extends React.Component {
5640
5526
  key: animationId
5641
5527
  }, /*#__PURE__*/React.createElement(Echo, _extends({
5642
5528
  animationDurationMs: animationDurationMs,
5643
- onAnimationFinish: () => onAnimationFinish(animationId)
5529
+ onAnimationFinish: () => onAnimationFinish == null ? void 0 : onAnimationFinish(animationId)
5644
5530
  }, echo)));
5645
5531
  }));
5646
5532
  }));
5647
5533
  }
5648
5534
  }
5649
- EchoManager.propTypes = {
5650
- echoes: PropTypes.arrayOf(echoPropType),
5651
- onAnimationFinish: PropTypes.func.isRequired
5652
- };
5653
5535
 
5654
5536
  /**
5655
5537
  * A popover that renders a set of keys floating above the page.
@@ -5673,9 +5555,6 @@ class MultiSymbolPopover extends React.Component {
5673
5555
  }));
5674
5556
  }
5675
5557
  }
5676
- MultiSymbolPopover.propTypes = {
5677
- keys: PropTypes.arrayOf(keyConfigPropType)
5678
- };
5679
5558
  const styles$6 = StyleSheet.create({
5680
5559
  container: {
5681
5560
  flexDirection: "column-reverse",
@@ -5696,7 +5575,6 @@ const styles$6 = StyleSheet.create({
5696
5575
  // classnames specified in popover.less.
5697
5576
  const animationTransitionName = "popover";
5698
5577
  const animationDurationMs = 200;
5699
-
5700
5578
  // A container component used to position a popover absolutely at a specific
5701
5579
  // position.
5702
5580
  class PopoverContainer extends React.Component {
@@ -5715,10 +5593,6 @@ class PopoverContainer extends React.Component {
5715
5593
  }));
5716
5594
  }
5717
5595
  }
5718
- PopoverContainer.propTypes = {
5719
- bounds: boundingBoxPropType.isRequired,
5720
- childKeys: PropTypes.arrayOf(keyConfigPropType).isRequired
5721
- };
5722
5596
  class PopoverManager extends React.Component {
5723
5597
  render() {
5724
5598
  const {
@@ -5739,16 +5613,15 @@ class PopoverManager extends React.Component {
5739
5613
  })) : null;
5740
5614
  }
5741
5615
  }
5742
- PopoverManager.propTypes = {
5743
- popover: popoverPropType
5744
- };
5745
-
5746
- const _excluded$1 = ["initialBounds"];
5747
5616
 
5617
+ const _excluded = ["initialBounds"];
5748
5618
  // eslint-disable-next-line react/no-unsafe
5749
5619
  class Keypad extends React.Component {
5750
5620
  constructor(...args) {
5751
5621
  super(...args);
5622
+ this._isMounted = void 0;
5623
+ this._resizeTimeout = void 0;
5624
+ this._container = void 0;
5752
5625
  this._computeContainer = () => {
5753
5626
  const domNode = ReactDOM.findDOMNode(this);
5754
5627
  this._container = domNode.getBoundingClientRect();
@@ -5770,7 +5643,7 @@ class Keypad extends React.Component {
5770
5643
  // Throttle resize events -- taken from:
5771
5644
  // https://developer.mozilla.org/en-US/docs/Web/Events/resize
5772
5645
  if (this._resizeTimeout == null) {
5773
- this._resizeTimeout = setTimeout(() => {
5646
+ this._resizeTimeout = window.setTimeout(() => {
5774
5647
  this._resizeTimeout = null;
5775
5648
  if (this._isMounted) {
5776
5649
  this._updateSizeAndPosition();
@@ -5808,12 +5681,16 @@ class Keypad extends React.Component {
5808
5681
  const {
5809
5682
  initialBounds
5810
5683
  } = echo,
5811
- rest = _objectWithoutPropertiesLoose(echo, _excluded$1);
5684
+ rest = _objectWithoutPropertiesLoose(echo, _excluded);
5812
5685
  return _extends({}, rest, {
5813
5686
  initialBounds: {
5687
+ // @ts-expect-error TS2533
5814
5688
  top: initialBounds.top - this._container.top,
5689
+ // @ts-expect-error TS2533
5815
5690
  right: initialBounds.right - this._container.left,
5691
+ // @ts-expect-error TS2533
5816
5692
  bottom: initialBounds.bottom - this._container.top,
5693
+ // @ts-expect-error TS2533
5817
5694
  left: initialBounds.left - this._container.left,
5818
5695
  width: initialBounds.width,
5819
5696
  height: initialBounds.height
@@ -5826,7 +5703,12 @@ class Keypad extends React.Component {
5826
5703
  // the bottom left corners of the keys over which they appear.
5827
5704
  const relativePopover = popover && _extends({}, popover, {
5828
5705
  bounds: {
5829
- bottom: this._container.height - (popover.bounds.bottom - this._container.top),
5706
+ bottom:
5707
+ // @ts-expect-error TS2533
5708
+ this._container.height - (
5709
+ // @ts-expect-error TS2533
5710
+ popover.bounds.bottom - this._container.top),
5711
+ // @ts-expect-error TS2533
5830
5712
  left: popover.bounds.left - this._container.left,
5831
5713
  width: popover.bounds.width
5832
5714
  }
@@ -5841,23 +5723,12 @@ class Keypad extends React.Component {
5841
5723
  }));
5842
5724
  }
5843
5725
  }
5844
- Keypad.propTypes = {
5845
- children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
5846
- removeEcho: PropTypes.func.isRequired,
5847
- style: PropTypes.any,
5848
- // The props below are injected by redux
5849
-
5850
- // Whether the keypad is active, i.e., whether it should be rendered as
5851
- // visible or invisible.
5852
- active: PropTypes.bool,
5853
- echoes: PropTypes.arrayOf(echoPropType).isRequired,
5854
- popover: popoverPropType
5855
- };
5856
5726
  const mapStateToProps$4 = state => {
5857
- return _extends({}, state.echoes, {
5727
+ return {
5728
+ echoes: state.echoes.echoes,
5858
5729
  active: state.keypad.active,
5859
5730
  popover: state.gestures.popover
5860
- });
5731
+ };
5861
5732
  };
5862
5733
  const mapDispatchToProps$1 = dispatch => {
5863
5734
  return {
@@ -6093,12 +5964,6 @@ class TwoPageKeypad extends React.Component {
6093
5964
  }
6094
5965
  }
6095
5966
  }
6096
- TwoPageKeypad.propTypes = {
6097
- currentPage: PropTypes.oneOf([0, 1]).isRequired,
6098
- leftPage: PropTypes.node.isRequired,
6099
- paginationEnabled: PropTypes.bool.isRequired,
6100
- rightPage: PropTypes.node.isRequired
6101
- };
6102
5967
  const styles$3 = StyleSheet.create({
6103
5968
  keypad: {
6104
5969
  // Set the background to light grey, so that when the user drags the
@@ -6133,11 +5998,16 @@ const {
6133
5998
  roundedTopLeft: roundedTopLeft$2,
6134
5999
  roundedTopRight: roundedTopRight$1
6135
6000
  } = Styles;
6001
+ const expressionKeypadLayout = {
6002
+ rows: 4,
6003
+ columns: 5,
6004
+ numPages: 2,
6005
+ // Since we include a two-key popover in the top-right, when the popover
6006
+ // is visible, the keypad will expand to fill the equivalent of five
6007
+ // rows vertically.
6008
+ maxVisibleRows: 4
6009
+ };
6136
6010
  class ExpressionKeypad extends React.Component {
6137
- // Though we include an infinite-key popover in the bottom-left, it's
6138
- // assumed that we don't need to accommodate cases in which that key
6139
- // contains more than four children.
6140
-
6141
6011
  render() {
6142
6012
  const {
6143
6013
  currentPage,
@@ -6191,8 +6061,7 @@ class ExpressionKeypad extends React.Component {
6191
6061
  keyConfig: KeyConfigs.NUM_1,
6192
6062
  borders: BorderStyles.BOTTOM
6193
6063
  }), /*#__PURE__*/React.createElement(ManyKeypadButton, {
6194
- keys: extraKeys,
6195
- borders: BorderStyles.NONE
6064
+ keys: extraKeys
6196
6065
  })), /*#__PURE__*/React.createElement(View, {
6197
6066
  style: [column$1, oneColumn]
6198
6067
  }, /*#__PURE__*/React.createElement(TouchableKeypadButton$1, {
@@ -6330,18 +6199,6 @@ class ExpressionKeypad extends React.Component {
6330
6199
  });
6331
6200
  }
6332
6201
  }
6333
- ExpressionKeypad.propTypes = {
6334
- currentPage: PropTypes.number.isRequired,
6335
- cursorContext: cursorContextPropType.isRequired,
6336
- dynamicJumpOut: PropTypes.bool,
6337
- extraKeys: PropTypes.arrayOf(keyIdPropType),
6338
- roundTopLeft: PropTypes.bool,
6339
- roundTopRight: PropTypes.bool
6340
- };
6341
- ExpressionKeypad.rows = 4;
6342
- ExpressionKeypad.columns = 5;
6343
- ExpressionKeypad.maxVisibleRows = 4;
6344
- ExpressionKeypad.numPages = 2;
6345
6202
  const styles$2 = StyleSheet.create({
6346
6203
  // NOTE(charlie): These backgrounds are applied to as to fill in some
6347
6204
  // unfortunate 'cracks' in the layout. However, not all keys in the first
@@ -6356,9 +6213,10 @@ const styles$2 = StyleSheet.create({
6356
6213
  }
6357
6214
  });
6358
6215
  const mapStateToProps$2 = state => {
6216
+ var _state$input$cursor;
6359
6217
  return {
6360
6218
  currentPage: state.pager.currentPage,
6361
- cursorContext: state.input.cursor.context,
6219
+ cursorContext: (_state$input$cursor = state.input.cursor) == null ? void 0 : _state$input$cursor.context,
6362
6220
  dynamicJumpOut: !state.layout.navigationPadEnabled
6363
6221
  };
6364
6222
  };
@@ -6375,11 +6233,16 @@ const {
6375
6233
  roundedTopLeft: roundedTopLeft$1,
6376
6234
  roundedTopRight
6377
6235
  } = Styles;
6378
- class FractionKeypad extends React.Component {
6236
+ const fractionKeypadLayout = {
6237
+ rows: 4,
6238
+ columns: 4,
6239
+ numPages: 1,
6379
6240
  // Since we include a two-key popover in the top-right, when the popover
6380
6241
  // is visible, the keypad will expand to fill the equivalent of five
6381
6242
  // rows vertically.
6382
-
6243
+ maxVisibleRows: 5
6244
+ };
6245
+ class FractionKeypad extends React.Component {
6383
6246
  render() {
6384
6247
  const {
6385
6248
  cursorContext,
@@ -6482,29 +6345,289 @@ class FractionKeypad extends React.Component {
6482
6345
  borders: BorderStyles.LEFT
6483
6346
  })));
6484
6347
  }
6485
- }
6486
- FractionKeypad.propTypes = {
6487
- cursorContext: cursorContextPropType.isRequired,
6488
- dynamicJumpOut: PropTypes.bool,
6489
- roundTopLeft: PropTypes.bool,
6490
- roundTopRight: PropTypes.bool
6491
- };
6492
- FractionKeypad.rows = 4;
6493
- FractionKeypad.columns = 4;
6494
- FractionKeypad.maxVisibleRows = 5;
6495
- FractionKeypad.numPages = 1;
6496
- const mapStateToProps$1 = state => {
6348
+ }
6349
+ const mapStateToProps$1 = state => {
6350
+ var _state$input$cursor;
6351
+ return {
6352
+ cursorContext: (_state$input$cursor = state.input.cursor) == null ? void 0 : _state$input$cursor.context,
6353
+ dynamicJumpOut: !state.layout.navigationPadEnabled
6354
+ };
6355
+ };
6356
+ var FractionKeypad$1 = connect(mapStateToProps$1, null, null, {
6357
+ forwardRef: true
6358
+ })(FractionKeypad);
6359
+
6360
+ const defaultKeypadType = KeypadTypes.EXPRESSION;
6361
+ const keypadForType = {
6362
+ [KeypadTypes.FRACTION]: fractionKeypadLayout,
6363
+ [KeypadTypes.EXPRESSION]: expressionKeypadLayout
6364
+ };
6365
+
6366
+ const initialKeypadState = {
6367
+ extraKeys: ["x", "y", Keys.THETA, Keys.PI],
6368
+ keypadType: defaultKeypadType,
6369
+ active: false
6370
+ };
6371
+ const keypadReducer = function keypadReducer(state = initialKeypadState, action) {
6372
+ switch (action.type) {
6373
+ case "DismissKeypad":
6374
+ return _extends({}, state, {
6375
+ active: false
6376
+ });
6377
+ case "ActivateKeypad":
6378
+ return _extends({}, state, {
6379
+ active: true
6380
+ });
6381
+ case "ConfigureKeypad":
6382
+ return _extends({}, state, {
6383
+ // Default `extraKeys` to the empty array.
6384
+ extraKeys: []
6385
+ }, action.configuration);
6386
+ case "PressKey":
6387
+ const keyConfig = KeyConfigs[action.key];
6388
+ // NOTE(charlie): Our keypad system operates by triggering key
6389
+ // presses with key IDs in a dumb manner, such that the keys
6390
+ // don't know what they can do--instead, the store is
6391
+ // responsible for interpreting key presses and triggering the
6392
+ // right actions when they occur. Hence, we figure off a
6393
+ // dismissal here rather than dispatching a dismiss action in
6394
+ // the first place.
6395
+ if (keyConfig.id === Keys.DISMISS) {
6396
+ return keypadReducer(state, {
6397
+ type: "DismissKeypad"
6398
+ });
6399
+ }
6400
+ return state;
6401
+ default:
6402
+ return state;
6403
+ }
6404
+ };
6405
+
6406
+ /**
6407
+ * An algorithm for computing the appropriate layout parameters for the keypad,
6408
+ * including the size of the buttons and whether or not to render fullscreen,
6409
+ * taking into account a number of factors including the size of the screen, the
6410
+ * orientation of the screen, the presence of browser chrome, the presence of
6411
+ * other exercise-related chrome, the size of the input box, the parameters that
6412
+ * define the keypad (i.e., the number of rows, columns, and pages), and so
6413
+ * forth.
6414
+ *
6415
+ * The computations herein make some strong assumptions about the sizes of
6416
+ * various other elements and the situations under which they will be visible
6417
+ * (e.g., browser chrome). However, this is just a heuristic--it's not crucial
6418
+ * that our buttons are sized in a pixel-perfect manner, but rather, that we
6419
+ * make a balanced use of space.
6420
+ *
6421
+ * Note that one goal of the algorithm is to avoid resizing the keypad in the
6422
+ * face of dynamic browser chrome. In order to avoid that awkwardness, we tend
6423
+ * to be conservative in our measurements and make things smaller than they
6424
+ * might need to be.
6425
+ */
6426
+ const minButtonHeight = 48;
6427
+ const maxButtonSize = 64;
6428
+ const minSpaceAboveKeypad = 32;
6429
+
6430
+ // These values are taken from an iPhone 5, but should be consistent with the
6431
+ // iPhone 4 as well. Regardless, these are meant to be representative of the
6432
+ // possible types of browser chrome that could appear in various context, rather
6433
+ // than pixel-perfect for every device.
6434
+ const safariNavBarWhenShrunk = 44;
6435
+ const safariNavBarWhenExpanded = 64;
6436
+ const safariToolbar = 44;
6437
+
6438
+ // In mobile Safari, the browser chrome is completely hidden in landscape,
6439
+ // though a shrunken navbar and full-sized toolbar on scroll. In portrait, the
6440
+ // shrunken navbar is always visible, but expands on scroll (and the toolbar
6441
+ // appears as well).
6442
+ const maxLandscapeBrowserChrome = safariNavBarWhenShrunk + safariToolbar;
6443
+ const maxPortraitBrowserChrome = safariToolbar + (safariNavBarWhenExpanded - safariNavBarWhenShrunk);
6444
+
6445
+ // This represents the 'worst case' aspect ratio that we care about (for
6446
+ // portrait layouts). It's taken from the iPhone 4. The height is computed by
6447
+ // taking the height of the device and removing the persistent, shrunken navbar.
6448
+ // (We don't need to account for the expanded navbar, since we include the
6449
+ // difference when reserving space above the keypad.)
6450
+ const worstCaseAspectRatio = 320 / (480 - safariNavBarWhenShrunk);
6451
+ const computeLayoutParameters = ({
6452
+ numColumns,
6453
+ numMaxVisibleRows,
6454
+ numPages
6455
+ }, {
6456
+ pageWidthPx,
6457
+ pageHeightPx
6458
+ }, {
6459
+ deviceOrientation,
6460
+ deviceType
6461
+ }, {
6462
+ navigationPadEnabled,
6463
+ paginationEnabled,
6464
+ toolbarEnabled
6465
+ }) => {
6466
+ // First, compute some values that will be used in multiple computations.
6467
+ const effectiveNumColumns = paginationEnabled ? numColumns : numColumns * numPages;
6468
+
6469
+ // Then, compute the button dimensions based on the provided parameters.
6470
+ let buttonDimensions;
6471
+ if (deviceType === DeviceTypes.PHONE) {
6472
+ const isLandscape = deviceOrientation === DeviceOrientations.LANDSCAPE;
6473
+
6474
+ // In many cases, the browser chrome will already have been factored
6475
+ // into `pageHeightPx`. But we have no way of knowing if that's
6476
+ // the case or not. As such, we take a conservative approach and
6477
+ // assume that the chrome is _never_ included in `pageHeightPx`.
6478
+ const browserChromeHeight = isLandscape ? maxLandscapeBrowserChrome : maxPortraitBrowserChrome;
6479
+
6480
+ // Count up all the space that we need to reserve on the page.
6481
+ // Namely, we need to account for:
6482
+ // 1. Space between the keypad and the top of the page.
6483
+ // 2. The presence of the exercise toolbar.
6484
+ // 3. The presence of the view pager indicator.
6485
+ // 4. Any browser chrome that may appear later.
6486
+ const reservedSpace = minSpaceAboveKeypad + browserChromeHeight + (toolbarEnabled ? toolbarHeightPx : 0) + (paginationEnabled ? pageIndicatorHeightPx : 0);
6487
+
6488
+ // Next, compute the effective width and height. We can use the page
6489
+ // width as the effective width. For the height, though, we take
6490
+ // another conservative measure when in portrait by assuming that
6491
+ // the device has the worst possible aspect ratio. In other words,
6492
+ // we ignore the device height in portrait and assume the worst.
6493
+ // This prevents the keypad from changing size when browser chrome
6494
+ // appears and disappears.
6495
+ const effectiveWidth = pageWidthPx;
6496
+ const effectiveHeight = isLandscape ? pageHeightPx : pageWidthPx / worstCaseAspectRatio;
6497
+ const maxKeypadHeight = effectiveHeight - reservedSpace;
6498
+
6499
+ // Finally, compute the button height and width. In computing the
6500
+ // height, accommodate for the maximum number of rows that will ever be
6501
+ // visible (since the toggling of popovers can increase the number of
6502
+ // visible rows).
6503
+ const buttonHeightPx = Math.max(Math.min(maxKeypadHeight / numMaxVisibleRows, maxButtonSize), minButtonHeight);
6504
+ let buttonWidthPx;
6505
+ if (numPages > 1) {
6506
+ const _effectiveNumColumns = paginationEnabled ? numColumns : numColumns * numPages;
6507
+ buttonWidthPx = effectiveWidth / _effectiveNumColumns;
6508
+ } else {
6509
+ buttonWidthPx = isLandscape ? maxButtonSize : effectiveWidth / numColumns;
6510
+ }
6511
+ buttonDimensions = {
6512
+ widthPx: buttonWidthPx,
6513
+ heightPx: buttonHeightPx
6514
+ };
6515
+ } else if (deviceType === DeviceTypes.TABLET) {
6516
+ buttonDimensions = {
6517
+ widthPx: maxButtonSize,
6518
+ heightPx: maxButtonSize
6519
+ };
6520
+ } else {
6521
+ throw new Error("Invalid device type: " + deviceType);
6522
+ }
6523
+
6524
+ // Finally, determine whether the keypad should be rendered in the
6525
+ // fullscreen layout by determining its resultant width.
6526
+ const numSeparators = (navigationPadEnabled ? 1 : 0) + (!paginationEnabled ? numPages - 1 : 0);
6527
+ const keypadWidth = effectiveNumColumns * buttonDimensions.widthPx + (navigationPadEnabled ? navigationPadWidthPx : 0) + numSeparators * innerBorderWidthPx;
6497
6528
  return {
6498
- cursorContext: state.input.cursor.context,
6499
- dynamicJumpOut: !state.layout.navigationPadEnabled
6529
+ buttonDimensions,
6530
+ layoutMode: keypadWidth >= pageWidthPx ? LayoutModes.FULLSCREEN : LayoutModes.COMPACT
6500
6531
  };
6501
6532
  };
6502
- var FractionKeypad$1 = connect(mapStateToProps$1, null, null, {
6503
- forwardRef: true
6504
- })(FractionKeypad);
6533
+
6534
+ const initialLayoutState = {
6535
+ gridDimensions: {
6536
+ numRows: keypadForType[defaultKeypadType].rows,
6537
+ numColumns: keypadForType[defaultKeypadType].columns,
6538
+ numMaxVisibleRows: keypadForType[defaultKeypadType].maxVisibleRows,
6539
+ numPages: keypadForType[defaultKeypadType].numPages
6540
+ },
6541
+ buttonDimensions: {
6542
+ widthPx: 48,
6543
+ heightPx: 48
6544
+ },
6545
+ pageDimensions: {
6546
+ pageWidthPx: 0,
6547
+ pageHeightPx: 0
6548
+ },
6549
+ layoutMode: LayoutModes.FULLSCREEN,
6550
+ paginationEnabled: false,
6551
+ navigationPadEnabled: false
6552
+ };
6553
+
6554
+ /**
6555
+ * Compute the additional layout state based on the provided page and grid
6556
+ * dimensions.
6557
+ */
6558
+ const layoutParametersForDimensions = (pageDimensions, gridDimensions) => {
6559
+ const {
6560
+ pageWidthPx,
6561
+ pageHeightPx
6562
+ } = pageDimensions;
6563
+
6564
+ // Determine the device type and orientation.
6565
+ const deviceOrientation = pageWidthPx > pageHeightPx ? DeviceOrientations.LANDSCAPE : DeviceOrientations.PORTRAIT;
6566
+ const deviceType = Math.min(pageWidthPx, pageHeightPx) > tabletCutoffPx ? DeviceTypes.TABLET : DeviceTypes.PHONE;
6567
+
6568
+ // Using that information, make some decisions (or assumptions)
6569
+ // about the resulting layout.
6570
+ const navigationPadEnabled = deviceType === DeviceTypes.TABLET;
6571
+ const paginationEnabled = deviceType === DeviceTypes.PHONE && deviceOrientation === DeviceOrientations.PORTRAIT;
6572
+ const deviceInfo = {
6573
+ deviceOrientation,
6574
+ deviceType
6575
+ };
6576
+ const layoutOptions = {
6577
+ navigationPadEnabled,
6578
+ paginationEnabled,
6579
+ // HACK(charlie): It's not great that we're making assumptions about
6580
+ // the toolbar (which is rendered by webapp, and should always be
6581
+ // visible and anchored to the bottom of the page for phone and
6582
+ // tablet exercises). But this is primarily a heuristic (the goal is
6583
+ // to preserve a 'good' amount of space between the top of the
6584
+ // keypad and the top of the page) so we afford to have some margin
6585
+ // of error.
6586
+ toolbarEnabled: true
6587
+ };
6588
+ return _extends({}, computeLayoutParameters(gridDimensions, pageDimensions, deviceInfo, layoutOptions), {
6589
+ // Pass along some of the layout information, so that other
6590
+ // components in the heirarchy can adapt appropriately.
6591
+ navigationPadEnabled,
6592
+ paginationEnabled
6593
+ });
6594
+ };
6595
+ const layoutReducer = function layoutReducer(state = initialLayoutState, action) {
6596
+ switch (action.type) {
6597
+ case "ConfigureKeypad":
6598
+ const {
6599
+ keypadType
6600
+ } = action.configuration;
6601
+ const gridDimensions = {
6602
+ numRows: keypadForType[keypadType].rows,
6603
+ numColumns: keypadForType[keypadType].columns,
6604
+ numMaxVisibleRows: keypadForType[keypadType].maxVisibleRows,
6605
+ numPages: keypadForType[keypadType].numPages
6606
+ };
6607
+ return _extends({}, state, layoutParametersForDimensions(state.pageDimensions, gridDimensions), {
6608
+ gridDimensions
6609
+ });
6610
+ case "SetPageSize":
6611
+ const {
6612
+ pageWidthPx,
6613
+ pageHeightPx
6614
+ } = action;
6615
+ const pageDimensions = {
6616
+ pageWidthPx,
6617
+ pageHeightPx
6618
+ };
6619
+ return _extends({}, state, layoutParametersForDimensions(pageDimensions, state.gridDimensions), {
6620
+ pageDimensions
6621
+ });
6622
+ default:
6623
+ return state;
6624
+ }
6625
+ };
6505
6626
 
6506
6627
  class VelocityTracker {
6507
6628
  constructor(options) {
6629
+ this.options = void 0;
6630
+ this._events = void 0;
6508
6631
  this.options = _extends({
6509
6632
  velocityTimeout: 100
6510
6633
  }, options);
@@ -6567,224 +6690,116 @@ class VelocityTracker {
6567
6690
  }
6568
6691
  }
6569
6692
 
6570
- const keypadForType = {
6571
- [KeypadTypes.FRACTION]: FractionKeypad$1,
6572
- [KeypadTypes.EXPRESSION]: ExpressionKeypad$1
6693
+ // We default to the right-most page. This is done so-as to enforce a
6694
+ // consistent orientation between the view pager layout and the flattened
6695
+ // layout, where our default page appears on the far right.
6696
+ const getDefaultPage = numPages => numPages - 1;
6697
+ const initialPagerState = {
6698
+ animateToPosition: false,
6699
+ currentPage: getDefaultPage(keypadForType[defaultKeypadType].numPages),
6700
+ // The cumulative differential in the horizontal direction for the
6701
+ // current swipe.
6702
+ dx: 0,
6703
+ numPages: keypadForType[defaultKeypadType].numPages,
6704
+ pageWidthPx: 0,
6705
+ velocityTracker: new VelocityTracker()
6573
6706
  };
6574
- const createStore = () => {
6575
- const initialInputState = {
6576
- keyHandler: null,
6577
- cursor: {
6578
- context: NONE
6579
- }
6580
- };
6581
- const inputReducer = function inputReducer(state = initialInputState, action) {
6582
- switch (action.type) {
6583
- case "SetKeyHandler":
6584
- return _extends({}, state, {
6585
- keyHandler: action.keyHandler
6586
- });
6587
- case "PressKey":
6588
- const keyConfig = KeyConfigs[action.key];
6589
- if (keyConfig.type !== KeyTypes.KEYPAD_NAVIGATION) {
6590
- // This is probably an anti-pattern but it works for the
6591
- // case where we don't actually control the state but we
6592
- // still want to communicate with the other object
6593
- return _extends({}, state, {
6594
- cursor: state.keyHandler(keyConfig.id)
6595
- });
6596
- }
6597
-
6598
- // TODO(kevinb) get state from MathQuill and store it?
6599
- return state;
6600
- case "SetCursor":
6601
- return _extends({}, state, {
6602
- cursor: action.cursor
6603
- });
6604
- default:
6605
- return state;
6606
- }
6607
- };
6608
- const defaultKeypadType = KeypadTypes.EXPRESSION;
6609
- const initialKeypadState = {
6610
- extraKeys: ["x", "y", Keys.THETA, Keys.PI],
6611
- keypadType: defaultKeypadType,
6612
- active: false
6613
- };
6614
- const keypadReducer = function keypadReducer(state = initialKeypadState, action) {
6615
- switch (action.type) {
6616
- case "DismissKeypad":
6617
- return _extends({}, state, {
6618
- active: false
6619
- });
6620
- case "ActivateKeypad":
6621
- return _extends({}, state, {
6622
- active: true
6623
- });
6624
- case "ConfigureKeypad":
6625
- return _extends({}, state, {
6626
- // Default `extraKeys` to the empty array.
6627
- extraKeys: []
6628
- }, action.configuration);
6629
- case "PressKey":
6630
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'key' does not exist on type '{ type: string; }'.
6631
- const keyConfig = KeyConfigs[action.key];
6632
- // NOTE(charlie): Our keypad system operates by triggering key
6633
- // presses with key IDs in a dumb manner, such that the keys
6634
- // don't know what they can do--instead, the store is
6635
- // responsible for interpreting key presses and triggering the
6636
- // right actions when they occur. Hence, we figure off a
6637
- // dismissal here rather than dispatching a dismiss action in
6638
- // the first place.
6639
- if (keyConfig.id === Keys.DISMISS) {
6640
- return keypadReducer(state, {
6641
- type: "DismissKeypad"
6642
- });
6643
- }
6644
- return state;
6645
- default:
6646
- return state;
6647
- }
6648
- };
6707
+ const pagerReducer = function pagerReducer(state = initialPagerState, action) {
6708
+ switch (action.type) {
6709
+ case "ConfigureKeypad":
6710
+ const {
6711
+ keypadType
6712
+ } = action.configuration;
6713
+ const {
6714
+ numPages
6715
+ } = keypadForType[keypadType];
6716
+ return _extends({}, state, {
6717
+ numPages,
6718
+ animateToPosition: false,
6719
+ currentPage: getDefaultPage(numPages),
6720
+ dx: 0
6721
+ });
6722
+ case "SetPageSize":
6723
+ return _extends({}, state, {
6724
+ pageWidthPx: action.pageWidthPx
6725
+ });
6726
+ case "PressKey":
6727
+ const keyConfig = KeyConfigs[action.key];
6649
6728
 
6650
- // We default to the right-most page. This is done so-as to enforce a
6651
- // consistent orientation between the view pager layout and the flattened
6652
- // layout, where our default page appears on the far right.
6653
- const getDefaultPage = numPages => numPages - 1;
6654
- const initialPagerState = {
6655
- animateToPosition: false,
6656
- currentPage: getDefaultPage(keypadForType[defaultKeypadType].numPages),
6657
- // The cumulative differential in the horizontal direction for the
6658
- // current swipe.
6659
- dx: 0,
6660
- numPages: keypadForType[defaultKeypadType].numPages,
6661
- pageWidthPx: 0,
6662
- velocityTracker: new VelocityTracker()
6663
- };
6664
- const pagerReducer = function pagerReducer(state = initialPagerState, action) {
6665
- switch (action.type) {
6666
- case "ConfigureKeypad":
6667
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'configuration' does not exist on type '{ type: string; }'.
6668
- const {
6669
- keypadType
6670
- } = action.configuration;
6671
- const {
6672
- numPages
6673
- } = keypadForType[keypadType];
6674
- return _extends({}, state, {
6675
- numPages,
6676
- animateToPosition: false,
6677
- currentPage: getDefaultPage(numPages),
6678
- dx: 0
6679
- });
6680
- case "SetPageSize":
6681
- return _extends({}, state, {
6682
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'pageWidthPx' does not exist on type '{ type: string; }'.
6683
- pageWidthPx: action.pageWidthPx
6684
- });
6685
- case "PressKey":
6686
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'key' does not exist on type '{ type: string; }'.
6687
- const keyConfig = KeyConfigs[action.key];
6688
-
6689
- // Reset the keypad page if the user performs a math operation.
6690
- if (keyConfig.type === KeyTypes.VALUE || keyConfig.type === KeyTypes.OPERATOR) {
6691
- return pagerReducer(state, {
6692
- type: "ResetKeypadPage"
6693
- });
6694
- }
6695
- return state;
6696
- case "ResetKeypadPage":
6729
+ // Reset the keypad page if the user performs a math operation.
6730
+ if (keyConfig.type === KeyTypes.VALUE || keyConfig.type === KeyTypes.OPERATOR) {
6697
6731
  return _extends({}, state, {
6698
6732
  animateToPosition: true,
6699
6733
  // We start at the right-most page.
6700
6734
  currentPage: getDefaultPage(state.numPages),
6701
6735
  dx: 0
6702
6736
  });
6703
- case "PageKeypadRight":
6737
+ }
6738
+ return state;
6739
+ case "OnSwipeChange":
6740
+ state.velocityTracker.push(action.dx);
6741
+ return _extends({}, state, {
6742
+ animateToPosition: false,
6743
+ dx: action.dx
6744
+ });
6745
+ case "OnSwipeEnd":
6746
+ const {
6747
+ pageWidthPx,
6748
+ velocityTracker
6749
+ } = state;
6750
+ const {
6751
+ dx
6752
+ } = action;
6753
+ const velocity = velocityTracker.getVelocity();
6754
+
6755
+ // NOTE(charlie): These will need refinement. The velocity comes
6756
+ // from Framer.
6757
+ const minFlingVelocity = 0.1;
6758
+ const minFlingDistance = 10;
6759
+ const shouldPageRight = dx < -pageWidthPx / 2 || velocity < -minFlingVelocity && dx < -minFlingDistance;
6760
+ const shouldPageLeft = dx > pageWidthPx / 2 || velocity > minFlingVelocity && dx > minFlingDistance;
6761
+ if (shouldPageRight) {
6704
6762
  const nextPage = Math.min(state.currentPage + 1, state.numPages - 1);
6705
6763
  return _extends({}, state, {
6706
6764
  animateToPosition: true,
6707
6765
  currentPage: nextPage,
6708
6766
  dx: 0
6709
6767
  });
6710
- case "PageKeypadLeft":
6768
+ } else if (shouldPageLeft) {
6711
6769
  const prevPage = Math.max(state.currentPage - 1, 0);
6712
6770
  return _extends({}, state, {
6713
6771
  animateToPosition: true,
6714
6772
  currentPage: prevPage,
6715
6773
  dx: 0
6716
6774
  });
6717
- case "OnSwipeChange":
6718
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'dx' does not exist on type '{ type: string; }'.
6719
- state.velocityTracker.push(action.dx);
6720
- return _extends({}, state, {
6721
- animateToPosition: false,
6722
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'dx' does not exist on type '{ type: string; }'.
6723
- dx: action.dx
6724
- });
6725
- case "OnSwipeEnd":
6726
- const {
6727
- pageWidthPx,
6728
- velocityTracker
6729
- } = state;
6730
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'dx' does not exist on type '{ type: string; }'.
6731
- const {
6732
- dx
6733
- } = action;
6734
- const velocity = velocityTracker.getVelocity();
6735
-
6736
- // NOTE(charlie): These will need refinement. The velocity comes
6737
- // from Framer.
6738
- const minFlingVelocity = 0.1;
6739
- const minFlingDistance = 10;
6740
- const shouldPageRight = dx < -pageWidthPx / 2 || velocity < -minFlingVelocity && dx < -minFlingDistance;
6741
- const shouldPageLeft = dx > pageWidthPx / 2 || velocity > minFlingVelocity && dx > minFlingDistance;
6742
- if (shouldPageRight) {
6743
- return pagerReducer(state, {
6744
- type: "PageKeypadRight"
6745
- });
6746
- } else if (shouldPageLeft) {
6747
- return pagerReducer(state, {
6748
- type: "PageKeypadLeft"
6749
- });
6750
- }
6751
- return _extends({}, state, {
6752
- animateToPosition: true,
6753
- dx: 0
6754
- });
6755
- default:
6756
- return state;
6757
- }
6758
- };
6775
+ }
6776
+ return _extends({}, state, {
6777
+ animateToPosition: true,
6778
+ dx: 0
6779
+ });
6780
+ default:
6781
+ return state;
6782
+ }
6783
+ };
6784
+
6785
+ const createStore = () => {
6786
+ // TODO(matthewc)[LC-752]: gestureReducer can't be moved from this file
6787
+ // because it depends on `store` being in scope (see note below)
6759
6788
  const createGestureManager = swipeEnabled => {
6760
6789
  return new GestureManager({
6761
6790
  swipeEnabled
6762
6791
  }, {
6763
6792
  onSwipeChange: dx => {
6764
- store.dispatch({
6765
- type: "OnSwipeChange",
6766
- dx
6767
- });
6793
+ store.dispatch(onSwipeChange(dx));
6768
6794
  },
6769
6795
  onSwipeEnd: dx => {
6770
- store.dispatch({
6771
- type: "OnSwipeEnd",
6772
- dx
6773
- });
6796
+ store.dispatch(onSwipeEnd(dx));
6774
6797
  },
6775
6798
  onActiveNodesChanged: activeNodes => {
6776
- store.dispatch({
6777
- type: "SetActiveNodes",
6778
- activeNodes
6779
- });
6799
+ store.dispatch(setActiveNodes(activeNodes));
6780
6800
  },
6781
6801
  onClick: (key, layoutProps, inPopover) => {
6782
- store.dispatch(_extends({
6783
- type: "PressKey",
6784
- key
6785
- }, layoutProps, {
6786
- inPopover
6787
- }));
6802
+ store.dispatch(pressKey(key, layoutProps.borders, layoutProps.initialBounds, inPopover));
6788
6803
  }
6789
6804
  }, [], [Keys.BACKSPACE, Keys.UP, Keys.RIGHT, Keys.DOWN, Keys.LEFT]);
6790
6805
  };
@@ -6829,136 +6844,6 @@ const createStore = () => {
6829
6844
  return state;
6830
6845
  }
6831
6846
  };
6832
-
6833
- // Used to generate unique animation IDs for the echo animations. The actual
6834
- // values are irrelevant as long as they are unique.
6835
- let _lastAnimationId = 0;
6836
- const initialEchoState = {
6837
- echoes: []
6838
- };
6839
- const echoReducer = function echoReducer(state = initialEchoState, action) {
6840
- switch (action.type) {
6841
- case "PressKey":
6842
- const keyConfig = KeyConfigs[action.key];
6843
-
6844
- // Add in the echo animation if the user performs a math
6845
- // operation.
6846
- if (keyConfig.type === KeyTypes.VALUE || keyConfig.type === KeyTypes.OPERATOR) {
6847
- return _extends({}, state, {
6848
- echoes: [...state.echoes, {
6849
- animationId: "" + _lastAnimationId++,
6850
- animationType: action.inPopover ? EchoAnimationTypes.LONG_FADE_ONLY : EchoAnimationTypes.FADE_ONLY,
6851
- borders: action.borders,
6852
- id: keyConfig.id,
6853
- initialBounds: action.initialBounds
6854
- }]
6855
- });
6856
- }
6857
- return state;
6858
- case "RemoveEcho":
6859
- const remainingEchoes = state.echoes.filter(echo => {
6860
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'animationId' does not exist on type 'never'.
6861
- return echo.animationId !== action.animationId;
6862
- });
6863
- return _extends({}, state, {
6864
- echoes: remainingEchoes
6865
- });
6866
- default:
6867
- return state;
6868
- }
6869
- };
6870
- const initialLayoutState = {
6871
- gridDimensions: {
6872
- numRows: keypadForType[defaultKeypadType].rows,
6873
- numColumns: keypadForType[defaultKeypadType].columns,
6874
- numMaxVisibleRows: keypadForType[defaultKeypadType].maxVisibleRows,
6875
- numPages: keypadForType[defaultKeypadType].numPages
6876
- },
6877
- buttonDimensions: {
6878
- widthPx: 48,
6879
- heightPx: 48
6880
- },
6881
- pageDimensions: {
6882
- pageWidthPx: 0,
6883
- pageHeightPx: 0
6884
- },
6885
- layoutMode: LayoutModes.FULLSCREEN,
6886
- paginationEnabled: false,
6887
- navigationPadEnabled: false
6888
- };
6889
-
6890
- /**
6891
- * Compute the additional layout state based on the provided page and grid
6892
- * dimensions.
6893
- */
6894
- const layoutParametersForDimensions = (pageDimensions, gridDimensions) => {
6895
- const {
6896
- pageWidthPx,
6897
- pageHeightPx
6898
- } = pageDimensions;
6899
-
6900
- // Determine the device type and orientation.
6901
- const deviceOrientation = pageWidthPx > pageHeightPx ? DeviceOrientations.LANDSCAPE : DeviceOrientations.PORTRAIT;
6902
- const deviceType = Math.min(pageWidthPx, pageHeightPx) > tabletCutoffPx ? DeviceTypes.TABLET : DeviceTypes.PHONE;
6903
-
6904
- // Using that information, make some decisions (or assumptions)
6905
- // about the resulting layout.
6906
- const navigationPadEnabled = deviceType === DeviceTypes.TABLET;
6907
- const paginationEnabled = deviceType === DeviceTypes.PHONE && deviceOrientation === DeviceOrientations.PORTRAIT;
6908
- const deviceInfo = {
6909
- deviceOrientation,
6910
- deviceType
6911
- };
6912
- const layoutOptions = {
6913
- navigationPadEnabled,
6914
- paginationEnabled,
6915
- // HACK(charlie): It's not great that we're making assumptions about
6916
- // the toolbar (which is rendered by webapp, and should always be
6917
- // visible and anchored to the bottom of the page for phone and
6918
- // tablet exercises). But this is primarily a heuristic (the goal is
6919
- // to preserve a 'good' amount of space between the top of the
6920
- // keypad and the top of the page) so we afford to have some margin
6921
- // of error.
6922
- toolbarEnabled: true
6923
- };
6924
- return _extends({}, computeLayoutParameters(gridDimensions, pageDimensions, deviceInfo, layoutOptions), {
6925
- // Pass along some of the layout information, so that other
6926
- // components in the heirarchy can adapt appropriately.
6927
- navigationPadEnabled,
6928
- paginationEnabled
6929
- });
6930
- };
6931
- const layoutReducer = function layoutReducer(state = initialLayoutState, action) {
6932
- switch (action.type) {
6933
- case "ConfigureKeypad":
6934
- const {
6935
- keypadType
6936
- } = action.configuration;
6937
- const gridDimensions = {
6938
- numRows: keypadForType[keypadType].rows,
6939
- numColumns: keypadForType[keypadType].columns,
6940
- numMaxVisibleRows: keypadForType[keypadType].maxVisibleRows,
6941
- numPages: keypadForType[keypadType].numPages
6942
- };
6943
- return _extends({}, state, layoutParametersForDimensions(state.pageDimensions, gridDimensions), {
6944
- gridDimensions
6945
- });
6946
- case "SetPageSize":
6947
- const {
6948
- pageWidthPx,
6949
- pageHeightPx
6950
- } = action;
6951
- const pageDimensions = {
6952
- pageWidthPx,
6953
- pageHeightPx
6954
- };
6955
- return _extends({}, state, layoutParametersForDimensions(pageDimensions, state.gridDimensions), {
6956
- pageDimensions
6957
- });
6958
- default:
6959
- return state;
6960
- }
6961
- };
6962
6847
  const reducer = Redux.combineReducers({
6963
6848
  input: inputReducer,
6964
6849
  keypad: keypadReducer,
@@ -7025,10 +6910,6 @@ class NavigationPad extends React.Component {
7025
6910
  })));
7026
6911
  }
7027
6912
  }
7028
- NavigationPad.propTypes = {
7029
- roundTopLeft: PropTypes.bool,
7030
- style: PropTypes.any
7031
- };
7032
6913
  const buttonSizePx = 48;
7033
6914
  const borderRadiusPx = 4;
7034
6915
  const borderWidthPx$1 = 1;
@@ -7087,11 +6968,12 @@ const {
7087
6968
  centered,
7088
6969
  fullWidth
7089
6970
  } = Styles;
7090
-
7091
6971
  // eslint-disable-next-line react/no-unsafe
7092
6972
  class KeypadContainer extends React.Component {
7093
6973
  constructor(...args) {
7094
6974
  super(...args);
6975
+ this._resizeTimeout = void 0;
6976
+ this.hasMounted = void 0;
7095
6977
  this.state = {
7096
6978
  hasBeenActivated: false,
7097
6979
  viewportWidth: "100vw"
@@ -7100,19 +6982,20 @@ class KeypadContainer extends React.Component {
7100
6982
  // Throttle the resize callbacks.
7101
6983
  // https://developer.mozilla.org/en-US/docs/Web/Events/resize
7102
6984
  if (this._resizeTimeout == null) {
7103
- this._resizeTimeout = setTimeout(() => {
6985
+ this._resizeTimeout = window.setTimeout(() => {
7104
6986
  this._resizeTimeout = null;
7105
6987
  this._onResize();
7106
6988
  }, 66);
7107
6989
  }
7108
6990
  };
7109
6991
  this._onResize = () => {
6992
+ var _this$props$onPageSiz, _this$props;
7110
6993
  // Whenever the page resizes, we need to force an update, as the button
7111
6994
  // heights and keypad width are computed based on horizontal space.
7112
6995
  this.setState({
7113
6996
  viewportWidth: window.innerWidth
7114
6997
  });
7115
- this.props.onPageSizeChange(window.innerWidth, window.innerHeight);
6998
+ (_this$props$onPageSiz = (_this$props = this.props).onPageSizeChange) == null ? void 0 : _this$props$onPageSiz.call(_this$props, window.innerWidth, window.innerHeight);
7116
6999
  };
7117
7000
  this.renderKeypad = () => {
7118
7001
  const {
@@ -7194,7 +7077,10 @@ class KeypadContainer extends React.Component {
7194
7077
  // NOTE(charlie): We render the transforms as pure inline styles to
7195
7078
  // avoid an Aphrodite bug in mobile Safari.
7196
7079
  // See: https://github.com/Khan/aphrodite/issues/68.
7197
- const dynamicStyle = _extends({}, active ? inlineStyles.active : inlineStyles.hidden, !active && !hasBeenActivated ? inlineStyles.invisible : {});
7080
+ let dynamicStyle = _extends({}, active ? inlineStyles.active : inlineStyles.hidden);
7081
+ if (!active && !hasBeenActivated) {
7082
+ dynamicStyle = _extends({}, dynamicStyle, inlineStyles.invisible);
7083
+ }
7198
7084
  const keypadContainerStyle = [row, centered, fullWidth, styles.keypadContainer, ...(Array.isArray(style) ? style : [style])];
7199
7085
  const keypadStyle = [row, styles.keypadBorder, layoutMode === LayoutModes.FULLSCREEN ? styles.fullscreen : styles.compact];
7200
7086
 
@@ -7221,19 +7107,6 @@ class KeypadContainer extends React.Component {
7221
7107
  }, this.renderKeypad())));
7222
7108
  }
7223
7109
  }
7224
- KeypadContainer.propTypes = {
7225
- active: PropTypes.bool,
7226
- extraKeys: PropTypes.arrayOf(keyIdPropType),
7227
- keypadType: PropTypes.oneOf(Object.keys(KeypadTypes)).isRequired,
7228
- layoutMode: PropTypes.oneOf(Object.keys(LayoutModes)).isRequired,
7229
- navigationPadEnabled: PropTypes.bool.isRequired,
7230
- onDismiss: PropTypes.func,
7231
- // A callback that should be triggered with the root React element on
7232
- // mount.
7233
- onElementMounted: PropTypes.func,
7234
- onPageSizeChange: PropTypes.func.isRequired,
7235
- style: PropTypes.any
7236
- };
7237
7110
  const keypadAnimationDurationMs = 300;
7238
7111
  const borderWidthPx = 1;
7239
7112
  const styles = StyleSheet.create({
@@ -7293,10 +7166,13 @@ const inlineStyles = {
7293
7166
  }
7294
7167
  };
7295
7168
  const mapStateToProps = state => {
7296
- return _extends({}, state.keypad, {
7169
+ return {
7170
+ extraKeys: state.keypad.extraKeys,
7171
+ keypadType: state.keypad.keypadType,
7172
+ active: state.keypad.active,
7297
7173
  layoutMode: state.layout.layoutMode,
7298
7174
  navigationPadEnabled: state.layout.navigationPadEnabled
7299
- });
7175
+ };
7300
7176
  };
7301
7177
  const mapDispatchToProps = dispatch => {
7302
7178
  return {
@@ -7309,11 +7185,9 @@ var KeypadContainer$1 = connect(mapStateToProps, mapDispatchToProps, null, {
7309
7185
  forwardRef: true
7310
7186
  })(KeypadContainer);
7311
7187
 
7312
- const _excluded = ["onElementMounted"];
7313
7188
  class ProvidedKeypad extends React.Component {
7314
- constructor(...args) {
7315
- super(...args);
7316
- this.mounted = void 0;
7189
+ constructor(props) {
7190
+ super(props);
7317
7191
  this.store = void 0;
7318
7192
  this.activate = () => {
7319
7193
  this.store.dispatch(activateKeypad());
@@ -7342,25 +7216,17 @@ class ProvidedKeypad extends React.Component {
7342
7216
  this.getDOMNode = () => {
7343
7217
  return ReactDOM.findDOMNode(this);
7344
7218
  };
7345
- }
7346
- UNSAFE_componentWillMount() {
7347
7219
  this.store = createStore();
7348
7220
  }
7349
- componentDidMount() {
7350
- this.mounted = true;
7351
- }
7352
- componentWillUnmount() {
7353
- this.mounted = false;
7354
- }
7355
7221
  render() {
7356
- const _this$props = this.props,
7357
- {
7358
- onElementMounted
7359
- } = _this$props,
7360
- rest = _objectWithoutPropertiesLoose(_this$props, _excluded);
7222
+ const {
7223
+ onElementMounted,
7224
+ onDismiss,
7225
+ style
7226
+ } = this.props;
7361
7227
  return /*#__PURE__*/React.createElement(Provider, {
7362
7228
  store: this.store
7363
- }, /*#__PURE__*/React.createElement(KeypadContainer$1, _extends({
7229
+ }, /*#__PURE__*/React.createElement(KeypadContainer$1, {
7364
7230
  onElementMounted: element => {
7365
7231
  // Append the dispatch methods that we want to expose
7366
7232
  // externally to the returned React element.
@@ -7373,10 +7239,12 @@ class ProvidedKeypad extends React.Component {
7373
7239
  getDOMNode: this.getDOMNode
7374
7240
  });
7375
7241
  onElementMounted && onElementMounted(elementWithDispatchMethods);
7376
- }
7377
- }, rest)));
7242
+ },
7243
+ onDismiss: onDismiss,
7244
+ style: style
7245
+ }));
7378
7246
  }
7379
7247
  }
7380
7248
 
7381
- export { CursorContexts, KeyConfigs, ProvidedKeypad as Keypad, MathInput as KeypadInput, KeypadTypes, keypadConfigurationPropType, keypadElementPropType };
7249
+ export { cursorContexts as CursorContexts, KeyConfigs, ProvidedKeypad as Keypad, MathInput as KeypadInput, KeypadTypes, keypadElementPropType };
7382
7250
  //# sourceMappingURL=index.js.map