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