@khanacademy/math-input 14.1.0 → 14.2.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.
- package/CHANGELOG.md +17 -0
- package/dist/components/aphrodite-css-transition-group/index.d.ts +51 -0
- package/dist/components/aphrodite-css-transition-group/transition-child.d.ts +11 -0
- package/dist/components/aphrodite-css-transition-group/types.d.ts +18 -0
- package/dist/components/aphrodite-css-transition-group/util.d.ts +7 -0
- package/dist/components/keypad/mobile-keypad.d.ts +10 -12
- package/dist/es/index.js +324 -54
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +326 -55
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/components/aphrodite-css-transition-group/index.tsx +78 -0
- package/src/components/aphrodite-css-transition-group/transition-child.tsx +191 -0
- package/src/components/aphrodite-css-transition-group/types.ts +20 -0
- package/src/components/aphrodite-css-transition-group/util.ts +97 -0
- package/src/components/keypad/__tests__/__snapshots__/mobile-keypad.test.tsx.snap +593 -0
- package/src/components/keypad/__tests__/mobile-keypad.test.tsx +115 -0
- package/src/components/keypad/mobile-keypad.tsx +66 -79
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {render, screen} from "@testing-library/react";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import "@testing-library/jest-dom";
|
|
4
|
+
|
|
5
|
+
import MobileKeypad from "../mobile-keypad";
|
|
6
|
+
|
|
7
|
+
describe("mobile keypad", () => {
|
|
8
|
+
it("should render keypad when active", () => {
|
|
9
|
+
// Arrange
|
|
10
|
+
// Act
|
|
11
|
+
const {container} = render(
|
|
12
|
+
<MobileKeypad
|
|
13
|
+
onAnalyticsEvent={async () => undefined}
|
|
14
|
+
setKeypadActive={(keypadActive: boolean) => undefined}
|
|
15
|
+
keypadActive={true}
|
|
16
|
+
/>,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// Assert
|
|
20
|
+
expect(container).toMatchSnapshot();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should not render the keypad when not active", () => {
|
|
24
|
+
// Arrange
|
|
25
|
+
// Act
|
|
26
|
+
const {container} = render(
|
|
27
|
+
<MobileKeypad
|
|
28
|
+
onAnalyticsEvent={async () => undefined}
|
|
29
|
+
setKeypadActive={(keypadActive: boolean) => undefined}
|
|
30
|
+
keypadActive={false}
|
|
31
|
+
/>,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Assert
|
|
35
|
+
expect(container).toMatchSnapshot();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should render the keypad when going from keypadActive=false to keypadActive=true", () => {
|
|
39
|
+
// Arrange
|
|
40
|
+
const {rerender} = render(
|
|
41
|
+
<MobileKeypad
|
|
42
|
+
onAnalyticsEvent={async () => undefined}
|
|
43
|
+
setKeypadActive={(keypadActive: boolean) => undefined}
|
|
44
|
+
keypadActive={false}
|
|
45
|
+
/>,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(screen.queryAllByRole("button")).toHaveLength(0);
|
|
49
|
+
|
|
50
|
+
// Act
|
|
51
|
+
rerender(
|
|
52
|
+
<MobileKeypad
|
|
53
|
+
onAnalyticsEvent={async () => undefined}
|
|
54
|
+
setKeypadActive={(keypadActive: boolean) => undefined}
|
|
55
|
+
keypadActive={true}
|
|
56
|
+
/>,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Assert
|
|
60
|
+
expect(screen.queryAllByRole("tab")).not.toHaveLength(0);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should fire an 'opened' event when activated", () => {
|
|
64
|
+
// Arrange
|
|
65
|
+
const onAnalyticsEvent = jest.fn();
|
|
66
|
+
|
|
67
|
+
// Act
|
|
68
|
+
render(
|
|
69
|
+
<MobileKeypad
|
|
70
|
+
onAnalyticsEvent={onAnalyticsEvent}
|
|
71
|
+
setKeypadActive={(keypadActive: boolean) => undefined}
|
|
72
|
+
keypadActive={true}
|
|
73
|
+
/>,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Assert
|
|
77
|
+
expect(onAnalyticsEvent).toHaveBeenCalledWith({
|
|
78
|
+
type: "math-input:keypad-opened",
|
|
79
|
+
payload: {
|
|
80
|
+
virtualKeypadVersion: "MATH_INPUT_KEYPAD_V2",
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should fire an 'closed' event when dismissed", async () => {
|
|
86
|
+
const onAnalyticsEvent = jest.fn();
|
|
87
|
+
|
|
88
|
+
// Arrange
|
|
89
|
+
const {rerender, unmount} = render(
|
|
90
|
+
<MobileKeypad
|
|
91
|
+
onAnalyticsEvent={onAnalyticsEvent}
|
|
92
|
+
setKeypadActive={(keypadActive: boolean) => undefined}
|
|
93
|
+
keypadActive={true}
|
|
94
|
+
/>,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Act
|
|
98
|
+
rerender(
|
|
99
|
+
<MobileKeypad
|
|
100
|
+
onAnalyticsEvent={onAnalyticsEvent}
|
|
101
|
+
setKeypadActive={(keypadActive: boolean) => undefined}
|
|
102
|
+
keypadActive={false}
|
|
103
|
+
/>,
|
|
104
|
+
);
|
|
105
|
+
unmount();
|
|
106
|
+
|
|
107
|
+
// Assert
|
|
108
|
+
expect(onAnalyticsEvent).toHaveBeenCalledWith({
|
|
109
|
+
type: "math-input:keypad-closed",
|
|
110
|
+
payload: {
|
|
111
|
+
virtualKeypadVersion: "MATH_INPUT_KEYPAD_V2",
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -3,6 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import ReactDOM from "react-dom";
|
|
4
4
|
|
|
5
5
|
import {View} from "../../fake-react-native-web/index";
|
|
6
|
+
import AphroditeCssTransitionGroup from "../aphrodite-css-transition-group";
|
|
6
7
|
|
|
7
8
|
import Keypad from "./keypad";
|
|
8
9
|
import {expandedViewThreshold} from "./utils";
|
|
@@ -17,16 +18,7 @@ import type {
|
|
|
17
18
|
import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";
|
|
18
19
|
import type {StyleType} from "@khanacademy/wonder-blocks-core";
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
* This is the v2 equivalent of v1's ProvidedKeypad. It follows the same
|
|
22
|
-
* external API so that it can be hot-swapped with the v1 keypad and
|
|
23
|
-
* is responsible for connecting the keypad with MathInput and the Renderer.
|
|
24
|
-
*
|
|
25
|
-
* Ideally this strategy of attaching methods on the class component for
|
|
26
|
-
* other components to call will be replaced props/callbacks since React
|
|
27
|
-
* doesn't support this type of code anymore (functional components
|
|
28
|
-
* can't have methods attached to them).
|
|
29
|
-
*/
|
|
21
|
+
const AnimationDurationInMS = 200;
|
|
30
22
|
|
|
31
23
|
type Props = {
|
|
32
24
|
onElementMounted?: (arg1: any) => void;
|
|
@@ -39,21 +31,28 @@ type Props = {
|
|
|
39
31
|
|
|
40
32
|
type State = {
|
|
41
33
|
containerWidth: number;
|
|
42
|
-
hasBeenActivated: boolean;
|
|
43
34
|
keypadConfig?: KeypadConfiguration;
|
|
44
35
|
keyHandler?: KeyHandler;
|
|
45
36
|
cursor?: Cursor;
|
|
46
37
|
};
|
|
47
38
|
|
|
39
|
+
/**
|
|
40
|
+
* This is the v2 equivalent of v1's ProvidedKeypad. It follows the same
|
|
41
|
+
* external API so that it can be hot-swapped with the v1 keypad and
|
|
42
|
+
* is responsible for connecting the keypad with MathInput and the Renderer.
|
|
43
|
+
*
|
|
44
|
+
* Ideally this strategy of attaching methods on the class component for
|
|
45
|
+
* other components to call will be replaced props/callbacks since React
|
|
46
|
+
* doesn't support this type of code anymore (functional components
|
|
47
|
+
* can't have methods attached to them).
|
|
48
|
+
*/
|
|
48
49
|
class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
|
|
49
50
|
_containerRef = React.createRef<HTMLDivElement>();
|
|
50
51
|
_containerResizeObserver: ResizeObserver | null = null;
|
|
51
52
|
_throttleResize = false;
|
|
52
|
-
hasMounted = false;
|
|
53
53
|
|
|
54
54
|
state: State = {
|
|
55
55
|
containerWidth: 0,
|
|
56
|
-
hasBeenActivated: false,
|
|
57
56
|
};
|
|
58
57
|
|
|
59
58
|
componentDidMount() {
|
|
@@ -78,6 +77,15 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
|
|
|
78
77
|
);
|
|
79
78
|
}
|
|
80
79
|
}
|
|
80
|
+
|
|
81
|
+
this.props.onElementMounted?.({
|
|
82
|
+
activate: this.activate,
|
|
83
|
+
dismiss: this.dismiss,
|
|
84
|
+
configure: this.configure,
|
|
85
|
+
setCursor: this.setCursor,
|
|
86
|
+
setKeyHandler: this.setKeyHandler,
|
|
87
|
+
getDOMNode: this.getDOMNode,
|
|
88
|
+
});
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
componentWillUnmount() {
|
|
@@ -109,9 +117,6 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
|
|
|
109
117
|
|
|
110
118
|
activate: () => void = () => {
|
|
111
119
|
this.props.setKeypadActive(true);
|
|
112
|
-
this.setState({
|
|
113
|
-
hasBeenActivated: true,
|
|
114
|
-
});
|
|
115
120
|
};
|
|
116
121
|
|
|
117
122
|
dismiss: () => void = () => {
|
|
@@ -161,74 +166,64 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
|
|
|
161
166
|
|
|
162
167
|
render(): React.ReactNode {
|
|
163
168
|
const {keypadActive, style} = this.props;
|
|
164
|
-
const {
|
|
165
|
-
this.state;
|
|
169
|
+
const {containerWidth, cursor, keypadConfig} = this.state;
|
|
166
170
|
|
|
167
171
|
const containerStyle = [
|
|
168
|
-
// internal styles
|
|
169
172
|
styles.keypadContainer,
|
|
170
|
-
keypadActive && styles.activeKeypadContainer,
|
|
171
173
|
// styles passed as props
|
|
172
174
|
...(Array.isArray(style) ? style : [style]),
|
|
173
175
|
];
|
|
174
176
|
|
|
175
|
-
// If the keypad is yet to have ever been activated, we keep it invisible
|
|
176
|
-
// so as to avoid, e.g., the keypad flashing at the bottom of the page
|
|
177
|
-
// during the initial render.
|
|
178
|
-
// Done inline (dynamicStyle) since stylesheets might not be loaded yet.
|
|
179
|
-
let dynamicStyle = {};
|
|
180
|
-
if (!keypadActive && !hasBeenActivated) {
|
|
181
|
-
dynamicStyle = {visibility: "hidden"};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
177
|
const isExpression = keypadConfig?.keypadType === "EXPRESSION";
|
|
185
178
|
const convertDotToTimes = keypadConfig?.times;
|
|
186
179
|
|
|
187
180
|
return (
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
} as const;
|
|
207
|
-
|
|
208
|
-
this.hasMounted = true;
|
|
209
|
-
this.props.onElementMounted?.(
|
|
210
|
-
elementWithDispatchMethods,
|
|
211
|
-
);
|
|
212
|
-
}
|
|
181
|
+
<AphroditeCssTransitionGroup
|
|
182
|
+
transitionEnterTimeout={AnimationDurationInMS}
|
|
183
|
+
transitionLeaveTimeout={AnimationDurationInMS}
|
|
184
|
+
transitionStyle={{
|
|
185
|
+
enter: {
|
|
186
|
+
transform: "translate3d(0, 100%, 0)",
|
|
187
|
+
transition: `${AnimationDurationInMS}ms ease-out`,
|
|
188
|
+
},
|
|
189
|
+
enterActive: {
|
|
190
|
+
transform: "translate3d(0, 0, 0)",
|
|
191
|
+
},
|
|
192
|
+
leave: {
|
|
193
|
+
transform: "translate3d(0, 0, 0)",
|
|
194
|
+
transition: `${AnimationDurationInMS}ms ease-out`,
|
|
195
|
+
},
|
|
196
|
+
leaveActive: {
|
|
197
|
+
transform: "translate3d(0, 100%, 0)",
|
|
198
|
+
},
|
|
213
199
|
}}
|
|
214
200
|
>
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
201
|
+
{keypadActive ? (
|
|
202
|
+
<View
|
|
203
|
+
style={containerStyle}
|
|
204
|
+
forwardRef={this._containerRef}
|
|
205
|
+
>
|
|
206
|
+
<Keypad
|
|
207
|
+
onAnalyticsEvent={this.props.onAnalyticsEvent}
|
|
208
|
+
extraKeys={keypadConfig?.extraKeys}
|
|
209
|
+
onClickKey={(key) => this._handleClickKey(key)}
|
|
210
|
+
cursorContext={cursor?.context}
|
|
211
|
+
fractionsOnly={!isExpression}
|
|
212
|
+
convertDotToTimes={convertDotToTimes}
|
|
213
|
+
divisionKey={isExpression}
|
|
214
|
+
trigonometry={isExpression}
|
|
215
|
+
preAlgebra={isExpression}
|
|
216
|
+
logarithms={isExpression}
|
|
217
|
+
basicRelations={isExpression}
|
|
218
|
+
advancedRelations={isExpression}
|
|
219
|
+
expandedView={
|
|
220
|
+
containerWidth > expandedViewThreshold
|
|
221
|
+
}
|
|
222
|
+
showDismiss
|
|
223
|
+
/>
|
|
224
|
+
</View>
|
|
225
|
+
) : null}
|
|
226
|
+
</AphroditeCssTransitionGroup>
|
|
232
227
|
);
|
|
233
228
|
}
|
|
234
229
|
}
|
|
@@ -239,14 +234,6 @@ const styles = StyleSheet.create({
|
|
|
239
234
|
left: 0,
|
|
240
235
|
right: 0,
|
|
241
236
|
position: "fixed",
|
|
242
|
-
transitionProperty: "all",
|
|
243
|
-
transition: `200ms ease-out`,
|
|
244
|
-
visibility: "hidden",
|
|
245
|
-
transform: "translate3d(0, 100%, 0)",
|
|
246
|
-
},
|
|
247
|
-
activeKeypadContainer: {
|
|
248
|
-
transform: "translate3d(0, 0, 0)",
|
|
249
|
-
visibility: "visible",
|
|
250
237
|
},
|
|
251
238
|
});
|
|
252
239
|
|