@khanacademy/math-input 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -1
  3. package/{build/math-input.css → dist/es/index.css} +0 -150
  4. package/dist/es/index.js +2 -0
  5. package/dist/es/index.js.map +1 -0
  6. package/dist/index.css +586 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.flow +2 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/strings.js +71 -0
  12. package/index.html +20 -0
  13. package/less/echo.less +56 -0
  14. package/less/main.less +5 -0
  15. package/less/overrides.less +129 -0
  16. package/less/popover.less +22 -0
  17. package/less/tabbar.less +6 -0
  18. package/package.json +38 -70
  19. package/src/actions/index.js +57 -0
  20. package/src/components/__tests__/gesture-state-machine_test.js +437 -0
  21. package/src/components/__tests__/node-manager_test.js +89 -0
  22. package/src/components/__tests__/two-page-keypad_test.js +42 -0
  23. package/src/components/app.js +73 -0
  24. package/src/components/common-style.js +47 -0
  25. package/src/components/compute-layout-parameters.js +157 -0
  26. package/src/components/corner-decal.js +56 -0
  27. package/src/components/echo-manager.js +160 -0
  28. package/src/components/empty-keypad-button.js +49 -0
  29. package/src/components/expression-keypad.js +323 -0
  30. package/src/components/fraction-keypad.js +176 -0
  31. package/src/components/gesture-manager.js +226 -0
  32. package/src/components/gesture-state-machine.js +283 -0
  33. package/src/components/icon.js +74 -0
  34. package/src/components/iconography/arrow.js +22 -0
  35. package/src/components/iconography/backspace.js +29 -0
  36. package/src/components/iconography/cdot.js +29 -0
  37. package/src/components/iconography/cos.js +30 -0
  38. package/src/components/iconography/cube-root.js +36 -0
  39. package/src/components/iconography/dismiss.js +25 -0
  40. package/src/components/iconography/divide.js +34 -0
  41. package/src/components/iconography/down.js +16 -0
  42. package/src/components/iconography/equal.js +33 -0
  43. package/src/components/iconography/exp-2.js +29 -0
  44. package/src/components/iconography/exp-3.js +29 -0
  45. package/src/components/iconography/exp.js +29 -0
  46. package/src/components/iconography/frac.js +44 -0
  47. package/src/components/iconography/geq.js +33 -0
  48. package/src/components/iconography/gt.js +33 -0
  49. package/src/components/iconography/index.js +45 -0
  50. package/src/components/iconography/jump-into-numerator.js +41 -0
  51. package/src/components/iconography/jump-out-base.js +30 -0
  52. package/src/components/iconography/jump-out-denominator.js +41 -0
  53. package/src/components/iconography/jump-out-exponent.js +30 -0
  54. package/src/components/iconography/jump-out-numerator.js +41 -0
  55. package/src/components/iconography/jump-out-parentheses.js +33 -0
  56. package/src/components/iconography/left-paren.js +33 -0
  57. package/src/components/iconography/left.js +16 -0
  58. package/src/components/iconography/leq.js +33 -0
  59. package/src/components/iconography/ln.js +29 -0
  60. package/src/components/iconography/log-n.js +29 -0
  61. package/src/components/iconography/log.js +29 -0
  62. package/src/components/iconography/lt.js +33 -0
  63. package/src/components/iconography/minus.js +32 -0
  64. package/src/components/iconography/neq.js +33 -0
  65. package/src/components/iconography/parens.js +33 -0
  66. package/src/components/iconography/percent.js +49 -0
  67. package/src/components/iconography/period.js +26 -0
  68. package/src/components/iconography/plus.js +32 -0
  69. package/src/components/iconography/radical.js +36 -0
  70. package/src/components/iconography/right-paren.js +33 -0
  71. package/src/components/iconography/right.js +16 -0
  72. package/src/components/iconography/sin.js +30 -0
  73. package/src/components/iconography/sqrt.js +32 -0
  74. package/src/components/iconography/tan.js +30 -0
  75. package/src/components/iconography/times.js +33 -0
  76. package/src/components/iconography/up.js +16 -0
  77. package/src/components/input/__tests__/context-tracking_test.js +177 -0
  78. package/src/components/input/__tests__/math-wrapper.jsx +33 -0
  79. package/src/components/input/__tests__/mathquill_test.js +747 -0
  80. package/src/components/input/cursor-contexts.js +29 -0
  81. package/src/components/input/cursor-handle.js +137 -0
  82. package/src/components/input/drag-listener.js +75 -0
  83. package/src/components/input/math-input.js +924 -0
  84. package/src/components/input/math-wrapper.js +959 -0
  85. package/src/components/input/scroll-into-view.js +72 -0
  86. package/src/components/keypad/button-assets.js +492 -0
  87. package/src/components/keypad/button.js +106 -0
  88. package/src/components/keypad/button.stories.js +27 -0
  89. package/src/components/keypad/index.js +64 -0
  90. package/src/components/keypad/keypad-page-items.js +106 -0
  91. package/src/components/keypad/keypad-pages.stories.js +32 -0
  92. package/src/components/keypad/keypad.stories.js +35 -0
  93. package/src/components/keypad/numeric-input-page.js +100 -0
  94. package/src/components/keypad/pre-algebra-page.js +98 -0
  95. package/src/components/keypad/trigonometry-page.js +90 -0
  96. package/src/components/keypad-button.js +366 -0
  97. package/src/components/keypad-container.js +303 -0
  98. package/src/components/keypad.js +154 -0
  99. package/src/components/many-keypad-button.js +44 -0
  100. package/src/components/math-icon.js +65 -0
  101. package/src/components/multi-symbol-grid.js +182 -0
  102. package/src/components/multi-symbol-popover.js +59 -0
  103. package/src/components/navigation-pad.js +139 -0
  104. package/src/components/node-manager.js +129 -0
  105. package/src/components/popover-manager.js +76 -0
  106. package/src/components/popover-state-machine.js +173 -0
  107. package/src/components/prop-types.js +82 -0
  108. package/src/components/provided-keypad.js +99 -0
  109. package/src/components/styles.js +38 -0
  110. package/src/components/svg-icon.js +25 -0
  111. package/src/components/tabbar/__tests__/tabbar_test.js +65 -0
  112. package/src/components/tabbar/icons.js +69 -0
  113. package/src/components/tabbar/item.js +138 -0
  114. package/src/components/tabbar/tabbar.js +61 -0
  115. package/src/components/tabbar/tabbar.stories.js +60 -0
  116. package/src/components/tabbar/types.js +3 -0
  117. package/src/components/text-icon.js +52 -0
  118. package/src/components/touchable-keypad-button.js +146 -0
  119. package/src/components/two-page-keypad.js +99 -0
  120. package/src/components/velocity-tracker.js +76 -0
  121. package/src/components/z-indexes.js +9 -0
  122. package/src/consts.js +74 -0
  123. package/src/data/key-configs.js +349 -0
  124. package/src/data/keys.js +72 -0
  125. package/src/demo.js +8 -0
  126. package/src/fake-react-native-web/index.js +12 -0
  127. package/src/fake-react-native-web/text.js +56 -0
  128. package/src/fake-react-native-web/view.js +91 -0
  129. package/src/index.js +13 -0
  130. package/src/native-app.js +84 -0
  131. package/src/store/index.js +505 -0
  132. package/src/utils.js +18 -0
  133. package/tools/svg-to-react/convert.py +111 -0
  134. package/tools/svg-to-react/icons/math-keypad-icon-0.svg +32 -0
  135. package/tools/svg-to-react/icons/math-keypad-icon-1.svg +32 -0
  136. package/tools/svg-to-react/icons/math-keypad-icon-2.svg +32 -0
  137. package/tools/svg-to-react/icons/math-keypad-icon-3.svg +32 -0
  138. package/tools/svg-to-react/icons/math-keypad-icon-4.svg +32 -0
  139. package/tools/svg-to-react/icons/math-keypad-icon-5.svg +32 -0
  140. package/tools/svg-to-react/icons/math-keypad-icon-6.svg +32 -0
  141. package/tools/svg-to-react/icons/math-keypad-icon-7.svg +32 -0
  142. package/tools/svg-to-react/icons/math-keypad-icon-8.svg +32 -0
  143. package/tools/svg-to-react/icons/math-keypad-icon-9.svg +32 -0
  144. package/tools/svg-to-react/icons/math-keypad-icon-addition.svg +34 -0
  145. package/tools/svg-to-react/icons/math-keypad-icon-cos.svg +38 -0
  146. package/tools/svg-to-react/icons/math-keypad-icon-delete.svg +36 -0
  147. package/tools/svg-to-react/icons/math-keypad-icon-dismiss.svg +36 -0
  148. package/tools/svg-to-react/icons/math-keypad-icon-division.svg +36 -0
  149. package/tools/svg-to-react/icons/math-keypad-icon-equals-not.svg +50 -0
  150. package/tools/svg-to-react/icons/math-keypad-icon-equals.svg +48 -0
  151. package/tools/svg-to-react/icons/math-keypad-icon-exponent-2.svg +38 -0
  152. package/tools/svg-to-react/icons/math-keypad-icon-exponent-3.svg +38 -0
  153. package/tools/svg-to-react/icons/math-keypad-icon-exponent.svg +38 -0
  154. package/tools/svg-to-react/icons/math-keypad-icon-fraction.svg +42 -0
  155. package/tools/svg-to-react/icons/math-keypad-icon-greater-than.svg +46 -0
  156. package/tools/svg-to-react/icons/math-keypad-icon-jump-out-base.svg +44 -0
  157. package/tools/svg-to-react/icons/math-keypad-icon-jump-out-denominator.svg +48 -0
  158. package/tools/svg-to-react/icons/math-keypad-icon-jump-out-exponent.svg +44 -0
  159. package/tools/svg-to-react/icons/math-keypad-icon-jump-out-parentheses.svg +44 -0
  160. package/tools/svg-to-react/icons/math-keypad-icon-less-than.svg +46 -0
  161. package/tools/svg-to-react/icons/math-keypad-icon-log-10.svg +36 -0
  162. package/tools/svg-to-react/icons/math-keypad-icon-log-e.svg +36 -0
  163. package/tools/svg-to-react/icons/math-keypad-icon-log.svg +38 -0
  164. package/tools/svg-to-react/icons/math-keypad-icon-multiplication-cross.svg +40 -0
  165. package/tools/svg-to-react/icons/math-keypad-icon-multiplication-dot.svg +38 -0
  166. package/tools/svg-to-react/icons/math-keypad-icon-percent.svg +42 -0
  167. package/tools/svg-to-react/icons/math-keypad-icon-radical-2.svg +36 -0
  168. package/tools/svg-to-react/icons/math-keypad-icon-radical-3.svg +38 -0
  169. package/tools/svg-to-react/icons/math-keypad-icon-radical.svg +38 -0
  170. package/tools/svg-to-react/icons/math-keypad-icon-radix-character.svg +32 -0
  171. package/tools/svg-to-react/icons/math-keypad-icon-sin.svg +38 -0
  172. package/tools/svg-to-react/icons/math-keypad-icon-subtraction.svg +32 -0
  173. package/tools/svg-to-react/icons/math-keypad-icon-tan.svg +38 -0
  174. package/tools/svg-to-react/symbol_map.py +41 -0
  175. package/LICENSE.txt +0 -21
  176. package/build/math-input.js +0 -1
@@ -0,0 +1,157 @@
1
+ /**
2
+ * An algorithm for computing the appropriate layout parameters for the keypad,
3
+ * including the size of the buttons and whether or not to render fullscreen,
4
+ * taking into account a number of factors including the size of the screen, the
5
+ * orientation of the screen, the presence of browser chrome, the presence of
6
+ * other exercise-related chrome, the size of the input box, the parameters that
7
+ * define the keypad (i.e., the number of rows, columns, and pages), and so
8
+ * forth.
9
+ *
10
+ * The computations herein make some strong assumptions about the sizes of
11
+ * various other elements and the situations under which they will be visible
12
+ * (e.g., browser chrome). However, this is just a heuristic--it's not crucial
13
+ * that our buttons are sized in a pixel-perfect manner, but rather, that we
14
+ * make a balanced use of space.
15
+ *
16
+ * Note that one goal of the algorithm is to avoid resizing the keypad in the
17
+ * face of dynamic browser chrome. In order to avoid that awkwardness, we tend
18
+ * to be conservative in our measurements and make things smaller than they
19
+ * might need to be.
20
+ */
21
+
22
+ import {DeviceTypes, DeviceOrientations, LayoutModes} from "../consts.js";
23
+
24
+ import {
25
+ pageIndicatorHeightPx,
26
+ toolbarHeightPx,
27
+ navigationPadWidthPx,
28
+ innerBorderWidthPx,
29
+ } from "./common-style.js";
30
+
31
+ const minButtonHeight = 48;
32
+ const maxButtonSize = 64;
33
+ const minSpaceAboveKeypad = 32;
34
+
35
+ // These values are taken from an iPhone 5, but should be consistent with the
36
+ // iPhone 4 as well. Regardless, these are meant to be representative of the
37
+ // possible types of browser chrome that could appear in various context, rather
38
+ // than pixel-perfect for every device.
39
+ const safariNavBarWhenShrunk = 44;
40
+ const safariNavBarWhenExpanded = 64;
41
+ const safariToolbar = 44;
42
+
43
+ // In mobile Safari, the browser chrome is completely hidden in landscape,
44
+ // though a shrunken navbar and full-sized toolbar on scroll. In portrait, the
45
+ // shrunken navbar is always visible, but expands on scroll (and the toolbar
46
+ // appears as well).
47
+ const maxLandscapeBrowserChrome = safariNavBarWhenShrunk + safariToolbar;
48
+ const maxPortraitBrowserChrome =
49
+ safariToolbar + (safariNavBarWhenExpanded - safariNavBarWhenShrunk);
50
+
51
+ // This represents the 'worst case' aspect ratio that we care about (for
52
+ // portrait layouts). It's taken from the iPhone 4. The height is computed by
53
+ // taking the height of the device and removing the persistent, shrunken navbar.
54
+ // (We don't need to account for the expanded navbar, since we include the
55
+ // difference when reserving space above the keypad.)
56
+ const worstCaseAspectRatio = 320 / (480 - safariNavBarWhenShrunk);
57
+
58
+ export const computeLayoutParameters = (
59
+ {numColumns, numMaxVisibleRows, numPages},
60
+ {pageWidthPx, pageHeightPx},
61
+ {deviceOrientation, deviceType},
62
+ {navigationPadEnabled, paginationEnabled, toolbarEnabled},
63
+ ) => {
64
+ // First, compute some values that will be used in multiple computations.
65
+ const effectiveNumColumns = paginationEnabled
66
+ ? numColumns
67
+ : numColumns * numPages;
68
+
69
+ // Then, compute the button dimensions based on the provided parameters.
70
+ let buttonDimensions;
71
+ if (deviceType === DeviceTypes.PHONE) {
72
+ const isLandscape = deviceOrientation === DeviceOrientations.LANDSCAPE;
73
+
74
+ // In many cases, the browser chrome will already have been factored
75
+ // into `pageHeightPx`. But we have no way of knowing if that's
76
+ // the case or not. As such, we take a conservative approach and
77
+ // assume that the chrome is _never_ included in `pageHeightPx`.
78
+ const browserChromeHeight = isLandscape
79
+ ? maxLandscapeBrowserChrome
80
+ : maxPortraitBrowserChrome;
81
+
82
+ // Count up all the space that we need to reserve on the page.
83
+ // Namely, we need to account for:
84
+ // 1. Space between the keypad and the top of the page.
85
+ // 2. The presence of the exercise toolbar.
86
+ // 3. The presence of the view pager indicator.
87
+ // 4. Any browser chrome that may appear later.
88
+ const reservedSpace =
89
+ minSpaceAboveKeypad +
90
+ browserChromeHeight +
91
+ (toolbarEnabled ? toolbarHeightPx : 0) +
92
+ (paginationEnabled ? pageIndicatorHeightPx : 0);
93
+
94
+ // Next, compute the effective width and height. We can use the page
95
+ // width as the effective width. For the height, though, we take
96
+ // another conservative measure when in portrait by assuming that
97
+ // the device has the worst possible aspect ratio. In other words,
98
+ // we ignore the device height in portrait and assume the worst.
99
+ // This prevents the keypad from changing size when browser chrome
100
+ // appears and disappears.
101
+ const effectiveWidth = pageWidthPx;
102
+ const effectiveHeight = isLandscape
103
+ ? pageHeightPx
104
+ : pageWidthPx / worstCaseAspectRatio;
105
+ const maxKeypadHeight = effectiveHeight - reservedSpace;
106
+
107
+ // Finally, compute the button height and width. In computing the
108
+ // height, accommodate for the maximum number of rows that will ever be
109
+ // visible (since the toggling of popovers can increase the number of
110
+ // visible rows).
111
+ const buttonHeightPx = Math.max(
112
+ Math.min(maxKeypadHeight / numMaxVisibleRows, maxButtonSize),
113
+ minButtonHeight,
114
+ );
115
+
116
+ let buttonWidthPx;
117
+ if (numPages > 1) {
118
+ const effectiveNumColumns = paginationEnabled
119
+ ? numColumns
120
+ : numColumns * numPages;
121
+ buttonWidthPx = effectiveWidth / effectiveNumColumns;
122
+ } else {
123
+ buttonWidthPx = isLandscape
124
+ ? maxButtonSize
125
+ : effectiveWidth / numColumns;
126
+ }
127
+
128
+ buttonDimensions = {
129
+ widthPx: buttonWidthPx,
130
+ heightPx: buttonHeightPx,
131
+ };
132
+ } else if (deviceType === DeviceTypes.TABLET) {
133
+ buttonDimensions = {
134
+ widthPx: maxButtonSize,
135
+ heightPx: maxButtonSize,
136
+ };
137
+ } else {
138
+ throw new Error("Invalid device type: " + deviceType);
139
+ }
140
+
141
+ // Finally, determine whether the keypad should be rendered in the
142
+ // fullscreen layout by determining its resultant width.
143
+ const numSeparators =
144
+ (navigationPadEnabled ? 1 : 0) +
145
+ (!paginationEnabled ? numPages - 1 : 0);
146
+ const keypadWidth =
147
+ effectiveNumColumns * buttonDimensions.widthPx +
148
+ (navigationPadEnabled ? navigationPadWidthPx : 0) +
149
+ numSeparators * innerBorderWidthPx;
150
+ return {
151
+ buttonDimensions,
152
+ layoutMode:
153
+ keypadWidth >= pageWidthPx
154
+ ? LayoutModes.FULLSCREEN
155
+ : LayoutModes.COMPACT,
156
+ };
157
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * A small triangular decal to sit in the corner of a parent component.
3
+ */
4
+
5
+ import {StyleSheet} from "aphrodite";
6
+ import PropTypes from "prop-types";
7
+ import * as React from "react";
8
+
9
+ import {View} from "../fake-react-native-web/index.js";
10
+
11
+ import {offBlack} from "./common-style.js";
12
+
13
+ class CornerDecal extends React.Component {
14
+ static propTypes = {
15
+ style: PropTypes.any,
16
+ };
17
+
18
+ render() {
19
+ const {style} = this.props;
20
+
21
+ const containerStyle = [
22
+ styles.container,
23
+ ...(Array.isArray(style) ? style : [style]),
24
+ ];
25
+
26
+ return (
27
+ <View style={containerStyle}>
28
+ <svg
29
+ width={triangleSizePx}
30
+ height={triangleSizePx}
31
+ viewBox="4 4 8 8"
32
+ >
33
+ <path
34
+ fill={offBlack}
35
+ opacity="0.3"
36
+ 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
37
+ />
38
+ </svg>
39
+ </View>
40
+ );
41
+ }
42
+ }
43
+
44
+ const triangleSizePx = 7;
45
+
46
+ const styles = StyleSheet.create({
47
+ container: {
48
+ position: "absolute",
49
+ top: 0,
50
+ right: 0,
51
+ width: triangleSizePx,
52
+ height: triangleSizePx,
53
+ },
54
+ });
55
+
56
+ export default CornerDecal;
@@ -0,0 +1,160 @@
1
+ /**
2
+ * A component that renders and animates the selection state effect effect.
3
+ */
4
+
5
+ import PropTypes from "prop-types";
6
+ import * as React from "react";
7
+ import {TransitionGroup, CSSTransition} from "react-transition-group";
8
+
9
+ import {KeyTypes, EchoAnimationTypes} from "../consts.js";
10
+ import KeyConfigs from "../data/key-configs.js";
11
+
12
+ import KeypadButton from "./keypad-button.js";
13
+ import {
14
+ echoPropType,
15
+ bordersPropType,
16
+ boundingBoxPropType,
17
+ keyIdPropType,
18
+ } from "./prop-types.js";
19
+ import * as zIndexes from "./z-indexes.js";
20
+
21
+ class Echo extends React.Component {
22
+ static propTypes = {
23
+ animationDurationMs: PropTypes.number.isRequired,
24
+ borders: bordersPropType,
25
+ id: keyIdPropType.isRequired,
26
+ initialBounds: boundingBoxPropType.isRequired,
27
+ onAnimationFinish: PropTypes.func.isRequired,
28
+ };
29
+
30
+ componentDidMount() {
31
+ // NOTE(charlie): This is somewhat unfortunate, as the component is
32
+ // encoding information about its own animation, of which it should be
33
+ // ignorant. However, there doesn't seem to be a cleaner way to make
34
+ // this happen, and at least here, all the animation context is
35
+ // colocated in this file.
36
+ const {animationDurationMs, onAnimationFinish} = this.props;
37
+ setTimeout(() => onAnimationFinish(), animationDurationMs);
38
+ }
39
+
40
+ render() {
41
+ const {borders, id, initialBounds} = this.props;
42
+ const {icon} = KeyConfigs[id];
43
+
44
+ const containerStyle = {
45
+ zIndex: zIndexes.echo,
46
+ position: "absolute",
47
+ pointerEvents: "none",
48
+ ...initialBounds,
49
+ };
50
+
51
+ // NOTE(charlie): In some browsers, Aphrodite doesn't seem to flush its
52
+ // styles quickly enough, so there's a flickering effect on the first
53
+ // animation. Thus, it's much safer to do the styles purely inline.
54
+ // <View> makes this difficult because some of its defaults, which are
55
+ // applied via StyleSheet, will override our inlines.
56
+ return (
57
+ <div style={containerStyle}>
58
+ <KeypadButton
59
+ name={id}
60
+ icon={icon}
61
+ type={KeyTypes.ECHO}
62
+ borders={borders}
63
+ />
64
+ </div>
65
+ );
66
+ }
67
+ }
68
+
69
+ class EchoManager extends React.Component {
70
+ static propTypes = {
71
+ echoes: PropTypes.arrayOf(echoPropType),
72
+ onAnimationFinish: PropTypes.func.isRequired,
73
+ };
74
+
75
+ _animationConfigForType = (animationType) => {
76
+ // NOTE(charlie): These must be kept in sync with the transition
77
+ // durations and classnames specified in echo.css.
78
+ let animationDurationMs;
79
+ let animationTransitionName;
80
+
81
+ switch (animationType) {
82
+ case EchoAnimationTypes.SLIDE_AND_FADE:
83
+ animationDurationMs = 400;
84
+ animationTransitionName = "echo-slide-and-fade";
85
+ break;
86
+
87
+ case EchoAnimationTypes.FADE_ONLY:
88
+ animationDurationMs = 300;
89
+ animationTransitionName = "echo-fade-only";
90
+ break;
91
+
92
+ case EchoAnimationTypes.LONG_FADE_ONLY:
93
+ animationDurationMs = 400;
94
+ animationTransitionName = "echo-long-fade-only";
95
+ break;
96
+
97
+ default:
98
+ throw new Error("Invalid echo animation type:", animationType);
99
+ }
100
+
101
+ return {
102
+ animationDurationMs,
103
+ animationTransitionName,
104
+ };
105
+ };
106
+
107
+ render() {
108
+ const {echoes, onAnimationFinish} = this.props;
109
+
110
+ return (
111
+ <span>
112
+ {Object.keys(EchoAnimationTypes).map((animationType) => {
113
+ // Collect the relevant parameters for the animation type, and
114
+ // filter for the appropriate echoes.
115
+ const {animationDurationMs, animationTransitionName} =
116
+ this._animationConfigForType(animationType);
117
+ const echoesForType = echoes.filter((echo) => {
118
+ return echo.animationType === animationType;
119
+ });
120
+
121
+ // TODO(charlie): Manage this animation with Aphrodite styles.
122
+ // Right now, there's a bug in the autoprefixer that breaks CSS
123
+ // transitions on mobile Safari.
124
+ // See: https://github.com/Khan/aphrodite/issues/68.
125
+ // As such, we have to do this with a stylesheet.
126
+ return (
127
+ <TransitionGroup key={animationType}>
128
+ {echoesForType.map((echo) => {
129
+ const {animationId} = echo;
130
+ return (
131
+ <CSSTransition
132
+ classNames={animationTransitionName}
133
+ enter={true}
134
+ exit={false}
135
+ timeout={{
136
+ enter: animationDurationMs,
137
+ }}
138
+ key={animationId}
139
+ >
140
+ <Echo
141
+ animationDurationMs={
142
+ animationDurationMs
143
+ }
144
+ onAnimationFinish={() =>
145
+ onAnimationFinish(animationId)
146
+ }
147
+ {...echo}
148
+ />
149
+ </CSSTransition>
150
+ );
151
+ })}
152
+ </TransitionGroup>
153
+ );
154
+ })}
155
+ </span>
156
+ );
157
+ }
158
+ }
159
+
160
+ export default EchoManager;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * A keypad button containing no symbols and triggering no actions on click.
3
+ */
4
+
5
+ import PropTypes from "prop-types";
6
+ import * as React from "react";
7
+ import {connect} from "react-redux";
8
+
9
+ import KeyConfigs from "../data/key-configs.js";
10
+
11
+ import GestureManager from "./gesture-manager.js";
12
+ import KeypadButton from "./keypad-button.js";
13
+
14
+ class EmptyKeypadButton extends React.Component {
15
+ static propTypes = {
16
+ gestureManager: PropTypes.instanceOf(GestureManager),
17
+ };
18
+
19
+ render() {
20
+ const {gestureManager, ...rest} = this.props;
21
+
22
+ // Register touch events on the button, but don't register its DOM node
23
+ // or compute focus state or anything like that. We want the gesture
24
+ // manager to know about touch events that start on empty buttons, but
25
+ // we don't need it to know about their DOM nodes, as it doesn't need
26
+ // to focus them or trigger presses.
27
+ return (
28
+ <KeypadButton
29
+ onTouchStart={(evt) => gestureManager.onTouchStart(evt)}
30
+ onTouchEnd={(evt) => gestureManager.onTouchEnd(evt)}
31
+ onTouchMove={(evt) => gestureManager.onTouchMove(evt)}
32
+ onTouchCancel={(evt) => gestureManager.onTouchCancel(evt)}
33
+ {...KeyConfigs.NOOP}
34
+ {...rest}
35
+ />
36
+ );
37
+ }
38
+ }
39
+
40
+ const mapStateToProps = (state) => {
41
+ const {gestures} = state;
42
+ return {
43
+ gestureManager: gestures.gestureManager,
44
+ };
45
+ };
46
+
47
+ export default connect(mapStateToProps, null, null, {forwardRef: true})(
48
+ EmptyKeypadButton,
49
+ );