@khanacademy/math-input 0.4.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +1 -1
- package/{build/math-input.css → dist/es/index.css} +0 -150
- package/dist/es/index.js +7798 -0
- package/dist/es/index.js.map +1 -0
- package/dist/index.css +586 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7768 -0
- package/dist/index.js.flow +2 -0
- package/dist/index.js.map +1 -0
- package/dist/strings.js +71 -0
- package/index.html +20 -0
- package/less/echo.less +56 -0
- package/less/main.less +5 -0
- package/less/overrides.less +129 -0
- package/less/popover.less +22 -0
- package/less/tabbar.less +6 -0
- package/package.json +60 -89
- package/src/actions/index.js +57 -0
- package/src/components/__tests__/gesture-state-machine_test.js +437 -0
- package/src/components/__tests__/node-manager_test.js +89 -0
- package/src/components/__tests__/two-page-keypad_test.js +42 -0
- package/src/components/app.js +73 -0
- package/src/components/common-style.js +47 -0
- package/src/components/compute-layout-parameters.js +157 -0
- package/src/components/corner-decal.js +56 -0
- package/src/components/echo-manager.js +160 -0
- package/src/components/empty-keypad-button.js +49 -0
- package/src/components/expression-keypad.js +323 -0
- package/src/components/fraction-keypad.js +176 -0
- package/src/components/gesture-manager.js +226 -0
- package/src/components/gesture-state-machine.js +283 -0
- package/src/components/icon.js +74 -0
- package/src/components/iconography/arrow.js +22 -0
- package/src/components/iconography/backspace.js +29 -0
- package/src/components/iconography/cdot.js +29 -0
- package/src/components/iconography/cos.js +30 -0
- package/src/components/iconography/cube-root.js +36 -0
- package/src/components/iconography/dismiss.js +25 -0
- package/src/components/iconography/divide.js +34 -0
- package/src/components/iconography/down.js +16 -0
- package/src/components/iconography/equal.js +33 -0
- package/src/components/iconography/exp-2.js +29 -0
- package/src/components/iconography/exp-3.js +29 -0
- package/src/components/iconography/exp.js +29 -0
- package/src/components/iconography/frac.js +44 -0
- package/src/components/iconography/geq.js +33 -0
- package/src/components/iconography/gt.js +33 -0
- package/src/components/iconography/index.js +45 -0
- package/src/components/iconography/jump-into-numerator.js +41 -0
- package/src/components/iconography/jump-out-base.js +30 -0
- package/src/components/iconography/jump-out-denominator.js +41 -0
- package/src/components/iconography/jump-out-exponent.js +30 -0
- package/src/components/iconography/jump-out-numerator.js +41 -0
- package/src/components/iconography/jump-out-parentheses.js +33 -0
- package/src/components/iconography/left-paren.js +33 -0
- package/src/components/iconography/left.js +16 -0
- package/src/components/iconography/leq.js +33 -0
- package/src/components/iconography/ln.js +29 -0
- package/src/components/iconography/log-n.js +29 -0
- package/src/components/iconography/log.js +29 -0
- package/src/components/iconography/lt.js +33 -0
- package/src/components/iconography/minus.js +32 -0
- package/src/components/iconography/neq.js +33 -0
- package/src/components/iconography/parens.js +33 -0
- package/src/components/iconography/percent.js +49 -0
- package/src/components/iconography/period.js +26 -0
- package/src/components/iconography/plus.js +32 -0
- package/src/components/iconography/radical.js +36 -0
- package/src/components/iconography/right-paren.js +33 -0
- package/src/components/iconography/right.js +16 -0
- package/src/components/iconography/sin.js +30 -0
- package/src/components/iconography/sqrt.js +32 -0
- package/src/components/iconography/tan.js +30 -0
- package/src/components/iconography/times.js +33 -0
- package/src/components/iconography/up.js +16 -0
- package/src/components/input/__tests__/context-tracking_test.js +177 -0
- package/src/components/input/__tests__/math-wrapper.jsx +33 -0
- package/src/components/input/__tests__/mathquill_test.js +747 -0
- package/src/components/input/cursor-contexts.js +29 -0
- package/src/components/input/cursor-handle.js +137 -0
- package/src/components/input/drag-listener.js +75 -0
- package/src/components/input/math-input.js +924 -0
- package/src/components/input/math-wrapper.js +959 -0
- package/src/components/input/scroll-into-view.js +72 -0
- package/src/components/keypad/button-assets.js +492 -0
- package/src/components/keypad/button.js +106 -0
- package/src/components/keypad/button.stories.js +29 -0
- package/src/components/keypad/index.js +64 -0
- package/src/components/keypad/keypad-page-items.js +106 -0
- package/src/components/keypad/keypad-pages.stories.js +32 -0
- package/src/components/keypad/keypad.stories.js +35 -0
- package/src/components/keypad/numeric-input-page.js +100 -0
- package/src/components/keypad/pre-algebra-page.js +98 -0
- package/src/components/keypad/trigonometry-page.js +90 -0
- package/src/components/keypad-button.js +366 -0
- package/src/components/keypad-container.js +303 -0
- package/src/components/keypad.js +154 -0
- package/src/components/many-keypad-button.js +44 -0
- package/src/components/math-icon.js +65 -0
- package/src/components/multi-symbol-grid.js +182 -0
- package/src/components/multi-symbol-popover.js +59 -0
- package/src/components/navigation-pad.js +139 -0
- package/src/components/node-manager.js +129 -0
- package/src/components/popover-manager.js +76 -0
- package/src/components/popover-state-machine.js +173 -0
- package/src/components/prop-types.js +82 -0
- package/src/components/provided-keypad.js +103 -0
- package/src/components/styles.js +38 -0
- package/src/components/svg-icon.js +25 -0
- package/src/components/tabbar/__tests__/tabbar_test.js +65 -0
- package/src/components/tabbar/icons.js +69 -0
- package/src/components/tabbar/item.js +138 -0
- package/src/components/tabbar/tabbar.js +61 -0
- package/src/components/tabbar/tabbar.stories.js +60 -0
- package/src/components/tabbar/types.js +3 -0
- package/src/components/text-icon.js +52 -0
- package/src/components/touchable-keypad-button.js +146 -0
- package/src/components/two-page-keypad.js +99 -0
- package/src/components/velocity-tracker.js +76 -0
- package/src/components/z-indexes.js +9 -0
- package/src/consts.js +74 -0
- package/src/data/key-configs.js +349 -0
- package/src/data/keys.js +72 -0
- package/src/demo.js +8 -0
- package/src/fake-react-native-web/index.js +12 -0
- package/src/fake-react-native-web/text.js +56 -0
- package/src/fake-react-native-web/view.js +91 -0
- package/src/index.js +14 -0
- package/src/native-app.js +84 -0
- package/src/store/index.js +505 -0
- package/src/utils.js +18 -0
- package/tools/svg-to-react/convert.py +111 -0
- package/tools/svg-to-react/icons/math-keypad-icon-0.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-1.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-2.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-3.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-4.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-5.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-6.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-7.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-8.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-9.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-addition.svg +34 -0
- package/tools/svg-to-react/icons/math-keypad-icon-cos.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-delete.svg +36 -0
- package/tools/svg-to-react/icons/math-keypad-icon-dismiss.svg +36 -0
- package/tools/svg-to-react/icons/math-keypad-icon-division.svg +36 -0
- package/tools/svg-to-react/icons/math-keypad-icon-equals-not.svg +50 -0
- package/tools/svg-to-react/icons/math-keypad-icon-equals.svg +48 -0
- package/tools/svg-to-react/icons/math-keypad-icon-exponent-2.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-exponent-3.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-exponent.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-fraction.svg +42 -0
- package/tools/svg-to-react/icons/math-keypad-icon-greater-than.svg +46 -0
- package/tools/svg-to-react/icons/math-keypad-icon-jump-out-base.svg +44 -0
- package/tools/svg-to-react/icons/math-keypad-icon-jump-out-denominator.svg +48 -0
- package/tools/svg-to-react/icons/math-keypad-icon-jump-out-exponent.svg +44 -0
- package/tools/svg-to-react/icons/math-keypad-icon-jump-out-parentheses.svg +44 -0
- package/tools/svg-to-react/icons/math-keypad-icon-less-than.svg +46 -0
- package/tools/svg-to-react/icons/math-keypad-icon-log-10.svg +36 -0
- package/tools/svg-to-react/icons/math-keypad-icon-log-e.svg +36 -0
- package/tools/svg-to-react/icons/math-keypad-icon-log.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-multiplication-cross.svg +40 -0
- package/tools/svg-to-react/icons/math-keypad-icon-multiplication-dot.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-percent.svg +42 -0
- package/tools/svg-to-react/icons/math-keypad-icon-radical-2.svg +36 -0
- package/tools/svg-to-react/icons/math-keypad-icon-radical-3.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-radical.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-radix-character.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-sin.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-subtraction.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-tan.svg +38 -0
- package/tools/svg-to-react/symbol_map.py +41 -0
- package/LICENSE.txt +0 -21
- 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
|
+
);
|